Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00286 005393 13645174 na godz. na dobę w sumie
Programowanie. Teoria i praktyka z wykorzystaniem C++ - książka
Programowanie. Teoria i praktyka z wykorzystaniem C++ - książka
Autor: Liczba stron: 1112
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-2233-7 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c++ - programowanie
Porównaj ceny (książka, ebook, audiobook).

Zaczerpnij wiedzę o C++ od samego twórcy języka!

Jeśli zależy Ci na tym, aby zdobyć rzetelną wiedzę i perfekcyjne umiejętności programowania z użyciem języka C++, powinieneś uczyć się od wybitnego eksperta i twórcy tego języka - Bjarne Stroustrupa, który jako pierwszy zaprojektował i zaimplementował C++. Podręcznik, który trzymasz w ręku, daje Ci szansę odkrycia wszelkich tajników tego języka, obszernie opisanego w międzynarodowym standardzie i obsługującego najważniejsze techniki programistyczne. C++ umożliwia pisanie wydajnego i eleganckiego kodu, a większość technik w nim stosowanych można przenieść do innych języków programowania.

Książka 'Programowanie w C++. Teoria i praktyka' zawiera szczegółowy opis pojęć i technik programistycznych, a także samego języka C++, oraz przykłady kodu. Znajdziesz tu również omówienia zagadnień zaawansowanych, takich jak przetwarzanie tekstu i testowanie. Z tego podręcznika dowiesz się, na czym polega wywoływanie funkcji przeciążonych i dopasowywanie wyrażeń regularnych. Zobaczysz też, jaki powinien być standard kodowania. Poznasz sposoby projektowania klas graficznych i systemów wbudowanych, tajniki implementacji, wykorzystywania funkcji oraz indywidualizacji operacji wejścia i wyjścia. Korzystając z tego przewodnika, nauczysz się od samego mistrza pisać doskonałe, wydajne i łatwe w utrzymaniu programy.

Wykorzystaj wiedzę Bjarne Stroustrupa i pisz profesjonalne programy w C++!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Programowanie. Teoria i praktyka z wykorzystaniem C++ Autor: Bjarne Stroustrup T³umaczenie: £ukasz Piwko ISBN: 978-83-246-2233-7 Tytu³ orygina³u: Principles and Practice Using C++ Format: 172×245, stron: 1112 Zaczerpnij wiedzê o C++ od samego twórcy jêzyka! (cid:129) Jak zacz¹æ pracê w zintegrowanym œrodowisku programistycznym? (cid:129) Jak profesjonalnie tworzyæ programy u¿ytkowe? (cid:129) Jak korzystaæ z biblioteki graficznego interfejsu u¿ytkownika? Jeœli zale¿y Ci na tym, aby zdobyæ rzeteln¹ wiedzê i perfekcyjne umiejêtnoœci programowania z u¿yciem jêzyka C++, powinieneœ uczyæ siê od wybitnego eksperta i twórcy tego jêzyka – Bjarne Stroustrupa, który jako pierwszy zaprojektowa³ i zaimplementowa³ C++. Podrêcznik, który trzymasz w rêku, daje Ci szansê odkrycia wszelkich tajników tego jêzyka, obszernie opisanego w miêdzynarodowym standardzie i obs³uguj¹cego najwa¿niejsze techniki programistyczne. C++ umo¿liwia pisanie wydajnego i eleganckiego kodu, a wiêkszoœæ technik w nim stosowanych mo¿na przenieœæ do innych jêzyków programowania. Ksi¹¿ka „Programowanie w C++. Teoria i praktyka” zawiera szczegó³owy opis pojêæ i technik programistycznych, a tak¿e samego jêzyka C++, oraz przyk³ady kodu. Znajdziesz tu równie¿ omówienia zagadnieñ zaawansowanych, takich jak przetwarzanie tekstu i testowanie. Z tego podrêcznika dowiesz siê, na czym polega wywo³ywanie funkcji przeci¹¿onych i dopasowywanie wyra¿eñ regularnych. Zobaczysz te¿, jaki powinien byæ standard kodowania. Poznasz sposoby projektowania klas graficznych i systemów wbudowanych, tajniki implementacji, wykorzystywania funkcji oraz indywidualizacji operacji wejœcia i wyjœcia. Korzystaj¹c z tego przewodnika, nauczysz siê od samego mistrza pisaæ doskona³e, wydajne i ³atwe w utrzymaniu programy. (cid:129) Techniki programistyczne (cid:129) Infrastruktura algorytmiczna (cid:129) Biblioteka standardowa C++ (cid:129) Instrukcje steruj¹ce i obs³uga b³êdów (cid:129) Implementacja i wykorzystanie funkcji (cid:129) Kontrola typów (cid:129) Interfejsy klas (cid:129) Indywidualizacja operacji wejœcia i wyjœcia (cid:129) Projektowanie klas graficznych (cid:129) Wektory i pamiêæ wolna (cid:129) Kontenery i iteratory (cid:129) Programowanie systemów wbudowanych (cid:129) Makra Wykorzystaj wiedzê Bjarne Stroustrupa i pisz profesjonalne programy w C++! Spis treĂci 19 21 22 23 23 25 26 27 28 29 29 32 34 34 35 35 35 36 37 37 38 3 WstÚp Sïowo do studentów Sïowo do nauczycieli Pomoc PodziÚkowania Uwagi do czytelnika 0.1. Struktura ksiÈĝki 0.1.1. Informacje ogólne 0.1.2. mwiczenia, praca domowa itp. 0.1.3. Po przeczytaniu tej ksiÈĝki 0.2. Filozofia nauczania i uczenia siÚ 0.2.1. KolejnoĂÊ tematów 0.2.2. Programowanie a jÚzyk programowania 0.2.3. PrzenoĂnoĂÊ 0.3. Programowanie a informatyka 0.4. KreatywnoĂÊ i rozwiÈzywanie problemów 0.5. Uwagi dla autorów 0.6. Bibliografia 0.7. Noty biograficzne Bjarne Stroustrup Lawrence „Pete” Petersen 4 SPIS TRE¥CI Rozdziaï 1. Komputery, ludzie i programowanie 1.1. WstÚp 1.2. Oprogramowanie 1.3. Ludzie 1.4. Informatyka 1.5. Komputery sÈ wszÚdzie 1.5.1. Komputery z ekranem i bez 1.5.2. Transport 1.5.3. Telekomunikacja 1.5.4. Medycyna 1.5.5. Informacja 1.5.6. SiÚgamy w kosmos 1.5.7. I co z tego 1.6. Ideaïy dla programistów CzÚĂÊ I Podstawy Rozdziaï 2. Witaj, Ăwiecie! 2.1. Programy 2.2. Klasyczny pierwszy program 2.3. Kompilacja 2.4. ’Èczenie 2.5. ¥rodowiska programistyczne Rozdziaï 3. Obiekty, typy i wartoĂci 3.1. Dane wejĂciowe 3.2. Zmienne 3.3. Typy danych wejĂciowych 3.4. Operacje i operatory 3.5. Przypisanie i inicjacja 3.5.1. Przykïad usuwania powtarzajÈcych siÚ sïów 3.6. Zïoĝone operatory przypisania 3.6.1. Przykïad zliczania powtarzajÈcych siÚ sïów 3.7. Nazwy 3.8. Typy i obiekty 3.9. Kontrola typów 3.9.1. Konwersje bezpieczne dla typów 3.9.2. Konwersje niebezpieczne dla typów 39 40 40 42 45 46 46 47 48 50 51 52 53 54 61 63 64 64 67 69 70 77 78 80 81 82 85 87 89 89 90 92 94 95 96 SPIS TRE¥CI Rozdziaï 4. Wykonywanie obliczeñ 4.1. Wykonywanie obliczeñ 4.2. Cele i narzÚdzia 4.3. Wyraĝenia 4.3.1. Wyraĝenia staïe 4.3.2. Operatory 4.3.3. Konwersje 4.4. Instrukcje 4.4.1. Selekcja 4.4.2. Iteracja 4.5. Funkcje 4.5.1. Po co zaprzÈtaÊ sobie gïowÚ funkcjami 4.5.2. Deklarowanie funkcji 4.6. Wektor 4.6.1. PowiÚkszanie wektora 4.6.2. Przykïad wczytywania liczb do programu 4.6.3. Przykïad z uĝyciem tekstu 4.7. WïaĂciwoĂci jÚzyka Rozdziaï 5. BïÚdy 5.1. WstÚp 5.2. ½ródïa bïÚdów 5.3. BïÚdy kompilacji 5.3.1. BïÚdy skïadni 5.3.2. BïÚdy typów 5.3.3. Nie bïÚdy 5.4. BïÚdy konsolidacji 5.5. BïÚdy czasu wykonania 5.5.1. RozwiÈzywanie problemu przez wywoïujÈcego 5.5.2. RozwiÈzywanie problemu przez wywoïywanego 5.5.3. Raportowanie bïÚdów 5.6. WyjÈtki 5.6.1. Nieprawidïowe argumenty 5.6.2. BïÚdy zakresu 5.6.3. Nieprawidïowe dane wejĂciowe 5.6.4. BïÚdy zawÚĝania zakresu 5.7. BïÚdy logiczne 5.8. Szacowanie 5.9. Debugowanie 5.9.1. Praktyczna rada dotyczÈca debugowania 5.10. Warunki wstÚpne i koñcowe 5.10.1. Warunki koñcowe 5.11. Testowanie 5 103 104 105 107 108 109 111 112 113 119 122 124 125 126 127 128 130 132 139 140 141 142 142 143 144 145 146 147 148 149 151 151 152 154 156 157 159 161 162 165 167 168 6 SPIS TRE¥CI Rozdziaï 6. Pisanie programu 6.1. Problem 6.2. PrzemyĂlenie problemu 6.2.1. Etapy rozwoju oprogramowania 6.2.2. Strategia 6.3. WracajÈc do kalkulatora 6.3.1. Pierwsza próba 6.3.2. Tokeny 6.3.3. Implementowanie tokenów 6.3.4. Uĝywanie tokenów 6.3.5. Powrót do tablicy 6.4. Gramatyki 6.4.1. Dygresja — gramatyka jÚzyka angielskiego 6.4.2. Pisanie gramatyki 6.5. Zamiana gramatyki w kod 6.5.1. Implementowanie zasad gramatyki 6.5.2. Wyraĝenia 6.5.3. Skïadniki 6.5.4. Podstawowe elementy wyraĝeñ 6.6. Wypróbowywanie pierwszej wersji 6.7. Wypróbowywanie drugiej wersji 6.8. Strumienie tokenów 6.8.1. Implementacja typu Token_stream 6.8.2. Wczytywanie tokenów 6.8.3. Wczytywanie liczb 6.9. Struktura programu Rozdziaï 7. Koñczenie programu 7.1. Wprowadzenie 7.2. WejĂcie i wyjĂcie 7.3. Obsïuga bïÚdów 7.4. Liczby ujemne 7.5. Reszta z dzielenia 7.6. Oczyszczanie kodu 7.6.1. Staïe symboliczne 7.6.2. Uĝycie funkcji 7.6.3. Ukïad kodu 7.6.4. Komentarze 7.7. Odzyskiwanie sprawnoĂci po wystÈpieniu bïÚdu 7.8. Zmienne 7.8.1. Zmienne i definicje 7.8.2. Wprowadzanie nazw 7.8.3. Nazwy predefiniowane 7.8.4. Czy to juĝ koniec? 175 176 176 177 177 179 180 182 183 185 187 188 192 193 194 194 195 199 200 201 205 206 207 209 210 211 217 218 218 220 224 225 227 227 229 230 231 233 236 236 240 242 243 SPIS TRE¥CI Rozdziaï 8. Szczegóïy techniczne — funkcje itp. 8.1. Szczegóïy techniczne 8.2. Deklaracje i definicje 8.2.1. Rodzaje deklaracji 8.2.2. Deklaracje staïych i zmiennych 8.2.3. DomyĂlna inicjacja 8.3. Pliki nagïówkowe 8.4. Zakres 8.5. Wywoïywanie i wartoĂÊ zwrotna funkcji 8.5.1. Deklarowanie argumentów i typu zwrotnego 8.5.2. Zwracanie wartoĂci 8.5.3. Przekazywanie przez wartoĂÊ 8.5.4. Przekazywanie argumentów przez staïÈ referencjÚ 8.5.5. Przekazywanie przez referencjÚ 8.5.6. Przekazywanie przez wartoĂÊ a przez referencjÚ 8.5.7. Sprawdzanie argumentów i konwersja 8.5.8. Implementacja wywoïañ funkcji 8.6. PorzÈdek wykonywania instrukcji 8.6.1. WartoĂciowanie wyraĝeñ 8.6.2. Globalna inicjacja 8.7. Przestrzenie nazw 8.7.1. Dyrektywy i deklaracje using Rozdziaï 9. Szczegóïy techniczne — klasy itp. 9.1. Typy zdefiniowane przez uĝytkownika 9.2. Klasy i skïadowe klas 9.3. Interfejs i implementacja 9.4. Tworzenie klas 9.4.1. Struktury i funkcje 9.4.2. Funkcje skïadowe i konstruktory 9.4.3. Ukrywanie szczegóïów 9.4.4. Definiowanie funkcji skïadowych 9.4.5. Odwoïywanie siÚ do bieĝÈcego obiektu 9.4.6. Raportowanie bïÚdów 9.5. Wyliczenia 9.6. PrzeciÈĝanie operatorów 9.7. Interfejsy klas 9.7.1. Typy argumentów 9.7.2. Kopiowanie 9.7.3. Konstruktory domyĂlne 9.7.4. Staïe funkcje skïadowe 9.7.5. Skïadowe i funkcje pomocnicze 9.8. Klasa Date 7 247 248 249 252 252 254 254 256 261 261 263 264 265 267 269 271 272 276 277 277 279 280 287 288 289 289 291 291 293 294 296 298 299 300 302 303 304 306 306 309 310 312 8 SPIS TRE¥CI CzÚĂÊ II WejĂcie i wyjĂcie Rozdziaï 10. Strumienie wejĂcia i wyjĂcia 10.1. WejĂcie i wyjĂcie 10.2. Model strumieni wejĂcia i wyjĂcia 10.3. Pliki 10.4. Otwieranie pliku 10.5. Odczytywanie i zapisywanie plików 10.6. Obsïuga bïÚdów wejĂcia i wyjĂcia 10.7. Wczytywanie pojedynczej wartoĂci 10.7.1. Rozïoĝenie problemu na mniejsze czÚĂci 10.7.2. Oddzielenie warstwy komunikacyjnej od funkcji 10.8. Definiowanie operatorów wyjĂciowych 10.9. Definiowanie operatorów wejĂciowych 10.10. Standardowa pÚtla wejĂciowa 10.11. Wczytywanie pliku strukturalnego 10.11.1. Reprezentacja danych w pamiÚci 10.11.2. Odczytywanie struktur wartoĂci 10.11.3. Zmienianie reprezentacji Rozdziaï 11. Indywidualizacja operacji wejĂcia i wyjĂcia 11.1. RegularnoĂÊ i nieregularnoĂÊ 11.2. Formatowanie danych wyjĂciowych 11.2.1. Wysyïanie na wyjĂcie liczb caïkowitych 11.2.2. Przyjmowanie na wejĂciu liczb caïkowitych 11.2.3. Wysyïanie na wyjĂcie liczb zmiennoprzecinkowych 11.2.4. Precyzja 11.2.5. Pola 11.3. Otwieranie plików i pozycjonowanie 11.3.1. Tryby otwierania plików 11.3.2. Pliki binarne 11.3.3. Pozycjonowanie w plikach 11.4. Strumienie ïañcuchowe 11.5. Wprowadzanie danych wierszami 11.6. Klasyfikowanie znaków 11.7. Stosowanie niestandardowych separatorów 11.8. Zostaïo jeszcze tyle do poznania Rozdziaï 12. Model graficzny 12.1. Czemu grafika? 12.2. Model graficzny 12.3. Pierwszy przykïad 319 321 322 323 325 326 328 330 332 334 337 338 339 340 341 342 343 347 353 354 354 355 356 357 358 360 361 361 362 365 365 367 368 370 376 381 382 383 384 SPIS TRE¥CI 12.4. Biblioteka GUI 12.5. WspóïrzÚdne 12.6. Figury geometryczne 12.7. Uĝywanie klas figur geometrycznych 12.7.1. Nagïówki graficzne i funkcja main 12.7.2. Prawie puste okno 12.7.3. Klasa Axis 12.7.4. Rysowanie wykresu funkcji 12.7.5. WielokÈty 12.7.6. ProstokÈty 12.7.7. Wypeïnianie kolorem 12.7.8. Tekst 12.7.9.Obrazy 12.7.10. Jeszcze wiÚcej grafik 12.8. Uruchamianie programu 12.8.1. Pliki ěródïowe Rozdziaï 13. Klasy graficzne 13.1. PrzeglÈd klas graficznych 13.2. Klasy Point i Line 13.3. Klasa Lines 13.4. Klasa Color 13.5. Typ Line_style 13.6. Typ Open_polyline 13.7. Typ Closed_polyline 13.8. Typ Polygon 13.9. Typ Rectangle 13.10. Wykorzystywanie obiektów bez nazw 13.11. Typ Text 13.12. Typ Circle 13.13. Typ Ellipse 13.14. Typ Marked_polyline 13.15. Typ Marks 13.16. Typ Mark 13.17. Typ Image Rozdziaï 14. Projektowanie klas graficznych 14.1. Zasady projektowania 14.1.1. Typy 14.1.2. Operacje 14.1.3. Nazewnictwo 14.1.4. ZmiennoĂÊ 9 387 388 388 389 390 390 392 394 394 395 397 398 399 400 401 402 407 408 410 412 414 416 418 419 420 422 426 428 430 431 433 434 435 436 443 444 444 445 446 448 10 SPIS TRE¥CI 14.2. Klasa Shape 14.2.1. Klasa abstrakcyjna 14.2.2. Kontrola dostÚpu 14.2.3. Rysowanie figur 14.2.4. Kopiowanie i zmiennoĂÊ 14.3. Klasy bazowe i pochodne 14.3.1. Ukïad obiektu 14.3.2. Tworzenie podklas i definiowanie funkcji wirtualnych 14.3.3. Przesïanianie 14.3.4. DostÚp 14.3.5. Czyste funkcje wirtualne 14.4. Zalety programowania obiektowego Rozdziaï 15. Graficzne przedstawienie funkcji i danych 15.1. Wprowadzenie 15.2. Rysowanie wykresów prostych funkcji 15.3. Typ Function 15.3.1. Argumenty domyĂlne 15.3.2. WiÚcej przykïadów 15.4. Typ Axis 15.5. WartoĂÊ przybliĝona funkcji wykïadniczej 15.6. Przedstawianie danych na wykresach 15.6.1. Odczyt danych z pliku 15.6.2. Ukïad ogólny 15.6.3. Skalowanie danych 15.6.4. Budowanie wykresu Rozdziaï 16. Graficzne interfejsy uĝytkownika 16.1. Róĝne rodzaje interfejsów uĝytkownika 16.2. Przycisk Next 16.3. Proste okno 16.3.1. Funkcje zwrotne 16.3.2. PÚtla oczekujÈca 16.4. Typ Button i inne pochodne typu Widget 16.4.1. Widgety 16.4.2. Przyciski 16.4.3. Widgety In_box i Out_box 16.4.4. Menu 16.5. Przykïad 16.6. Inwersja kontroli 16.7. Dodawanie menu 16.8. Debugowanie kodu GUI 448 450 451 454 456 458 459 461 461 463 464 465 471 472 472 476 477 478 479 481 486 487 488 489 490 497 498 499 500 501 504 505 505 506 507 507 508 511 513 517 SPIS TRE¥CI CzÚĂÊ III Dane i algorytmy Rozdziaï 17. Wektory i pamiÚÊ wolna 17.1. Wprowadzenie 17.2. Podstawowe wiadomoĂci na temat typu vector 17.3. PamiÚÊ, adresy i wskaěniki 17.3.1. Operator sizeof 17.4. PamiÚÊ wolna a wskaěniki 17.4.1. Alokacja obiektów w pamiÚci wolnej 17.4.2. DostÚp poprzez wskaěniki 17.4.3. Zakresy 17.4.4. Inicjacja 17.4.5. Wskaěnik zerowy 17.4.6. Dealokacja pamiÚci wolnej 17.5. Destruktory 17.5.1. Generowanie destruktorów 17.5.2. Destruktory a pamiÚÊ wolna 17.6. DostÚp do elementów 17.7. Wskaěniki na obiekty klas 17.8. Babranie siÚ w typach — void* i rzutowanie 17.9. Wskaěniki i referencje 17.9.1. Wskaěniki i referencje jako parametry 17.9.2. Wskaěniki, referencje i dziedziczenie 17.9.3. Przykïad — listy 17.9.4. Operacje na listach 17.9.5. Zastosowania list 17.10. Wskaěnik this 17.10.1. WiÚcej przykïadów uĝycia typu Link Rozdziaï 18. Wektory i tablice 18.1. Wprowadzenie 18.2. Kopiowanie 18.2.1. Konstruktory kopiujÈce 18.2.2. Przypisywanie z kopiowaniem 18.2.3. Terminologia zwiÈzana z kopiowaniem 18.3. Podstawowe operacje 18.3.1. Konstruktory jawne 18.3.2. Debugowanie konstruktorów i destruktorów 18.4. Uzyskiwanie dostÚpu do elementów wektora 18.4.1. Problem staïych wektorów 18.5. Tablice 18.5.1. Wskaěniki na elementy tablicy 18.5.2. Wskaěniki i tablice 11 523 525 526 527 529 531 532 533 534 535 536 537 538 540 542 542 544 545 546 548 549 550 551 552 554 555 557 563 564 564 566 567 569 570 571 573 575 576 577 578 580 12 SPIS TRE¥CI 18.5.3. Inicjowanie tablic 18.5.4. Problemy ze wskaěnikami 18.6. Przykïady — palindrom 18.6.1. Wykorzystanie ïañcuchów 18.6.2. Wykorzystanie tablic 18.6.3. Wykorzystanie wskaěników Rozdziaï 19. Wektory, szablony i wyjÈtki 19.1. Analiza problemów 19.2. Zmienianie rozmiaru 19.2.1. Reprezentacja 19.2.2. Rezerwacja pamiÚci i pojemnoĂÊ kontenera 19.2.3. Zmienianie rozmiaru 19.2.4. Funkcja push_back() 19.2.5. Przypisywanie 19.2.6. Podsumowanie dotychczasowej pracy nad typem vector 19.3. Szablony 19.3.1. Typy jako parametry szablonów 19.3.2. Programowanie ogólne 19.3.3. Kontenery a dziedziczenie 19.3.4. Liczby caïkowite jako parametry szablonów 19.3.5. Dedukcja argumentów szablonu 19.3.6. Uogólnianie wektora 19.4. Sprawdzanie zakresu i wyjÈtki 19.4.1. Dygresja — uwagi projektowe 19.4.2. Wyznanie na temat makr 19.5. Zasoby i wyjÈtki 19.5.1. Potencjalne problemy z zarzÈdzaniem zasobami 19.5.2. Zajmowanie zasobów jest inicjacjÈ 19.5.3. Gwarancje 19.5.4. Obiekt auto_ptr 19.5.5. Technika RAII dla wektora Rozdziaï 20. Kontenery i iteratory 20.1. Przechowywanie i przetwarzanie danych 20.1.1. Praca na danych 20.1.2. Uogólnianie kodu 20.2. Ideaïy twórcy biblioteki STL 20.3. Sekwencje i iteratory 20.3.1. Powrót do przykïadu 20.4. Listy powiÈzane 20.4.1. Operacje list 20.4.2. Iteracja 20.5. Jeszcze raz uogólnianie wektora 582 583 586 586 587 588 593 594 596 597 598 599 599 600 601 602 603 605 607 608 610 610 613 614 615 617 617 619 620 621 622 629 630 630 631 634 637 639 641 642 643 645 SPIS TRE¥CI 20.6. Przykïad — prosty edytor tekstu 20.6.1. Wiersze 20.6.2. Iteracja 20.7. Typy vector, list oraz string 20.7.1. Funkcje insert() i erase() 20.8. Dostosowanie wektora do biblioteki STL 20.9. Dostosowywanie wbudowanych tablic do STL 20.10. PrzeglÈd kontenerów 20.10.1. Kategorie iteratorów Rozdziaï 21. Algorytmy i sïowniki 21.1. Algorytmy biblioteki standardowej 21.2. Najprostszy algorytm — find() 21.2.1. Kilka przykïadów z programowania ogólnego 21.3. Ogólny algorytm wyszukiwania — find_if() 21.4. Obiekty funkcyjne 21.4.1. Abstrakcyjne spojrzenie na obiekty funkcyjne 21.4.2. Predykaty skïadowych klas 21.5. Algorytmy numeryczne 21.5.1. Akumulacja 21.5.2. Uogólnianie funkcji accumulate() 21.5.3. Iloczyn skalarny 21.5.4. Uogólnianie funkcji inner_product() 21.6. Kontenery asocjacyjne 21.6.1. Sïowniki 21.6.2. Opis ogólny kontenera map 21.6.3. Jeszcze jeden przykïad zastosowania sïownika 21.6.4. Kontener unordered_map 21.6.5. Zbiory 21.7. Kopiowanie 21.7.1. Funkcja copy() 21.7.2. Iteratory strumieni 21.7.3. Utrzymywanie porzÈdku przy uĝyciu kontenera set 21.7.4. Funkcja copy_if() 21.8. Sortowanie i wyszukiwanie CzÚĂÊ IV Poszerzanie horyzontów Rozdziaï 22. Ideaïy i historia 22.1. Historia, ideaïy i profesjonalizm 22.1.1. Cele i filozofie jÚzyków programowania 22.1.2. Ideaïy programistyczne 22.1.3. Style i paradygmaty 13 647 649 650 653 654 656 658 660 662 667 668 669 670 671 673 674 675 676 677 678 679 681 681 682 684 687 689 691 693 693 694 696 696 697 703 705 706 706 708 714 14 SPIS TRE¥CI 22.2. Krótka historia jÚzyków programowania 22.2.1. Pierwsze jÚzyki 22.2.2. Korzenie nowoczesnych jÚzyków programowania 22.2.3. Rodzina Algol 22.2.4. Simula 22.2.5. C 22.2.6. C++ 22.2.7. DziĂ 22.2.8. ½ródïa informacji Rozdziaï 23. Przetwarzanie tekstu 23.1. Tekst 23.2. ’añcuchy 23.3. Strumienie wejĂcia i wyjĂcia 23.4. Sïowniki 23.4.1. Szczegóïy implementacyjne 23.5. Problem 23.6. Wyraĝenia regularne 23.7. Wyszukiwanie przy uĝyciu wyraĝeñ regularnych 23.8. Skïadnia wyraĝeñ regularnych 23.8.1. Znaki i znaki specjalne 23.8.2. Rodzaje znaków 23.8.3. Powtórzenia 23.8.4. Grupowanie 23.8.5. Alternatywa 23.8.6. Zbiory i przedziaïy znaków 23.8.7. BïÚdy w wyraĝeniach regularnych 23.9. Dopasowywanie przy uĝyciu wyraĝeñ regularnych 23.10. ½ródïa Rozdziaï 24. Dziaïania na liczbach 24.1. Wprowadzenie 24.2. Rozmiar, precyzja i przekroczenie zakresu 24.2.1. Ograniczenia typów liczbowych 24.3. Tablice 24.4. Tablice wielowymiarowe w stylu jÚzyka C 24.5. Biblioteka Matrix 24.5.1. Wymiary i dostÚp 24.5.2. Macierze jednowymiarowe 24.5.3. Macierze dwuwymiarowe 24.5.4. WejĂcie i wyjĂcie macierzy 24.5.5. Macierze trójwymiarowe 717 718 719 724 731 733 736 738 740 745 746 746 750 750 755 757 759 761 764 764 765 766 767 767 768 769 770 775 779 780 780 783 784 785 786 787 789 792 794 795 SPIS TRE¥CI 24.6. Przykïad — rozwiÈzywanie równañ liniowych 24.6.1. Klasyczna eliminacja Gaussa 24.6.2. Wybór elementu centralnego 24.6.3. Testowanie 24.7. Liczby losowe 24.8. Standardowe funkcje matematyczne 24.9. Liczby zespolone 24.10. ½ródïa Rozdziaï 25. Programowanie systemów wbudowanych 25.1. Systemy wbudowane 25.2. Podstawy 25.2.1. PrzewidywalnoĂÊ 25.2.2. Ideaïy 25.2.3. ¿ycie z awariÈ 25.3. ZarzÈdzanie pamiÚciÈ 25.3.1. Problemy z pamiÚciÈ wolnÈ 25.3.2. Alternatywy dla ogólnej pamiÚci wolnej 25.3.3. Przykïad zastosowania puli 25.3.4. Przykïad uĝycia stosu 25.4. Adresy, wskaěniki i tablice 25.4.1. Niekontrolowane konwersje 25.4.2. Problem — ěle dziaïajÈce interfejsy 25.4.3. RozwiÈzanie — klasa interfejsu 25.4.4. Dziedziczenie a kontenery 25.5. Bity, bajty i sïowa 25.5.1. Bity i operacje na bitach 25.5.2. Klasa bitset 25.5.3. Liczby ze znakiem i bez znaku 25.5.4. Manipulowanie bitami 25.5.5. Pola bitowe 25.5.6. Przykïad — proste szyfrowanie 25.6. Standardy pisania kodu 25.6.1. Jaki powinien byÊ standard kodowania 25.6.2. Przykïadowe zasady 25.6.3. Prawdziwe standardy kodowania Rozdziaï 26. Testowanie 26.1. Czego chcemy 26.1.1. Zastrzeĝenie 26.2. Dowody 26.3. Testowanie 26.3.1. Testowanie regresyjne 26.3.2. Testowanie jednostkowe 15 796 797 798 799 800 802 803 804 809 810 813 815 815 816 818 819 822 823 824 825 825 826 829 832 834 835 839 840 844 846 847 851 852 854 859 865 866 867 867 867 868 869 16 SPIS TRE¥CI 26.3.3. Algorytmy i nie-algorytmy 26.3.4. Testy systemowe 26.3.5. Testowanie klas 26.3.6. Znajdowanie zaïoĝeñ, które siÚ nie potwierdzajÈ 26.4. Projektowanie pod kÈtem testowania 26.5. Debugowanie 26.6. WydajnoĂÊ 26.6.1. Kontrolowanie czasu 26.7. ½ródïa Rozdziaï 27. JÚzyk C 27.1. C i C++ to rodzeñstwo 27.1.1. ZgodnoĂÊ jÚzyków C i C++ 27.1.2. Co jest w jÚzyku C++, czego nie ma w C 27.1.3. Biblioteka standardowa jÚzyka C 27.2. Funkcje 27.2.1. Brak moĝliwoĂci przeciÈĝania nazw funkcji 27.2.2. Sprawdzanie typów argumentów funkcji 27.2.3. Definicje funkcji 27.2.4. Wywoïywanie C z poziomu C++ i C++ z poziomu C 27.2.5. Wskaěniki na funkcje 27.3. Mniej waĝne róĝnice miÚdzy jÚzykami 27.3.1. Przestrzeñ znaczników struktur 27.3.2. Sïowa kluczowe 27.3.3. Definicje 27.3.4. Rzutowanie w stylu jÚzyka C 27.3.5. Konwersja typu void* 27.3.6. Typ enum 27.3.7. Przestrzenie nazw 27.4. PamiÚÊ wolna 27.5. ’añcuchy w stylu jÚzyka C 27.5.1. ’añcuchy w stylu jÚzyka C i const 27.5.2. Operacje na bajtach 27.5.3. Przykïad — funkcja strcpy() 27.5.4. Kwestia stylu 27.6. WejĂcie i wyjĂcie — nagïówek stdio 27.6.1. WyjĂcie 27.6.2. WejĂcie 27.6.3. Pliki 27.7. Staïe i makra 27.8. Makra 27.8.1. Makra podobne do funkcji 27.8.2. Makra skïadniowe 27.8.3. Kompilacja warunkowa 27.9. Przykïad — kontenery intruzyjne 875 882 886 889 890 891 891 893 895 899 900 901 903 904 905 906 906 907 909 911 912 912 913 914 915 916 917 917 918 919 922 922 923 923 924 924 925 927 927 928 929 930 931 932 SPIS TRE¥CI Dodatki Dodatek A Zestawienie wïasnoĂci jÚzyka A.1. Opis ogólny A.2. Literaïy A.3. Identyfikatory A.4. Zakres, pamiÚÊ oraz czas trwania A.5. Wyraĝenia A.6. Instrukcje A.7. Deklaracje A.8. Typy wbudowane A.9. Funkcje A.10. Typy zdefiniowane przez uĝytkownika A.11. Wyliczenia A.12. Klasy A.13. Szablony A.14. WyjÈtki A.15. Przestrzenie nazw A.16. Aliasy A.17. Dyrektywy preprocesora Dodatek B Biblioteka standardowa B.1. PrzeglÈd B.2. Obsïuga bïÚdów B.3. Iteratory B.4. Kontenery B.5. Algorytmy B.6. Biblioteka STL B.7. Strumienie wejĂcia i wyjĂcia B.8. Przetwarzanie ïañcuchów B.9. Obliczenia B.10. Funkcje biblioteki standardowej C B.11. Inne biblioteki Dodatek C Podstawy Ărodowiska Visual Studio C.1. Uruchamianie programu C.2. Instalowanie Ărodowiska Visual Studio C.3. Tworzenie i uruchamianie programu C.4. Póěniej 17 941 943 944 946 950 950 953 962 964 965 968 971 972 972 983 986 988 988 989 991 992 995 997 1001 1008 1016 1018 1024 1028 1032 1040 1043 1044 1044 1044 1046 18 SPIS TRE¥CI Dodatek D Instalowanie biblioteki FLTK D.1. Wprowadzenie D.2. Pobieranie biblioteki FLTK z internetu D.3. Instalowanie biblioteki FLTK D.4. Korzystanie z biblioteki FLTK w Visual Studio D.5. Sprawdzanie, czy wszystko dziaïa Dodatek E Implementacja GUI E.1. Implementacja wywoïañ zwrotnych E.2. Implementacja klasy Widget E.3. Implementacja klasy Window E.4. Klasa Vector_ref E.5. Przykïad — widgety Sïowniczek Bibliografia Skorowidz ZdjÚcia 1047 1048 1048 1048 1049 1050 1051 1052 1053 1054 1055 1056 1059 1065 1069 1105 6 Pisanie programu PisaÊ program, znaczy rozumieÊ. — Kristen Nygaard P isanie programu polega na stopniowym modyfikowaniu swojego wyobraĝe- nia na temat tego, co siÚ chce zrobiÊ i jak siÚ chce to wyraziÊ. W tym i nastÚp- nym rozdziale zbudujemy program. Zaczniemy od pierwszego mglistego pomy- sïu, przejdziemy etapy analizy, projektowania, implementacji, testowania, po- nownego projektowania, na ponownej implementacji koñczÈc. Chcemy pokazaÊ proces myĂlowy, który ma miejsce podczas tworzenia oprogramowania. W miÚ- dzyczasie omówimy organizacjÚ programu, typy definiowane przez uĝytkownika oraz techniki przetwarzania danych wejĂciowych. 6.1. Problem 6.2. PrzemyĂlenie problemu 6.2.1. Etapy rozwoju oprogramowania 6.2.2. Strategia 6.3. WracajÈc do kalkulatora 6.3.1. Pierwsza próba 6.3.2. Tokeny 6.3.3. Implementowanie tokenów 6.3.4. Uĝywanie tokenów 6.3.5. Powrót do tablicy 6.4. Gramatyki 6.4.1. Dygresja — gramatyka jÚzyka angielskiego 6.4.2. Pisanie gramatyki 6.5. Zamiana gramatyki w kod 6.5.1. Implementowanie zasad gramatyki 6.5.2. Wyraĝenia 6.5.3. Skïadniki 6.5.4. Podstawowe elementy wyraĝeñ 6.6. Wypróbowywanie pierwszej wersji 6.7. Wypróbowywanie drugiej wersji 6.8. Strumienie tokenów 6.8.1. Implementacja typu Token_stream 6.8.2. Wczytywanie tokenów 6.8.3. Wczytywanie liczb 6.9. Struktura programu 175 176 ROZDZIA’ 6 • PISANIE PROGRAMU 6.1. Problem Pisanie programu zaczyna siÚ od postawienia problemu. To znaczy, jest problem, do rozwiÈzania którego chcemy napisaÊ program. Aby ten program byï dobry, naleĝy dokïadnie zrozumieÊ problem. JeĂli program bÚdzie rozwiÈzywaï nie ten problem, co trzeba, to nie bÚdzie przydatny bez wzglÚdu na to, jak moĝe byÊ elegancki. Czasami zdarzajÈ siÚ szczÚĂliwe przypadki, ĝe program speïnia jakieĂ przypadkowe poĝyteczne zadanie, mimo ĝe zostaï napisany w caïkiem innym celu. Lepiej jednak nie liczyÊ na takie szczÚĂcie. My chcemy napisaÊ taki program, który bÚ- dzie w prosty i jasny sposób rozwiÈzywaï dokïadnie ten problem, dla którego zostaï napisany. Jaki program byïoby najlepiej napisaÊ na tym etapie nauki? Taki, który: x Ilustruje techniki projektowania i pisania programów. x Umoĝliwia zapoznanie siÚ z charakterem decyzji, które programista musi podejmo- waÊ, oraz implikacjami, które te decyzje pociÈgajÈ. x Nie wymaga zastosowania zbyt wielu nowych konstrukcji programistycznych. x Jest wystarczajÈco skomplikowany, aby zmusiÊ nas do przemyĂlenia jego projektu. x Moĝna napisaÊ na kilka sposobów. x RozwiÈzuje ïatwy do zrozumienia problem. x RozwiÈzuje problem wart uwagi. x Jest na tyle maïy, ĝe moĝna go w caïoĂci przedstawiÊ i zrozumieÊ. Wybór padï na program „zmuszajÈcy komputer do wykonywania typowych dziaïañ arytme- tycznych na wyraĝeniach, które mu podamy” — tzn. chcemy napisaÊ prosty kalkulator. Pro- gramy tego typu sÈ uĝyteczne. Moĝna je znaleěÊ w kaĝdym komputerze biurkowym, a nawet moĝna znaleěÊ takie komputery, które pozwalajÈ uruchamiaÊ tylko tego typu programy — kalkulatory kieszonkowe. JeĂli np. wpiszemy 2+3.1*4 bÚdziemy oczekiwaÊ, ĝe program zwróci wynik 14.4 Niestety kalkulator taki nie bÚdzie miaï ĝadnych funkcji, których juĝ nie speïnia nasz kom- puter, ale to by byïo zbyt wysokie wymaganie wobec pierwszego programu. 6.2. PrzemyĂlenie problemu Od czego wiÚc zaczÈÊ? PomyĂl o postawionym problemie i tym, jak go rozwiÈzaÊ. Po pierwsze zdecyduj siÚ, co program ma robiÊ i w jaki sposób bÚdziesz siÚ z nim komunikowaÊ. Póěniej bÚdziesz mógï zastanowiÊ siÚ nad tym, jak napisaÊ odpowiedni kod. Opisz wstÚpnÈ wersjÚ swojego rozwiÈzania i zastanów siÚ, co naleĝy poprawiÊ. Moĝesz spróbowaÊ przedyskutowaÊ problem i jego rozwiÈzanie z kolegÈ lub koleĝankÈ. Próba przedstawienia komuĂ wïasnych myĂli jest doskonaïym sposobem na znalezienie sïabych punktów wïasnego rozumowania, 6.2. PRZEMY¥LENIE PROBLEMU 177 nawet lepszym niĝ napisanie ich. Papier (lub komputer) nie bÚdzie z TobÈ dyskutowaÊ i kry- tycznie oceniaÊ Twoich poglÈdów. Idealny etap projektowania to taki, który przeprowadza siÚ w towarzystwie. Niestety nie ma takiej ogólnej strategii rozwiÈzywania problemów, która odpowiadaïaby wszystkim ludziom i pozwalaïa rozwiÈzaÊ kaĝdy problem. Istnieje mnóstwo ksiÈĝek, których autorzy twierdzÈ, ĝe pomogÈ Ci efektywniej rozwiÈzywaÊ problemy, oraz caïa masa publikacji na temat projektowania programów. Nasza droga nie wiedzie poprzez nie. W zamian opiszemy garĂÊ ogólnych wskazówek, które mogÈ byÊ pomocne w rozwiÈzywaniu mniejszych problemów. NastÚpnie szybko przejdziemy do wypróbowywania tych sugestii na naszym maïym kalkula- torze. CzytajÈc treĂÊ naszych rozwaĝañ na temat kalkulatora, oceniaj to, co widzisz, bardzo scep- tycznym okiem. Aby zachowaÊ realizm, przedstawimy ewolucjÚ naszego programu poprzez szereg wersji. Opiszemy argumenty, które doprowadziïy nas do powstania kaĝdej z nich. Oczy- wiĂcie znaczna czÚĂÊ tej argumentacji musi byÊ niekompletna lub bïÚdna, inaczej szybko byĂmy skoñczyli ten rozdziaï. Naszym celem jest pokazanie przykïadowych problemów i procesów myĂlowych charakterystycznych dla procesu projektowania i implementowania programu. Wersja, z której jesteĂmy ostatecznie zadowoleni, zostanie przedstawiona dopiero na koñcu nastÚpnego rozdziaïu. PamiÚtaj, ĝe w tym i nastÚpnym rozdziale proces dochodzenia do finalnej wersji progra- mu — droga wiodÈca przez niepeïne rozwiÈzania, pomysïy i bïÚdy — jest co najmniej tak samo waĝny, jak wersja ostateczna i waĝniejszy niĝ narzÚdzia techniczne jÚzyka programowania, których bÚdziemy uĝywaÊ (wrócimy do nich póěniej). 6.2.1. Etapy rozwoju oprogramowania Poniĝej przedstawimy nieco terminologii zwiÈzanej z tworzeniem oprogramowania. PracujÈc nad rozwiÈzaniem problemu, wielokrotnie powtarza siÚ nastÚpujÈce fazy: x Analiza — przemyĂl, co masz zrobiÊ, i opisz, jak to aktualnie rozumiesz. Taki opis nazywa siÚ zestawem wymagañ lub specyfikacjÈ. Nie bÚdziemy szczegóïowo opisywaÊ technik opracowywania i opisywania takich wymogów. Temat tej ksiÈĝki tego nie obejmuje, ale naleĝy pamiÚtaÊ, ĝe w miarÚ zwiÚkszania siÚ rozmiaru problemu techniki te nabierajÈ wagi. x Projektowanie — utworzenie ogólnej struktury systemu i podjÚcie decyzji, na jakie czÚĂci podzieliÊ implementacjÚ oraz jak powinny siÚ one ze sobÈ komunikowaÊ. W ramach projektowania zastanów siÚ jakie narzÚdzia — np. biblioteki — moĝesz wykorzystaÊ do opracowania struktury programu. x Implementacja — napisz kod, usuñ bïÚdy oraz sprawdě za pomocÈ testów, czy robi to, co powinien. 6.2.2. Strategia Oto garĂÊ wskazówek, które — jeĂli zostanÈ zastosowane z rozwagÈ i wyobraěniÈ — bÚdÈ po- mocne w wielu projektach programistycznych: x Jaki problem jest do rozwiÈzania? Przede wszystkim naleĝy staraÊ siÚ dokïadnie opisaÊ, co chce siÚ zrobiÊ. NajczÚĂciej oznacza to sporzÈdzenie opisu problemu lub, jeĂli ktoĂ 178 ROZDZIA’ 6 • PISANIE PROGRAMU inny daï nam zadanie do wykonania, próbÚ odszyfrowania, co dokïadnie to oznacza. W tym momencie naleĝy przyjÈÊ punkt widzenia uĝytkownika (nie programisty czy implementatora). To znaczy, zamiast zastanawiaÊ siÚ, jak rozwiÈzaÊ problem, pomyĂl, co program ma w ogóle robiÊ. Zadawaj pytania typu: „Co ten program moĝe dla mnie zro- biÊ?” albo „W jaki sposób chciaïbym komunikowaÊ siÚ z tym programem?”. PamiÚtaj, ĝe wiÚkszoĂÊ z nas moĝe korzystaÊ z wïasnego bogatego doĂwiadczenia jako uĝytkownika komputerów. x Czy problem zostaï jasno nakreĂlony? W realnym Ăwiecie nigdy nie jest. Nawet w takim Êwiczeniu trudno jest opisaÊ go wystarczajÈco precyzyjnie. Dlatego staramy siÚ wszystko wyjaĂniÊ. Szkoda by byïo, gdybyĂmy rozwiÈzali nie ten problem, co trzeba. Inna puïapka to wygórowane wymagania. ObmyĂlajÈc, co byĂmy chcieli, ïatwo moĝe- my ulec chciwoĂci lub nadmiernym ambicjom. Zawsze lepiej jest obniĝyÊ wymagania, aby uïatwiÊ napisanie specyfikacji programu, jego zrozumienie, uĝytkowanie oraz (takÈ trzeba mieÊ nadziejÚ) implementacjÚ. Gdy zadziaïa, zawsze moĝna napisaÊ wzbogaconÈ wersjÚ 2.0. x Czy problem wydaje siÚ moĝliwy do rozwiÈzania w przewidzianym czasie oraz przy okreĂlonym zasobie umiejÚtnoĂci i dostÚpnych narzÚdziach? Nie ma sensu rozpo- czynaÊ projektu, którego nie mamy szans ukoñczyÊ. JeĂli jest za maïo czasu na im- plementacjÚ (wïÈcznie z testowaniem) wszystkich wymaganych funkcji programu, zazwyczaj lepiej jest go w ogóle nie zaczynaÊ. Lepiej zamiast tego zdobyÊ wiÚcej zasobów (zwïaszcza czasu) lub (idealnie) zmieniÊ wymagania, aby uïatwiÊ zadanie. x Spróbuj podzieliÊ program na dajÈce siÚ ogarnÈÊ myĂlami czÚĂci. Nawet najmniejszy program rozwiÈzujÈcy realny problem moĝna podzieliÊ na czÚĂci. x Znasz jakieĂ narzÚdzia, biblioteki itp., które mogÈ byÊ pomocne? Odpowiedě pra- wie zawsze brzmi: tak. Od samego poczÈtku nauki programowania masz do dys- pozycji zawartoĂÊ standardowej biblioteki C++. Póěniej poznasz znacznÈ jej czÚĂÊ i dowiesz siÚ, jak poszukiwaÊ jeszcze wiÚcej. BÚdziesz korzystaÊ z bibliotek graficz- nych, macierzy itp. MajÈc odrobinÚ doĂwiadczenia, bÚdziesz w stanie znaleěÊ tysiÈ- ce bibliotek w internecie. PamiÚtaj — nie ma sensu wywaĝaÊ otwartych drzwi, jeĂli tworzy siÚ oprogramowanie do realnego uĝytku. Co innego w czasie nauki pro- gramowania. Wówczas takie dziaïania w celu sprawdzenia, jak to zrobili inni, majÈ gïÚboki sens. Caïy czas, który oszczÚdzisz dziÚki wykorzystaniu istniejÈcych bi- bliotek, moĝesz poĂwiÚciÊ na pracÚ nad innymi czÚĂciami problemu albo na odpo- czynek. SkÈd wiadomo, czy dana biblioteka speïnia nasze wymagania i prezentuje odpowiedniÈ jakoĂÊ? To trudne pytanie. Moĝna spytaÊ znajomych, popytaÊ na grupach dyskusyjnych i wypróbowaÊ kilka krótkich przykïadów, zanim siÚ zdecy- dujemy. x WyodrÚbnij takie czÚĂci rozwiÈzania, które moĝna opisaÊ oddzielnie od reszty (i po- tencjalnie wykorzystaÊ w kilku miejscach programu, a nawet w innych programach). UmiejÚtnoĂÊ znajdowania takich czÚĂci przychodzi z doĂwiadczeniem, dlatego w ksiÈĝce tej przedstawiamy wiele takich przykïadów. UĝywaliĂmy juĝ wektorów, ïañcuchów i strumieni (cin i cout). W tym rozdziale po raz pierwszy przedstawimy przykïady projektów, implementacji i wykorzystania czÚĂci programów dostÚpnych jako typy 6.3. WRACAJkC DO KALKULATORA 179 zdefiniowane przez uĝytkownika (Token i Token_stream). Jeszcze wiÚcej takich przy- kïadów znajduje siÚ w rozdziaïach 8. oraz 13. – 15., w których zostanÈ opisane tech- niki ich tworzenia. Na razie zastanów siÚ nad takÈ analogiÈ — gdybyĂmy projektowali samochód, zaczÚlibyĂmy od opisu jego podzespoïów, takich jak koïa, silnik, fotele, klamki do drzwi itp. KaĝdÈ z nich wyprodukowalibyĂmy osobno, aby nastÚpnie uĝyÊ jej do zïoĝenia caïego pojazdu. W nowoczesnym samochodzie wykorzystuje siÚ dziesiÈtki tysiÚcy takich czÚĂci. Realne programy nie róĝniÈ siÚ w niczym od samochodów pod tym wzglÚdem (poza tym, ĝe czÚĂciami sÈ fragmenty kodu). Nie przyszïo by nam do gïowy budowaÊ samochodu z surowych materiaïów, jak ĝelazo, plastik i drewno. Analogicznie nie tworzymy powaĝnych programów, bezpoĂrednio uĝywajÈc wyraĝeñ, instrukcji typów wbudowanych w jÚzyk. Projektowanie i imple- mentowanie takich czÚĂci jest najwaĝniejszym tematem tej ksiÈĝki, a nawet w ogólne programowania. Zajrzyj teĝ do rozdziaïów 9. (typy definiowane przez uĝytkownika), 14. (hierarchie klas) oraz 20. (typy ogólne). x Zbuduj niewielkÈ wersjÚ programu z ograniczonÈ funkcjonalnoĂciÈ, która rozwiÈzuje najwaĝniejszÈ czÚĂÊ problemu. Rzadko siÚ zdarza, abyĂmy od samego poczÈtku dokïad- nie znali charakter problemu. CzÚsto nam siÚ tak wydaje (chyba wiadomo, co to jest pro- gram kalkulator?), ale w rzeczywistoĂci jest inaczej. Tylko dziÚki wytÚĝonemu myĂle- niu o problemie (analiza) i eksperymentowaniu (projekt i implementacja) moĝna na tyle dokïadnie zrozumieÊ problem, aby napisaÊ dobry program. Dlatego tworzymy ograni- czonÈ wersjÚ, aby: x OdkryÊ wïasne braki w rozumowaniu, pojmowaniu problemu oraz narzÚdziach. x SprawdziÊ, czy nie trzeba zmieniÊ niektórych szczegóïów w specyfikacji problemu, aby zadanie byïo wykonalne. Rzadko siÚ zdarza, aby w czasie analizy i we wstÚpnej fazie projektowania udaïo siÚ przewidzieÊ wszystkie moĝliwe trudnoĂci. Naleĝy skorzystaÊ z informacji, które zyskujemy podczas pisania i testowania kodu. TakÈ wstÚpnÈ wersjÚ o ograniczonej funkcjonalnoĂci czasami nazywa siÚ prototypem. JeĂli (co jest prawdopodobne) ta pierwsza wersja nie dziaïa lub jest tak brzydka, ĝe nie mamy ochoty siÚ niÈ dïuĝej zajmowaÊ, wyrzucamy jÈ i tworzymy nowy prototyp, tym ra- zem wzbogaceni o nowe doĂwiadczenia. Powtarzamy ten proces aĝ do uzyskania zadowa- lajÈcego wyniku. Nie kontynuuj pracy w baïaganie, który z czasem tylko siÚ pogorszy. x Zbuduj peïnÈ wersjÚ, najlepiej wykorzystujÈc w tym celu czÚĂci z wersji wstÚpnej. Chodzi o to, aby program tworzyÊ z dziaïajÈcych czÚĂci, a nie pisaÊ caïy kod na raz. Moĝna oczywiĂcie mieÊ nadziejÚ, ĝe jakimĂ cudem nieprzetestowane fragmenty kodu bÚdÈ dziaïaÊ i na dodatek robiÊ to, co zaplanowano. 6.3. WracajÈc do kalkulatora W jaki sposób bÚdziemy komunikowaÊ siÚ z kalkulatorem? To ïatwe — wiemy juĝ, jak posïu- giwaÊ siÚ strumieniami cin i cout, a graficzne interfejsy uĝytkownika (GUI) zostanÈ opisane dopiero w rozdziale 16. W zwiÈzku z tym wybór padï na okno konsoli. Program bÚdzie po- bieraï z klawiatury wyraĝenia, obliczaï ich wartoĂÊ i drukowaï wynik na ekranie. Na przykïad: 180 ROZDZIA’ 6 • PISANIE PROGRAMU Wyraĝenie: 2+2 Wynik: 4 Wyraĝenie: 2+2*3 Wynik: 8 Wyraĝenie: 2+3–25/5 Wynik: 0 Wyraĝenia, tzn. 2+2 i 2+2*3, powinien wpisywaÊ uĝytkownik. Reszta naleĝy do programu. Wy- Ăwietlenie sïowa Wyraĝenie: bÚdzie zachÚtÈ dla uĝytkownika do wpisania wyraĝenia. Mogliby- Ămy napisaÊ ProszÚ wpisaÊ wyraĝenie i znak nowego wiersza:, ale to wydawaïo nam siÚ zbyt roz- wlekïe. Z drugiej strony taki przyjemny znaczek byïby chyba za bardzo tajemniczy. Takie szkicowanie przykïadów uĝycia we wczesnej fazie pracy jest bardzo waĝne. DziÚki temu moĝna siÚ dowiedzieÊ, jaki jest minimalny zestaw funkcji programu. W projektowaniu i analizie przykïady takie nazywajÈ siÚ przypadkami uĝycia. WiÚkszoĂÊ ludzi, którzy po raz pierwszy stykajÈ siÚ z problemem kalkulatora, wpada na nastÚpujÈcy pomysï, jeĂli chodzi o gïównÈ logikÚ programu: wczytaj_wiersz oblicz // wykonuje pracÚ wydrukuj_wynik Takie zapiski to oczywiĂcie nie jest prawdziwy kod, tylko tzw. pseudokod. Stosuje siÚ go we wczesnych fazach projektowania, gdy nie ma jeszcze pewnoĂci co do tego, jakÈ zastosowaÊ no- tacjÚ. Np., czy obliczenia ma byÊ wywoïaniem funkcji? JeĂli tak, to jakie bÚdzie przyjmowaÊ argumenty? Jest po prostu za wczeĂnie na zadawanie takich pytañ. 6.3.1. Pierwsza próba Na tym etapie nie jesteĂmy jeszcze gotowi napisaÊ programu kalkulatora. Nie przemyĂleli- Ămy jeszcze wszystkiego, ale myĂlenie to ciÚĝka praca i — jak wiÚkszoĂÊ programistów — nie moĝemy siÚ doczekaÊ, ĝeby juĝ coĂ napisaÊ. Spróbujemy wiÚc swoich siï i napiszemy prosty kalkulator, aby zobaczyÊ, do czego nas to doprowadzi. Nasz pierwszy pomysï wyglÈda tak: #include std_lib_facilities.h int main() { cout Wpisz wyraĝenie (obsïugujemy operatory + i –): ; int lval = 0; int rval; char op; int res; cin lval op rval; // Wczytuje coĂ w rodzaju 1 + 3. if (op== + ) res = lval + rval; // dodawanie else if (op== – ) res = lval – rval; // odejmowanie cout Wynik: res ; keep_window_open(); return 0; } 6.3. WRACAJkC DO KALKULATORA 181 Wczytujemy parÚ wartoĂci oddzielonych operatorem, np. 2+2, obliczamy wynik (tu 4) i druku- jemy go na ekranie. ZmiennÈ przechowujÈcÈ wartoĂÊ z lewej strony operatora nazwaliĂmy lval, a z prawej strony rval. To nawet dziaïa! Co z tego, ĝe program nie jest ukoñczony? To wspaniaïe uczucie zrobiÊ coĂ, co dziaïa! Moĝe to programowanie i informatyka sÈ ïatwiejsze, niĝ gïoszÈ plotki? Moĝliwe, ale nie dajmy siÚ ponieĂÊ emocjom z powodu tego pierwszego sukcesu. Oto lista czynnoĂci: 1. OczyĂciÊ kod. 2. DodaÊ obsïugÚ dzielenia i mnoĝenia (np. 2*3). 3. DodaÊ obsïugÚ wyraĝeñ zawierajÈcych wiÚcej niĝ jeden operand (np. 1+2+3). W szczególnoĂci pamiÚtamy, ĝe zawsze naleĝy sprawdzaÊ, czy uĝytkownik podaï sensowne dane (zapomnieliĂmy z poĂpiechu wczeĂniej), oraz ĝe porównywanie jednej wartoĂci z wieloma staïymi lepiej wykonaÊ za pomocÈ instrukcji switch niĝ if. ’Èczenie dziaïañ w ïañcuchy, np. 1+2+3+4, obsïuĝymy, sumujÈc wartoĂci w czasie wczyty- wania. Tzn. wczytujemy 1, widzimy +2, wiÚc dodajemy 2 do 1 (uzyskujÈc w ten sposób wy- nik 3). Dalej widzimy +3, a wiÚc dodajemy 3 do poprzedniego wyniku itd. Po kilku falstartach i poprawieniu kilku bïÚdów skïadni uzyskaliĂmy nastÚpujÈcy rezultat: #include std_lib_facilities.h int main() { cout Wpisz wyraĝenie (obsïugujemy operatory +, –, * oraz /): ; int lval = 0; int rval; char op; cin lval; // Wczytywanie pierwszego argumentu wyraĝenia z lewej. if (!cin) error( Na poczÈtku nie ma argumentu wyraĝenia. ); while (cin op) { // Wczytywanie operatora i prawego argumentu wyraĝenia na zmianÚ. cin rval; if (!cin) error( Nie ma drugiego argumentu wyraĝenia. ); switch(op) { case + : lval += rval; // Dodawanie: lval = lval + rval break; case – : lval –= rval; // Odejmowanie: lval = lval – rval break; case * : lval *= rval; // Mnoĝenie: lval = lval · rval break; case / : lval /= rval; // Dzielenie: lval = lval / rval break; default: // Koniec operatorów — drukowanie wyniku. cout Wynik: lval ; keep_window_open(); return 0; } 182 ROZDZIA’ 6 • PISANIE PROGRAMU } error( Nieprawidïowe wyraĝenie. ); } WyglÈda nieěle, ale gdy wpiszemy wyraĝenie 1+2*3, to ujrzymy wynik 9 zamiast 7, którego spodziewalibyĂmy siÚ na podstawie wiedzy zdobytej w szkole podstawowej. Analogicznie wynikiem wyraĝenia 1-2*3 bÚdzie -3 zamiast spodziewanego -5. Kalkulator wykonuje dziaïania w zïej kolejnoĂci — wyraĝenie 1+2*3 jest liczone jako (1+2)*3 zamiast 1+(2*3). Analogicznie 1-2*3 jest liczone jako (1-2)*3 zamiast 1-(2*3). Lipa! MoglibyĂmy uznaÊ, ĝe zasada, iĝ „mnoĝe- nie wiÈĝe mocniej niĝ dodawanie” jest gïupiÈ i przestarzaïÈ konwencjÈ, ale nie moĝemy zi- gnorowaÊ wielowiekowej tradycji, aby uïatwiÊ sobie programowanie. 6.3.2. Tokeny Musimy zatem znaleěÊ sposób na wczytywanie czÚĂci wiersza „na zapas”, aby sprawdziÊ, czy nie ma tam gdzieĂ operatora * (albo /). JeĂli jest, musimy zmieniÊ kolejnoĂÊ wykonywania dziaïañ. Niestety próbujÈc wczytaÊ nieco danych z wyprzedzeniem, napotkamy kilka trudnoĂci: 1. Nie wymagamy, aby wyraĝenie znajdowaïo siÚ w jednym wierszu. Na przykïad po- niĝsze teĝ jest poprawne: 1 + 2 2. Jak znaleěÊ znaki * i / wĂród cyfr i plusów w kilku wierszach danych wejĂciowych? 3. Jak zapamiÚtaÊ, gdzie znajdowaï siÚ znaleziony znak *? 4. Jak wykonaÊ obliczenia, które nie sÈ ĂciĂle typu „od lewej do prawej”? PostanowiliĂmy byÊ wielkimi optymistami i zajÈÊ siÚ tylko punktami 1 – 3. Ostatnim zaj- miemy siÚ trochÚ póěniej. Poszukamy pomocy. Przecieĝ ktoĂ na pewno zna typowy sposób wczytywania danych typu liczby i operatory i zapisywania ich w taki sposób, aby moĝna je byïo ïatwo wykorzystaÊ w obli- czeniach. Ta konwencjonalna i przydatna technika nazywa siÚ rozbiorem na skïadniki, czyli tokeny (ang. tokenize) — wczytuje siÚ dane i dzieli je na tokeny. Na przykïad wyraĝenie 45+11.5/7 zostaïoby rozïoĝone na tokeny 45 + 11.5 / 7 Token to sekwencja znaków, która reprezentuje pewnÈ caïoĂÊ, np. liczbÚ lub operator. Kompila- tor C++ dzieli na tokeny kod ěródïowy. W istocie róĝne formy rozkïadu na czynniki sÈ podstawÈ analizy wiÚkszoĂci rodzajów tekstów. W wyraĝeniach matematycznych w jÚzyku C++ potrzebujemy trzech rodzajów tokenów: 6.3. WRACAJkC DO KALKULATORA 183 literaïy zmiennoprzecinkowe — zgodnie z definicjÈ w C++, np. 3.14, 0.274e2 i 42; x x operatory — +, -, *, / oraz ; x nawiasy — ( i ). Wydaje siÚ, ĝe trudnoĂci mogÈ nastrÚczaÊ literaïy zmiennoprzecinkowe. Wczytanie liczby 12 wydaje siÚ znacznie ïatwiejsze niĝ 12.3e-3. Ale kalkulatory zazwyczaj wykonujÈ dziaïania na liczbach zmiennoprzecinkowych. Analogicznie podejrzewamy, ĝe aby nasz kalkulator byï przy- datny, musi obsïugiwaÊ nawiasy. W jaki sposób reprezentuje siÚ takie tokeny w programie? Moĝna spróbowaÊ zapamiÚty- waÊ, gdzie kaĝdy token siÚ zaczyna, a gdzie koñczy, ale to moĝe byÊ uciÈĝliwe (zwïaszcza jeĂli pozwolimy na wpisywanie wyraĝeñ obejmujÈcych wiÚcej niĝ jeden wiersz). Dodatkowo, jeĂli bÚdziemy zapisywaÊ wartoĂci jako sekwencje znaków, bÚdziemy musieli póěniej znaleěÊ sposób na odczytanie tych wartoĂci. To znaczy, jeĂli liczbÚ 42 zapiszemy jako znaki 4 i 2, póěniej bÚdziemy musieli odgadnÈÊ, ĝe te dwa znaki reprezentujÈ liczbÚ 42 (tzn. 4*10+2). Oczywi- stym i konwencjonalnym rozwiÈzaniem tego problemu jest przedstawienie kaĝdego tokenu jako pary (rodzaj, wartoĂÊ). Pierwszy element informuje o rodzaju tokenu — liczba, operator, nawias. Drugi natomiast np. w przypadku liczb okreĂla dokïadnÈ wartoĂÊ. Jak wiÚc wykorzystaÊ pomysï par (rodzaj, wartoĂÊ) w kodzie? Zdefiniujemy typ Token do reprezentowania tokenów. Po co? Przypomnij sobie, po co uĝywamy typów: przechowujÈ potrzebne nam dane i pozwalajÈ wykonywaÊ na nich róĝne operacje. Na przykïad typ int pozwala przechowywaÊ liczby caïkowite i umoĝliwia dodawanie, odejmowanie, mnoĝenie oraz dzielenie tych liczb. Natomiast typ string przechowuje ïañcuchy znaków i pozwala je np. ïÈczyÊ. W jÚ- zyku C++ i jego bibliotece standardowej dostÚpnych jest wiele typów, np. char, int, double, string, vector i ostream. Nie ma jednak typu Token. W istocie moĝna wymieniÊ mnóstwo ty- pów — tysiÈce, a nawet dziesiÈtki tysiÚcy — które chcielibyĂmy mieÊ do dyspozycji, a któ- rych nie ma w jÚzyku ani jego bibliotece standardowej. Do naszych ulubionych typów, które nie sÈ standardowo dostÚpne, naleĝÈ Matrix (zobacz rozdziaï 24.), Date (zobacz rozdziaï 9.) oraz reprezentujÈce go liczby caïkowite nieskoñczonej precyzji (poszukaj w internecie informacji na temat typu Bignum). JeĂli przemyĂlisz to, dojdziesz do wniosku, ĝe jÚzyk nie moĝe standardo- wo obsïugiwaÊ dziesiÈtek tysiÚcy typów — kto by je zdefiniowaï i zaimplementowaï, kto by je potem znalazï, nie mówiÈc juĝ o tym, jak gruby musiaïby byÊ podrÚcznik do nauki takiego jÚzyka. JÚzyk C++ wzorem innych nowoczesnych jÚzyków programowania rozwiÈzuje ten problem, pozwalajÈc uĝytkownikowi definiowaÊ wïasne (niestandardowe) typy (ang. user- defined type — typ zdefiniowany przez uĝytkownika). 6.3.3. Implementowanie tokenów Jak powinien wyglÈdaÊ token? To znaczy, jakie wïaĂciwoĂci powinien mieÊ nasz typ Token? Musi nadawaÊ siÚ do reprezentowania operatorów (np. + i -) i wartoĂci liczbowych (np. 42 i 3.14). Oczywistym rozwiÈzaniem jest zaimplementowanie czegoĂ takiego, co moĝe zawieraÊ informacjÚ na temat rodzaju tokenu i w razie potrzeby jego wartoĂÊ: 184 ROZDZIA’ 6 • PISANIE PROGRAMU Pomysï ten moĝna zaimplementowaÊ w jÚzyku C++ na wiele sposobów. Przedstawiamy najprostszy, który wydaje nam siÚ uĝyteczny: class Token { // Bardzo prosty typ zdefiniowany przez uĝytkownika. public: char kind; double value; }; Token to typ (tak samo jak int czy char), a wiÚc moĝna go uĝywaÊ do definiowania zmiennych i przechowywania wartoĂci. Skïada siÚ z dwóch czÚĂci (nazywanych skïadowymi) — kind (rodzaj) oraz value (wartoĂÊ). Sïowo kluczowe class oznacza „typ zdefiniowany przez uĝyt- kownika”. Wskazuje definicjÚ typu z zerem lub wiÚkszÈ liczbÈ skïadowych. Pierwsza skïadowa o nazwie kind jest znakiem char, a wiÚc moĝna jej uĝyÊ do przechowywania znaków + i * , które bÚdÈ reprezentowaïy operatory. Przy uĝyciu tego typu moĝna tworzyÊ nastÚpujÈce in- strukcje: Token t; // Zmienna t jest typu Token. t.kind = + ; // Zmienna t reprezentuje znak +. Token t2; // Zmienna t2 jest innym obiektem typu Token. t2.kind = 8 ; // Cyfra 8 oznacza rodzaj (kind) tokenu bÚdÈcy liczbÈ. t2.value = 3.14; Aby uzyskaÊ dostÚp do skïadowej, posïugujemy siÚ odpowiedniÈ notacjÈ — nazwa_obiektu. ´nazwa_skïadowej. Tekst t.kind moĝna przeczytaÊ jako „rodzaj obiektu t”, a t2.value jako „wartoĂÊ obiektu t2”. Obiekty typu Token moĝna kopiowaÊ tak samo jak typu int: Token tt = t; // Inicjacja kopii if (tt.kind != t.kind) error( To niemoĝliwe! ); t = t2; // przypisanie cout t.value; // wydrukuje 3.14 MajÈc typ Token, wyraĝenie (1.5+4)*11 moĝna przedstawiÊ za pomocÈ siedmiu tokenów: Naleĝy zauwaĝyÊ, ĝe proste tokeny, jak +, nie majÈ wartoĂci, a wiÚc do ich reprezentowania niepotrzebna jest skïadowa value. PotrzebowaliĂmy znaku, który oznaczaïby liczbÚ. Wybór padï na 8 , poniewaĝ nie jest to operator ani znak interpunkcyjny. Wykorzystanie 8 w taki sposób jest dosyÊ tajemnicze, ale na razie moĝe byÊ. Token jest przykïadem typu zdefiniowanego przez uĝytkownika w jÚzyku C++. Typy takie poza danymi skïadowymi mogÈ teĝ zawieraÊ funkcje (operacje) skïadowe. Istnieje wiele po- wodów, dla których siÚ je definiuje. My zdefiniujemy tylko dwie, aby uïatwiÊ sobie inicjowanie obiektów typu Token: class Token { public: char kind; // Rodzaj tokenu 6.3. WRACAJkC DO KALKULATORA 185 double value; // Dla liczb: wartoĂÊ. Token(char ch) // Tworzy Token ze znaku. :kind(ch), value(0) { } Token(char ch, double val) // Tworzy Token ze znaku i wartoĂci typu double. :kind(ch), value(val) { } }; Nie sÈ to zwykïe funkcje skïadowe, tylko tzw. konstruktory. MajÈ takÈ samÈ nazwÚ jak ich typ i sïuĝÈ do inicjalizowania (tworzenia) obiektów typu Token. Na przykïad: Token t1( + ); // Inicjacja zmiennej t1 — t1.kind = + . Token t2( 8 ,11.5); // Inicjacja zmiennej t2 — t2.kind = 8 i t2.value = 11.5. Tekst :kind(ch), value(0) w pierwszym konstruktorze oznacza: „Zainicjuj skïadowÈ kind war- toĂciÈ ch, a value ustaw na 0”. W drugim konstruktorze znajduje siÚ tekst :kind(ch), value(val), który oznacza: „Zainicjuj skïadowÈ kind wartoĂciÈ ch, a value ustaw na val”.W obu przypadkach nie trzeba robiÊ nic wiÚcej, aby utworzyÊ obiekt typu Token, dlatego treĂÊ funkcji jest pusta — { }. Specjalna skïadnia inicjujÈca (ang. member initializer list — lista wartoĂci inicjujÈcych skïadowe), która zaczyna siÚ dwukropkiem, jest uĝywana tylko w konstruktorach. Zauwaĝ, ĝe konstruktor nie zwraca wartoĂci. Dlatego nie trzeba (a nawet nie moĝna) de- finiowaÊ typu zwrotnego konstruktora. WiÚcej na temat konstruktorów napiszemy w roz- dziaïach 9.4.2 i 9.7. 6.3.4. Uĝywanie tokenów Moĝe spróbujemy dokoñczyÊ nasz kalkulator! Chociaĝ warto by byïo najpierw opracowaÊ ja- kiĂ plan dziaïania. Jak bÚdziemy wykorzystywaÊ obiekty typu Token w programie? Moĝemy wczytaÊ dane wejĂciowe do wektora takich obiektów: Token get_token(); // Wczytuje token ze strumienia cin. vector Token tok; // Tutaj bÚdziemy zapisywaÊ tokeny. int main() { while (cin) { Token t = get_token(); tok.push_back(t); } // … } Teraz moĝemy wczytaÊ wyraĝenie w caïoĂci i dopiero po tym obliczyÊ jego wartoĂÊ. W przy- padku 11*12 otrzymamy coĂ takiego: W strukturze tej moĝemy znaleěÊ operator i jego operandy. Gdy to zrobimy, z ïatwoĂciÈ wyko- namy dziaïanie mnoĝenia, poniewaĝ liczby 11 i 12 zostaïy zapisane jako wartoĂci liczbowe, a nie ïañcuchy. 186 ROZDZIA’ 6 • PISANIE PROGRAMU Teraz przeanalizujemy bardziej skomplikowane wyraĝenie. Dla wyraĝenia 1+2*3 tok bÚ- dzie zawieraÊ piÚÊ obiektów typu Token: W tym przypadku operator mnoĝenia moĝna znaleěÊ za pomocÈ prostej pÚtli: for (int i = 0; i tok.size(); ++i) { if (tok[i].kind== * ) { // ZnaleěliĂmy operator mnoĝenia! double d = tok[i–1].value*tok[i+1].value; // Co teraz? } } No dobrze, ale co teraz? Co zrobimy z iloczynem d? Jak okreĂliÊ kolejnoĂÊ wykonywania dziaïañ w wyraĝeniu? Operator + znajduje siÚ przed *, a wiÚc nie moĝemy po prostu wykonaÊ wszystkich dziaïañ od lewej do prawej. Moĝemy spróbowaÊ od prawej do lewej! To by siÚ sprawdziïo w przypadku wyraĝenia 1+2*3, ale juĝ nie dla wyraĝenia 1*2+3. Nie wspominajÈc juĝ o takim czymĂ, jak 1+2*3+4. To wyraĝenie trzeba obliczyÊ „od Ărodka” — 1+(2*3)+4. Jak pora- dzimy sobie z nawiasami? Wydaje siÚ, ĝe utknÚliĂmy. Musimy siÚ wycofaÊ, przestaÊ na chwilÚ programowaÊ i pomyĂleÊ nad wczytywaniem oraz tym, jak rozumiemy ïañcuch wejĂciowy i jak obliczamy jego wartoĂÊ jako wyraĝenia. Pierwsza entuzjastyczna próba rozwiÈzania problemu (napisania kalkulatora) zakoñczyïa siÚ klapÈ. Pierwsze podejĂcia czÚsto tak siÚ koñczÈ i sytuacje takie sÈ nam potrzebne, poniewaĝ dziÚki nim moĝemy lepiej zrozumieÊ naturÚ problemu. W tym przypadku nawet poznaliĂmy przydatne pojÚcie tokenu, które samo jest przykïadem pojÚcia pary (nazwa,wartoĂÊ), z którym bÚdziemy jeszcze wielokrotnie siÚ spotykaÊ. Musisz jednak pamiÚtaÊ, ĝe takie bezmyĂlne i nie- planowane pisanie kodu nie powinno zajmowaÊ zbyt duĝo czasu. Przed dokonaniem analizy (próbÈ zrozumienia problemu) i opracowaniem projektu (opracowaniem ogólnej struktury rozwiÈzania) powinno siÚ napisaÊ bardzo maïo kodu. WYPRÓBUJ Z drugiej strony, czemu nie moglibyĂmy znaleěÊ prostego rozwiÈzania tego problemu? Nie wydaje siÚ aĝ taki trudny. PodjÚcie próby rozwiÈzania problemu, nawet zakoñ- czone fiaskiem, moĝe doprowadziÊ nas do lepszego zrozumienia problemu i opra- cowania rozwiÈzania. PomyĂl, co moĝesz zrobiÊ od razu. Jako przykïad niech posïuĝy wyraĝenie 12.5+2. Moĝemy rozbiÊ je na tokeny, dojĂÊ do wniosku, ĝe to bardzo proste wyraĝenie i obliczyÊ wynik. Moĝe byïoby trochÚ baïaganu, ale rozwiÈzanie jest pro- ste, wiÚc moĝe warto podÈĝyÊ w tym kierunku i znaleěÊ coĂ, co wystarczy! PomyĂl, co byĂ zrobiï, gdybyĂ znalazï operatory + i * w wierszu 2+3*4. To takĝe moĝna obli- czyÊ „na piechotÚ”. Jak byĂmy sobie poradzili ze skomplikowanym wyraĝeniem typu 1+2*3/4 5+(6-7*(8))? Jak byĂmy radzili sobie z bïÚdami, np. 2+*3 albo 2 3? PomyĂl nad tym przez chwilÚ. Moĝesz zrobiÊ sobie notatki na kartce, nakreĂl moĝliwe roz- wiÈzania oraz wypisz interesujÈce lub waĝne wyraĝenia. 6.3. WRACAJkC DO KALKULATORA 187 6.3.5. Powrót do tablicy Jeszcze raz przeanalizujemy problem, tym razem starajÈc siÚ nie wyrywaÊ z nieprzemyĂlany- mi pomysïami. Jedyne, co odkryliĂmy, to fakt, ĝe obliczenie przez program tylko jednego wyraĝenia sprawia nam trudnoĂci. ChcielibyĂmy mieÊ moĝliwoĂÊ obliczenia wielu wyraĝeñ w jednym uruchomieniu programu. W zwiÈzku z tym wzbogacamy nasz pseudokod w na- stÚpujÈcy sposób: while (nie_skoñczone) { wczytaj_wiersz oblicz // wykonaj pracÚ wydrukuj_wynik } To z pewnoĂciÈ komplikuje sprawÚ, ale musimy wziÈÊ pod uwagÚ fakt, ĝe kalkulatorów zazwy- czaj uĝywa siÚ do wykonywania kilku obliczeñ po kolei. Czy mamy kazaÊ uĝytkownikowi uru- chamiaÊ nasz program ponownie, aby wykonaÊ kaĝde obliczenie? MoglibyĂmy, ale w wielu nowoczesnych systemach operacyjnych uruchamianie programów trwa za dïugo, a wiÚc lepiej tego nie robiÊ. Kiedy patrzymy na nasz pseudokod, nasze poczÈtkowe próby rozwiÈzania problemu i przy- kïady uĝycia, nasuwa siÚ nam kilka pytañ (i kilka nieĂmiaïych odpowiedzi): 1. JeĂli uĝytkownik wpisze 45+5/7, jak znajdziemy poszczególne elementy — 45, 5, / i 7? Odpowiedě: podzielimy na tokeny! 2. W jaki sposób oznaczymy koniec wyraĝenia? OczywiĂcie znakiem nowego wiersza (zaw- sze podejrzliwie traktuj zwroty typu „oczywiĂcie” — „oczywiĂcie” to nie ĝaden powód! 3. Jak zaprezentujemy wyraĝenie 45+5/7 jako dane, aby moĝna byïo obliczyÊ wynik? Przed wykonaniem dodawania musimy w jakiĂ sposób zamieniÊ znaki 4 i 5 w liczbÚ caïkowitÈ 45 (tj. 4*10+5). Zatem podziaï na tokeny jest czÚĂciÈ rozwiÈzania. 4. Jak sprawiÊ, aby wyraĝenie 45+5/7 byïo obliczane jako 45+(5/7), a nie (45+5)/7? 5. Ile wynosi 5/7? Okoïo .71, a wiÚc to nie jest liczba caïkowita. Z doĂwiadczenia wiemy, ĝe uĝytkownicy kalkulatorów oczekujÈ wyników zmiennoprzecinkowych. Czy po- winniĂmy pozwoliÊ na wpisywanie liczb zmiennoprzecinkowych? OczywiĂcie! 6. Czy moĝemy pozwoliÊ na uĝywanie zmiennych? MoglibyĂmy na przykïad napisaÊ: v=7 m=9 v*m Dobry pomysï, ale zostawimy to na póěniej. Na razie zajmiemy siÚ podstawowÈ funk- cjonalnoĂciÈ. Najwaĝniejsza decyzja to prawdopodobnie odpowiedě na pytanie w punkcie 6. W rozdziale 7.8 zobaczysz, ĝe odpowiedě ta pociÈgnie za sobÈ prawie podwojenie rozmiaru wstÚpnej wersji pro- jektu. To podwoiïoby czas potrzebny na uruchomienie wstÚpnej wersji programu. Podejrze- wamy, ĝe poczÈtkujÈcy potrzebowaïby nawet cztery razy wiÚcej czasu i niewykluczone, ĝe stra- ciïby w koñcu cierpliwoĂÊ. We wczesnych fazach prac nad projektem naleĝy zawsze unikaÊ 188 ROZDZIA’ 6 • PISANIE PROGRAMU przesady z liczbÈ funkcji. WstÚpna wersja zawsze powinna byÊ prosta i zawieraÊ tylko najwaĝ- niejsze funkcje. Kiedy uda Ci siÚ zmusiÊ coĂ do dziaïania, moĝesz postawiÊ sobie bardziej am- bitne wymagania. Budowa programu etapami jest znacznie ïatwiejsza niĝ wszystkiego na raz. Odpowiedě „tak” na pytanie 6. miaïaby jeszcze jeden zïy wynik: mogïoby byÊ trudno oprzeÊ siÚ pokusie dodania jeszcze innych funkcji. Moĝe warto pomyĂleÊ o funkcjach matematycznych albo o pÚtlach? Gdy zacznie siÚ dodawaÊ kolejne „fajne” funkcje, trudno przestaÊ. Z programistycznego punktu widzenia najbardziej kïopotliwe sÈ punkty 1, 3 i 4. Ponadto sÈ ze sobÈ powiÈzane, poniewaĝ gdy znajdziemy juĝ 45 i +, co mamy z nimi zrobiÊ? Tzn., jak zapisaÊ je w programie? OczywiĂcie czÚĂciowym rozwiÈzaniem tego problemu jest podziaï na tokeny, ale tylko czÚĂciowym. Co zrobiïby doĂwiadczony programista? Gdy mamy do rozwiÈzania jakiĂ trudny techniczny problem, czÚsto moĝna znaleěÊ jakieĂ standardowe rozwiÈzanie. Wiemy, ĝe ludzie piszÈ kalku- latory, przynajmniej od kiedy istniejÈ komputery przyjmujÈce dane symboliczne z klawiatury, a wiÚc od 50 lat. Musi byÊ jakieĂ standardowe rozwiÈzanie! W takiej sytuacji doĂwiadczony programista konsultuje siÚ z kolegami i przeszukuje dostÚpnÈ literaturÚ. Byïoby gïupstwem myĂleÊ, ĝe w jeden dzieñ uda siÚ wymyĂleÊ coĂ lepszego, niĝ inni wymyĂlili przez 50 lat. 6.4. Gramatyki Istnieje standardowa odpowiedě na pytanie, jak rozszyfrowaÊ znaczenie wyraĝenia: najpierw wprowadzone znaki naleĝy zebraÊ i podzieliÊ na tokeny (to juĝ sami odkryliĂmy). JeĂli uĝyt- kownik wpisze: 45+11.5/7 program powinien utworzyÊ nastÚpujÈcÈ listÚ tokenów: 45 + 11.5 / 7 Token to sekwencja znaków, którÈ uwaĝamy za jakÈĂ jednostkÚ, np. operator lub liczbÚ. Po utworzeniu tokenów program musi upewniaÊ siÚ, ĝe caïe wyraĝenie jest poprawnie rozumiane. Na przykïad wiemy, ĝe wyraĝenie 45+11.5/7 oznacza 45+(11.5/7), a nie (45+11.5)/7. SÚk w tym, jak nauczyÊ program tej przydatnej zasady (dzielenie „wiÈĝe mocniej” niĝ dodawanie)? Standardowa odpowiedě jest taka, ĝe piszemy gramatykÚ definiujÈcÈ skïadniÚ naszych danych wejĂciowych, a nastÚpnie piszemy program, w którym implementujemy zasady tej gramatyki. Na przykïad: // Prosta gramatyka wyraĝeñ: Expression: Term Expression + Term // dodawanie Expression – Term // odejmowanie Term: Primary 6.4. GRAMATYKI 189 Term * Primary // mnoĝenie Term / Primary // dzielenie Term Primary // reszta z dzielenia (modulo) Primary: Number ( Expression ) // grupowanie Number: floating-point-literal Jest to zestaw prostych zasad. OstatniÈ naleĝy czytaÊ nastÚpujÈco: „Number (liczba) to literaï zmiennoprzecinkowy”. Natomiast treĂÊ przedostatniej jest taka: „Primary (czynnik) jest liczbÈ lub znakiem ( , po którym jest wyraĝenie i znak ) ”. Reguïy dla Expression (wyraĝenia) i Term (skïadnika) sÈ podobne. Kaĝda z nich jest zdefiniowana z uwzglÚdnieniem jednej z reguï, które znajdujÈ siÚ dalej. Jak pamiÚtamy z podrozdziaïu 6.3.2, nasze tokeny — zgodnie z definicjÈ w jÚzyku C++ — to: x literaï zmiennoprzecinkowy (zgodny z definicjÈ w jÚzyku C++, np. 3.14, 0.274e2 lub 42); x +, -, *, / oraz — operatory; x ( i ) — nawiasy. UĝywajÈc gramatyki i tokenów, zrobiliĂmy bardzo duĝy pojÚciowy skok w stosunku do naszego poczÈtkowego pseudokodu. Tego rodzaju postÚpy chcielibyĂmy robiÊ zawsze, ale rzadko siÚ to udaje bez pomocy. Do tego wïaĂnie sïuĝÈ doĂwiadczenie, literatura i mentorzy. Na pierwszy rzut oka gramatyka ta wydaje siÚ bezsensowna. CzÚsto tak jest z notacjÈ technicznÈ. PamiÚtaj jednak, ĝe jest to ogólna i elegancka (co w koñcu docenisz) notacja do opisu czegoĂ, co potrafisz robiÊ przynajmniej od czasów szkoïy podstawowej. Nie masz pro- blemu z obliczeniem wyraĝenia 1-2*3 albo 1+2-3 lub 3*2+4/2. Potrafisz jednak wyjaĂniÊ, jak to robisz? Umiesz to tak wyjaĂniÊ, aby zrozumiaï to nawet ktoĂ, kto nigdy nie miaï stycznoĂci z konwencjonalnÈ arytmetykÈ? Czy Twoje wyjaĂnienia bÚdÈ miaïy zastosowanie dla wszyst- kich kombinacji operatorów i argumentów? Aby wystarczajÈco szczegóïowo i precyzyjnie objaĂniÊ coĂ komputerowi, potrzebna jest odpowiednia notacja — a gramatyka naleĝy do najlepszych konwencjonalnych narzÚdzi do jej tworzenia. Jak czyta siÚ gramatykÚ? MajÈc pewne dane wejĂciowe, zaczyna siÚ od pierwszej reguïy, Expression (wyraĝenie), i przeszukuje kolejne, znajdujÈc te, które pasujÈ do tokenów w miarÚ ich wczytywania. Wczytywanie strumienia tokenów zgodnie z zasadami gramatyki nazywa siÚ parsowaniem (analizÈ skïadniowÈ), a program, który to robi, nazywamy parserem (ang. parser) lub analizatorem skïadni (ang. syntax analyzer). Nasz analizator odczytuje tokeny od lewej do prawej, dokïadnie w takiej kolejnoĂci, jak je wpisujemy i czytamy. Wypróbujemy jakiĂ bardzo prosty przykïad: czy 2 jest wyraĝeniem? 1. Wyraĝenie (Expression) musi byÊ skïadnikiem (Term) lub koñczyÊ siÚ skïadnikiem. Skïadnik musi byÊ czynnikiem (Primary) lub koñczyÊ siÚ czynnikiem. Ten czynnik musi zaczynaÊ siÚ znakiem ( lub byÊ liczbÈ (Number). OczywiĂcie 2 nie jest znakiem (, tylko literaïem zmiennoprzecinkowym, a wiÚc liczbÈ, która jest czynnikiem. 190 ROZDZIA’ 6 • PISANIE PROGRAMU 2. Przed tym czynnikiem (liczba 2) nie ma znaku /, * ani , a wiÚc jest to kompletny skïadnik (a nie zakoñczenie wyraĝenia z operatorem /, * lub ). 3. Przed skïadnikiem tym (Primary 2) nie ma znaku + ani -, a wiÚc jest to peïne wyraĝenie (Expression), a nie zakoñczenie wyraĝenia z operatorem + lub -. W zwiÈzku z tym zgodnie z naszÈ gramatykÈ 2 jest wyraĝeniem. PrzeglÈd gramatyki moĝna przedstawiÊ graficznie: Na rysunku zostaïa przedstawiona Ăcieĝka, którÈ przemierzyliĂmy przez definicje. OdwracajÈc nasze rozumowanie, moĝemy powiedzieÊ, ĝe 2 jest wyraĝeniem, poniewaĝ jest literaïem zmien- noprzecinkowym, który jest liczbÈ, liczba jest czynnikiem, czynnik jest skïadnikiem, a skïad- nik wyraĝeniem. Spróbujmy czegoĂ bardziej skomplikowanego. Czy 2+3 jest wyraĝeniem? Naturalnie znaczna czÚĂÊ rozumowania bÚdzie taka sam jak dla 2: 1. Wyraĝenie musi byÊ skïadnikiem lub mieÊ go na koñcu. Skïadnik musi byÊ czynnikiem lub koñczyÊ siÚ czynnikiem, który z kolei musi zaczynaÊ siÚ od znaku ( lub byÊ liczbÈ. OczywiĂcie 2 nie jest znakiem (, ale jest literaïem zmiennoprzecinkowym, który jest liczbÈ, ta z kolei jest czynnikiem. 2. Przed tym czynnikiem (liczba 2) nie ma znaku /, * ani , a wiÚc jest to kompletny skïadnik (a nie zakoñczenie wyraĝenia z operatorem /, * lub ). 3. Za skïadnikiem tym (Primary 2) jest znak +, a wiÚc jest to koniec pierwszej czÚĂci wy- raĝenia i musimy poszukaÊ skïadnika za tym znakiem. Dokïadnie w taki sam sposób, jak w przypadku 2 dowiadujemy siÚ, ĝe 3 jest skïadnikiem. Poniewaĝ za 3 nie ma znaku + ani -, uznajemy, ĝe
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Programowanie. Teoria i praktyka z wykorzystaniem C++
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: