Część 1.
Rozdział 1.
Zaczynamy
Wprowadzenie
Witamy w „C++ dla każdego.” Ten rozdział pomoże ci efektywnie programować w C++.
Dowiesz się z niego:
dlaczego C++ jest standardowym językiem tworzenia oprogramowania
Jakie kroki należy wykonać przy opracowaniu programu w C++
w jaki sposób wpisać, skompilować i zbudować swój pierwszy, działający program w C++.
Krótka historia języka C++
Od czasu pierwszych komputerów elektronicznych, zbudowanych do wspomagania artyleryjskich obliczeń trajektorii podczas drugiej wojny światowej, języki programowania przebyły długą drogę. Na początku programiści używali najbardziej prymitywnych instrukcjami komputera: języka maszynowego. Te instrukcje były zapisywane jako długie ciągi zer i jedynek. Dlatego wymyślono tzw. asemblery, zamieniające instrukcje maszynowe na czytelne dla człowieka i łatwiejsze do zapamiętania mnemoniki, takie jak ADD czy MOV.
Z czasem pojawiły się języki wyższego poziomu, takie jak BASIC czy COBOL. Te języki umożliwiały stosowanie zapisu przypominającego słowa i zdania, np. LET I = 100. Te instrukcje były tłumaczone przez interpretery i kompilatory na język maszynowy.
Interpreter tłumaczy odczytywany program, bezpośrednio zamieniając jego instrukcje (czyli kod) na działania. Kompilator natomiast tłumaczy kod na pewną formę pośrednią. Ten proces jest nazywany kompilacją; w jej wyniku otrzymujemy plik obiektowy. Następnie kompilator wywołuje program łączący (tzw. linker), który zamienia plik obiektowy na program wykonywalny.
Ponieważ interpretery odczytują kod programu bezpośrednio i wykonują go na bieżąco, są łatwiejsze w użyciu dla programistów. Obecnie większość programów interpretowanych jest nazywanych skryptami, zaś sam interpreter nosi nazwę Script Engine (w wolnym tłumaczeniu: motor skryptu).
Niektóre języki, takie jak Visual Basic, nazywają interpreter biblioteką czasu działania. Java nazywa swój interpreter maszyną wirtualną (VM, Virtual Machine), jednak w pewnych przypadkach taka maszyna wirtualna jest dostarczana przez przeglądarkę WWW (taką jak Internet Explorer lub Netscape).
Kompilatory wymagają wprowadzenia dodatkowego kroku związanego z kompilowaniem kodu źródłowego (czytelnego dla człowieka) na kod obiektowy (czytelny dla maszyny). Ten dodatkowy krok jest dość niewygodny, ale dzięki niemu kompilowane programy działają bardzo szybko, gdyż czasochłonne zadanie przetłumaczenia kodu źródłowego na język maszynowy jest wykonywane tylko raz (podczas kompilacji) i nie jest już konieczne podczas działania programu.
Kolejną zaletą wielu języków kompilowanych (takich jak C++) jest posiadanie tylko programu wykonywalnego (bez konieczności posiadania interpretera). W przypadku języka interpretowanego, do uruchomienia programu konieczne jest posiadanie interpretera.
Przez wiele lat głównym celem programistów było uzyskanie niewielkich fragmentów szybko działającego kodu. Programy musiały być niewielkie, gdyż pamięć była droga; musiały być także szybkie, gdyż droga była również moc obliczeniowa. Gdy komputery stały się mniejsze, tańsze i szybsze, a także gdy spadła cena pamięci, te priorytety uległy zmianie. Obecnie czas pracy programisty jest dużo droższy niż koszty eksploatacji większości komputerów wykorzystywanych w codziennej pracy. Teraz najważniejszy jest dobrze napisany, łatwy w konserwacji kod. Łatwość konserwacji oznacza, że gdy zmienią się wymagania wobec działania programu, program można zmienić i rozbudować, bez ponoszenia większych wydatków.
UWAGA Słowo „program” jest używane w dwóch kontekstach: w odniesieniu do zestawu poszczególnych instrukcji (kodu źródłowego), tworzonego przez programistę oraz w odniesieniu się do całego programu przyjmujacego postać pliku wykonywalnego. Może to powodować znaczne nieporozumienia, w związku z czym będziemy starać się dokonać rozróżnienia pomiędzy kodem źródłowym a plikiem wykonywalnym.
Rozwiązywanie problemów
Problemy, które obecnie rozwiązują programiści, są zupełnie inne niż problemy rozwiązywane dwadzieścia lat temu. W latach osiemdziesiątych programy były tworzone w celu zarządzania dużymi ilościami nie poddanych obróbce danych danych. Zarówno osoby piszące kod, jak i osoby korzystające z programów, zajmowały się komputerami profesjonalnie. Obecnie z komputerów korzysta dużo osób, większość z nich ma niewielkie pojęcie o tym, jak działa program i komputer. Komputery są narzędziem używanym przez ludzi do konkretnej pracy, a nie w celu dodatkowego zmagania się z samym komputerem.
Można uważać za ironię, że wraz z pojawieniem się coraz łatwiejszych do opanowania przez ogół użytkowników programów, tworzymy programy, które same w sobie stają się coraz bardziej wymyślne i skomplikowane. Minęły już czasy wpisywania przez użytkownika tajemniczych poleceń po znaku zachęty, które powodowały wyświetlenie strumienia nie przetworzonych danych. Obecne programy korzystają z wymyślnych „przyjaznych interfejsów użytkownika”, posiadających wiele okien, menu, okien dialogowych oraz innych elementów, które wszyscy dobrze znamy.
Wraz z rozwojem sieci WWW, komputery wkroczyły w nową erę penetracji rynku; korzysta z nicj więcej osób niż kiedykolwiek, a ich oczekiwania są bardzo duże. Przez kilka lat, jakie upłynęły od czasu pierwszego wydania tej książki, programy stały się bardziej złożone, w związku z czym powstało zapotrzebowanie na pomocne w ich opanowaniu techniki programistyczne.
Wraz ze zmianą wymagań dotyczących oprogramowania, zmieniły się także same języki i technika pisania programów. Choć historia tych przemian jest fascynująca, w tej książce skupimy się na transformacjach jakie nastąpiły w trakcie przejścia od programowania proceduralnego do programowania obiektowego.
Programowanie proceduralne, strukturalne i obiektowe
Do niedawna program był traktowany jako seria procedur, działających na danych. Procedura (funkcja) jest zestawem specyficznych, wykonywanych jedna po drugiej instrukcji. Dane były całkowicie odseparowane od procedur, zaś zadaniem programisty było zapamiętanie, która funkcja wywołuje inne funkcje, oraz jakie dane były w wyniku tego zmieniane. W celu uniknięcia wielu potencjalnych błędów opracowane zostało programowanie strukturalne.
Główną ideą programowania strukturalnego jest: „dziel i rządź.” Program komputerowy może być uważany za zestaw zadań. Każde zadanie, które jest zbyt skomplikowane aby można było je łatwo opisać, jest rozbijane na zestaw mniejszych zadań składowych, aż do momentu gdy, wszystkie zadania są wystarczająco łatwe do zrozumienia.
Na przykład, obliczenie przeciętnej pensji przeciętnego pracownika przedsiębiorstwa jest dość złożonym zadaniem. Można je jednak podzielić na następujące podzadania:
1. Obliczenie, ile zarabiają poszczególne osoby.
2. Policzenie ilości pracowników.
3. Zsumowanie wszystkich pensji.
4. Podzielenie tej sumy przez ilość pracowników.
Sumowanie pensji można podzielić na następujące kroki:
1. Odczytanie danych dotyczących każdego pracownika.
2. Odwołanie się do danych dotyczących pensji.
3. Dodanie pensji do naliczanej sumy.
4. Przejście do danych dotyczących następnego pracownika.
Z kolei, uzyskanie danych na temat pracownika można rozbić na:
1. Otwarcie pliku pracowników.
2. Przejście do właściwych danych.
3. Odczyt danych z dysku.
Programowanie strukturalne stanowi niezwykle efektywny sposób rozwiązywania złożonych problemów. Jednak pod koniec lat osiemdziesiątych ograniczenia takiej metody programowania objawiły się aż nazbyt jasno.
Po pierwsze, w trakcie tworzenia oprogramowania naturalnym dążeniem jest traktowanie danych (na przykład danych pracownika) oraz tego, co można z nimi zrobić (sortować, modyfikować, itd.), jako pojedynczej całości. Niestety, w programowaniu strukturalnym struktury danych są oddzielone od manipulujących nimi funkcji, a w programie strukturalnym nie istnieje naturalny sposób ich połączenia. Programowanie strukturalne jest często nazywane programowaniem proceduralnym, gdyż skupia się na procedurach (a nie na „obiektach”).
Po drugie, programiści zmuszeni są wciąż wymyślać nowe rozwiązania starych problemów. Czasem nazywa się to „wymyślaniem koła”; stanowi to przeciwieństwo „ponownego wykorzystania.” Idea ponownego wykorzystania oznacza tworzenie komponentów, posiadających znane wcześniej właściwości, które mogą być w miarę potrzeb dołączane do programu. Pomysł został zapożyczony z rozwiązań sprzętowych — gdy inżynier potrzebuje nowego tranzystora, zwykle nie musi go wymyślać — przegląda duże pudło z tranzystorami i wybiera ten, który spełnia dane wymagania, ewentualnie tylko nieco go modyfikując. Inżynier oprogramowania nie miał podobnej możliwości.
Na to zapotrzebowanie próbuje odpowiedzieć programowanie zorientowane obiektowo, dostarcza ono technik zarządzania złożonymi elementami, umożliwia ponowne wykorzystanie komponentów i łączy w logiczną całość dane oraz manipulujące nimi funkcje.
Zadaniem programowania zorientowanego obiektowo jest modelowanie „obiektów” (tzn. rzeczy), a nie „danych.” Modelowanymi obiektami mogą być zarówno elementy na ekranie, takie jak przyciski czy pola list, jak i obiekty świata rzeczywistego, np. motocykle, samoloty, koty czy woda.
Obiekty posiadają charakterystyki (szybki, obszerny, czarny, mokry) oraz możliwości (przyspieszanie, latanie, mruczenie, bulgotanie). Zadaniem programowania zorientowanego obiektowo jest reprezentacja tych obiektów w języku programowania.
C++ i programowanie zorientowane obiektowo
Język C++ wspiera programowanie zorientowane obiektowo, obejmuje swym działaniem trzy podstawy takiego stylu programowania: kapsułkowanie, dziedziczenie oraz polimorfizm.
Kapsułkowanie
Gdy inżynier chce dodać do tworzonego urządzenia rezystor, zwykle nie buduje go samodzielnie od początku — podchodzi do pojemnika z rezystorami, sprawdza kolorowe paski, oznaczające właściwości, i wybiera potrzebny element. Z punktu widzenia inżyniera rezystor jest „czarną skrzynką” — nieważny jest sposób w jaki działa (o ile tylko zachowuje się zgodnie ze swoją specyfikacją). Inżynier nie musi zastanawiać się nad wnętrzem rezystora, aby użyć go w swoim projekcie.
Właściwość samozawierania się jest nazywana kapsułkowaniem. W kapsułkowaniu możemy zakładać opcję ukrywania danych. Ukrywanie danych jest możliwością, dzięki której obiekt może być używany przez osobę nie posiadającą wiedzy o tym, w jaki sposób działa. Skoro możemy korzystać z lodówki bez znajomości zasad działania kompresora, możemy też użyć dobrze zaprojektowanego obiektu nie znając jego wewnętrznych danych składowych.
Sytuacja wygląda podobnie, gdy z rezystora korzysta inżynier: nie musi wiedzieć niczego o jego wewnętrznym stanie. Wszystkie właściwości rezystora są zakapsułkowane w obiekcie rezystora (nie są rozrzucone po całym układzie elektronicznym). Do efektywnego korzystania z rezystora nie jest potrzebna wiedza o sposobie jego działania. Można powiedzieć, że jego dane są ukryte wewnątrz obudowy.
C++ wspiera kapsułkowanie poprzez tworzenie typów zdefiniowanych przez użytkownika, zwanych klasami. O tym, jak tworzyć klasy, dowiesz się z rozdziału szóstego, „Programowanie zorientowane obiektowo.” Po stworzeniu, dobrze zdefiniowana klasa działa jako spójna całość — jest używana jako jednostka. Wewnętrzne działanie klasy powinno być ukryte. Użytkownicy dobrze zdefiniowanych klas nie muszą wiedzieć, w jaki sposób one działają; muszą jedynie wiedzieć, jak z nich korzystać.
Dziedziczenie i ponowne wykorzystanie
Gdy inżynierowie z Acme Motors chcą zbudować nowy samochód, mają do wyboru dwie możliwości: mogą zacząć od początku lub zmodyfikować istniejący już model. Być może ich model, Gwiazda, jest prawie doskonały, ale chcą do niego dodać turbodoładowanie i sześciobiegową skrzynię biegów. Główny inżynier nie chciałby zaczynać od początku, zamiast tego wolałby zbudować nowy, podobny model, z tym dodatkowym wyposażeniem. Nowy model ma nosić nazwę Kwazar. Kwazar jest rodzajem Gwiazdy, wyposażonym w nowe elementy (według NASA, kwazary są bardzo jasnymi ciałami, wydzielającymi ogromne ilości energii).
C++ wspiera dziedziczenie. Można dzięki niemu deklarować nowe typy, będące rozszerzeniem istniejących już typów. Mówi się, że nowa podklasa jest wyprowadzona z istniejącego typu i czasem nazywa się ją typem wyprowadzonym (pochodnym). Kwazar jest wyprowadzony z Gwiazdy i jako taki dziedziczy jej możliwości, ale w razie potrzeby może je uzupełnić lub zmodyfikować. Dziedziczenie i jego zastosowania w C++ zostaną omówione w rozdziale dwunastym, „Dziedziczenie” oraz szesnastym, „Zaawansowane dziedziczenie.”
Polimorfizm
Nowy Kwazar może reagować na naciśnięcie pedału gazu inaczej niż Gwiazda. Kwazar może korzystać z wtrysku paliwa i turbodoładowania, natomiast w Gwieździe benzyna po prostu wpływa do gaźnika. Użytkownik jednak nie musi wiedzieć o tych różnicach, po prostu naciska na pedał gazu i samochód robi to, co do niego należy, bez względu na to, jakim jest pojazdem.
C++ sprawia że różne obiekty „robią odpowiednie rzeczy” poprzez mechanizm zwany polimorfizmem funkcji i polimorfizmem klas. „Poli” oznacza wiele, zaś „morfizm” oznacza w tym przypadku formę. Pojęcie „polimorfizm” oznacza, że ta sama nazwa może przybierać wiele form, zostanie ono szerzej omówione w rozdziale dziesiątym, „Funkcje zaawansowane” oraz czternastym, „Polimorfizm.”
Jak ewoluowało C++
Gdy powszechnie znane stały się analiza, projektowanie i programowanie zorientowane obiektowo, Bjarne Stroustrup sięgnął do najpopularniejszego języka przenaczonego do tworzenia komercyjnego oprogramowania, C, i rozszerzył go, uzupełniając o elementy umożliwiające programowanie zorientowane obiektowo.
Choć C++ stanowi nadzbiór języka C, a wszystkie poprawne programy C są także poprawnymi programami C++, różnica pomiędzy C a C++ jest bardzo znacząca. C++ przez wiele lat czerpało korzyści ze swego pokrewieństwa z C, gdyż programiści mogli łatwo przejść z C do tego nowego języka. Aby w pełni skorzystać z jego zalet, wielu programistów musiał pozbyć się swoich przyzwyczajeń i nauczyć nowego sposobu formułowania i rozwiązywania problemów programistycznych.
Czy należy najpierw poznać C?
Natychmiast nasuwa się więc pytanie: skoro C++ jest nadzbiorem C, to czy powinienem najpierw nauczyć się C? Stroustrup i większość innych programistów C++ nie tylko zgadza się, że wcześniejsze poznanie języka C nie jest konieczne, ale także że brak jego znajomości może stanowić pewną zaletę.
Programowanie w C opiera się na programowaniu strukturalnym; natomiast C++ jest oparte na programowaniu zorientowanym obiektowo. Poznawanie języka C tylko po to, by „oduczyć” się niepożądanych nawyków nabytych podczas pracy z C, jest błędem.
Nie zakładamy że masz jakiekolwiek doświadczenie programistyczne. Jeśli jednak jesteś programistą C, kilka pierwszych rozdziałów tej książki będzie stanowić dla ciebie powtórzenie posiadanych już wiadomości. Prawdziwą pracę nad tworzeniem obiektowo zorientowanego oprogramowania zaczniemy dopiero od rozdziału szóstego.
C++ a Java i C#
C++ jest obecnie dominującym językiem oprogramowania komercyjnego. W ostatnich latach pojawiła się dla niego silna konkurencja w postaci Javy, jednak „wahadło wróciło” i wielu programistów, którzy porzucili C++ dla Javy, zaczyna do niego powracać. Języki te są tak podobne, że opanowanie jednego jest równoznaczne z opanowaniem dziewięćdziesięciu procent drugiego.
C# jest nowym językiem, opracowanym przez Microsoft dla platformy .Net. C# stanowi w zasadzie podzbiór C++, i choć oba języki różnią się w kilku zasadniczych sprawach, poznanie C++ oznacza poznanie około dziewięćdziesięciu procent C#. Upłynie jeszcze wiele lat, zanim przekonamy się, czy C# będzie poważnym konkurentem dla C++; jednak nawet, gdy tak się stanie, praca włożona w poznanie C++ z pewnością okaże się doskonałą inwestycją.
Standard ANSI
Międzynarodowy standard języka C++ został stworzony przez komitet ASC (Accredited Standards Committee), działający w ramach ANSI (American National Standards Institute).
Standard C++ jest nazywany standardem ISO (International Standards Organization), standardem NCITS (National Committee for Information Technology Standards), standardem X3 (starsza nazwa NCITS) oraz standardem ANSI/ISO. W tej książce będziemy odwoływali się do standardu ANSI, gdyż to określenie jest najbardziej popularne.
Standard ANSI próbuje zapewnić przenośność C++ — zapewnia na przykład to, że kod zgodny ze standardem ANSI napisany dla kompilatora Microsoftu skompiluje się bez błędów w kompilatorze innego producenta. Ponieważ kod w tej książce jest zgodny ze standardem ANSI, powinien kompilować się bez błędów na Macintoshu, w Windows lub w komputerze z procesorem Alpha.
Dla większości osób uczących się języka C++, standard ANSI będzie niewidoczny. Ten standard istnieje już od dłuższego czasu i obsługuje go większość głównych producentów. Włożyliśmy wiele trudu, by zapewnić że cały kod w tej książce jest zgodny z ANSI.
Przygotowanie do programowania
C++, bardziej niż inne języki, wymaga od programisty zaprojektowania programu przed jego napisaniem. Banalne problemy, takie jak te przedstawiane w kilku pierwszych rozdziałach książki, nie wymagają projektowania. Jednak złożone problemy, z którymi profesjonalni programiści zmagają się każdego dnia, wymagają projektowania; zaś im dokładniejszy i pełniejszy projekt, tym większe prawdopodobieństwo, że program rozwiąże problemy w zaplanowanym czasie, nie przekraczając budżetu. Dobry projekt sprawia także, że program jest w dużym stopniu pozbawiony błędów i łatwy w konserwacji. Obliczono, że połączony koszt debuggowania i konserwacji stanowi co najmniej dziewięćdziesiąt procent kosztów tworzenia oprogramowania. Ponieważ dobry projekt może te koszty zredukować, staje się ważnym czynnikiem wpływającym na ostateczne wydatki związane z tworzenia programu.
Pierwszym pytaniem, jakie powinniśmy zadać, przygotowując się do projektowania programu jest: jaki problem ma zostać rozwiązany? Każdy program powinien ustanawiać jasny, dobrze określony celem - przekonasz się, że w tej książce nawet najprostszy program spełnia ten postulat.
Drugie pytanie, stawiane przez każdego dobrego programistę, to: czy można to osiągnąć bez konieczności pisania własnego oprogramowania? Ponowne wykorzystanie starego programu, użycie pióra i papieru lub zakup istniejącego oprogramowania, jest często lepszym rozwiązaniem problemu niż pisanie nowego programu. Programista znajdujący takie alternatywy nigdy nie będzie narzekał na brak pracy; znajdowanie najtańszych rozwiązań dzisiejszych problemów otwiera nowe możliwości na przyszłość.
Zakładając, że rozumiesz problem, i że wymaga on napisania nowego programu, jesteś gotów do rozpoczęcia projektowania.
Proces pełnego zrozumienia problemu (analiza) i znajdowania jego rozwiązania (projekt) stanowi niezbędną podstawę dla pisania komercyjnych aplikacji na najwyższym, profesjonalnym poziomie.
Twoje środowisko programowania
Zakładamy, że twój kompilator posiada tryb, w którym może wypisywać tekst bezpośrednio na ekranie, bez konieczności wykorzystania środowiska graficznego, na przykład Windows czy Macintosh. Poszukaj opcji takiej, jak console, console wizard czy easy window lub przejrzyj dokumentację kompilatora.
Kompilator może posiadać własny, wbudowany edytor tekstów, lub do tworzenia plików programów możesz użyć komercyjnego edytora lub procesora tekstów. Ważne jest, by bez względu na to, jaki program stosujemy, miał on możliwość zapisywania zwykłych plików tekstowych, nie zawierających osadzonych w tekście kodów i poleceń formatowania. Bezpiecznymi pod tym względem edytorami są na przykład Notatnik w Windows, program Edit w DOS-ie, Brief, Epsilon, Emacs i vi. Wiele komercyjnych procesorów tekstu, takich jak WordPerfect, Word czy tuziny innych, także oferuje możliwość zapisywania zwykłych plików tekstowych.
Pliki tworzone za pomocą edytora tekstów są nazywane plikami źródłowymi, w przypadku języka C++ zwykle posiadają nazwy z rozszerzeniem .cpp, .cp lub .c. W tej książce wszystkie pliki źródłowe posiadają rozszerzenie .cpp, ale sprawdź w swoim kompilatorze, jakich plików oczekuje.
UWAGA Większość kompilatorów C++ nie „zwraca uwagi” na rozszerzenia nadawane nazwom plików źródłowych, ale jeśli nie określisz tych plików, wiele z nich domyślnie korzysta z rozszerzenia .cpp. Należy jednak zachować ostrożność, gdyż niektóre kompilatory traktują pliki .c jako pliki języka C, zaś pliki .cpp jako pliki języka C++. Sprawdź koniecznie dokumentację kompilatora.
Tak |
Nie |
Do tworzenia plików źródłowych używaj prostego edytora tekstów lub skorzystaj z edytora wbudowanego w kompilator. Zapisuj pliki, nadając im rozszerzenie .c, .cp lub .cpp. Sprawdź w dokumentacji kompilatora i linkera, w jaki sposób należy kompilować i budować programy. |
Nie używaj procesora tekstów zapisującego wraz z tekstem specjalne znaki formatujące. Jeśli korzystasz z takiego procesora, zapisuj pliki jako tekst ASCII. |
Tworzenie programu
Choć kod źródłowy w pliku wygląda na niezrozumiały i każdy, kto nie zna C++, będzie miał trudności ze zrozumieniem jego przeznaczenia, kod ten przyjmuje czytelną dla człowieka postać. Plik kodu źródłowego nie jest programem i, w odróżnieniu od pliku wykonywalnego, nie może zostać wykonany (uruchomiony).
Tworzenie pliku obiektowego za pomocą kompilatora
Do zamiany kodu źródłowego w program używamy kompilatora. Sposób uruchomienia go i wskazania mu plików źródłowych zależy od konkretnego kompilatora; sprawdź w tym celu posiadaną przez ciebie dokumentację.
Gdy kod źródłowy zostanie skompilowany, tworzony jest plik obiektowy. Ten plik ma często rozszerzenie .obj - jednak w dalszym ciągu nie jest to program wykonywalny. Aby zmienić go w program wykonywalny, należy użyć tzw. linkera, czyli programu łączącego.
Tworzenie pliku wykonywalnego za pomocą linkera
Programy C++ zwykle powstają w wyniku łączenia jednego lub więcej plików .obj z jedną lub więcej bibliotekami. Biblioteka (ang. library) jest zbiorem połączonych plików, dostarczanym wraz z kompilatorem. Może też zostać nabyta osobno lub stworzona i skompilowana samodzielnie. Wszystkie kompilatory C++ są dostarczane wraz z bibliotekami użytecznych funkcji (lub procedur) oraz klas, które można zastosować w programie. O klasach i funkcjach porozmawiamy szczegółowo w następnych rozdziałach.
Kroki konieczne do stworzenia pliku wykonywalnego to:
1. Stworzenie pliku kodu źródłowego z rozszerzeniem .cpp.
2. Skompilowanie kodu źródłowego do pliku z rozszerzeniem .obj.
3. Połączenie pliku .obj z wymaganymi bibliotekami w celu stworzenia programu wykonywalnego.
Cykl tworzenia programu
Gdyby każdy program zadziałał już przy pierwszej próbie uruchomienia, wtedy pełny cykl tworzenia wyglądałby następująco: pisanie programu, kompilowanie kodu źródłowego, łączenie plików .obj, uruchomienie programu wykonywalnego. Niestety, prawie każdy program (nawet najbardziej trywialny) może zawierać błędy, często nazywane „pluskwami”. Niektóre błędy uniemożliwiają kompilację, inne uniemożliwiają łączenie, zaś jeszcze inne objawiają się dopiero podczas działania programu.
Bez względu na rodzaj błędu, należy go poprawić - oznacza to edycję kodu źródłowego, ponowną kompilacja i łączenie, oraz ponowne uruchomienie programu. Cały ten cykl został przedstawiony na rysunku 1.1, schematycznie obrazuje on kolejne kroki w cyklu tworzenia programu wykonywalnego.
Rys. 1.1. Kroki wykonywane podczas tworzenia programu w języku C --> [Author:Daniluk] ++
|
HELLO.cpp — twój pierwszy program w C++
Tradycyjne książki o programowaniu zaczynają od wypisania na ekranie słów „Witaj Świecie” lub od innej „wariacji na ten temat”. Ta uświecona tradycją formuła zostanie zachowana także i tu.
Wpisz swój pierwszy program bezpośrednio do edytora, dokładnie przepisując jego treść. Gdy będziesz pewien, że został wpisany poprawnie, zapisz go do pliku, skompiluj, połącz i uruchom. Program wypisze na ekranie słowa „Witaj Świecie”. Nie martw się na razie tym, jak działa; teraz powinieneś jedynie poznać cykl tworzenia programu. Każdy element programu zostanie omówiony w kilku następnych rozdziałach.
OSTRZEŻENIE Na przedstawionym poniżej listingu po lewej stronie umieszczone zostały numery linii. Te numery służą jedynie jako punkty odniesienia dla opisu w tekście. Nie należy ich wpisywać do kodu programu. Na przykład, w linii 1. listingu 1.1 należy wpisać:
#include <iostream>
Listing 1.1. HELLO.cpp, program „Witaj Świecie”.
0: #include <iostream>
1:
2: int main()
3: {
4: std::cout << "Witaj Swiecie!\n";
5: return 0;
6: }
Upewnij się, czy wpisałeś kod dokładnie tak, jak na listingu. Zwróć szczególną uwagę na znaki przestankowe. Znaki << w linii 4. są symbolem przekierowania, który na większości klawiatur uzyskuje się wciskając klawisz Shift, po czym dwukrotnie naciskając klawisz przecinka. Pomiędzy słowami std i cout w linii 4. występują dwa dwukropki (:). Linie 4. i 5. kończą się średnikiem (;).
Upewnij się także, czy postępujesz zgodnie z zaleceniami kompilatora. Większość kompilatorów potrafi połączyć (zbudować) program wykonywalny automatycznie, ale sprawdź to w dokumentacji. Jeśli pojawią się błędy, dokładnie przejrzyj kod i sprawdź, czym różni się od kodu z listingu. Gdy zauważysz błąd w pierwszej linii, na przykład cannot find file iostream (nie można znaleźć pliku iostream), sprawdź w dokumentacji kompilatora, w jaki sposób należy ustawić ścieżkę do dołączanych plików lub zmienne środowiskowe. Gdy otrzymasz błąd informujący o braku prototypu dla main, tuż przed linią 2. dopisz linię int main();. W takim przypadku musisz dopisać tę linię przed początkiem funkcji main w każdym programie pojawiającym się w tej książce. Większość kompilatorów tego nie wymaga, ale istnieje kilka wyjątków.
Pełny program będzie wyglądał następująco:
1: #include <iostream>
2:
3: int main(); // większość kompilatorów nie wymaga tej linii
4: int main()
5: {
6: std::cout << "Witaj Swiecie!\n";
7: return 0;
8: }
UWAGA Trudno jest czytać program samemu, nie wiedząc jak są wymawiane specjalne znaki i słowa kluczowe. Pierwszą linię odczytujemy jako : „hasz-inklad ajoustrim”. Linia 6. to „es-ti-di-si-aut Witaj Świecie.”
Spróbuj uruchomić plik HELLO.exe; program powinien wypisać:
Witaj Swiecie!
bezpośrednio na ekranie. Jeśli tak się stało, gratulacje! Właśnie wpisałeś, skompilowałeś i uruchomiłeś swój pierwszy program w C++. Być może nie wygląda to efektownie, ale każdy profesjonalny programista C++ zaczynał dokładnie od tego właśnie programu.
Korzystanie z bibliotek standardowych
Jeśli masz bardzo stary kompilator, przedstawiony powyżej program nie będzie działał — nie zostaną odnalezione nowe biblioteki standardu ANSI. W takim przypadku zmień kod programu na:
0: #include <iostream.h>
1:
2: int main()
3: {
4: cout << "Witaj Swiecie!\n";
5: return 0;
6: }
Zwróć uwagę, że tym razem nazwa biblioteki kończy się na .h (kropka-h) i że nie korzystamy już z std:: na początku linii 4. Jest to stary, poprzedzający ANSI styl plików nagłówkowych. Jeśli twój kompilator zadziała z tym programem, lecz nie poradzi sobie z wersją przedstawioną wcześniej, oznacza to, że jest prawdziwym antykiem. Nadaje się jedynie do wykorzystania w trakcie czytania kilku pierwszych rozdziałów, ale gdy przejdziemy do wzorców i wyjątków, taki kompilator już nie wystarczy.
Zaczynamy pracę z kompilatorem
Ta książka nie jest związana z określonym kompilatorem. Oznacza to, że zawarte w niej programy powinny działać z każdym zgodnym z ANSI kompilatorem C++, na każdej dostępnej platformie (Windows, Mac, UNIX, Linux, itd.).
Większość programistów pracuje jednak w Windows, zaś większość profesjonalnych programistów używa kompilatorów Microsoftu. Nie jestem w stanie opisać szczegółów kompilowania i łączenia za pomocą każdego istniejącego kompilatora, ale mogę jedynie pokazać od czego zacząć w kompilatorze Visual C++ 6. Inne kompilatory działają podobne, zatem będziesz wiedział, od czego rozpocząć.
Kompilatory mimo wszystko różnią się od siebie, więc pamiętaj o przejrzeniu dokumentacji --> [Author:Daniluk] .
Budowanie projektu Hello World
Aby stworzyć i przetestować program Hello World, wykonaj następujące kroki:
Uruchom kompilator.
W menu File (plik) wybierz polecenie New (nowy).
Wybierz pozycję Win32 Console Application (aplikacja konsoli Win32) i w polu Project name wpisz nazwę projektu, taką jak Przyklad 1. Następnie kliknij na przycisku OK.
W oknie dialogowym wybierz opcję An Empty Project (pusty projekt) i kliknij na przycisku OK.
W menu File wybierz polecenie New.
Wybierz pozycję C++ Source File (plik źródłowy C++) i nadaj jej nazwę prz1.
Wpisz kod programu, w sposób opisany nieco wcześniej.
W menu Build (buduj) wybierz polecenie Build Przyklad1.exe.
Sprawdź, czy nie pojawiły się błędy kompilacji lub łączenia.
Naciśnij Ctrl+F5, aby uruchomić program.
Naciśnij spację, aby zakończyć program.
Często zadawane pytanie
Mogę uruchomić program, ale znika on tak szybko, że nie mogę odczytać wypisywanego tekstu. Co się dzieje?
Odpowiedź
Sprawdź w dokumentacji kompilatora; powinna ona zawierać informacje na temat sposobu zachowania na ekranie wyników działania programu. W przypadku kompilatorów Microsoftu najprościej jest użyć kombinacji Ctrl+F5.
W przypadku starszych kompilatorów Borlanda należy kliknąć prawym przyciskiem myszy w oknie edycji kodu, kliknąć na poleceniu Target Export, zmienić opcję Platform na Win 3.1 (16), po czym ponownie przekompilować i uruchomić program. Okno wyników pozostanie otwarte do momentu, w którym sam je zamkniesz.
Na zkończenie, w każdym kompilatorze, bezpośrednio przed instrukcją return (tj. pomiędzy liniami 4. i 5. na listingu 1.1), możesz dodać przedstawione poniżej linie:
int x;
std::cin >> x;
Spowodują one wstrzymanie działania programu i oczekiwanie na wprowadzenie jakiejś wartości. Aby zakończyć działanie programu, wpisz liczbę (na przykład 1), po czym naciśnij klawisz Enter.
Znaczenie std::cin i std::cout zostanie omówione w następnych rozdziałach. Na razie uznaj je za swego rodzaju magiczne zaklęcia.
Prawdopodobnie bardzo wielu czytelników posiada kompilator Borlanda (np. C++Builder). Pisząc programy dla Windows w środowisku Buildera, należy zwrócić uwagę na pewne charakterystyczne dla tego środowiska cechy.
1. Dobrym zwyczajem jest poinformowanie kompilatora o zakończeniu listy plików nagłówkowych, tj. plików zapisanych w ostrych nawiasach (absolutnie nie dotyczy to tzw. modułów z rozszerzeniem .h). Dokonujemy tego, korzystając z dyrektywy prekompilatora #pragma hdrstop (ang. header stop). Zapis ten znacznie przyśpieszy proces konsolidacji projektu.
2. Jeżeli tworzymy aplikacje konsolowe za pomocą Borland C++Buildera w celu „przytrzymania” ekranu (w tym wypadku normalnego tekstowego okienka DOS), zawsze możemy użyć funkcji getch()przynależnej do prototypu conio.h. Należy jednak pamiętać, że funkcja ta podtrzymywana jest obecnie jedynie w Win32 i nie należy już do szerokiego standardu ANSI C/C++.
3. Przestrzeń strumieni wejścia-wyjścia w C++Builder jest dostatecznie dobrze zdefiniowana, dlatego w tym wypadku nie jest konieczne jawne wskazywanie kompilatorowi miejsca ich pochodzenia.
Poniższy przykład ilustruje te cechy.
0: #include <iostream.h>
1: #include <conio>
2: #pragma hdrstop
3: int main()
4: {
5: cout << "Witaj Swiecie "<< endl;
6: cout << "Nacisnij klawisz...";
7: getch();
8: return 0;
9: }
Należy zwrócić uwagę, iż przy następującym zapisie, wykorzystującym jawne wskazanie przestrzeni strumieni wejścia-wyjścia, działanie programu będzie również poprawne:
0: #include <iostream>
1: #include <conio>
2: #pragma hdrstop
3: int main()
4: {
5: std::cout << "Witaj Swiecie "<< std::endl;
6: std::cout << "Nacisnij klawisz...";
7: getch();
8: return 0;
9: }
Błędy kompilacji
Błędy kompilacji mogą pojawić się z wielu powodów. Zwykle są rezultatem pomyłki przy wpisywaniu lub innych, mniej istotnych przyczyn. Dobry kompilator nie tylko poinformuje, co jest nie tak, ale także wskaże dokładnie miejsce kodu, w którym został popełniony błąd. Najlepsze kompilatory sugerują nawet, co należy z tym zrobić!
Możesz zobaczyć, co się stanie gdy celowo umieścimy w programie błąd. Jeśli program HELLO.cpp działa poprawnie, zmodyfikuj go teraz i usuń zamykający nawias klamrowy z linii 6. Teraz program będzie wyglądał tak, jak na listingu 1.2.
Listing 1.2. Demonstracja błędu kompilacji.
0: #include <iostream>
1:
2: int main()
3: {
4: std::cout << "Witaj Swiecie!\n";
5: return 0;
Ponownie skompiluj program; powinieneś zauważyć błąd podobny do tego:
Hello.cpp(7) : fatal error C1004: unexpected end of file found
Ten błąd informuje o nazwie pliku i numerze linii, w której wystąpił problem, oraz o przyczynie pojawienia się problemu (przyznam jednak, że ten komunikat jest nieco tajemniczy).
Czasem błędy informują jedynie o ogólnej przyczynie problemu. Gdyby kompilator mógł idealnie zidentyfikować każdy z problemów, mógłby poprawiać kod samodzielnie.
Plik .obj jest kodem wynikowym programu (ang. object code). Stanowi translację (przekład) tekstu źródłowego na język zrozumiały dla komputera. Kod wynikowy jest zawsze wczytywany przez linker (konsolidator) — przyp.tłum.
Jak zwykle w takich przypadkach, pojawia się problem polskich znaków diakrytycznych. Wpisanie w kodzie programu słów „Witaj Świecie” w dosłownym brzmieniu, spowodowałoby pojawianie się na ekranie dziwnego znaczka (w miejscu litery Ś). W związku z tym w treści listingów, w tekstach wypisywanych przez program, zrezygnowałem ze stosowania polskich znaków diakrytycznych, zastępując je odpowiednikami łacińskimi. — przyp.tłum.
Szczegółowy opis tworzenia projektu za pomocą kompilatorów Borlanda można znaleźć w książce Andrzeja Daniluka „C++Builder 5. Ćwiczenia praktyczne,” Helion 2001. — przyp.redakcji.
W kompilatorach najnowszej generacji firmy Borland komunikat o ww. błędzie jest wyświetlony w formie nie wymagającej głębszego zastanowiania się nad jego znaczeniem:
[C++ Error] Hello.cpp(6): E2134 Compound statement missing } — przyp.redakcji.
2
Do boxu „Edycja kodu źródłowego” powinny prowadzić strzałki zwrotne od rąbów: „Błędów kompilacji”, „Błędów łączenia” oraz „błędów uruchomienia”. Absolutnie nie mogę dostać się do tego rysunku w miom Wordzie, zatem czynność tą pozostawiam Redakcji.
Nie wiem, czy tego rodzaju samocytowanie jest dopuszczalne (ale jest to chyba jedyna obecnie na rynku nowsza książka opisująca to „zjawisko”), jednak trzeba pamiętać, że w odróżnieniu od USA, gdzie być może (osobiście nie jestem przekonany) dominuje Visual C++ Microsoftu - to w Europie jednak chyba większymi względami wśród programistów cieszą się kompilatory Borlanda. Cytowana książka zawiera kompletny opis zagadnienia. Wbrew temu, co twierdzi autor proces budowania projektu w tych dwóch kompilatorach wcale nie jest tak bardzo podobny.