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 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, 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štkujš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 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