Programowanie w języku C - Wiesław Porębski.pdf

(2600 KB) Pobierz
www.pbtorrent.pl
2
1. Podstawowe elementy języka C++
Przedmowa
C++ należy do grupy obiektowych języków programowania. Korzenie tych języków sięgają końca lat
sześćdziesiątych; rok 1967 uważa się za datę powstania pierwszego języka obiektowego, Simula-67.
Język Simula-67 bazował na wcześniejszej pracy nad specjalizowanym językiem Simula-1,
przeznaczonym do symulacji zdarzeń dyskretnych, zachodzących w reaktorach jądrowych. Twórcami
Simuli byli dwaj badacze norwescy, Kristen Nygaard i Ole-Johan Dahl, pracujący w Norweskim
Centrum Obliczeniowym. W języku tym wprowadzono szereg pojęć i koncepcji, które z niewielkimi
zmianami przejęły współczesne języki obiektowe. W szczególności, obok typów wbudowanych (integer,
real, Boolean), podobnych do stosowanych w najbardziej wówczas znanym języku proceduralnym
ALGOL, wprowadzono w Simuli pojęcie klasy. Wprowadzono również mechanizm dziedziczenia, który
przekazuje klasie potomnej cechy klasy rodzicielskiej.
Kolejnym znaczącym krokiem w rozwoju języków obiektowych było opracowanie przez Alana Kay z
Xerox PARC języka Smalltalk. Język ten powstał w latach 1970-1972 i posłużył jako pierwowzór dla
współczesnej jego wersji, opracowanej przez Adele Goldberg w roku 1983; wersja ta znana jest obecnie
pod nazwą Smalltalk-80.
Okresem szczególnej aktywności w badaniach nad językami programowania były lata siedemdziesiąte.
Powstało wtedy prawdopodobnie kilka tysięcy różnych języków i ich dialektów, ale przetrwało w
szerszym obiegu zaledwie kilka. Tak więc na rynku utrzymały się języki: Smalltalk, Ada (sukcesor
języków ALGOL 68 i Pascal, z pewnym wkładem od języków Simula, Alphard i CLU), C++
(pochodzący z mariażu języków C i Simula), Eiffel (oryginalne dzieło Bertranda Meyera, bazujący po
części na Simuli) oraz obiektowe wersje języków Pascal (Object Pascal, Turbo Pascal) i Modula-2
(Modula-3).
Powstało również szereg języków obiektowych dla konstruowania systemów ekspertowych oraz tzw.
sztucznej inteligencji. Wśród nich także nastąpiła ostra selekcja i powszechnie stosowanych języków
pozostało niewiele; można tu wymienić dwa szerzej znane: CLOS (akronim od Common Lisp Object
System) oraz CLIPS (akronim od C Language Integrated Production System), który po rozszerzeniu o
mechanizm dziedziczenia znany jest obecnie pod nazwą COOL (akronim od. CLIPS Object-Oriented
Language).
Spróbujmy teraz określić, jakie wspólne własności mają wszystkie wymienione języki. Obecnie uważa
się, że język programowania można uznać za obiektowy, jeżeli spełnia następujące wymagania:
• Pozwala definiować klasy i ich wystąpienia, nazywane obiektami . Definicja klasy w języku
obiektowym jest podobna do definicji abstrakcyjnego typu danych w językach proceduralnych
(typu, który nie jest wbudowany, lecz może być zdefiniowany przez programistę). W językach
Eiffel, Smalltalk i Simula klasa jest niepodzielną jednostką syntaktyczną, zawierającą definicje
struktur danych i definicje operacji wykonywanych na tych strukturach. Natomiast w językach
hybrydowych, jak np. Object Pascal, Turbo Pascal, CLOS i C++ klasa jest traktowana jako
“deklaracja typu” umieszczana w jednym pliku, a definicje operacji (nazywane metodami,
funkcjami składowymi, lub funkcjami pierwotnymi) umieszcza się zwykle w innym pliku.
Konkretna operacja może być implementowana za pomocą jednej tylko metody lub wielu metod.
W pierwszym przypadku nazywamy ją monomorficzną , zaś w drugim Ä polimorficzną albo
wirtualną . Wystąpienia obiektów danej klasy są tworzone według tego samego wzorca, zawartego
w definicji klasy. W rezultacie wszystkie obiekty danej klasy mają takie same struktury danych
(atrybuty) i operacje. Tym niemniej każdy obiekt ma własną “tożsamość”, a więc, po utworzeniu,
istnieje niezależnie od innych obiektów tej samej klasy.
• Zapewnia ukrywanie informacji (hermetyzację, ang. encapsulation), co można rozumieć jako
zamknięcie obiektu w swego rodzaju “czarnej skrzynce” lub “kapsułce”. W większości języków
obiektowych hermetyzacja nie zawsze jest pełna, ponieważ zawierają one mechanizmy kontroli
3
1. Podstawowe elementy języka C++
dostępu do elementów klasy. Jako zasadę przyjmuje się ukrywanie przed użytkownikiem definicji
struktur danych i definicji operacji, przy czym same operacje (lub tylko część z nich) są publicznie
dostępne. Zbiór publicznie dostępnych operacji dla obiektu danej klasy nazywa się często
publicznym interfejsem obiektu. Taka konstrukcja klas jest logiczna, ponieważ dla użytkownika
klasy istotne jest tylko to, jak się nazywa dana operacja, do czego służy i jak się ją uaktywnia
(wywołuje). Hermetyzację realizuje się zwykle w ten sposób, że użytkownik nie ma dostępu do
kodu źródłowego z definicjami klas i operacji, a jedynie do publicznych interfejsów. Dzięki temu
zapewnia się ochronę klas (przede wszystkim predefiniowanych klas bibliotecznych) przed
nieuprawnionym dostępem.
• Posiada wbudowany mechanizm dziedziczenia , dzięki któremu można tworzyć klasy potomne
(podklasy, klasy pochodne) od jednej lub kilku klas rodzicielskich, nazywanych superklasami lub
klasami bazowymi. Klasy potomne mogą z kolei być klasami rodzicielskimi dla swoich klas
pochodnych, co pozwala tworzyć “drzewa” (hierarchie) klas. W praktyce klasa bazowa jest na
ogół prostą konstrukcją językową, zaś klasa pochodna jest jej specjalizacją, co zwykle prowadzi
do rozszerzenia definicji klasy bazowej o nowe elementy. Elementami tymi mogą być nowe
struktury danych i nowe metody, bądź też operacje o tych samych nazwach co w klasie bazowej,
ale o innych definicjach.
Mechanizm dziedziczenia jest niezwykle efektywny: nie wymaga kopiowania kodu źródłowego
klasy bazowej, ponieważ klasa pochodna automatycznie dziedziczy wszystkie lub wybrane cechy
klasy bazowej. W rezultacie mamy lepszą, bardziej przejrzystą organizację programu.
• Dysponuje cechami polimorfizmu. Polimorfizm jest terminem zapożyczonym z biologii i oznacza
dosłownie wielopostaciowość. W odniesieniu do programów obiektowych polimorfizm można
określić krótko: jeden interfejs (operacja), wiele metod. Najprostszą postacią (wbudowany
polimorfizm ad hoc) polimorfizmu jest wykorzystanie tego samego symbolu dla semantycznie nie
związanych operacji. Polimorfizm tego rodzaju jest charakterystyczny dla większości
współczesnych języków programowania wysokiego poziomu, nie tylko obiektowych. Przykładem
może być używanie tych samych symboli operacji arytmetycznych, np. symbolu mnożenia “*”,
przy mnożeniu liczb całkowitych i rzeczywistych, chociaż w każdym konkretnym przypadku
będzie wywoływana inna metoda mnożenia. Realizacja takiego wywołania wymaga wyznaczenia
adresu danej metody; jeżeli adresy odpowiednich metod są przekazywane w fazie kompilacji, to
proces ten nazywany jest wiązaniem wczesnym lub statycznym. W językach obiektowych
mechanizm wiązania wczesnego wykorzystuje się również przy przeciążaniu operatorów , tj.
nadawaniu innego znaczenia operatorom wbudowanym w język oraz (co jest specyfiką języka
C++) przy przeciążaniu funkcji. Jednak o sile języka obiektowego decyduje możliwość
wykorzystania wiązania późnego (dynamicznego), gdy adres wywoływanej metody staje się znany
dopiero w fazie wykonania programu. Wiązanie późne występuje dla hierarchii klas
zaprojektowanej w taki sposób, że zdefiniowane w pierwotnej klasie bazowej (“korzeniu” drzewa
klas) metody są redefiniowane w klasach pochodnych z zachowaniem tej samej nazwy, typu,
liczby i typów argumentów. Tego rodzaju metody nazywa się wirtualnymi . Zauważmy, że
konstrukcja taka nie pozwala na związanie wywołania operacji z jej metodą w fazie kompilacji,
ponieważ postać wywołania jest dokładnie taka sama dla każdej operacji w całej hierarchii klas.
Dopiero w fazie wykonania, gdy zostanie utworzony obiekt odpowiedniej klasy, można wywołać
metodę wirtualną dla tego obiektu.
Język C++ zawiera wszystkie wymienione cechy, a ponadto takie, które czynią go bardzo efektywnym;
dzięki temu staje się de facto standardem przemysłowym. De facto, ponieważ dotychczasowe prace
komitetów normalizacyjnych ANSI X3J16 oraz ISO WG-21 doprowadziły jedynie do opublikowania w
roku 1994 zarysu standardu. Tekstem źródłowym dla obu komitetów była wykładnia języka,
opublikowana w książce Margaret Ellis i twórcy C++, Bjarne Stroustrupa, The Annotated C++
Reference Manual [2].
Pierwotnym zamysłem Stroustrupa było wyposażenie bardzo efektywnego języka C w klasy,
4
1. Podstawowe elementy języka C++
wzorowane na klasach Simuli. Zrealizował go w roku 1980, konstruując preprocesor rozszerzonego w
ten sposób języka C. Nowy język, nazwany “C with classes”, był wtedy traktowany raczej jako dialekt
języka C, chociaż zawierał już większość cech języka C++ (dziedziczenie, kontrola dostępu,
konstruktory i destruktory, klasy zaprzyjaźnione, silna typizacja). W latach 80-tych Stroustrup wraz z
grupą współpracowników wprowadzał dalsze mechanizmy i konstrukcje językowe (funkcje rozwijalne,
argumenty domyślne, przeciążanie operatorów i funkcji, referencje, stałe symboliczne, zarządzanie
pamięcią, dziedziczenie mnogie, funkcje wirtualne, szablony klas i funkcji, obsługa wyjątków), co wraz
ze ściślejszą kontrolą typów doprowadziło język C++ do obecnego kształtu.
C++ jest językiem wysoce modularnym; każdy program można zdekomponować na oddzielnie
kompilowane moduły z publicznym interfejsem i ukrytą implementacją. I odwrotnie: do każdego
programu można dołączać wcześniej opracowane moduły, przechowywane na dysku w postaci tzw.
plików nagłówkowych.
Kluczowym pojęciem w C++ jest klasa , traktowana jako typ definiowany przez użytkownika. W
definicji języka nie przewidziano standardowej biblioteki klas jako części środowiska programowego,
chociaż opracowanie [4], sygnowane przez AT&T, zawiera opis i sposób korzystania z bibliotek
wejścia/wyjścia. W praktyce można więc spotkać wiele bibliotek klas, opracowanych niezależnie od
standardu AT&T, jak np.
– bibliotekę NIH (USA, National Institutes of Health), wzorowaną na bibliotece języka Smalltalk;
– bibliotekę Interviews, która pozwala na dogodne używanie systemu X Window z poziomu C++;
– bibliotekę GNU C++ (g++), opracowaną w ramach projektu GNU;
– biblioteki dla tworzenia obiektów trwałych (POET, ObjectStore, ONTOS, Versant).
– biblioteki specjalizowane, jak np. RHALE++ (dla obliczeń matematycznych w fizyce), SIMLIB
(dla symulacji sieci przełączanych).
Z bieżących informacji wynika, że komitety ANSI/ISO przyjęły już standardy dla następujących klas
bibliotecznych: array (szablon tablic), dynarray (szablon tablic dynamicznych), string (szablon
łańcuchów), wstring (szablon łańcuchów z rozszerzonym kodem znaków), bits<N> (szablon zbioru
bitowego o ustalonej liczności), bitstring (szablon zbioru o zmiennej liczności) i complex (liczby
zespolone). W najbliższym czasie można się spodziewać przyjęcia standardów dla szablonów klas:
vector, list i associative array (map).
Ze względu na brak niektórych standardów, biblioteki klas są używane w niniejszej książce raczej
oszczędnie; prawie wyłącznie będą to biblioteki wejścia/wyjścia z bardzo nielicznymi odstępstwami.
Dzięki temu zamieszczone w tekście przykłady (a jest ich ponad 160) były kompilowane i wykonywane
zarówno w środowisku Windows’95 (kompilator Borland C++, wersja 5.01, kompilator Visual C++
v.4.0), jak i w środowisku Unix (kompilatory CC, GNU gcc, GNU g++, v.2.8.1).
Prezentowany tekst należy traktować raczej jako wstęp do programowania w języku C++, a nie jako
wyczerpujący podręcznik (zarówno w sensie kompletności wykładu, jak i znużenia potencjalnego
czytelnika). W stwierdzeniu tym nie należy upatrywać samokrytyki; w książce o rozsądnej objętości
można dokładnie opisać składnię i semantykę języka C++, ale pragmatyka może mieć potencjalnie (i
ma) tak wiele kontekstów, że nie jest praktycznie możliwe opisanie wszystkich możliwych wariantów i
niuansów.
Chociaż język C++ został “nadbudowany” nad językiem C, zrozumienie prezentowanego tekstu,
przykładów i programów, nie wymaga umiejętności programowania w języku C. Tym niemniej
założono, że czytelnik ma pewne doświadczenia programistyczne w którymś z języków wysokiego
poziomu, jak np. Pascal, Modula-2, czy wreszcie wspomniany język C. Bardzo polecam uważne
przestudiowanie zamieszczonych przykładów; ponieważ zdecydowana większość z nich to kompletne
programy, warto je skompilować i wykonać w dostępnym środowisku programowym. Sądzę, że
towarzyszące przykładom dyskusje i analizy programów okażą się interesujące nie tylko dla początku-
jących, ale i dla zaawansowanych programistów, których uwadze polecam rozdziały 10 i 11,
poświęcone obsłudze wyjątków i dynamicznej kontroli typów. Zawarte w tych rozdziałach opisy i
dyskusje oparto na ostatnich ustaleniach wspomnianych komitetów ANSI/ISO, a kompilacja i
5
1. Podstawowe elementy języka C++
wykonanie programów wymagają dostępu do najnowszych kompilatorów, np. Borland C++ w wersji
5.01 lub CC w wersji 4.0.
Gdańsk, w sierpniu 1998
W. M. Porębski
Zgłoś jeśli naruszono regulamin