Programowanie obiektowe i C++ dla matematyków Bartosz Szreder szreder (at) mimuw... 04 X 2011 Wszelkie uwagi, poprawki, braki czy sugestie najlepiej wysyłać na maila. 1. wprowadzenie do Linuksa: kompilacja i uruchamianie programów, korzystanie z dokumentacji; 2. pokazanie składni języka, prostego wczytywania i wypisywania za pomocą iostream; 3. przedstawienie paralel i różnic pomiędzy Pascalem i C/C++: możliwości deklarowania zmiennych w różnych miejscach, a niekoniecznie zaraz na początku bloku jak w Pascalu; konieczne nawiasy w warunkach oraz && i jako analogie do pascalowych and i or logicznych; wygląd pętli for i while; deklarowanie i używanie funkcji; o przestrzeniach nazw i pomocniczym stosowaniu using namespace std; w krótkich programach; o kilku dobrych manierach dotyczących programowania znaczące nazwy zmiennych, wcięcia w kodzie źródłowym, nieprogramowanie metodą kopiuj-wklej; 4. przykłady z silnią i liczbami Fibonnacciego: implementacje pętlowe (for i while w przypadku silni); implementacje rekurencyjne i związane z tym problemy (warunek stopu, wykładniczy czas działania w liczbach Fibonnacciego); Przydatne linki: http://mst.mimuw.edu.pl/lecture.php?lecture=poc http://www.cplusplus.com/ http://www.kernel.org/doc/documentation/codingstyle całkiem sensowny styl programowania w C 1
1 Powłoka systemowa i kompilacje Poleceniem g++ w terminalu kompilujemy program, np: $ g++ mojprogram.cpp Jeśli nie będzie błędów, spowoduje to wygenerowanie pliku wykonywalnego a.out. Uruchamiamy go w następujący sposób: $./a.out Można też powiedzieć kompilatorowi, żeby wygenerował plik wykonywalny o nazwie innej niż domyślna: $ g++ -o uruchom_mnie mojprogram.cpp Teraz mamy plik wykonywalny uruchom_mnie, który odpalamy $./uruchom_mnie 1.1 Edytory Fajnym edytorem jest kate, zwłaszcza jak się włączy w nim kolorowanie składni (Tools Highlighting Sources C++) i automatyczne wcinanie kodu (Tools Indentation C Style). Te opcje można też bardziej permanentnie ustawić po zanurkowaniu w konfigurację. Edytor kate ma też wbudowany terminal konsole, z poziomu którego można od razu kompilować i uruchamiać programy. Wkrótce będziemy też korzystać z jakiegoś większego zintegrowanego środowiska programistycznego: NetBeans, KDevelop (polecany dla użytkowników kate), Eclipse... 2 Wczytywanie i wypisywanie Prosty program wypisujący napis na standardowe wyjście: 003 int main() { 005 std::cout << "hello"; 006 return 0; 007 } Program robiący to samo, ale zapisujący po drodze napis do zmiennej typu string: #include <string> 003 int main() 005 { 006 std::string napis = "hello"; 007 std::cout << napis; 2
008 return 0; 009 } Program wczytujący jedną liczbę ze standardowego wejścia i wypisujący ją: 003 int main() { 005 int x; 006 std::cin >> x; 007 std::cout << x; 008 return 0; 009 } Co się stanie, jeśli podana zostanie nie liczba, a jakiś śmieć? 2.1 Technikalia cout to Console OUTput, cin to Console INput. Elementy biblioteki standardowej C++ znajdują się w tzw. przestrzeni nazw (ang. namespace) nazwanej std, stąd konieczność pisania std:: przed każdym użyciem typów i funkcji biblioteki standardowej. Trochę to uciążliwe w małych, ćwiczebnych programach, więc uprościmy sobie życie włączając całą przestrzeń nazw std: 007 int x; 008 cin >> x; 009 cout << x; 010 return 0; 011 } W poważnych projektach to nie jest dobra praktyka i nie należy pochopnie tego robić (do zastanowienia dlaczego?). Wypisywanie specjalnych znaków, takich jak przejście do nowego wiersza, uzyskuje się za pomocą mechanizmu znaku ucieczki (ang. escape symbol), którym w C i C++ jest backslash: przejście do nowego wiersza (newline): \n znak tabulacji: \t wypisanie backslasha: \\ Komentarze w kodzie można wstawiać na dwa sposoby: podwójnym znakiem slasha // uzyskujemy komentarz działający do końca wiersza, 3
komentarz rozpinający dowolnie długi blok tekstu można umieścić w bloku /*... */. Takich komentarzy nie należy zagnieżdżać. 3 Obliczenia arytmetyczne Poniższy program wczytuje dwie liczby całkowite i wykonuje na nich różne obliczenia, wypisując wynik na standardowe wyjście. 007 int a, b; 008 009 cin >> a >> b; 010 cout << a << " + " << b << " = " << a + b << "\n"; 011 cout << a << " - " << b << " = " << a - b << "\n"; 012 //dzielenie całkowitoliczbowe - odpowiednik pascalowego div 013 cout << a << " / " << b << " = " << a / b << "\n"; 014 //reszta z dzielenia - odpowiednik pascalowego mod 015 cout << a << " % " << b << " = " << a % b << "\n"; 016 017 return 0; 018 } 3.1 Silnia Ustalmy, że silnia z liczby naturalnej n zdefiniowana jest następująco: { 1 dla n = 0 n! = n (n 1)! dla n > 0 Program obliczający silnię wprost z definicji (rekurencyjnie) mógłby wyglądać tak: 005 int silnia(int arg) 007 if (arg == 0) 008 return 1; 009 else 010 return arg * silnia(arg - 1); 4
011 } 012 013 int main() 014 { 015 int n; 016 cin >> n; 017 cout << silnia(n) << "\n"; 018 return 0; 019 } natomiast implementacja iteracyjna może wyglądać tak (pętla for): 007 int n, silnia = 1; 008 cin >> n; 009 for (int i = 2; i <= n; ++i) 010 silnia = silnia * i; 011 cout << silnia << "\n"; 012 return 0; 013 } albo tak (pętla while): 007 int n, silnia = 1; 008 cin >> n; 009 010 int i = 2; 011 while (i <= n) { 012 silnia = silnia * i; 013 i = i + 1; 014 } 015 cout << silnia << "\n"; 016 017 return 0; 018 } 5
Co się stanie, jeśli podamy na wejściu jakiegoś śmiecia albo liczbę ujemną? Czy program zakończy swoje działanie kiedyś? Jeśli tak, to kiedy i od czego to zależy? Czy wyniki dla n 13 są poprawne i dlaczego nie? Uwaga! W językach z rodziny C przypisanie wykonuje się pojedynczym znakiem równości, zaś porównanie podwójnym! Widać to w przykładzie rekurencyjnego obliczania silni. 3.2 Liczby Fibonacciego Definiujemy F ib 0 = 0, F ib 1 = 1 i F ib n = F ib n 1 + F ib n 2 dla n > 1. Implementacja rekurencyjna: 005 int fibonacci(int n) 007 if (n == 0) 008 return 0; 009 if (n == 1) 010 return 1; 011 return fibonacci(n - 1) + fibonacci(n - 2); 012 } 013 014 int main() 015 { 016 int n; 017 cin >> n; 018 cout << fibonacci(n) << "\n"; 019 return 0; 020 } i iteracyjna: 005 int fibonacci(int n) 007 if (n == 0) 008 return 0; 009 if (n == 1) 010 return 1; 011 012 int a = 0, b = 1, c; 013 for (int i = 2; i <= n; ++i) { 6
014 c = a + b; 015 a = b; 016 b = c; 017 } 018 return c; 019 } 020 021 int main() 022 { 023 int n; 024 cin >> n; 025 cout << fibonacci(n) << "\n"; 026 return 0; 027 } Porównaj czas działania obu programów dla n = 40, 41, 42,... Dlaczego wersja rekurencyjna jest dużo wolniejsza? 7