Programowanie w języku C++ - ebook
Programowanie w języku C++ - ebook
Wyjątkowy podręcznik omawiający podstawy nowoczesnego języka C++. Profesor Bogusław Cyganek, ceniony ekspert w dziedzinach widzenia komputerowego, sztucznej inteligencji oraz programowania systemów mikroprocesorowych, prezentuje obszerne lekcje programowania obiektowego w języku C++ i koncentruje się na dostarczeniu szybkiej i łatwej do opanowania wiedzy dla osób początkujących, jak również średniozaawansowanych. Publikacja jest przeznaczona studentów kierunków technicznych. Będzie ona również przydatna dla specjalistów oraz osób już programujących, ale pragnących poznać nowoczesną wersję języka C++. Książka uczy programowania poprzez: prowadzenie czytelników od prostych technik z nowoczesnym C++ i Biblioteką Standardową do bardziej zaawansowanych metod obiektowych i funkcji językowych, diagramów UML oraz wzorców projektowych, jak również metodyk projektowania oprogramowania, dostarczanie praktycznych przykładów ułatwiających zrozumienie technik programowania i konstrukcji języka C++ wspieranie dobrych praktyk programistycznych i rozwoju osobistego, ograniczenie opisów tekstowych na rzecz rysunków, tabel, diagramów i innych materiałów objaśniających, omówienie zagadnień pokrewnych, jak podstawy języka C, narzędzia CMake oraz git, jak również metody testowania oprogramowania i inne użyteczne biblioteki, dostęp do strony internetowej zawierającej przykładowy kod i przydatne linki do zasobów edukacyjnych, pytania testowe i egzaminacyjne na końcu każdego rozdziału. Jest to niezbędna publikacja dla wszystkich, którzy stawiają pierwsze kroki w nauce programowania z użyciem nowoczesnego języka C++.
Kategoria: | Informatyka |
Zabezpieczenie: |
Watermark
|
ISBN: | 978-83-01-22906-1 |
Rozmiar pliku: | 55 MB |
FRAGMENT KSIĄŻKI
Przez ostatnie półwiecze rozwój nowoczesnych, efektywnych języków programowania znacząco wpłynął tak na technikę, jak i na społeczeństwo w ogóle. Rozwój ten miał kluczowy wpływ na rewolucję techniczną, której jesteśmy świadkami – sztuczna inteligencja; inteligentne miasta, pojazdy i domy, aż po inteligentne telefony; współczesna łączność; sieci 5/6G; internet rzeczy; autonomiczne samochody i drony; panowanie nad przestrzenią powietrzną oraz kosmiczną itd. W znacznym stopniu ten ogromny naukowy i techniczny postęp możliwy był dzięki językowi programowania C++. Posiadając właściwości zorientowane obiektowo, przetarł on szlak szybkiej konstrukcji zaawansowanych systemów komputerowych i znajduje się w samym centrum techniki, jaką dziś obserwujemy.
Najnowsze wersje C++ oferują dziesiątki nowych, potężnych funkcjonalności, ogromną bibliotekę standardową (ang. Standard Library) i setki pakietów oprogramowania wspieranych tak przez tysiące entuzjastów, jak również przez lata doświadczeń w tworzeniu kodu. C++ pozostaje jednym z najpopularniejszych i najszerzej stosowanych na świecie języków programowania. Jeśli zaś chodzi o wydajność, jest po prostu numerem jeden.
Język taki jak C++ pełni jednak jedynie rolę pasa transmisyjnego między ludźmi a komputerami. Tym, co naprawdę się liczy, jest to, co chcemy, żeby komputery robiły, i w jaki sposób „mówimy” im, jak mają to zrobić. Ten sposób to właśnie umiejętność programowania. Tak więc głównym celem tej książki jest nauka podstaw programowania jednocześnie z nauką współczesnego C++.
Nauka programowania w dużej mierze polega na odpowiednim skojarzeniu uprzednio poznanych przykładów, a następnie zaadaptowaniu ich w inteligentny sposób do aktualnych potrzeb. Dlatego też książka ta kładzie szczególny nacisk na odpowiednio dobrane, nietrywialne przykłady dotyczące wytwarzania oprogramowania. Większość tematów jest wyjaśniona na podstawie rzeczywistych problemów programistycznych: pokazujemy, jak je rozwiązać, jakich technologii należy użyć i jak to działa. Z treścią zaś zawsze idzie forma: materiał jest zorganizowany przy użyciu dziesiątek diagramów, schematów i tabel pomagających w zrozumieniu i zapamiętaniu informacji, jak również do szybkiego znajdowania odpowiednich konstrukcji w trakcie nauki oraz podczas realizacji fascynujących projektów.
Dobrej zabawy!
Kraków
Bogusław CyganekPODZIĘKOWANIA
Pisanie książki to olbrzymie przedsięwzięcie. Nie byłoby ono możliwe bez pomocy przyjaciół, kolegów, współpracowników i wielu innych osób, którym chciałbym wyrazić moją najgłębszą wdzięczność. W szczególny sposób chciałbym podziękować moim licznym koleżankom i kolegom z Akademii Górniczo-Hutniczej w Krakowie. Szczególne podziękowania w tym względzie chciałbym wyrazić profesorowi Arturowi Rydoszowi za skrupulatną weryfikację manuskryptu oraz doktorantom Mateuszowi Knapikowi i Jakubowi Grabkowi za ich pomoc w przygotowaniu tej książki.
Szczególne podziękowania kieruję w stronę profesora Kazimierza Wiatra, dyrektora Akademickiego Centrum Komputerowego Cyfronet AGH, za jego ciągłe wsparcie i dobre słowo.
Jestem też wdzięczny zespołowi wydawnictwa Wiley, który wniósł istotny wkład w urzeczywistnienie tej książki. Wykonał on kapitalną pracę, dzięki której pozycja ta jest tak dobra, jak to tylko możliwe.
Jestem również wdzięczny wielu moim koleżankom i kolegom na całym świecie, w szczególności moim studentom i czytelnikom tej książki oraz poprzednich publikacji mojego autorstwa, za ich e-maile, pytania, sugestie, informacje o błędach oraz wszystkie rozmowy, które udało nam się odbyć. Wszystko to pomogło mi napisać lepszy tekst i wytworzyć lepsze oprogramowanie. Proszę również o Wasze wsparcie tak teraz, jak i w przyszłości.
Na koniec chciałbym podziękować mojej rodzinie: żonie Magdzie, dzieciom Nadii i Kamilowi oraz mojej matce, za ich cierpliwość, wsparcie oraz słowa zachęty, które słyszałem, pracując nad tą książką.PODZIĘKOWANIA DO WYDANIA POLSKIEGO
Szczególne podziękowania chciałbym przekazać Wydawnictwu Naukowemu PWN za podjęcie się przygotowania oraz realizację polskiego wydania mojej książki. Bardzo zależało mi, aby moi polscy studenci mogli dysponować bardziej dostępną wersją – im też chciałbym podziękować za liczne słowa wsparcia i współpracę, jak również im chciałbym dedykować to wydanie. Wyrazy uszanowania należą się wydawcy, paniom Wioletcie Szczygielskiej-Dybciak oraz Dorocie Siudowskiej-Mieszkowskiej, jak również drużynie tłumaczy, panom: Wojciechowi Fenrichowi, Krzysztofowi Kapustce oraz Witoldowi Sikorskiemu.
Chciałbym również podziękować Instytutowi Elektroniki Akademii Górniczo-Hutniczej w Krakowie za wsparcie finansowe. W szczególności moje podziękowania przekazuję panu dyrektorowi IE AGH, profesorowi Krzysztofowi Winczy, zastępcy dyrektora IE AGH, panu profesorowi Witoldowi Machowskiemu, jak również panom dziekanom Wydziału Informatyki, Elektroniki i Telekomunikacji: profesorowi Sławomirowi Gruszczyńskiemu oraz doktorowi Jackowi Kołodziejowi.WYKAZ SKRÓTÓW
------- --------------------------------------------------------------------------------------------------------
ALU jednostka arytmetyczno-logiczna
API interfejs programistyczny aplikacji
ASCII jeden z systemów kodowania znaków (ang. American Standard Code for Information Interchange)
BCD zapis dziesiętny kodowany dwójkowo (ang. binary-coded decimal)
BIN dwójkowe
C flaga przeniesienia
U1 kod uzupełnień do jedności
U2 kod uzupełnień do dwóch
CPU procesor; centralna jednostka obliczeniowa
CRTP ciekawie rekurencyjny wzorzec szablonu (ang. curiously recurring template pattern)
DEC dziesiętne
DSP cyfrowe procesory sygnałowe
DP wzorzec projektowy
ECMA Europejskie Stowarzyszenie Producentów Komputerów (ang. European Computer Manufacturers Association)
ELF format Executable and Linkable Format
FB ułamkowa liczba binarna
FIFO pierwszy na wejściu, pierwszy na wyjściu (ang. first-in-first-out)
FP zmiennoprzecinkowe
FPGA bezpośrednio programowalna macierz bramek
FX stałoprzecinkowe
GPU procesor graficzny
GUI graficzny interfejs użytkownika
HEX szesnastkowe
HTTP Protokół Przesyłania Hipertekstu (ang. Hypertext Transfer Protocol)
IB binarna liczba całkowita
IDE zintegrowane środowisko programowania
IEEE Instytut Inżynierów Elektryków i Elektroników (ang. Institute of Electrical and Electronics Engineers)
ISO Międzynarodowa Organizacja Normalizacyjna (ang. International Organization for Standardization)
IoT Internet rzeczy (ang. Internet of Things)
LIFO ostatni na wejściu, pierwszy na wyjściu (ang. last-in-first-out)
LSB najmniej znaczący bit; najmłodszy bit
MSB najbardziej znaczący bit; najstarszy bit
NaN nie-liczba
NL nowa linia
OCT ósemkowe
OO zorientowane obiektowo
OOD projektowanie obiektowe; projektowanie zorientowane obiektowo
OOP programowanie obiektowe; programowanie zorientowane obiektowo
OS system operacyjny
PC licznik programu
PE format portable executable
Q iloraz
R reszta z dzielenia
RAII zarządzanie pozyskiwaniem zasobu poprzez inicjalizację
RPN odwrócona notacja polska
RVO optymalizacja wartości zwracanej
RTTI identyfikacja typu w czasie wykonywania
SDK zestaw narzędzi programistycznych (ang. Software Development Kit)
ZM znak/moduł
STL standardowa biblioteka szablonowa
SL biblioteka standardowa
SP wskaźnik stosu
TDD programowanie sterowanie testami
UB niezdefiniowane zachowanie
ULP jednostki na ostatnim miejscu
UML zunifikowany język modelowania
V overflow (flaga)
VFT tabela funkcji wirtualnych
XML rozszerzalny język znaczników
Z zero (flaga)
------- --------------------------------------------------------------------------------------------------------1. WPROWADZENIE
Sukces nie jest ostateczny. Porażka nie jest śmiertelna. Tym, co się liczy, jest odwaga, by iść dalej.
Winston Churchill
Książka ta jest wynikiem mojej fascynacji komputerami i programowaniem w języku C++. Jest też rezultatem mojej ponaddwudziestoletniej przygody z nauczaniem podstaw informatyki, a w szczególności języka C++, studentów na Wydziałach Informatyki, Elektroniki i Telekomunikacji, jak również Elektrotechniki, Automatyki, Informatyki i Inżynierii Biomedycznej oraz Inżynierii Mechanicznej i Robotyki Akademii Górniczo-Hutniczej w Krakowie. Pracowałem również w kilku firmach jako programista i konsultant, stając się starszym inżynierem oprogramowania i projektantem oprogramowania. Kierowałem również zespołami programistów i służyłem moim młodszym kolegom jako nauczyciel.
Uczenie się programowania za pomocą języków komputerowych jest i powinno sprawiać frajdę, ale nauczenie się ich dobrze może być trudne. Nauczanie C++ stanowi też znacznie większe wyzwanie niż dekadę temu. Język znacznie się rozrósł i posiadł nowe fascynujące cechy, które chcielibyśmy zrozumieć i wykorzystać w celu zwiększenia naszej produktywności. W chwili, gdy piszę te słowa, najnowszą wersją języka jest C++20. W książce korzystamy z bogatej funkcjonalności C++17, jak również pokazujemy nieco nowych cech C++20. Ponadto w wielu przypadkach dobrze jest także znać przynajmniej niektóre ze starszych konstrukcji, choćby dlatego, że są one wszechobecne w wielu projektach programistycznych, bibliotekach, frameworkach itd. Dla przykładu, pewnego razu pracowałem nad projektem w C++, a dotyczącym przetwarzania strumieni wideo. Modyfikując jedną z wersji bibliotek odpowiedzialnych za wejście/wyjście JPEG, odkryłem tzw. wycieki pamięci. Choć cały projekt napisany był we współczesnym C++, musiałem znaleźć i naprawić błąd w starym kodzie napisanym w C. Znalezienie powodu zajęło mi trochę czasu, ale gdy się udało, usunięcie problemu było już tylko chwilą.
Kolejnym problemem jest to, że kod, jaki spotykamy w czasie naszej codziennej pracy, różni się od tego, czego nauczyliśmy się w trakcie zajęć. Dlaczego? Z wielu powodów. Pierwszym z nich jest pisany latami i często odziedziczony kod. Innymi słowy, znaczy to, że proces pisania ciągnie się bardzo długo. Co więcej, nawet niewielkie projekty mają tendencje do rozrastania się, by po kilku latach osiągnąć często monstrualne rozmiary. Ponadto kod piszą programiści w różnym stopniu zaawansowani w sztuce programowania, a także z różnym doświadczeniem i poczuciem humoru. Na przykład jeden z moich kolegów-programistów zaczynał każdy swój plik z kodem od wiersza. W rezultacie programiści muszą nie tylko rozumieć, utrzymywać i naprawiać błędy w rzeczywistym oprogramowaniu, ale też niekiedy czytać wiersze. Oto, co tworzy rozbieżność między ładnymi, wypolerowanymi fragmentami kodu pokazywanymi na zajęciach a tym, co niesie „samo życie”. Tak więc jakie umiejętności są potrzebne, by odnieść sukces w programowaniu?
Dlaczego napisałem tę książkę, skoro istnieje tak wiele stron internetowych, list dyskusyjnych, specjalnych grup zainteresowań, przykładów kodu i dostępnych online książek poświęconych tworzeniu oprogramowania? Choć wszystko to często stanowi wspaniały i wysoce użyteczny, dostępny od ręki punkt odniesienia, niekiedy trudno jest odnaleźć miejsca lub zasoby, które prowadzą nas krok po kroku przez proces uczenia się. Jeszcze trudniej jest odnaleźć dobre przykłady, które uczą kluczowych technik programowania i są jednocześnie krótkie, praktyczne, a zarazem konkretne. Chciałbym więc podzielić się z wami efektami synergii między teoretycznymi opisami popartymi przykładowymi projektami, jakie zebrałem w trakcie moich lat pracy jako programista i wykładowca.
Spójrzmy teraz na krótki, ogólny zarys tematyki tej książki. C++ jest jednym z najbardziej wpływowych, najpowszechniej stosowanych i najbardziej fascynujących języków programowania. Stworzony został przez Bjarne’a Stroustrupa w latach 80. XX wieku. W ciągu ostatniej dekady wprowadzono w nim fundamentalne i szeroko zakrojone zmiany. Korzenie C++ tkwią w językach programowania C oraz Simula (Stroustrup B., Evolving a language 2007) (Stroustrup B., The C++ Programming Language 2013). Jak zobaczymy, podstawowe konstrukcje, takie jak instrukcje i wyrażenia, są w przypadku C oraz C++ niemal takie same. Przez lata kompilatory C++ były też w stanie sprawnie radzić sobie z kodem w C. C jest językiem, który w dużym stopniu wpłynął na naszą techniczną rewolucję, udowadniając, że jest kluczowym narzędziem w tworzeniu bardzo wpływowego systemu operacyjnego Unix, którego następcami są wszystkie inne systemy operacyjne, włączając w to Windowsa, Linuksa i Androida. Za sprawą kompatybilności z wieloma platformami oraz niewielkiemu rozmiarowi kodu wynikowego, C jest nadal używany w systemach wbudowanych, urządzeniach FPGA oraz kartach graficznych (procesorach graficznych, ang. graphics processing unit, w skrócie GPU) oraz do przyspieszenia kodu na platformach równoległych. Istnieje też mnóstwo bibliotek napisanych w C, które nadal są w użyciu, jak te zawierające efektywne algorytmy numeryczne czy te służące do przetwarzania obrazów, by wymienić tylko kilka z nich. Simula był też jednym z pierwszych języków korzystających z klas i przyczynił się do powstania metodyk programowania zorientowanego obiektowo, co stało się kamieniem węgielnym większości przedsięwzięć technologicznych. Tak więc – parafrazując – C++ odziedziczyło coś po obu tych językach: po C publicznie, a po Simuli prywatnie.
Choć istnieje wiele języków programowania, nauczenie się C++ warte jest wysiłku, szczególnie w przypadku tych osób, które planują pracę lub już są zaangażowane w jakiekolwiek przedsięwzięcia programistyczne, w szczególności te dotyczące systemów i wydajności. By zrozumieć najważniejsze cechy C++, wystarczy przeczytać tę książkę; zagłębimy się w nie później. W ramach wprowadzenia przyjrzyjmy się jednak pokrótce tym najbardziej charakterystycznym.
■ Wolność dla programistów i bogactwo funkcjonalności – zarówno niskopoziomowe, jak i wysoce abstrakcyjne konstrukcje mogą być wykorzystywane w wielu kontekstach. Tak jak w przypadku szwajcarskiego scyzoryka, istnieje niebezpieczeństwo ich niepoprawnego użycia, ale wolność i bogactwo funkcjonalności prowadzą na najwyższe poziomy produktywności w wielu kontekstach i szerokich zastosowaniach. Co jednak najważniejsze – kochamy wolność.
■ Wysoka wydajność – był to zawsze główny cel tego języka. Najważniejsze w tym aspekcie jest to, by móc dostosować wiele programistycznych funkcjonalności do konkretnej potrzeby bez dużego narzutu. C++ został zaprojektowany tak, by spełnić ten wymóg. Można to sparafrazować w następujący sposób: „nie płać za coś, z czego nie korzystasz”. Jak zwykle pojawia się jednak cena, którą trzeba zapłacić, taka jak niezainicjalizowane zmienne czy niezwolnione zasoby. Nowe własności współczesnego C++ sprawiają jednak, że problemy te są mniej poważne, pozwalając kodowi napisanemu w C++ nadal plasować się w najwyższej lidze wydajności.
■ Systemowe, nisko- i wysokopoziomowe programowanie zorientowane obiektowo (ang. object oriented programming, w skrócie OOP) na tej samej platformie – C++ jest wykorzystywane do implementacji systemów wymagających niskopoziomowego dostępu. W wielu przypadkach język ten wykorzystywany jest do konstruowania pomostów m.in. językami: np. w dziedzinie analiz numerycznych do Fortrana, a w dziedzinie programowania systemów do C lub Assemblera. Ponadto ten sam język jest wykorzystywany do implementacji wysokopoziomowych aplikacji, takich jak edytory tekstu, platformy CAD, bazy danych i gry. C++ to język silnie zorientowany obiektowo (ang. object oriented – OO) spełniający wszystkie paradygmaty tego podejścia, takie jak abstrakcja, enkapsulacja, dziedziczenie, polimorfizm, możliwość przeładowania operatorów itd. Własności te, wzmocnione przez szablony i wzorce projektowe, mocno wspierają tworzenie oprogramowania, w szczególności dla wielkich systemów.
■ Silna typizacja języka – każdy obiekt scharakteryzowany jest przez swój typ. Ten silny wymóg dotyczący typu prowadzi do powstania kodu, który jest weryfikowany przez kompilator, a nie przez użytkownika w czasie wykonania, jak dzieje się w przypadku niektórych języków, które nie posiadają tej cechy. Niemniej jednak obiekty mogą zostać przekonwertowane z jednego typu na drugi dzięki wbudowanym lub stworzonym przez użytkownika operatorom konwersji. Relatywnie nowe mechanizmy dedukcji typu ze słowem kluczowym auto znacznie uprościły też użycie typów i zwyczajnie pozwoliły zaoszczędzić nam pisania.
■ Obsługa wyjątków – to, jak należy obsłużyć problemy obliczeniowe w trakcie wykonania, zawsze było istotnym pytaniem. Na przykład, co powinno zadziać się w kodzie, jeśli plik z kluczowymi ustawieniami nie może zostać otwarty lub gdy doszło do dzielenia przez zero? Dobry system obsługi wyjątków z wbudowanym mechanizmem odwijania stosu w dużym stopniu ułatwia zarządzanie w takich sytuacjach.
■ Wejście i wyjście (IO) – C++ był pierwszym językiem, który pobił swoją konkurencję, wprowadzając jasną, rozszerzalną i wysoce efektywną hierarchię obiektów IO i uzyskał kontrolę nad dziesiątkami stylów formatowania i flag. Cecha ta – choć nie bez pewnych ograniczeń i szczypty krytycyzmu – może zostać wykorzystana do dodania zdolności w zakresie wejście i wyjścia do zdefiniowanych przez użytkownika typów w sposób szybki i elegancki, a wszystko to za pomocą przeładowania operatorów.
■ Semantyka przenoszenia – do najważniejszych celów C++ zawsze należała wydajność. Duża liczba przetwarzanych obiektów wpływa na nią negatywnie, zwłaszcza jeśli te obiekty są duże i intensywnie kopiowane. W wielu przypadkach kopiowanie obiektów nie jest jednak konieczne, ponieważ dane mogą być prosto i efektywnie podmieniane. Za wysoce efektywną semantyką przenoszenia (ang. move semantics), dostępną we współczesnym C++, kryje się właśnie mechanizm podmiany danych, co pozwoliło również podnieść jakość generowanego kodu.
■ Wyrażenia lambda – ten relatywnie nowy sposób zapisu funkcji przypominających wyrażenia znacznie udoskonalił proces przekazywania wyspecjalizowanych działań lub cech do algorytmów. Wraz ze słowem kluczowym auto, wyrażenia lambda pozwoliły na tworzenie bardziej eleganckiego kodu i zwiększenie produktywności.
■ Inteligentne wskaźniki – choć inteligentne wskaźniki (ang. smart pointers) znajdują się wśród dziesiątek konstrukcji programistycznych dostępnych w bibliotece standardowej (ang. Standard Library – w skrócie SL), zmieniły sposób, w jaki C++ obchodzi się z zasobami systemowymi. Przez lata wycieki pamięci (ang. memory leaks), jakie mogłyby z łatwością przydarzyć się w niedbale napisanym kodzie C lub C++, były głównym zarzutem podnoszonym przeciwko C++ w systemach, w których istotny jest wysoki poziom bezpieczeństwa, a także w przypadku programowania rozwiązań sieciowych i internetowych. Inteligentne wskaźniki w sposób robiący wrażenie zmieniły ten obraz – jeśli będziemy ich konsekwentnie używać, mogą zapobiegać wyciekom pamięci bez potrzeby stosowania mechanizmów takich jak odśmiecacze pamięci (ang. garbage collectors), które negatywnie wpływają na wydajność systemu.
■ Szablony i programowanie generyczne – zauważono, że gdy powstaje kod ogromnych rozmiarów, wiele struktur i funkcji się powtarza: układ jest niemal taki sam, a zmienia się jedynie kilka typów. Szablony łagodzą problem powtarzania kodu, pozwalając nam pisać funkcje i klasy, w przypadku których konkretne typy i parametry mogą być różne, a podać je trzeba tuż przed utworzeniem instancji takiego konstruktu. Z tego powodu kod stał się bardziej generyczny, ponieważ możliwe jest zakodowanie komponentów, które mogą działać z różnymi typami – nawet tymi, które nie są znane w chwili implementacji komponentu. Dobry przykład stanowi klasa std::vector z SL, reprezentująca dynamicznie rosnącą tablicę obiektów. Jest ona w stanie przechowywać niemal każdy obiekt, który może zostać automatycznie zainicjalizowany.
■ Biblioteki – biblioteka standardowa posiada dziesiątki kontenerów danych, algorytmów i podbibliotek powstałych z myślą o wyszukiwaniu z użyciem wyrażeń regularnych, programowaniu równoległym, systemie plików i pomiarze czasu. Istnieją też inne wysoce wydajne biblioteki służące do obliczeń, obróbki grafiki, programowania gier, przetwarzania i rozpoznawania obrazów, uczenia się maszyn i sztucznej inteligencji (ang. Machine Learning & Artificial Intelligence – w skrócie ML/AI), obróbki dźwięku i tworzenia innych programów narzędziowych. Dostęp do tych zasobów często jest otwarty.
■ Automatyczne generowanie kodu przez kompilator – jak również tzw. metaprogramowanie, stało się możliwe dzięki niedawno powstałemu mechanizmowi wyrażeń stałych (ang. constant‐expression), który pozwala na kompilowanie i wykonywanie fragmentów kodu, w wyniku czego już na tym etapie mogą być wyliczone pewne wartości, które następnie już jako gotowe są wstawiane do docelowego kodu.
■ Bogaty zestaw narzędzi programistycznych – chodzi tu o kompilatory (ang. compilers), konsolidatory (ang. linkers), profilery (ang. profilers), generatory projektów (ang. project generators), narzędzia służące do wersjonowania (ang. versioning tools), repozytoria, edytory, platformy IDE (zintegrowane środowiska programistyczne, ang. Integrated Development Environment, w skrócie właśnie IDE), narzędzia służące do analizy kodu, oprogramowanie typu CAD służące do projektowania oprogramowania, i znacznie więcej dodatkowych narzędzi.
Jest to jedynie krótka charakterystyka języka C++. W wielu dyskusjach pada argument, że ceną, jaką płacimy za wszystkie te funkcjonalności, jest złożoność języka, co zarazem sprawia, że krzywa uczenia się jest relatywnie stroma. Być może to prawda, ale pamiętajmy, że nie musimy uczyć się wszystkiego naraz. Parafrazując Oprah Winfrey, poznając własności C++: „Możesz mieć wszystko. Tyle że nie wszystko od razu”.
Być może słyszeliście o zasadzie 80/20, zwanej niekiedy regułą Pareto. Dla przykładu, w informatyce mówi ona, że 80% czasu pracy procesora przypada na 20% kodu lub też że 80% błędów spowodowanych jest przez 20% kodu. I tak dalej. Najważniejsze jest tu to, że większość rzeczy w życiu nie jest rozłożona równomiernie i zwykle 20% wysiłków będzie odpowiadać za 80% efektu. W odniesieniu do uczenia się C++, moje wrażenie jest takie, że do pewnego stopnia możemy tu zastosować regułę Pareto. Celem dwóch pierwszych rozdziałów tej książki jest przedstawienie koniecznych podstaw. Ile programów może napisać ktoś, kto posiadł tę wiedzę? Miejmy nadzieję, że wiele. Nie znaczy to jednak, że pozostała część tej książki nie jest ważna. Przeciwnie, wstępne części dają nam solidne podstawy, ale bardziej zaawansowane własności pozwalają nam wrzucić wyższe biegi, których potrzebujemy, by w pełni wykorzystać C++ i stać się zawodowymi projektantami i twórcami oprogramowania. Jak osiągnąć te cele? Tak jak w przypadku wielu innych dyscyplin, odpowiedź brzmi: ćwiczyć, ćwiczyć i jeszcze raz ćwiczyć! Mam nadzieję, że książka ta będzie w tym pomocna. A oto jej krótka charakterystyka.
■ Celem tej książki jest zaprezentowanie fundamentów informatyki: elementarnych algorytmów i struktur danych wraz z podstawami współczesnego języka C++. Na przykład różne algorytmy wyszukiwania, mnożenia macierzy, odnajdywania miejsc zerowych funkcji i efektywnego sumowania z kompensacją zostaną przedstawione za pomocą kodu C++. Podstawowe wektorowe i łańcuchowe struktury danych, stosy, listy i drzewa również są opisane za pomocą przykładów napisanych w języku C++.
■ Szczególny nacisk położony jest na uczenie się za pomocą przykładów. Dobre przykłady są kluczem do zrozumienia programowania i C++. Do najbardziej interesujących rzeczywistych przypadków dochodzi jednak w skomplikowanym kodzie produkcyjnym, który często zawiera tysiące linii i pisany był przez różne osoby przez lata pracy. Taki kod jest trudny do analizy w książce o ograniczonym rozmiarze i przeznaczonej dla studentów. Tak więc klucz stanowią nietrywialne, praktyczne przykłady kodu, które niekiedy mają swój początek w rzeczywistych projektach i zawsze są napisane tak, by ilustrowały nauczane zagadnienia.
■ Co się tyczy stylu, w jakim książka została zredagowana, celem było tu wykorzystanie rysunków, podsumowań i tabel, a nie pisanie całych stron czystego tekstu, choć i tekst jest niemniej ważny do szczegółowego objaśnienia kodu. Tabele zawierające podsumowanie kluczowych zagadnień programistycznych, takich jak instrukcje C++, operatory, biblioteka systemu plików, algorytmy biblioteki standardowej itd., powinny posłużyć jako przydatne „ściągi” w codziennej pracy programistycznej.
■ Nacisk położony jest na podstawowe kontenery i algorytmy biblioteki standardowej C++, które zostały opisane wraz z ich powstałymi niedawno implementacjami równoległymi.
■ Szczególny nacisk położony jest na zrozumienie odpowiednich etapów tworzenia oprogramowania, zaczynając od analizy problemu przez implementację i testowanie. Ważne jest również zrozumienie oprogramowania w kontekście jego wykonania na współczesnym komputerze. Choć zawsze zaczynamy „od ogółu do szczegółu”, to zagadnienia bardziej szczegółowe, takie jak organizacja kodu i danych w pamięci komputera czy wpływ architektur wielordzeniowych procesorów z warstwami pamięci podręcznej (ang. cache memory), również zostały omówione.
■ Nacisk położony jest na tworzenie oprogramowania z wykorzystaniem metodologii projektowania zorientowanego obiektowo (ang. object-oriented design, w skrócie OOD) i programowania zorientowanego obiektowo.
■ Wyjaśnione i wykorzystane zostały metodologia oraz kluczowe diagramy zunifikowanego języka modelowania UML (ang. Unified Modeling Language, w skrócie właśnie UML).
■ Niektóre z najbardziej powszechnych i najbardziej praktycznych wzorców projektowych (ang. design patterns – w skrócie DP), takie jak handle-body i adapter, są przedstawione w ich rzeczywistych zastosowaniach.
■ Nie wzbraniamy się przed przedstawianiem starszych technologii i bibliotek, które nadal można napotkać na uniwersyteckich kursach dotyczących systemów operacyjnych lub systemów wbudowanych (ang. embedded systems), a także w starszym kodzie. W tym celu w dodatku zamieszczono wydzieloną sekcję zawierającą krótkie wprowadzenie do języka programowania C oraz dyrektyw preprocesora, jak zawsze wzbogacone o przykłady.
■ W osobnym rozdziale przedstawiono wprowadzenie do różnych reprezentacji numerycznych i arytmetyki komputerów. Znaleźć w nim można wstęp do dziedziny obliczeń zmiennoprzecinkowych i algorytmów numerycznych. Informacje te przydadzą się na różnych poziomach uczenia się informatyki.
■ Za pomocą przykładów przedstawiony jest również tzw. ekosystem tworzenia oprogramowania. Szczególna uwaga poświęcona jest tu testowaniu oprogramowania oraz praktycznemu wykorzystaniu narzędzi do tworzenia oprogramowania.
■ Rozdziały zorganizowane są w taki sposób, by stanowiły odrębną całość i można je było czytać osobno. Zostały też jednak uporządkowane w sposób ułatwiający lekturę całej książki rozdział po rozdziale.
Książka ta powstała z myślą o studentach studiów pierwszego i drugiego stopnia stawiających pierwsze kroki w dziedzinie informatyki i programowania w C++, jak również tych, którzy mają już pewne doświadczenia w programowaniu, a chcieliby poszerzyć swoje umiejętności. Najbardziej przyda się ona na zajęciach z programowania dla studentów elektroniki, informatyki i telekomunikacji oraz pokrewnych dziedzin, takich jak teleinformatyka, inżynieria mechaniczna, mechatronika, robotyka, fizyka, matematyka itd. Książka może też przydać się studentom innych kierunków oraz programistom chcącym podnieść swoje umiejętności w zakresie współczesnego języka C++. Aby wykorzystać tę książkę, trzeba spełnić kilka niewygórowanych warunków wstępnych, a mianowicie:
■ Dobrze byłoby mieć za sobą podstawowe wprowadzenie do programowania.
■ Przyda się też znajomość matematyki na poziomie szkoły średniej.
Z książki tej można korzystać na wiele sposobów. Jako całość najlepiej pasuje ona do trzech lub czterech semestrów powiązanych ze sobą kursów będących wprowadzeniem do programowania, programowania zorientowanego obiektowo, a w szczególności programowania w języku C++, jak również zajęć z zaawansowanych metod i technik programowania. Można ją wykorzystać w ramach zajęć z systemów operacyjnych oraz programowania systemów wbudowanych. Może też posłużyć jako dodatkowy materiał na zajęciach dotyczących rozpoznawania oraz przetwarzania obrazów. W ten sposób korzystamy z niej w Akademii Górniczo-Hutniczej w Krakowie.
Do każdego rozdziału można też podejść osobno i osobno go przeczytać. A gdy książka posłuży już jako instrukcja, dzięki zawartych w niej licznych podsumowaniach, tabelach, rysunkach i indeksach, można wykorzystać ją jako referencyjny podręcznik dla praktyków i studentów.
1.1. Struktura książki
Diagram na rysunku 1.1 przedstawia sposób organizacji tej książki oraz możliwe ścieżki jej czytania. Poniższa lista przedstawia treść rozdziałów:
Rozdział 2: „Wprowadzenie do programowania” – jest to podstawowe wprowadzenie w dziedzinę programowania. Zaczynamy od przedstawienia modelu sprzętowego, który pomoże zrozumieć, co tak naprawdę robią programy komputerowe. Następnie przejdziemy do kolejnego przedstawienia ekosystemu deweloperskiego C++, kompilatorów dostępnych w internecie oraz zintegrowanego środowiska programistycznego (IDE). Dalej znajdziemy trzy przykładowe projekty koncentrujące się na obliczeniach w pojedynczej funkcji main z bardzo ograniczonym zbiorem instrukcji i operatorów, a także wprowadzenie do wszechobecnych obiektów std::cout oraz std::cin z biblioteki standardowej, reprezentujących – odpowiednio – wyjście na ekran i wejście z klawiatury. W ostatnim przykładzie wprowadzone zostają std::vector oraz std::string, służące do reprezentacji – odpowiednio – tablic dynamicznych i ciągów tekstowych. Zbiór mechanizmów C++ wprowadzonych w tym rozdziale – choć ograniczony – pozwoli nam na napisanie całkiem sporej grupy prostych programów.
Rysunek 1.1. Struktura tej książki pokazana na diagramie stanu w języku UML. Rozdziały 2 i 3 stanowią wprowadzenie do tematu tej książki. Na poziom zaawansowany przechodzimy wraz z rozdziałami 4, 5 i 6. Dalej mamy rozdział 7 poświęcony arytmetyce komputerowej oraz rozdział 8, który traktuje o programowaniu równoległym. Do dodatku można odwołać się z poziomu każdego z wcześniejszych rozdziałów
Rozdział 3: „Podstawy C++” – rozdział ten stanowi zasadnicze wprowadzenie w podstawowe, ale jednocześnie bardzo istotne konstrukcje C++. Najpierw omawiamy wbudowane typy danych oraz metody ich inicjalizacji. Następnie ponownie przyglądamy się std::vector oraz std::string – tym razem bardziej szczegółowo. W kolejnych sekcjach poznajemy słowo kluczowe auto, wprowadzenie do algorytmów, struktur i klas biblioteki standardowej, tablice o stałej wielkości tworzone za pomocą std::array, referencje, wskaźniki, instrukcje, funkcje (włączając w to funkcje lambda), krotki (ang. tuples) i wiązania strukturalne, a także operatory. Wraz z wieloma małymi przykładami przedstawione są trzy relatywnie proste, ale kompletne projekty: reprezentacja macierzy, klasa do reprezentacji trójmianów kwadratowych oraz projekt zawierający dwie niestandardowe klasy służące do reprezentacji i wymiany walut. Celem tych przykładów, tak jak i całego rozdziału, jest nauczenie, w jaki sposób poprawnie tworzyć i korzystać z pojedynczej klasy wraz z jej danymi i funkcjami składowymi (ang. data and function members).
Rozdział 4: „Zgłębianie programowania obiektowego” – rozdział ten pozwala nam opanować średniozaawansowane i zaawansowane techniki C++. Specjalny nacisk położony jest na OOD i OOP. Po omówieniu głównych paradygmatów OOD i OOP przedstawiona jest anatomia klas wraz z zasadami dostępu. Następnie wprowadzone zostaje przeładowanie operatorów (ang. operator overloading), które od razu przećwiczymy na klasie służącej do reprezentacji liczb zespolonych. W dalszej kolejności omówione są specjalne składowe klasy (ang. special class members). Pojawiają się tematy takie jak głębokie i płytkie kopiowanie (ang. deep and shallow copy) czy korzyści, jakie daje semantyka przenoszenia. Dalej znajdujemy wprowadzenie do szablonów (ang. templates) i programowania generycznego (ang. generic programming), jak zawsze wzbogacone o liczne przykłady. Następnie analizujemy relacje między klasami. Zaprezentowane zostają hierarchie klas, a także dynamiczne i statyczne mechanizmy wirtualne (ang. dynamic and static virtual mechanisms). Analizie poddane zostają relacje „has-a” („posiada”) oraz „is-a” („jest”); nie zabraknie też wskazówek dotyczących tego, kiedy korzystać z której.
Rozdział 5: „Zarządzanie pamięcią” – ta część poświęcona jest różnym aspektom czasu życia obiektu oraz jego zakresowi, jak również tworzeniu i pozbywaniu się obiektów oraz dostępowi do nich. Znaczna większość tego rozdziału poświęcona jest sprytnym (inteligentnym) wskaźnikom (ang. smart pointers). Przedstawione są przykłady kodu pozwalającego na skonstruowanie listy za pomocą wspólnych sprytnych wskaźników, a także wzorzec projektowy fabryka (ang. factory design pattern).
Rozdział 6: „Zaawansowane programowanie obiektowe” – rozdział ten przedstawia kilka dodatkowych, bardzo praktycznych technik i metod programistycznych. Celem jest tu przećwiczenie metod przedstawionych w poprzednich rozdziałach, a także zaznajomienie się z obiektami funkcyjnymi, dopasowywaniem ciągów z wykorzystaniem wyrażeń regularnych, implementacja maszyny stanów oraz wzorca projektowego handle-body. Omówione są zagadnienia takie jak system plików, zegar systemowy i pomiar czasu, zakresy oraz interfejs użytkownika. Zaprezentowane są również takie techniki programistyczne, jak parsowanie wyrażeń, budowanie drzewa, przechodzenie przy użyciu wzorca projektowego wizytatora (ang. visitor design pattern; wzorzec ten występuje także pod nazwą „odwiedzający”), jak również interpretowanie wyrażeń za pomocą wzorca projektowego interpretera (ang. interpreter design pattern).
Rozdział 7: „Arytmetyka komputerowa” – rozdział ten jest podzielony na dwie części, poświęcone odpowiednio liczbom stało- i zmiennoprzecinkowym. Rozpoczyna się on od bardzo podstawowych informacji dotyczących arytmetyki komputerowej, takich jak interpretacja bajtów, systemy konwersji itd. Można więc korzystać z niego w charakterze lekcji wprowadzającej następującej po rozdziale 2. Znaleźć w nim można jednak znacznie więcej niż proste obliczenia. Zagłębimy się w ważne kwestie, takie jak błędy zaokrąglenia, utrata cyfr znaczących oraz standard operacji na liczbach zmiennoprzecinkowych IEEE 754. Zbadamy również kilka zaawansowanych technik programistycznych, np. kiedy kompilator może generować kod w trakcie kompilacji albo jak obliczyć aproksymacje funkcji i jak poprawnie sumować bufory w przypadku wielkich danych.
Rozdział 8: „Podstawy programowania równoległego” – ta część stanowi wprowadzenie w dziedzinę obliczeń równoległych. Po pierwsze, wyjaśnione są tu nowe zjawiska spowodowane zbieżnym, jednoczesnym działaniem wielu rdzeni mikroprocesora, jak również jednoczesnym dostępem do wspólnych obiektów. Następnie przedstawione i przetestowane są trzy nowe komponenty oprogramowania służące do obliczeń równoległych. Najprostszy wywołuje zrównoleglone wersje algorytmów z biblioteki standardowej. C++ zawiera jednak osobną bibliotekę pozwalającą na prowadzenie obliczeń równoległych – w odniesieniu do niej zaprezentowane są zadania asynchroniczne. Ostatnim z omawianych komponentów jest biblioteka OpenMP. Za jej pomocą przetestujemy, w jaki sposób można napisać zrównoleglone sekcje, jak zrównoleglić pętle i w jaki sposób mierzyć czas wykonania z wykorzystaniem przykładów z mnożeniem macierzy.
Dodatek – w tej części przedstawione zostały różnorakie zagadnienia programistyczne. Zaczynamy od krótkiego przedstawienia preprocesora, po którym następuje zwięzłe wprowadzenie do języka C. Mimo iż do nauki języka C++ uprzednia znajomość C nie jest konieczna, pozwala ona jednak zrozumieć jego niektóre własności. Na przykład parametry funkcji main, proste tablice, unie i reprezentacje łańcuchów w wersji języka C spotykamy na co dzień. Przedstawione zostaną również inne zagadnienia, takie jak konsolidacja oraz binarna organizacja programów napisanych w C/C++, graficzne interfejsy użytkownika (ang. graphical user interface – skrót GUI) dostępne dla programów w C++, metody testowania oprogramowania oraz zestaw narzędzi programistycznych składający się z CMake, Gita i GitHuba oraz Profilera.
Jak już wspomniałem, książka ta nie musi być czytana liniowo. Rozdziały zostały zorganizowane w sposób ułatwiający ich osobny użytek. Dodatek do książki, podobnie jak wiele podsumowań i referencji, może być również wykorzystany niezależnie, w ramach prac nad projektami programistycznymi.
Dodatkowo ważne jest, by zdać sobie sprawę, że przedstawienie bardzo szczegółowych zagadnień programistycznych w sposób liniowy jest w zasadzie niemożliwe. Tak więc zrozumienie niektórych konstrukcji wykorzystanych w danym kontekście może nie być łatwe przy pierwszej lekturze, ale zostają one wyjaśnione w dalszych częściach książki.
1.2. Konwencje formatowania
Aby ułatwić poruszanie się po książce, przyjęto w niej kilka różnych sposobów formatowania. I tak:
■ Wypunktowania wykorzystywane są na początku i na końcu niektórych podrozdziałów, by przedstawić wprowadzane w nich kluczowe konstrukcje i/lub technologie.
■ Kod w C++ napisany jest przy użyciu koloru podkreślającego różne kategorie językowe. Jest umieszczony na jasnoniebieskim tle, a jego wiersze zostały ponumerowane, tak jak w poniższym przykładzie.
Listing 1.1. Przykład przyjętego w książce formatowania kodu C++ (z pliku main.cpp)
Wyjście w oknie terminala (zwanego także konsolą lub wierszem poleceń) jest również przedstawione na kolorowym tle, w następujący sposób:
Good day to you!
Kod na białym tle to albo starszy kod napisany w C, tak jak ten przedstawiony w dodatku A.2, lub kod, którego wykorzystanie w C++ z jakichś powodów nie jest rekomendowane, ale który został przedstawiony jako element wyjaśnienia jakiegoś zjawiska. W ten sposób możemy łatwo odróżnić te dwa typy kodu.
Na początku każdego fragmentu kodu znajduje się nagłówek, taki jak „Listing 1.1” w powyższym przykładzie. Opisuje on intencję kryjącą się za danym fragmentem kodu oraz – jeśli kod pochodzi z jednego z projektowych plików – zawiera w nawiasie zapisaną kursywą nazwę pliku zawierającego ten kod, np. main.cpp. Dla zwiększenia czytelności długie fragmenty kodu są często dzielone na kilka krótszych. W takich przypadkach nagłówek pojawia się tylko raz, powyżej pierwszego fragmentu, a numeracja wierszy zachowuje ciągłość do końca całego bloku kodu.
■ W większości listingów kodu wiersze zostały ponumerowane. Odniesienia do tych numerów linii tworzone są przy użyciu nawiasów kwadratowych . Na przykład w listingu 1.1 standardowy nagłówek iostream dołączony jest w wierszu , funkcja main jest zdefiniowana w wierszach , a wiersze zawierają komentarze, które w języku C++ rozpoczynają się od podwójnego ukośnika //. Aby zwrócić uwagę na istotność komentarzy, oznaczone są one kolorem czerwonym.
■ Kod taki jak std::cout, który reprezentuje obiekt ekranu, zapisany jest za pomocą specjalnej czcionki o stałej szerokości. Z kolei nazwy plików – takie jak iostream i main.cpp – zapisane są czcionką pochyłą.
■ Przedstawionych jest wiele funkcji i obiektów należących do biblioteki standardowej. Jak widzieliśmy, można je łatwo odróżnić dzięki prefiksowi std::. Prefiks ten można jednak pominąć, jak w przypadku endl, jeśli dyrektywa using std::endl (co znaczy „end-of-line”, czyli „koniec wiersza”) jest umieszczona na górze kodu. Stąd w przedstawionym kodzie wykorzystane są dwie wersje – w zależności od kontekstu, ale i od ilości dostępnego miejsca.
■ Istnieją dwa rodzaje podrozdziałów:
– przedstawiające nowy materiał,
– przedstawiające przykładowe projekty, zbudowane wokół samodzielnych projektów w celu przećwiczenia konkretnych technik programistycznych.
■ Podrozdziały zawierające dodatkowy lub zaawansowany materiał, których natychmiastowa lektura nie jest konieczna w bieżącym kontekście przedstawionego materiału, są oznaczone za pomocą znaczka .
■ Końcowe fragmenty podrozdziału zawierające ważny materiał często zawierają listę rzeczy „Do zapamiętania”, np.:
Do zapamiętania
■ Preferuj łatwe do odczytania nazwy dla funkcji i obiektów.
■ W kodzie dodawaj znaczące komentarze kładące nacisk na istotę rozwiązania.
W celu reprezentacji algorytmów za pomocą pseudokodu wykorzystywany jest następujący format:
Algorytm 1.1. Przykładowy algorytm w pseudokodzie
---------- --------------
Wejście: Wartość x
Wyjście: Kwadrat z x
1 Ustaw limit:
t ← 1e‐10
2 ...
---------- --------------
■ Istnieją dwa rodzaje odnośników:
– Twarde odnośniki do książek, artykułów pokonferencyjnych, artykułów w czasopismach oraz witryn internetowych, np. (Stroustrup B., The C++ Programming Language 2013; Cppreference.com 2018).
– Miękkie odnośniki (najczęściej linki do stron w sieci), umieszczone w stopce.
■ Rozdziały kończą sekcje „Pytań i odpowiedzi”, które zwykle poszerzają informacje na temat przedstawionych technik.
■ Jeśli wzmiankowane są specjalne kombinacje klawiszy, są one oznaczone w rozpoznawalny sposób, tak jak w przypadku kombinacji Ctrl+Alt+T (która w Linuksie otwiera okno terminala).
1.3. O kodzie i projektach
Jak napomknąłem wcześniej, książka zawiera dziesiątki fragmentów przykładowego kodu. Proces uczenia się polega między innymi na własnoręcznym uruchomieniu tego kodu oraz zrozumieniu, jak on działa. Pora więc na kilka wskazówek dotyczących tego, jak korzystać z kodu:
■ Cały kod dostępny jest w sieci w repozytorium GitHub (https://github.com/BogCyg/BookCpp_PL). Krótkie wprowadzenie do GitHuba znajduje się w podrozdziale A.6.2.
■ Choć kod ten można łatwo skopiować, skompilować i wykonać, najlepszym sposobem na wyrobienie sobie umiejętności programistycznych jest samodzielne pisanie kodu. Może to polegać na jego prostym przepisywaniu lub – jeszcze lepiej – próbie napisania własnej wersji kodu po tym, jak już zrozumie się jego zasadniczą ideę. Gdy nam się to uda, spróbujmy go uruchomić, a jeśli nie jesteśmy czegoś pewni, sprawdźmy przykład z książki. Następnie możemy poprawić nasze rozwiązanie i spróbować ponownie.
■ Najszybszym sposobem na skompilowanie kodu i sprawdzenie, co takiego robi, jest jego skopiowanie (ale jedynie kodu, a nie znaków formatowania czy numerów wierszy) do jednego z systemów pozwalających na kompilację online, o których więcej można przeczytać w podrozdziale 2.4.2. Sprawdzi się to dobrze, ale jedynie w przypadku relatywnie niewielkich projektów – platformy dostępne w sieci mają pewne ograniczenia, jeśli chodzi o działania w zakresie wejścia i wyjścia, np. zapisu do pliku.
■ W przypadku większych projektów rekomendowane podejście polega na samodzielnym budowaniu projektu na swoim komputerze za pomocą własnych narzędzi programistycznych. By to zrobić, potrzebne są dwie rzeczy: w miarę aktualna wersja środowiska C++, takiego jak jakieś IDE, tak jak przedstawione zostało to w podrozdziałach 2.4 i 2.5, oraz narzędzie CMake, które zostało opisane w dodatku w podrozdziale A.6.1. Choć projekty mogą zostać zbudowane lokalnie jedynie przy użyciu IDE, CMake znacznie ułatwia ten proces, uwzględniając wiele systemów operacyjnych, platform programistycznych, narzędzi, wersji źródeł etc. Jest to również rzeczywisty standard branżowy, który dobrze jest znać. Co więcej, niektóre IDE zawierają już CMake.
■ Ponieważ oprogramowanie stale ewoluuje, mogą pojawić się różnice między kodem w książce i kodem w repozytorium. Tak więc w celu wyjaśnienia technik programistycznych i własności C++, punkt odniesienia powinien stanowić kod z książki. Jednak aby zbudować aktualną wersję projektu, należy korzystać z kodu z repozytorium.
Współczesne kompilatory C++ to prawdziwe dzieła sztuki. Na przykład przed skonstruowaniem kompletnej postaci wykonywalnej współczesny kompilator może nawet dokonać prekompilacji fragmentów kodu, a te z kolei mogą następnie od razu zostać wykonane przez kompilator, aby obliczyć rezultaty osiągalne na tym etapie, które następnie można bezpośrednio umieścić w ostatecznym kodzie, tak by uniknąć obliczeń w czasie wykonania. Omówimy również, jak czerpać korzyści z tych funkcjonalności. Jeśli jednak coś jest nie tak i kod się nie skompiluje, niekiedy niełatwo jest ustalić przyczynę takiego stanu rzeczy oraz – co ważniejsze – w jaki sposób rozwiązać ten problem. Kompilator próbuje powiedzieć nam, co dokładnie jest nie tak, ale ponieważ błędy mogą pojawić się wszędzie i na różnych poziomach, komunikat o błędzie może stanowić prawdziwą zagadkę. Kompilatory nadal nie są w stanie powiedzieć nam, co dokładnie powinniśmy zrobić, by wykaraskać się z kłopotów. Być może sztuczna inteligencja da nam tu nowe możliwości. Na tę chwilę dostępne są takie funkcjonalności jak np. koncepty w C++20. Jak zawsze, nieco doświadczenia oraz kontakty z ogólnoświatową społecznością programistyczną to najważniejsze z zasobów, które mogą nam pomóc.
No więc, co powinniśmy zrobić, gdy kod się nie kompiluje? Oto kilka wskazówek dla początkujących:
■ Po pierwsze, nie piszmy długich fragmentów kodu bez sprawdzania, czy się kompilują. Znacznie lepsze podejście polega na zorganizowaniu kodu – o ile to możliwe – na sposób nieco nieliniowy: np. najpierw piszemy pustą funkcję i podajemy jedynie jej parametry, a następnie kompilujemy. Po drugie, dopisujemy kilka linii – również nieliniowo, np. pustą pętlę – i sprawdzamy, czy się kompiluje. Dodajemy więcej linii, kompilujemy itd. Na każdym etapie sprawdzamy, czy pojawiły się błędy. Jeśli tak, modyfikujemy kod w taki sposób, by się kompilował.
■ Gdy natrafiamy na błąd kompilacji (lub konsolidacji), powinniśmy zawsze przewinąć okno z błędami na samą górę i sprawdzić pierwszy wiersz. Pozostałe błędy często są jedynie wynikiem tego, że kompilator potknął się na tym pierwszym.
■ Starannie czytajmy każdy komunikat o błędzie. Jeśli to konieczne, skopiujmy go do edytora i podzielmy na części, co ułatwi nam jego lepsze zrozumienie. Wiadomości o błędach zawierają kody, dzięki którym możemy sprawdzić informacje na temat tych błędów w Internecie. Dla przykładu, komunikat o błędzie może wyglądać następująco: „error C2628: 'MonthDays' followed by 'void' is illegal (did you forget a ';'?)”. No tak, rzeczywiście brakuje średnika ; tuż po definicji struktury MonthDays. Dodanie go w tym przypadku załatwia problem. Ten przypadek był jednak dosyć prosty.
■ Niekiedy przyczyną błędu jest litera, wiersz, funkcja itd. znajdujące się kilka linii przed błędnym wierszem wskazanym przez kompilator. Jeśli utkniemy na jakimś błędzie, spróbujmy zweryfikować kilka wierszy znajdujących się przed wskazanym miejscem.
■ Poszukajmy wskazówek w Internecie. Witryny takie jak http://stackoverflow.com, http://codeguru.com itp. zawierają wiele praktycznych porad udzielonych przez wytrawnych programistów. Zawsze jednak sprawdzajmy datę danego posta – w sieci znajduje się też mnóstwo informacji nie pierwszej świeżości!
■ Jeśli fragment kodu uporczywie się nie kompiluje i nie mamy pojęcia, co jest grane, dezaktywujmy go na jakiś czas. Można to zrobić, ujmując go w komentarz, czyli umieszczając // na początku wiersza lub zamykając go w obrębie instrukcji #if 0 tymczasowo_dezaktywowany_kod #endif. Jeśli reszta się kompiluje, spróbujmy zawęzić dezaktywowany obszar i sprawdźmy ponownie.
■ Kod można napisać na wiele sposobów. Istnieje np. wiele rodzajów pętli, a instrukcja switch-case może zostać napisana przy użyciu if-else itd. Nie porzucajmy jednak kodu, który się nie kompiluje, nim nie znajdziemy i nie zrozumiemy, dlaczego tak się dzieje. Może to zająć trochę czasu, ale w ten sposób nauczymy się czegoś i unikniemy straty czasu w przyszłości, gdy napotkamy podobny problem. Jedynie gdy już zrozumiemy, co zrobiliśmy źle, powinniśmy rozważyć użycie innej lub lepszej konstrukcji językowej.
■ I rzecz ostatnia, ale nie mniej ważna: prośmy o konsultację inne osoby na uczelni lub w miejscu pracy albo pytajmy naszych nauczycieli – wysyłajmy im e-maile, prośmy o radę itd. Zawsze dobrze jest rozmawiać.