Spis treści
Przedmowa 13
Podziękowania 15
Podziękowania do wydania polskiego 17
Wykaz skrótów 19
1. Wprowadzenie 21
1.1. Struktura książki 27
1.2. Konwencje formatowania 30
1.3. O kodzie i projektach 32
2. Wprowadzenie do programowania 35
2.1. Model sprzętowy 35
2.2. Ekosystem tworzenia oprogramowania 39
2.3. Etapy tworzenia oprogramowania 42
2.4. Reprezentacja i uruchamianie algorytmów 44
2.4.1. Reprezentowanie algorytmów 44
2.4.2. Korzystanie z kompilatorów dostępnych w Internecie 46
2.4.3. Struktura programu w C++ 48
2.4.4. Analiza kodu 49
2.4.5. ( ) Budowa linuksowej postaci wykonywalnej 50
2.5. Przykładowy projekt – kalkulator procentu składanego 53
2.5.1. Analiza procentu składanego 54
2.5.2. Implementacja kalkulatora procentu składanego 55
2.5.3. Budowanie i uruchamianie oprogramowania 58
2.6. Przykładowy projekt – zliczanie wystąpień znaków w tekście 59
2.6.1. Analiza problemu i implementacja 59
2.6.2. Uruchomienie kodu C++ za pomocą kompilatora dostępnego w Internecie 61
2.6.3. Kod histogramu – wyjaśnienie 62
2.7. Podsumowanie 65 Pytania i ćwiczenia 65
3. Podstawy C++ 69
3.1. Stałe i zmienne – wbudowane typy danych, ich zakres oraz inicjalizacja 69
3.2. Przykładowy projekt – zbieranie ocen studentów 80
3.3. Nasz przyjaciel debugger 84
3.4. Podstawowa struktura danych – std::vector 87
3.5. Przykładowy projekt – implementowanie macierzy w postaci wektora wektorów 93
3.6. Specjalny wektor do przechowywania tekstu – std::string 95
3.7. Słowo kluczowe auto oraz decltype do automatycznej dedukcji typu 101
3.8. Popularne algorytmy standardowe 104
3.9. Struktury: zbieranie obiektów różnego typu 108
3.10. ( ) Tablice o stałym rozmiarze 113
3.10.1. Wielowymiarowe tablice o stałym rozmiarze 115
3.11. Referencje 117
3.12. ( ) Wskaźniki 121
3.12.1. Dostęp do obiektów za pomocą wskaźników 121
3.13. Instrukcje 126
3.13.1. Bloki instrukcji oraz dostęp do zmiennych – rola nawiasów klamrowych 127
3.13.2. Instrukcje C++ 129
3.13.2.1. Instrukcje warunkowe 129
3.13.2.2. Instrukcje pętli 135
3.13.2.3. Instrukcje pomocnicze – continue oraz break 140
3.13.2.4. Instrukcja goto 142
3.13.2.5. Strukturalna obsługa wyjątków – instrukcja try-catch 142
3.14. Funkcje 144
3.14.1. Anatomia funkcji w C++ 145
3.14.2. Przekazywanie argumentów do i z funkcji 150
3.14.2.1. Przekazywanie argumentów przez kopię (semantyka wartości) 151
3.14.2.2. Pośrednie przekazywanie argumentów przez referencję 153
3.14.2.3. ( ) Przekazywanie przez wskaźnik 155
3.14.3. Mechanizm wywoływania funkcji i funkcje wbudowane 157
3.14.4. Funkcje rekurencyjne i stos wywołań 159
3.14.5. Przeciążanie funkcji – rozwiązywanie widoczności za pomocą przestrzeni nazw 160
3.14.6. Funkcje lambda 163
3.14.7. ( ) Więcej na temat funkcji lambda 168
3.14.8. ( ) Wskaźniki do funkcji 174
3.14.9. ( ) Funkcje w środowisku obiektowym 176
3.15. Przykładowy projekt – opakowywanie obiektów strukturą z konstruktorem 178
3.15.1. EMatrix w środowisku obiektowym 181
3.15.2. Podstawowe operacje z użyciem EMatrix 182
3.15.3. Operacje wejścia i wyjścia na EMatrix 184
3.15.4. Proste operacje matematyczne na macierzy EMatrix 186
3.15.5. Organizowanie plików projektu i uruchamianie aplikacji 188
3.15.6. Rozszerzanie inicjalizacji macierzy za pomocą prostego generatora liczb losowych 191
3.16. Przykładowy projekt – reprezentowanie równań kwadratowych 193
3.16.1. Definicja klasy do reprezentowania wielomianów kwadratowych 194
3.16.2. Implementacja składowych TQuadEq 194
3.16.3. TQuadEq w akcji 205
3.17. Przykładowy projekt – krotki i powiązania strukturalne do konwertowania liczb rzymskich 206
3.17.1. Więcej o std::tuple i powiązaniu strukturalnym 210
3.17.2. Jak napisać test jednostkowy oprogramowania 212
3.17.3. Automatyzowanie testów jednostkowych – z użyciem standardowej biblioteki liczb losowych 213
3.18. Projekt przykładowy – tworzenie komponentu kalkulatora walutowego 216
3.18.1. Analiza problemu wymiany walut 216
3.18.2. Projekt oprogramowania CurrencyCalc 219
3.18.3. Klasa TCurrency reprezentująca rekordy walut 220
3.18.3.1. Manipulatory wejścia/wyjścia C++ 223
3.18.4. Klasa TCurrencyExchanger do wymiany walut 225
3.18.5. Łączenie wszystkiego w całość – kompletny program wymiany walut 230
3.19. Operatory 236
3.19.1. Podsumowanie operatorów C++ 239
3.19.2. Dalsze uwagi na temat operatorów 263
3.20. Podsumowanie 264
Pytania i ćwiczenia 265
4. Zgłębianie programowania obiektowego 269
4.1. Podstawowe reguły oraz filozofia projektowania i programowania obiektowego 270
4.2. Anatomia klasy 274
4.2.1. Konwencja nazewnictwa i samodokumentujący się kod 277
4.3. Reguły uzyskiwania dostępu do składowych klasy 277
4.4. Przykładowy projekt – klasa TComplex do przeciążania operatorów 280
4.4.1. Definicja klasy TComplex 281
4.4.2. Definicja składowych klasy TComplex 287
4.4.3. Funkcje testujące dla klasy TComplex 289
4.5. Więcej o referencjach 293
4.5.1. Referencje prawostronne i przekazywania 293
4.5.2. Referencje kontra wskaźniki 298
4.5.3. Pułapki z referencjami 299
4.6. Przykładowy projekt – opanowywanie składowych klasy z użyciem klasy TheCube 301
4.6.1. Automatyczna kontra jawna definicja konstruktorów 302
4.6.2. Układ i semantyka obiektu TheCube 312
4.6.3. Semantyka kopiowania płytkiego i głębokiego 313
4.6.4. Semantyka konstruktora przenoszącego i przypisania przenoszącego 314
4.6.5. Implementacja operatorów strumieniowania dla TheCube 316
4.6.6. Sprawdzanie TheCube 318
4.7. Projekt przykładowy – przenoszenie EMatrix do klasy 321
4.7.1. Definicja klasy EMatrix 322
4.7.2. Implementacja operatorów strumieniowania klasy 324
4.7.3. Implementacja operatorów arytmetycznych 328
4.7.4. Testowanie operacji na macierzach 330
4.8. Wprowadzenie do szablonów i programowania uogólnionego 332
4.8.1. Uogólnianie klasy przy użyciu szablonów 332
4.8.2. ( ) Specjalizacje szablonów 337
4.8.3. Funkcje szablonowe i sprawdzanie typu 338
4.8.4. Przykładowy projekt – projektowanie klas szablonowych przy użyciu TStack 340
4.8.4.1. Projekt i implementacja klasy TStackFor 342
4.8.4.2. Testowanie TStack 345
4.8.5. Szablonowe funkcje składowe 347
4.9. Relacje między klasami – „zna”, „ma” oraz „jest” 350
4.10. Przykładowy projekt – rozszerzanie funkcjonalności poprzez dziedziczenie klas z użyciem TComplexQuadEq 357
4.11. Funkcje wirtualne i polimorfizm 363
4.12. ( ) Więcej na temat mechanizmu wirtualnego 370
4.13. ( ) Ciekawie rekurencyjny wzorzec szablonu i statyczny polimorfizm 373
4.14. ( ) Klasy domieszki 377
4.15. Przykładowy projekt – klasa TLongNumberFor do wydajnego przechowywania liczb o dowolnej długości 379
4.15.1. Reprezentacja Binary-Coded Decimal 381
4.15.2. Kolejność bajtów 381
4.15.3. Definicja klasy TLongNumberFor 382
4.15.3.1. Operacje konwersji typu 385
4.15.3.2. Funkcja testująca TLongNumberFor 391
4.15.4. Projektowanie klas dla numeru PESEL 392
4.15.4.1. Agregowanie klasy PESEL 393
4.15.4.2. Odziedziczona klasa PESEL 395
4.15.4.3. Organizacja projektu LongNumber 396
4.15.5. ( ) Rozszerzanie funkcjonalności klasy TLongNumberFor z użyciem wzorca pełnomocnika 398
4.15.5.1. Definicja klasy Proxy 399
4.15.5.2. Testowanie funkcjonalności klasy TLongNumberFor z użyciem wzorca pełnomocnika 402
4.16. Silne typy 403
4.17. Podsumowanie 405 Pytania i ćwiczenia 405
5. Zarządzanie pamięcią 409
5.1. Rodzaje magazynów danych 409
5.2. Dynamiczny przydział pamięci – jak unikać wycieków pamięci 416
5.2.1. Wprowadzenie do inteligentnych wskaźników i zarządzania zasobami 419
5.2.1.1. RAII i odwijanie stosu 420
5.3. Inteligentne wskaźniki – omówienie z przykładami 421
5.3.1. ( ) Więcej o std::unique_ptr 421
5.3.1.1. Kontekst użycia std::unique_ptr 421
5.3.1.2. Wzorzec projektowy metody wytwórczej 435
5.3.1.3. Niestandardowe usuwanie unique_ptr 438
5.3.1.4. Konstrukcje do unikania podczas korzystania z unique_ptr 439
5.3.2. ( ) Więcej o shared_ptr i weak_ptr 440
5.4. Podsumowanie 443
Pytania i ćwiczenia 443
6. Zaawansowane programowanie obiektowe 445
6.1. Obiekty funkcyjne 445
6.2. Projekt przykładowy – rozszerzanie o wyszukiwanie walut w plikach XML oraz korzystanie z maszyny stanów i wyrażeń regularnych za pomocą
biblioteki regex 452
6.2.1. Dopasowywanie do wzorca za pomocą biblioteki wyrażeń regularnych 453
6.2.2. Wzorzec maszyny stanów 455
6.2.3. Implementowanie klasy rozszerzonej 456
6.2.4. Rozszerzenie projektu – wczytywanie informacji o walutach z internetu 463
6.2.5. Uruchamianie rozszerzonej wersji CurrencyCalc 469
6.2.6. Tworzenie biblioteki statycznej 474
6.2.7. System plików C++ 475
6.2.8. Interfejs użytkownika 484
6.2.8.1. Definicja klasy CC_GUI 485
6.2.8.2. Definicje składowych klasy CC_GUI i mechanizm wywołania zwrotnego 489
6.2.8.3. Uruchamianie aplikacji z GUI 497
6.3. Zegary systemowe i pomiar czasu 498
6.4. ( ) Mierzenie czasu wykonywania funkcji 502
6.5. Klasa Range 505
6.5.1. Programowanie funkcyjne i biblioteka std::ranges 510
6.6. Przykładowy projekt – parsowanie wyrażeń 511
6.6.1. Definiowanie wyrażeń języka i zasad gramatyki formalnej 512
6.6.2. Projektowanie biblioteki przetwarzania wyrażeń 515
6.6.3. Pierwszy interpreter poleceń 516
6.6.4. Budowanie drzewa składniowego z użyciem wzorca projektowego kompozytu 520
6.6.4.1. Wzorzec projektowy kompozytu do definiowania węzłów drzewa 521
6.6.4.2. Implementacja hierarchii TNode i współpraca z wizytatorami 522
6.6.4.3. Implementacja klasy ValueLeafNode 525
6.6.4.4. Implementacja klasy BinOperator 526
6.6.4.5. Implementacja klasy PlusOperator 528
6.6.4.6. Głębokie kopiowanie obiektów węzła – mechanizm prototypowania 529
6.6.5. Interpreter do budowy drzew składniowych 531
6.6.6. Stos dla inteligentnych wskaźników 536
6.6.7. Przechodzenie po drzewach za pomocą wzorca projektowego wizytatora 540
6.6.7.1. Wizytator ewaluujący wyrażenie 543
6.6.7.2. Wizytator wypisujący wyrażenie 545
6.6.8. Testowanie interpreterów 547
6.6.9. Reprezentowanie wyrażeń na stosie w odwrotnej notacji polskiej 551
6.6.9.1. Odwrócona notacja polska 551
6.6.9.2. Algorytm do ewaluowania wyrażenia RPN 552
6.7. Podsumowanie 558
Pytania i ćwiczenia 558
7. Arytmetyka komputerowa 563
7.1. Reprezentacja wartości całkowitej 563
7.1.1. Algorytm konwersji podstawy 565
7.1.2. Reprezentacje szesnastkowe i ósemkowe 566
7.1.3. Dodawanie binarne 567
7.1.4. Wartości ujemne i odejmowanie 568
7.2. Operacje przesunięcia binarnego 569
7.1.5. Flagi kontroli arytmetycznej 570
7.1.6. Reprezentowanie ułamków 573
7.2. Operacje przesunięcia binarnego 577
7.3. ( ) Przykładowy projekt – model programowy dla reprezentacji stałoprzecinkowej 578
7.3.1. Liczby stałoprzecinkowe i ich arytmetyka 579
7.3.2. Definicja klasy FxFor 579
7.3.3. Wybrane metody klasy FxFor 587
7.3.4. Zastosowania dla FxFor 593
7.4. Reprezentacje liczb zmiennoprzecinkowych 596
7.4.1. Reprezentacja liczb w formacie zmiennoprzecinkowym 598
7.4.2. Rozkład liczb zmiennoprzecinkowych i konsekwencje obliczeniowe 602
7.4.3. ( ) Błąd przybliżenia wartości rzeczywistej przy użyciu reprezentacji zmiennoprzecinkowej 606
7.4.4. Standard IEEE 754 dla arytmetyki zmiennoprzecinkowej 608
7.4.5. Standardowy model operacji zmiennoprzecinkowych 611
7.4.6. Obliczenia ze świadomością o błędach numerycznych 611
7.4.7. Przykładowy projekt – ewaluacja algorytmów sumujących 617
7.4.8. Przykładowy projekt – metoda Newtona do znajdywania miejsc zerowych funkcji 623
7.4.8.1. Funkcja do obliczania pierwiastka kwadratowego na podstawie iteracji Newtona 627
7.5. Podsumowanie 629
Pytania i ćwiczenia 630
8. Podstawy programowania równoległego 633
8.1. Podstawowe zagadnienia związane z obliczeniami równoległymi 633
8.2. Dodawanie równoległości do algorytmów standardowych 637
8.3. Uruchamianie zadań asynchronicznych 640
8.4. Zrównoleglanie za pomocą biblioteki OpenMP 643
8.4.1. Uruchamianie zespołu wątków i zapewnianie ochrony wyłącznego dostępu 644
8.4.2. Równoległa pętla for i operacje redukcji 646
8.4.3. Równoległość dla dużych ilości danych 649
8.5. Podsumowanie 658
Pytania i ćwiczenia 658
Dodatek 661
A.1. Dyrektywy preprocesora 661
A.2. Krótkie wprowadzenie do języka C 666
A.2.1. Tablice wbudowane 667
A.2.1.1. Wielowymiarowe tablice o stałym rozmiarze 670
A.2.2. Przekazywanie tablic do funkcji – funkcja main 671
A.2.3. Struktury C 676
A.2.4. Funkcje i operacje wejścia/wyjścia w C 677
A.2.5. Unie 678
A.2.6. Operacje wykonywane na pamięci i ciągach znaków 680
A.2.7. Łączenie kodu C i C++ 686
A.3. Łączenie i organizacja binarna obiektów C/C++ 686
A.4. Graficzny interfejs użytkownika i interfejs sieci Web dla projektów C++ 689
A.5. Konwertowanie wartości bin, oct, dec i hex za pomocą FixBinCalc 691
A.6. Zestaw narzędzi programistycznych 692
A.6.1. Narzędzie do generowania projektów (CMake) 692
A.6.2. Systemy kontroli wersji i repozytoria 697
A.6.3. Profiler 698
A.7. Testowanie oprogramowania 699
A.8. Podsumowanie 706
Pytania i ćwiczenia 706
Bibliografia 709
Indeks 713
Do zapamiętania
Wybieraj sensowne i czytelne nazwy (identyfikatory) dla obiektów (zmiennych, stałych, funkcji, klas itd.).
Wybieraj typy danych odpowiednie do obliczeń.
Staraj się używać typów ze znakiem.
Zawsze inicjalizuj zmienne. W przypadku inicjalizacji wartością zero wystarczy dodać { }.
3.2. Przykładowy projekt – zbieranie ocen studentów
Jak dotąd poznaliśmy sposób definiowania i korzystania z pojedynczych zmiennych i stałych różnego typu. Dosyć często jednak musimy przechowywać wiele takich obiektów jeden po drugim. Czasami nie wiemy, ile takich obiektów będziemy potrzebować. Aby to zrobić, możemy skorzystać ze struktury danych nazywanej wektorem (lub tablicą)10. Wektor zawierający cztery obiekty numeryczne został przedstawiony na rysunku 3.2.


4



Rysunek 3.2. Wektor składa się z pewnej liczby obiektów zajmujących ciągłą przestrzeń w pamięci. Tutaj mamy N = 4 obiektów, które mogą przechowywać dane zmiennoprzecinkowe. Dostęp do każdej komórki wektora możemy uzyskać poprzez podanie jej indeksu. Pierwszy element ma indeks 0, a ostatni dostępny jest pod indeksem N – 1
Zacznijmy od tego, że wektor jest strukturą danych o następujących cechach:
Zawiera obiekty tego samego typu.
Obiekty są rozmieszczone w pamięci w sposób zwarty, tj. są one ułożone jeden obok drugiego (bez odstępów).
10 Termin tablica zarezerwowany jest zwykle dla struktur o stałym rozmiarze (podrozdział 3.10), natomiast termin wektor używany jest dla obiektów, których rozmiar dynamicznie się zmienia.
3.2. Przykładowy projekt – zbieranie ocen studentów
Dostęp do każdego obiektu w wektorze możemy uzyskać poprzez dostarczenie jego indeksu do operatora indeksu []. Prawidłowe indeksy to wartości od 0 do N – 1, gdzie N oznacza liczbę elementów w wektorze.
W języku C++ wektory możemy tworzyć na wiele sposobów, jednak najbardziej elastycznym jest użycie obiektu std::vector z biblioteki standardowej, który:
Może być na początku pusty.
Może zostać uzupełniony nowymi obiektami, gdy staną się one dostępne.
Zawiera wiele przydatnych funkcji.
Zanim powiemy sobie szczegółowo, co może zrobić dla nas std::vector, zacznijmy od prostego programu przykładowego z listingu 3.3. Jego celem jest zebranie i przechowanie ocen studentów, a następnie obliczenie ich zaokrąglonej średniej.
Listing 3.3. Program do zbierania i obliczania średniej ocen studentów (w StudentGrades, main.cpp)
1 #include <vector> // Nagłówek pozwalający użyć std::vector
2 #include <iostream> // Nagłówki dla wejścia i wyjścia
3 #include <iomanip> // oraz formatowania wyjścia
4 #include <cmath> // Dla funkcji matematycznych
6 // Wprowadzamy poniższe, aby móc pisać vector zamiast std::vector
7 using std::cout, std::cin, std::endl, std::vector;
int main()
{
cout << "Enter your grades" << endl;
15 vector< double > studentGradeVec; // Pusty wektor wartości double
17 // Zbierz oceny studentów 18 for( ;; )
19 { 20 double grade {};
22 cin >> grade;
24 // Jeśli OK, wstaw nową ocenę na końcu wektora
25 if( grade >= 2.0 && grade <= 5.0 )
26 studentGradeVec.push_back( grade );
29 cout << "Enter more? [y/n] "; 30 char ans {};
cin >> ans;
33 if( ans == 'n' || ans == 'N' )
34 break; // sposób na wyjście z pętli
35 }
4.8. Wprowadzenie do szablonów i programowania uogólnionego
Widzieliśmy już klasy, które mogą działać z różnymi typami dostarczanymi podczas definiowania konkretnych obiektów. Najlepszym przykładem może być klasa std::vector, która może zostać zdefiniowana dla konkretnego typu obiektów do przechowywania, np. std::vector< int > do przechowywania int, std::vector< std::string > do przechowywania std::string itd. Jest to możliwe, ponieważ std::vector został zdefiniowany jako klasa szablonowa (ang. template class). W tym podrozdziale wyjaśnimy, co to oznacza i jak możemy definiować nasze własne klasy i funkcje szablonowe. Ściśle mówiąc, w tej części zapoznamy się z:
Funkcjami i klasami szablonowymi.
Sposobami konwertowania istniejącej funkcji lub klasy na bardziej ogólną wersję szablonową.
Sposobami projektowania klasy szablonowej i organizacji kodu szablonu.
Parametrami szablonu i sposobom ich dostarczania.
Ograniczaniem dozwolonych parametrów szablonu z użyciem warunków.
Specjalizacjami szablonów.
Funkcjami składowymi szablonów i sposobie ich wykorzystywania.
Dodatkowymi informacjami o constexpr , static_assert i wykonywaniu kodu w czasie kompilacji.
4.8.1. Uogólnianie klasy przy użyciu szablonów
W podrozdziale 4.4 omówiliśmy szczegóły klasy TheCube . Jest to samowystarczalna klasa, która jest w stanie przechować wartości typu double , zorganizowane w trójwymiarową kostkę danych. Z kolei w podrozdziale 4.4 zobaczyliśmy samowystarczalną klasę TComplex , która modeluje liczby zespolone. Implementuje ona również kilka operacji specyficznych dla dziedziny liczb zespolonych. Wyobraźmy sobie teraz, że chcemy utworzyć trójwymiarową kostkę danych, ale zamiast double chcemy przechowywać w nim wartości typu TComplex . Co możemy zrobić? Najprostszym rozwiązaniem i jednocześnie jedynym dostępnym w językach takich jak C jest skopiowanie kodu TheCube , wklejenie go i nadanie mu innej nazwy (przykładowo TComplexCube ), a następnie zamiana wszystkich wystąpień double na TComplex . Później, jeśli będzie nam potrzebna kostka tekstu, możemy powtórzyć tę procedurę, zamieniając tym razem wystąpienia double na string itd. Niestety, jeśli będziemy postępować w ten sposób, otrzymamy wiele niemal identycznych klas, różniących się tylko w kilku miejscach, w których pojawia się typ double . Co, jeśli zdecydujemy się wtedy usprawnić niewielki fragment kodu? Kuszące wydaje się utworzenie wspólnego wzorca: szablonu, w którym będziemy mogli wskazać miejsca do zmiany. Gdy obiekt takiej klasy będzie instancjonowany, będziemy mogli podać konkretny typ do wykorzystania w tych miejscach. Taki mechanizm istnieje w C++ i nazywany jest klasami szablonowymi. Pozwala nam on definiować klasy, które są wystarczająco uniwersalne, aby mogły działać z obiektami dowolnego typu – nawet takiego, który
4.8. Wprowadzenie do szablonów i programowania uogólnionego
dopiero utworzymy. Takie podejście programistyczne, które pasuje (prawie) do każdego typu, nazywamy programowaniem uogólnionym (ang. generic programming). W podobny sposób możemy utworzyć funkcje szablonowe – jako funkcje samodzielne lub składowe klasy (punkt 4.8.5).
W tym punkcie nauczymy się:
Konwertowania nieszablonowej klasy do jej szablonowej wersji.
Tworzenia instancji szablonu.
Aby zobaczyć, w jaki sposób możemy utworzyć klasę ogólną z użyciem mechanizmu szablonów C++, napiszmy szablonową wersję klasy TheCube , której definicja widnieje w tabeli 4.3. Aby ułatwić to zadanie, popracujmy z mniejszą wersją tej klasy o nazwie TinyCube, zdefiniowanej w listingu 4.12.
Listing 4.12. Definicja TinyCube z podkreślonymi specyficznymi typami (w CppBookCode, TinyCube.cpp)
1 class TinyCube 2 { 3 public:
5 static const int kDims = 3; // to samo dla wszystkich obiektów tej klasy
7 enum EDims { kx, ky, kz }; // skróty dla 3 wymiarów
vector< double > fDataBuf; // wektor do przechowywania danych
array< int, kDims > fDim; // przechowuje zakres każdego wymiaru
17 // Konstruktor parametryczny ‐ dx, dy, dz muszą być > 0 18 TinyCube( const int dx, const int dy, const int dz ) 19 : fDim{ dx, dy, dz }, fDataBuf( dx * dy * dz, 0.0 )
{
21 assert( dx > 0 && dy > 0 && dz > 0 );
22 assert( fDataBuf.size() == dx * dy * dz );
}
25 // Destruktor niczego nie robi. Dane zostaną usunięte przez wektor, jednak
26 // ~TinyCube() {} // jawna definicja destruktora wykluczałaby semantykę przekazywania
public:
30 // Uzyskaj dostęp do elementów przez referencję – dwukierunkowe
31 auto & Element( const int x, const int y, const int z ) 32 {
33 const auto offset = ( z * fDim[ ky ] + y ) * fDim[ kx ] + x;
34 return fDataBuf[ offset ]; // indeks zwracany przez referencję 35 } 36 37 };
6.2.8. Interfejs użytkownika
W poprzednich punktach zobaczyliśmy ewolucję projektu CurrencyExchange: jego dwie wersje, obie ujęte w hierarchicznych przestrzeniach nazw CurrExchanger i zagnieżdżonej w niej przestrzeni
. CurrExchanger definiuje bardzo podstawową funkcjonalność, podczas gdy rozszerza ją pod kątem automatycznego wczytywania informacji walutowych z internetu. Obie zostały przetestowane jako samodzielne aplikacje konsolowe. Zobaczyliśmy również, w jaki sposób możemy podzielić projekt na bibliotekę statyczną o nazwie CurrencyCalc_Lib oraz inny komponent, CurrencyCalc_Terminal, który buduje aplikację konsolową (punkt 6.2.6). Czas teraz pójść o krok dalej i dodać do tego projektu graficzny interfejs użytkownika (ang. graphical user interface, GUI). Z tego powodu utworzymy trzeci komponent oprogramowania do uruchamiania CurrencyExchanger jako aplikacji GUI: CurrencyCalc_GUI. Diagram komponentów UML na rysunku 6.8 zmieni się teraz na diagram z rysunku 6.9.


















OnLine_CurrExchanger




Rysunek 6.9. Diagram komponentów UML dla różnych komponentów oprogramowania używanych w naszym projekcie CurrencyCalc. Ponieważ silnik, tj. główny moduł, wymiany walut został wyodrębniony do osobnej biblioteki, możemy w prosty sposób skonstruować aplikację konsolową lub GUI bez żadnych zakłóceń
W tym punkcie interesuje nas głównie komponent CurrencyCalc_GUI, który będzie w stanie rysować graficzne widżety, a także przetwarzać zdarzenia systemowe, co za chwilę zobaczymy. Jednak komponenty GUI są silnie zależne od rodzaju systemu operacyjnego, z którym pracujemy. Z jednej strony oznacza to, że gdybyśmy próbowali napisać nasz komponent CurrencyCalc_GUI dla dwóch lub trzech różnych systemów operacyjnych, musielibyśmy napisać dwie lub trzy dosyć różne wersje tego komponentu. Z drugiej strony, nawet jeśli nasze zadanie byłoby dużo prostsze i celem byłby wyłącznie jeden system operacyjny, korzystanie z surowego API nie byłoby najbardziej produktywną opcją. Znacznie lepszym rozwiązaniem jest skorzystanie z bibliotek GUI tworzących rodzaj interfejsu między
485 6.2. Projekt przykładowy – rozszerzanie o wyszukiwanie walut w plikach XML...
aplikacją użytkownika, taką jak CurrencyCalc, i jednym lub więcej systemami operacyjnymi. Dodatek A.4 zawiera krótkie omówienie dostępnych bibliotek GUI dla języka C++. Niestety każda róża ma swój cierń, więc każda z nich ma swoje własne dziwactwa i ograniczenia. W naszym przykładzie zdecydowaliśmy się przetestować bibliotekę FLTK, głównie dlatego, że jest darmowa, napisana w całości w C++ i działa na wielu systemach.
Spójrzmy więc na diagram komponentów UML na rysunku 6.9. Najbardziej istotną zmianą jest to, że oddzieliliśmy wszystkie komponenty ściśle związane z wymianą walut od operacji wejścia/wyjścia powiązanych z użytkownikiem. Komponenty te są teraz ujęte w osobnych bibliotekach, podczas gdy akcje użytkownika przeniesione są do różnych projektów aplikacji. W rezultacie uzyskujemy następujące istotne cele:
Czytelność kodu – jest teraz znacznie łatwiej skoncentrować się na różnych poziomach naszego systemu. Inaczej mówiąc, podczas pracy nad dalszymi mechanizmami dla wymiany walut będziemy pracować wyłącznie z biblioteką CurrencyCalc_Lib. Podczas pracy nad interfejsami użytkownika nie będziemy musieli zmieniać niczego w bibliotece wymiany walut.
Ponowne wykorzystanie kodu – znacznie łatwiej jest teraz o ponowne wykorzystanie naszej biblioteki CurrencyCalc_Lib w dowolnym innym projekcie (z interfejsem lub bez interfejsu użytkownika), który wymaga akcji wymiany walut. Również znacznie prościej będzie można teraz rozszerzyć funkcjonalność tej biblioteki.
Dlatego zgodnie z regułą nie powinniśmy używać w naszych klasach obiektów cout i cin. Operacje wejścia/wyjścia powinny być implementowane za pomocą zewnętrznych przeciążonych operator << i
Nasz kod będzie wyświetlał grafikę. Aby tak się stało, musimy zainstalować bibliotekę FLTK. Szczegóły tej operacji opisane są na stronie projektu FLTK (FLTK 2019) i zależą od naszego systemu operacyjnego oraz zainstalowanych narzędzi. Jednak pomocny powinien być poniższy przewodnik:
1. Pobierz FLTK ze strony internetowej tego projektu i wypakuj zawartość w taki sposób, aby katalogi FLTK i CurrencyCalc znajdowały się na tym samym poziomie.
2. Przejdź do katalogu FLTK i uruchom [kropka] w celu zbudowania projektu FLTK w katalogu bieżącym.
3. Otwórz projekt i zbuduj bibliotekę. Po ukończeniu katalog FLTK/Lib/Debug powinien zawierać bibliotekę fltkd.lib (zweryfikuj jej datę i czas budowy).
6.2.8.1. Definicja klasy CC_GUI
Listing 6.8 pokazuje definicję klasy CC_GUI, które definiuje interfejs użytkownika dla naszej aplikacji CurrencyCalc, zaś listing 6.9 zawiera jej pełną implementację. Omówmy pokrótce sposób ich działania. Jak zwykle na początku nowej definicji powinniśmy poinformować kompilator o tym, z których klas zamierzamy korzystać. W tym przykładzie dokonujemy tego na dwa sposoby. Najpierw w liniach [1-5] dołączane są pliki nagłówkowe, zawierające definicje pewnych kontenerów biblioteki standardowej, a także definicji klasy