pobierz
Transkrypt
pobierz
C++ w przykładach Mariusz Różycki Cezary Obczyński Wersja aktualna na dzień 29 listopada 2015 Spis treści 1 Wypisywanie danych 4 2 Zmienne i stałe 10 3 Instrukcje warunkowe 16 4 Funkcje 24 5 Rekurencja 31 6 P˛etle while 41 7 P˛etle for 50 8 Wektory 55 9 Znaki i łańcuchy 65 10 Obsługa plików 66 11 Wskaźniki 67 12 Struktury 68 A Biblioteka Allegro 69 1 Wprowadzenie Ksia˛żka ta stanowi podr˛ecznik i zbiór zadań z podstaw programowania w C++ dla uczniów klas akademickich w Liceum Ogólnokształcacym ˛ im. Marsz. Stanisława Małachowskiego w Płocku. Korzystanie z tego podr˛ecznika Każdy rozdział składa si˛e z czterech cz˛eści. Przykłady Zawiera kilka przykładowych zadań wraz z rozwiazaniami ˛ i wyjaśnieniami. Każdy przykład wprowadza jedno nowe zagadnienie. Zapoznaj si˛e z każdym przykładem, w szczególności zwracajac ˛ uwag˛e na ramki oznaczone słowem Uwaga. Uruchom podane rozwiazania. ˛ Spróbuj je nieznacznie zmienić i zobaczyć jak wpłynie to na zachowanie wynikowego programu. Pytania Zawiera pytania, na które odpowiedź powinniście znać. Odpowiedź na niektóre z nich można znaleźć w opisie przykładów. Niektóre z pytań dotycza˛ zachowania danego fragmentu kodu, które najlepiej jest przetestować samodzielnie. Cz˛eść z pytań, oznaczona gwiazdkami, wymaga doczytania informacji w innych źródłach (z reguły Wikipedia wystarczy). Zadania Zawiera zadania do samodzielnego rozwiazania. ˛ Każde z zadań ma przypisana˛ liczb˛e punktów. Praca domowa polegać b˛edzie na rozwiazaniu ˛ takiej liczby zadań, aby uzyskać w sumie zadana˛ liczb˛e punktów. Nie ma obowiazku ˛ rozwiazywania ˛ wszystkich zadań z każdego rozdziału, jednak warto jest spróbować zrobić ich wi˛ecej niż wymaga tego praca domowa. Rozszerzenie Zawiera zadania przeznaczone dla osób, które posiadaja˛ umiej˛etności wykraczajace ˛ poza dotychczas omówiony materiał. Zaliczaja˛ si˛e one do pracy domowej, jednak warte sa˛ dużo wi˛ecej punktów. 2 Instalacja środowiska Code::Blocks Należy ściagn ˛ ać ˛ instalator z linku podanego na stronie. Proces instalacji wyglada ˛ tak samo jak dla wi˛ekszości programów Windowsowych. Używanie środowiska Code::Blocks Tworzenie nowego pliku Nowy plik można stworzyć wybierajac ˛ z górnego menu File → New → Empty file. Najlepiej jest go od razu zapisać. Pami˛etać należy, że nazwa pliku nie powinna zawierać polskich znaków, spacji oraz znaków interpunkcyjnych niedozwolonych w żadnym pliku (jak wykrzyknik czy ukośnik). Istotne jest również, aby plik posiadał rozszerzenie .cpp (Code::Blocks domyślnie zapisuje pliki z rozszerzeniem .c). Kompilacja i uruchamianie Przed uruchomieniem programu, jeżeli go zmodyfikowaliśmy, należy zapisać zmiany i skompilować kod. W środowisku Code::Blocks możemy skompilować i uruchomić program wybierajac ˛ Build → Build and run z menu kontekstowego lub wciskajac ˛ klawisz F9. 3 1. Wypisywanie danych Przykłady 1.1. Napisz program, który wypisze na ekran słowa „Hello, world!”. 1 2 3 4 5 6 7 8 9 // Przyklad 1: Hello, world! #include <iostream> using namespace std; int main() { cout << "Hello, world!" << endl; return 0; } Pierwsza linia jest przykładem komentarza. Wszystko co znajduje si˛e po podwójnym ukośniku, aż do końca linii, jest ignorowane przez kompilator. Można umieszczać tam wyjaśnienia kodu, które ułatwia˛ jego zrozumienie przez inne osoby (lub ciebie za kilka tygodni lub miesi˛ecy). Uwaga! Zwróć uwag˛e na brak polskich znaków, nie tylko w komentarzu, ale w całym kodzie. Ich używanie może prowadzić do bł˛edów kompilacji lub nieprawidłowego działania programu, dlatego lepiej ich unikać. Druga i trzecia linia mówia˛ kompilatorowi, że używać b˛edziemy strumieni wejścia i wyjścia (iostream), znajdujacych ˛ si˛e wewnatrz ˛ biblioteki standardowej (std). Czym dokładnie sa˛ strumienie dowiesz si˛e później. Póki co musisz wiedzieć, że służa˛ one do wypisywania oraz pobierania danych. Linie 5-9 okalaja˛ właściwa˛ seri˛e poleceń wydawana˛ komputerowi. Wszystko co znajduje si˛e mi˛edzy klamrami po int main() jest wykonywane przez komputer po kolei, od góry do dołu. Uwaga! Zauważ, że każda linia kodu mi˛edzy klamrami została wci˛eta o jeden poziom. Wci˛ecia nie sa˛ niezb˛edne do prawidłowego działania programu, jednak bardzo ułatwiaja˛ czytanie kodu. Warto wyrobić sobie nawyk prawidłowego formatowania już na samym poczatku. ˛ 4 Linia 6 odpowiada za wypisanie na ekran tekstu „Hello, world!”. Wszystko co znajduje si˛e mi˛edzy znakami cudzysłowu zostanie wypisane na ekran dokładnie tak, jak wyglada ˛ w kodzie, włacznie ˛ ze spacjami, liczbami i operacjami matematycznymi. Uwaga! Mi˛edzy cudzysłowami nie można umieszczać znaków nowej linii. Słowo endl oznacza koniec linii (od angielskiego end line). Przesłanie go do strumienia (bez cudzysłowów!) spowoduje przejście do nowego wiersza. W końcu linia 8 odpowiada za przekazanie do systemu liczby 0, oznaczajacej ˛ prawidłowe zakończenie działania programu. 1.2. Napisz program, który wypisze na ekran piernikowego ludzika: _ _(")_ (_ . _) / : \ (_/ \_) 1 2 3 4 5 6 7 8 9 10 11 12 13 // Przyklad 1.2: Piernikowy ludzik #include <iostream> using namespace std; int main() { cout << " _" << endl; cout << " _(\")_" << endl; cout << "(_ . _)" << endl; cout << " / : \\" << endl; cout << "(_/ \\_)" << endl; return 0; } W celu wypisania piernikowego ludzika, musimy przepisać wszystkie znaki go tworzace ˛ do edytora, umieścić je w cudzysłowach i wypisać. Tym razem jednak potrzebujemy wi˛ecej niż jednej linii z cout i endl. Problem pojawia si˛e, gdy chcemy wypisać na ekran znak cudzysłowu. Jego umieszczenie w kodzie oznaczać b˛edzie koniec tekstu do wypisania, a nie o to nam chodzi. Aby temu zaradzić, należy umieścić odwrócony ukośnik (backslash) przed problematycznym znakiem, jak w linii 7. Jest to znak dla kompilatora 5 (przynajmniej w tym wypadku): wiem, że nast˛epny znak ma specjalne znaczenie, ale ja chc˛e go po prostu wypisać. Podobny problem napotykamy w linii 9. Tym razem chcemy wypisać ukośnik, a znak cudzysłowu pozostawić jako zakończenie tekstu. Znów, umieszczamy ukośnik przed problematycznym znakiem, tym razem ukośnikiem. To samo musimy zrobić w linii 10. Uwaga! Dodatkowy ukośnik powinniśmy umieszczać także przed znakiem apostrofu oraz znakiem zapytania. Dodatkowo, umieszczenie go przed litera˛ n (czyli \n) da nam znak nowej linii. 1.3. Napisz program, który obliczy i wypisze wartość wyrażenia 94 · 31 + 624. 1 2 3 4 // Przyklad 1.3: Wartosc wyrazenia #include <iostream> using namespace std; 5 6 7 8 9 int main() { cout << 94*31 + 624 << endl; return 0; } Jeżeli pominiemy cudzysłowy, wyrażenie nie b˛edzie traktowane jako tekst, ale jako matematyczne działanie. Możemy w ten sposób wykonywać obliczenia na liczbach. Do dyspozycji mamy dodawanie (+), odejmowanie (-), mnożenie (*), dzielenie (/) oraz reszt˛e z dzielenia (%). Kolejność wykonywania działań jest taka, jak w matematyce. Najpierw wykonywane jest mnożenie i dzielenie (w tym dzielenie z reszta) ˛ od lewej do prawej, a nast˛epnie dodawanie i odejmowanie, również od lewej do prawej. Kolejność możemy zmieniać przy użyciu nawiasów, ale tylko okragłych. ˛ Uwaga! Dzielenie w C++ jest całkowitoliczbowe, to znaczy jeżeli dwie liczby sa˛ całkowite, to wynik ich dzielenia również b˛edzie całkowity. Wynik zaokraglany ˛ jest w dół, zatem 99/10 da nam 9. Uwaga! Znak % nie ma w C++ nic wspólnego z obliczaniem procentów. Służy do obliczania reszty z dzielenia jednej liczby przez druga.˛ 6 1.4. Napisz program, który obliczy przybliżona˛ wartość wyrażenia ja˛ na ekran. 1 2 3 4 5 6 7 8 9 10 √ 5 2 i wypisze // Przyklad 1.4: Pierwiastki #include <iostream> #include <cmath> using namespace std; int main() { cout << pow(sqrt(2), 5) << endl; return 0; } Oprócz podstawowych operacji arytmetycznych mamy także możliwość obliczania wartości innych funkcji matematycznych, jak pierwiastki, pot˛egi, logarytmy, funkcje trygonometryczne itp. Musimy jednak w tym celu użyć definicji z pliku cmath, o czym informujemy kompilator w linii 3. Otrzymujemy w ten sposób dost˛ep do funkcji takich jak sqrt (pierwiastek kwadratowy z liczby), pow (pot˛ega), sin, cos, log itp. Funkcji tych używamy podajac ˛ najpierw nazw˛e, a nast˛epnie argumenty w nawiasach okragłych. ˛ Jeżeli argumentów jest kilka, oddzielamy je przecinkami. Uwaga! Liczby ułamkowe piszemy w C++ z kropka.˛ Zatem prawidłowo powinniśmy napisać, na przykład, 2.5, ale nie 2,5. Uwaga! C++ nie posiada operatora pot˛egowania ^. Wyrażenie 2^5 nie zwróci bł˛edu w czasie kompilacji, ale jego wynikiem nie b˛edzie 32, tylko 0. Uwaga! Funkcje trygonometryczne (sin, cos, tan) przyjmuja˛ jako argumenty miar˛e kata ˛ w radianach, nie w stopniach. Uwaga! Nie jest dost˛epna funkcja obliczajaca ˛ wartość cotangensa danego kata. ˛ Można to ograniczenie obejść obliczajac ˛ wartość 1/tan(x). 7 Pytania 1.1. Co wypisze na ekran nast˛epujaca ˛ linia kodu? 1 cout << "2+2" << endl; 1.2. Co wypisze na ekran nast˛epujaca ˛ linia kodu? 1 cout << "/\\/\\\n" << endl; 1.3. Co wypisze na ekran nast˛epujaca ˛ linia kodu? 1 cout << 9999/1000 << endl; 1.4. Co wypisze na ekran nast˛epujaca ˛ linia kodu? 1 cout << 9999%1000 << endl; 1.5. Czym różnia˛ si˛e poniższe linie kodu? Jaki jest wpływ tej różnicy na wynik działania każdej z nich? 1 2 cout << "2+2*2" << endl; cout << 2+2*2 << endl; Zadania 1.1. (+5) Napisz program, który wypisze na ekran twoje imi˛e i nazwisko. 1.2. (+5) Napisz program, który wypisze na ekran pierwsze cztery wersy Inwokacji z „Pana Tadeusza”: Litwo! Ojczyzno moja! Ty jesteś jak zdrowie, Ile ci˛e trzeba cenić, ten tylko si˛e dowie, Kto ci˛e stracił. Dziś pi˛ekność twa˛ w całej ozdobie Widz˛e i opisuj˛e, bo t˛eskni˛e po tobie. 1.3. (+10) Napisz program, który wypisze na ekran lisa: /.|.\ ( o o ) \ / \ / O 8 1.4. (+10) Napisz program, który wypisze na ekran kota: |\_/| (^Y^)--.-, w-w__((__) 1.5. (+10) Napisz program, który wypisze na ekran sow˛e: ^...^ / o,o \ |):::(| ===w=w=== 1.6. (+5) Napisz program, który obliczy i wypisze wartość wyrażenia 123 · 456. 1.7. (+10) Napisz program, który obliczy i wypisze liczb˛e sekund, która upływa w ciagu ˛ jednego tygodnia. 1.8. (+10) Napisz program, który obliczy i wypisze reszt˛e z dzielenia wyrażenia (492 + 157) · (673 − 192) przez 11. 1.9. (+5) Napisz program, który obliczy i wypisze przybliżona˛ wartość pierwiastka kwadratowego z 3. 1.10. (+10) Napisz program, który obliczy i wypisze wartość wszystkich funkcji trygonometrycznych kata ˛ 30 stopni. Rozszerzenie 1.1. (+50) Napisz program, który b˛edzie wypisywać na ekran losowe cytaty. 1.2. (+80) Napisz program, który pobierze tekst jako argument wiersza poleceń, a nast˛epnie wypisze go w dymku obok sowy, lisa lub kota, na przykład wywołanie sowa.exe "Hello, world!" powinno wyświetlić: ^...^ / o,o \ |):::(| ===w=w=== --------------| Hello, world! | |/-------------- 1.3. (+130) Napisz program, który wczyta z pliku list˛e produktów (jeden na lini˛e), a nast˛epnie uporzadkuje ˛ je alfabetycznie i zapisze do oryginalnego pliku (nadpisujac ˛ oryginalna˛ zawartość). 9 2. Zmienne i stałe Przykłady 2.1. Napisz program, który wypisze na ekran wynik dzielenia 281 i 117 w postaci liczby mieszanej (tj. 2 47/117). 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std; int main() { int a = 281; int b = 117; cout << a/b << " "; cout << a%b << "/"; cout << b << endl; return 0; } Program ten da si˛e napisać bez użycia zmiennych. Jednak w takim przypadku, gdybyśmy chcieli zmienić liczb˛e 281 na inna,˛ potrzebowalibyśmy dokonać zmiany w dwóch miejscach (a w przypadku liczby 117 w trzech). Chcac ˛ uniknać ˛ tej duplikacji możemy nadać nazwy tym dwóm liczbom, tworzac ˛ zmienne, jak w liniach 5 i 6. Aby stworzyć zmienna,˛ podajemy najpierw jej typ (tutaj int, czyli liczba całkowita, od angielskiego integer), nast˛epnie jej nazw˛e, a na końcu, po znaku =, poczatkow ˛ a˛ wartość. Uwaga! Nazwa zmiennej może składać si˛e wyłacznie ˛ z liczb alfabetu angielskiego (małych i wielkich), cyfr oraz znaku podkreślenia, przy czym nie może zaczynać si˛e od cyfry. Wielkość liter ma znaczenie. Uwaga! Znak =, poza wygladem, ˛ nie ma absolutnie nic wspólnego ze znakiem równości stosowanym w matematyce. Jest to operator przypisania, oznaczajacy, ˛ że zmienna po lewej stronie otrzymuje wartość po prawej. Operator ten nie jest symetryczny: po lewej zawsze znajduje si˛e zmienna do której przypisujemy, a po prawej wartość do przypisania. 10 Tak stworzonych zmiennych możemy nast˛epnie używać w działaniach matematycznych odwołujac ˛ si˛e do nich poprzez ich nazwy, jak w liniach 8-10. 2.2. Napisz program, który zapyta użytkownika o liczb˛e naturalna,˛ pobierze ja,˛ a nast˛epnie wypisze na ekran jej kwadrat. 1 2 3 #include <iostream> using namespace std; 4 5 6 7 8 9 int main() { cout << "Podaj liczbe naturalna: "; 10 11 12 int n; cin >> n; cout << "Jej kwadrat jest rowny " << n*n << endl; return 0; } Aby pobrać wartość od użytkownika, musimy stworzyć zmienna,˛ w której b˛edziemy t˛e wartość przechowywać. Podanie poczatkowej ˛ wartości dla zmiennej nie jest obowiazkowe. ˛ W linii 7 zostało ono pomini˛ete, jako że poczatkowa ˛ wartość i tak zostałaby natychmiast nadpisana w linii 8. Uwaga! O ile zmienna nie otrzymuje natychmiast wartości w żaden inny sposób, zawsze pami˛etaj o podaniu poczatkowej ˛ wartości dla niej. Wartość zmiennej bez przypisanej wartości jest nieokreślona i może być absolutnie dowolna, co cz˛esto prowadzi do nieoczekiwanego zachowania programu. Uwaga! Jeżeli uczono ci˛e kiedyś, że zmienne należy tworzyć na poczatku ˛ bloku, jak najszybciej wyzbadź ˛ si˛e tego nawyku. Wynika on z ograniczeń technologicznych j˛ezyka C (na podstawie którego powstał C++, którego używamy tutaj) i jest absolutnym przeciwieństwem dobrych praktyk stosowanych obecnie. Zmienne należy tworzyć tuż przed ich pierwszym użyciem. Za pobranie wartości od użytkownika odpowiada linia 8. Zwróć uwag˛e na kierunek strzałek przy cout i cin. Te pierwsze kieruja˛ dane do strumienia, a te drugie ze strumienia do zmiennej. 2.3. Napisz program, który pobierze od użytkownika dwie liczby (nazwijmy je n i k), a nast˛epnie wypisze ułamek nk w postaci liczby mieszanej. 11 1 2 3 4 5 6 7 8 #include <iostream> using namespace std; int main() { cout << "Podaj wartosci n i k" << endl; int n, k; cin >> n >> k; 9 10 11 12 13 14 15 16 cout cout cout cout << << << << "Ulamek n/k w postaci liczby mieszanej: "; n/k << " "; n%k << "/"; k << endl; return 0; } Ten przykład jest rozszerzeniem przykładu 1. Zamiast obliczać wynik dla znanych liczb, pobieramy je od użytkownika, a nast˛epnie obliczamy i wypisujemy wynik. Zwróć uwag˛e na linie 7 i 8, w których tworzone sa˛ zmienne oraz pobierane sa˛ ich wartości. Możliwe jest tworzenie wielu zmiennych jednego typu jednocześnie. Wystarczy oddzielić ich nazwy przecinkami. Możliwe jest również pobranie dwóch liczb na raz tak jak w linii 8. 2.4. Napisz program, który pobierze od użytkownika promień koła (niekoniecznie całkowity), a nast˛epnie obliczy i wypisze na ekran jego średnic˛e. 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std; int main() { cout << "Podaj promien kola: "; double r; cin >> r; cout << "Srednica tego kola: " << 2*r << endl; return 0; } 12 Jako że treść zadania wymaga od nas działania z liczbami, które nie musza˛ być całkowite, typ zmiennej int nie jest tutaj odpowiedni. Stosujemy w zamian typ double, który służy do przechowywania tzw. liczb zmiennoprzecinkowych. Pozwala on na przechowywanie liczb bardzo dużych (do około 21024 ) jak i bardzo małych (najmniejsza możliwa liczba dodatnia w okolicach 2−1024 ). Jednakże precyzja takiej liczby jest dość ograniczona: jedynie pierwsze 9 miejsc znaczacych ˛ w zapisie dziesi˛etnym niesie za soba˛ jakiekolwiek znaczenie. Uwaga! Każdy wynik otrzymany w działaniu na liczbach zmiennoprzecinkowych jest jedynie przybliżeniem. Nawet proste obliczenie jak 0.1 + 0.2 może dać, dość nieoczekiwanie, wynik 0.29999999999. Wynika to z tego, że 0.1 jest w systemie dwójkowym liczba˛ o rozwini˛eciu nieskończonym, wi˛ec nie da si˛e jej dokładnie przedstawić wewnatrz ˛ zmiennej typu double. Liczb zmiennoprzecinkowych nie należy używać, o ile nie jest to absolutnie niezb˛edne. Nawet obliczenia na pieniadzach ˛ lepiej wykonywać na liczbach całkowitych (w postaci, na przykład, liczby groszy). 2.5. Napisz program, który pobierze od użytkownika promień koła, a nast˛epnie wypisze jego pole i obwód. 1 2 3 4 5 6 7 8 #include <iostream> using namespace std; int main() { const double PI = 3.1415926535; cout << "Podaj promien kola: "; double r; cin >> r; 9 10 11 12 13 14 15 cout << "Obwod kola: " << 2*PI*r << endl; cout << "Pole kola: " << PI*r*r << endl; return 0; } Oprócz zmiennych, możemy tworzyć także stałe. Różnia˛ si˛e one od zmiennych tym, że nie można zmieniać ich wartości, a co za tym idzie, podanie wartości poczatkowej ˛ jest obowiazkowe. ˛ Stałe tworzymy tak jak zmienne, z ta˛ różnica,˛ że przed typem piszemy słowo const (od angielskiego constant, czyli stała). Nazwy stałych z reguły piszemy wielkimi literami, ale nie jest to wymagane. 13 Pytania 2.1. Co wypisze nast˛epujacy ˛ fragment kodu? 1 2 3 4 5 int n = 5; cout << n << endl; n = 10; cout << n << endl; 2.2. Co wypisze nast˛epujacy ˛ fragment kodu? 1 2 3 4 int a = 5; int b = a; a = 10; cout << b << endl; 2.3. Co wypisze nast˛epujacy ˛ fragment kodu? 1 2 3 int a = 5; a = a+1; cout << a << endl; 2.4. Co wypisze nast˛epujacy ˛ fragment kodu? (1e100 oznacza 1 · 10100 ). 1 cout << 1e100 + 1 - 1e100 << endl; 2.5. F Ile miejsca w pami˛eci zajmuje pojedyncza zmienna typu int? Jaka jest najmniejsza i najwi˛eksza wartość, jaka może si˛e w niej zmieścić? 2.6. F Ile miejsca w pami˛eci zajmuje pojedyncza zmienna typu double? Zadania 2.1. (+10) Napisz program, który pobierze od użytkownika jedna˛ liczb˛e: długość przejechanego dystansu wyrażona˛ w milach. Program ma policzyć i wypisać długość tego dystansu w kilometrach. 2.2. (+10) Napisz program, który pobierze od użytkownika trzy liczby: a, b i c, b˛edace ˛ współczynnikami funkcji kwadratowej w postaci ogólnej. Program ma policzyć i wypisać na ekran wartość delty tej funkcji. 14 2.3. (+10) Napisz program, który pobierze od użytkownika jedna˛ liczb˛e: temperatur˛e powietrza w stopniach Celsjusza. Program ma policzyć i wypisać t˛e temperatur˛e wyrażona˛ w stopniach Fahrenheita (F = 95 C + 32). 2.4. (+10) Napisz program, który pobierze od użytkownika dwie liczby: długości przyprostokatnych ˛ trójkata ˛ prostokatnego. ˛ Program ma policzyć i wypisać na ekran długość przeciwprostokatnej ˛ tego trójkata. ˛ 2.5. (+10) Napisz program, który pobierze od użytkownika jedna˛ liczb˛e całkowita˛ n, a nast˛epnie policzy i wypisze sum˛e liczb naturalnych od 1 do n włacznie. ˛ 2.6. (+15) Napisz program, który pobierze od użytkownika trzy liczby: a, b i c, b˛edace ˛ współczynnikami funkcji kwadratowej w postaci ogólnej. Program ma policzyć i wypisać na ekran współrz˛edne wierzchołka tej funkcji. 2.7. (+20) Napisz program, który pobierze od użytkownika trzy liczby: długości boków pewnego trójkata. ˛ Program ma policzyć i wypisać pole powierzchni tego trójkata. ˛ 2.8. (+20) Napisz program, który pobierze od użytkownika cztery liczby: współrz˛edne dwóch punktów na płaszczyznie (x1 , y1 , x2 , y2 ). Program ma policzyć, a nast˛epnie wypisać odległość mi˛edzy tymi dwoma punktami. 2.9. (+10) Napisz program, który pobierze od użytkownika liczb˛e oznaczajac ˛ a˛ długość odcinka, a nast˛epnie wyznaczy złoty podział tego odcinka i wypisze na ekran odległości punktu podziału od każdego z punktów końcowych. 2.10. (+10) Napisz program, który pobierze od użytkownika liczb˛e opisujac ˛ a˛ długość promienia kuli, a nast˛epnie obliczy i wypisze obj˛etość i pole powierzchni tej kuli. Rozszerzenie 2.1. (+50) Napisz program, który pobierze z pliku list˛e pomiarów temperatur (jeden pomiar na lini˛e), a nast˛epnie policzy i wypisze na ekran amplitud˛e i średnia˛ tych pomiarów. 2.2. (+80) Napisz program, który wczyta z pliku tekst, a nast˛epnie zakoduje go klasycznym szyfrem Cezara i zapisze go do drugiego pliku. 2.3. (+130) Napisz program, który wczyta od użytkownika liczb˛e, a nast˛epnie wypisze na ekran wszystkie liczby pierwsze mniejsze od danej liczby. 15 3. Instrukcje warunkowe Przykłady 3.1. Napisz program, który pobierze od użytkownika liczb˛e i wypisze na ekran słowo „ujemna” lub „nieujemna”, w zależności od tego czy dana liczba jest ujemna czy nie. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std; int main() { cout << "Podaj liczbe: "; int n; cin >> n; if (n >= 0) { cout << "nie"; } cout << "ujemna"; return 0; } W zadaniu tym zachodzi potrzeba wykonania pewnego fragmentu kodu tylko jeżeli pewien warunek jest spełniony. W tym wypadku musimy wypisać najpierw słowo „nie”, jeżeli dana liczba jest wi˛eksza lub równa 0. Możemy do tego użyć wyrażenia warunkowego if, jak w liniach 10-12. Wyrażenie to składa si˛e z trzech cz˛eści: najpierw słowo kluczowe if, nast˛epnie warunek w nawiasach okragłych, ˛ a nast˛epnie, w nawiasach klamrowych, kod który ma być wykonany jeżeli warunek jest prawdziwy. Najprostsze warunki możemy tworzyć używajac ˛ operatorów porównania, takich jak < (mniejsze), > (wi˛eksze), == (równe) != (różne), <= (mniejsze lub równe) i >= (wi˛eksze lub równe). Ich znaczenie jest dokładnie takie samo jak odpowiadajacych ˛ im znaków porównania w matematyce. 16 Uwaga! Nie należy mylić operatora przypisania (=) z operatorem równości (==). Pierwszy z nich służy do zmiany wartości zmiennej, a drugi do porównania dwóch wartości. Użycie operatora przypisania tam, gdzie powinien być użyty operator porównania nie spowoduje bł˛edu kompilatora (program si˛e uruchomi), ale może prowadzić do nieoczekiwanych wyników. Mi˛edzy klamrami znajduje si˛e tylko jedno polecenie: wypisania na ekran słowa „nie”, które powinno pojawić si˛e na ekranie, jeżeli dana liczba jest wi˛eksza lub równa 0. Uwaga! Zwróć uwag˛e na wci˛ecie o dodatkowy poziom kodu znajdujacego ˛ si˛e mi˛edzy klamrami wyrażenia warunkowego. Nie jest ono niezb˛edne do działania programu, ale zdecydowanie ułatwia późniejsze czytanie kodu. Warto wyrobić sobie nawyk prawidłowego formatowania kodu już na samym poczatku ˛ nauki. Linia 13, znajdujaca ˛ si˛e już poza klamrami bloku warunkowego, zostanie wykonana zawsze, niezależnie od wartości danej przez użytkownika liczby. 3.2. Napisz program, który pobierze od użytkownika liczb˛e i wypisze na ekran jej wartość bezwzgl˛edna.˛ 1 2 3 4 5 6 7 #include <iostream> using namespace std; int main() { cout << "Podaj liczbe: "; int n; cin >> n; 8 9 10 11 12 13 14 15 16 17 18 cout << "Jej wartosc bezwzgledna wynosi "; if (n > 0) { cout << n << endl; } else { cout << -n << endl; } return 0; } 17 W tym zadaniu potrzebujemy wykonać jeden fragment kodu, jeżeli warunek jest spełniony, a inny fragment, jeżeli ten sam warunek nie jest spełniony. Moglibyśmy wykorzystać do tego dwa nast˛epujace ˛ po sobie wyrażenia warunkowe, z przeciwnymi sobie warunkami. Z tym podejściem wia˛ża˛ si˛e jednak problemy. Po pierwsze, musimy samodzielnie napisać warunek przeciwny do danego, co jest bardzo cz˛estym źródłem pomyłek. Po drugie, warunek może zależeć od wartości jakiejś zmiennej, której wartość zmienia si˛e wewnatrz ˛ kodu tego warunku. Przez to może si˛e okazać, że oba warunki b˛eda˛ spełnione, mimo że sa˛ przeciwne. Po trzecie, otrzymujemy w ten sposób duplikacj˛e kodu: jeżeli b˛edziemy chcieli zmienić warunek, musimy zmienić go w dwóch miejscach. Dlatego dużo lepszym rozwiazaniem ˛ jest rozszerzenie wyrażenia warunkowego o dodatkowy blok else, co oznacza „w przeciwnym wypadku”, jak w liniach 13-15. Tym razem nie potrzebujemy określać drugiego warunku, potrzebny jest tylko dodatkowy fragment kodu mi˛edzy kolejna˛ para˛ nawiasów klamrowych. Warunek dany w bloku warunkowym (linia 11) jest sprawdzany tylko raz, a nast˛epnie w zależności od tego czy jest on prawdziwy wykonywany jest kod znajdujacy ˛ si˛e zaraz za warunkiem lub kod znajdujacy ˛ si˛e po słowie else. 3.3. Napisz program, który pobierze od użytkownika liczb˛e i wypisze na ekran słowo „tak”, jeżeli jest ona parzysta, „nie” w przeciwnym wypadku. 1 2 3 4 5 6 7 8 9 10 #include<iostream> using namespace std; int main() { cout << "Podaj liczbe: "; int n; cin >> n; if (n % 2 == 0) { cout << "tak" << endl; } else { cout << "nie" << endl; } 11 12 13 14 15 16 17 return 0; } Przykładem mniej oczywistego rodzaju warunku, który możemy chcieć sprawdzić jest podzielność jednej liczby przez druga.˛ Szczególnym przypadkiem takiego warunku jest sprawdzanie parzystości liczby (podzielność przez 2). Nie jest jednak to tak oczywiste jak proste porównania mi˛edzy liczbami. 18 C++ nie posiada wbudowanego operatora który pozwoli nam sprawdzić podzielność. Możemy jednak w tym celu wykorzystać operator reszty z dzielenia (%), poznany już w pierwszym rozdziale. Z definicji, a dzieli b, jeżeli reszta z dzielenia b przez a wynosi 0. Stad ˛ matematyczny zapis a | b (a dzieli b) możemy zapisać w C++ w postaci warunku b % a == 0. W tym zadaniu chcemy w szczególności sprawdzić, czy dana przez użytkownika liczba jest parzysta. W tym celu sprawdzamy czy reszta jej dzielenia przez 2 wynosi 0 czy też nie (linia 10). Uwaga! Operator reszty z dzielenia działa tylko i wyłacznie ˛ dla liczb całkowitych. O ile w innych zadaniach zmiana typu int na double mogła spowodować co najwyżej problemy z precyzja˛ otrzymanych wyników, tak tutaj program po prostu si˛e nie uruchomi, jeżeli użyjemy typu double. 3.4. Napisz program, który pobierze od użytkownika liczb˛e i wypisze na ekran słowo „tak”, jeżeli jest ona dodatnia, ale mniejsza od 10, „nie” w przeciwnym wypadku. 1 2 3 4 5 #include <iostream> using namespace std; int main() { cout << "Podaj liczbe: "; 6 7 8 9 10 11 12 13 14 15 16 17 int n; cin >> n; if (n > 0 && n < 10) { cout << "tak" << endl; } else { cout << "nie" << endl; } return 0; } Warunki możemy rozszerzać używajac ˛ spójników logicznych. W C++ mamy do dyspozycji 3 takie łaczniki: ˛ && (i), || (lub) oraz ! (nie). Maja˛ one takie samo znaczenie jak odpowiadajace ˛ im łaczniki ˛ w logice. Możemy je także zastapić ˛ słowami, odpowiednio, and, or i not. 19 Uwaga! Nie można łaczyć ˛ warunków przy użyciu przecinków (np. a < b, ˛ kompilacji. Nie sa˛ dopuszczalne także podwójb < c) – spowoduje to bład ne nierówności (jak a < b < c) – te niestety nie sprawia,˛ że program si˛e nie uruchomi, ale moga˛ prowadzić do nieoczekiwanych wyników. Kolejność wykonywania działań wyglada ˛ nast˛epujaco: ˛ najpierw negacja, nast˛epnie koniunkcja, a na końcu alternatywa. Negacja ma również pierwszeństwo przed operatorami porównania, ale operatory porównania maja˛ pierwszeństwo przed koniunkcja˛ i alternatywa.˛ 3.5. Napisz program, który pobierze od użytkownika liczb˛e i wypisze na ekran słowo „dodatnia”, „ujemna” lub „zero”, w zależności od tego czy dana liczba jest dodatnia, ujemna lub jest zerem. 1 2 3 4 5 6 7 int main() { cout << "Podaj liczbe: "; int n; cin >> n; if (n > 0) { cout << "dodatnia" << endl; } else if (n == 0) { cout << "zero" << endl; } else { cout << "ujemna" << endl; } 8 9 10 11 12 13 14 15 16 return 0; } Może zajść potrzeba sprawdzenia 3 lub wi˛ecej wykluczajacych ˛ si˛e wzajemnie warunków. Możemy do tego wykorzystać konstrukcj˛e else if, co oznacza „w przeciwnym wypadku, jeżeli”. W ten sposób, jeżeli pierwszy warunek jest spełniony, pozostałe warunki nie sa˛ sprawdzane, a jeżeli nie jest, to sprawdzany jest drugi warunek itd. Z reguły na końcu takiego łańcucha warunków umieszczamy blok else, który zostanie wykonany jeżeli żaden z warunków nie jest spełniony. Tutaj mamy 3 wykluczajace ˛ si˛e warunki: n > 0, n == 0 i n < 0. Najpierw sprawdzamy pierwszy z nich używajac ˛ konstrukcji if, tak jak dotychczas. Nast˛epnie po kodzie zwiazanym ˛ z tym warunkiem umieszczamy słowa else if i kolejny warunek, z kolejnym fragmentem kodu. Po tym fragmencie umieszczamy słowo else i kolejny fragment kodu, tym razem już bez warunku. 20 Linia 8 zostanie wykonana jeżeli pierwszy warunek b˛edzie spełniony. Jeżeli nie, to sprawdzony zostanie drugi warunek w linii 9. Jeżeli ten b˛edzie spełniony, wykona si˛e linia 10, a jeżeli nie, to linia 12. Uwaga! Kolejne warunki nie musza˛ si˛e wzajemnie wykluczać. Pami˛etać należy jedynie o tym, że wykonany zostanie zawsze fragment kodu zwiazany ˛ tylko z jednym warunkiem: pierwszym warunkiem który b˛edzie spełniony. Kolejne warunki sa˛ ignorowane. Pytania 3.1. Jaki b˛edzie wynik działania poniższego fragmentu kodu? 1 2 3 4 5 6 7 8 int n = 1; if (n > 0) { n = n-2; } if (n < 0) { n = n+2; } cout << n << endl; 3.2. Jaki b˛edzie wynik działania poniższego fragmentu kodu? 1 2 3 4 5 int n = 0; if (n = 5) { cout << "piec" << endl; } cout << n << endl; 3.3. Jaki b˛edzie wynik działania poniższego fragmentu kodu? 1 2 3 4 5 6 7 8 int n = 0; // inna wartosc niz w poprzednim pytaniu! if (n > 0) { n = n-3; // inna wartosc niz w poprzednim pytaniu! } if (n < 0) { n = n+2; } cout << n << endl; 21 3.4. Jaki b˛edzie wynik działania poniższego fragmentu kodu? 1 2 3 4 5 6 7 8 int n = 2; int k = 0; if (k != 0 && n/k > 1) { cout << "A" << endl; } else { cout << "B" << endl; } 3.5. Jaki b˛edzie wynik działania poniższego fragmentu kodu? 1 2 3 int a = 10; int b = 10; 4 5 6 7 8 9 if (a <= b) { cout << "<=" << endl; } else if (a == b) { cout << "==" << endl; } else if (a != b) { cout << "!=" << endl; } else { cout << "zaden" << endl; } 10 11 12 Zadania 3.1. (+20) Napisz program, który pobierze od użytkownika dwie liczby: n i k, a nast˛epnie wypisze na ekran słowo „tak”, jeżeli n jest podzielne przez k lub „nie” w przeciwnym wypadku. 3.2. (+20) Ciag ˛ Collatza. Napisz program, który pobierze od użytkownika liczb˛e n, a nast˛epnie wypisze na ekran wartość n2 jeżeli n jest parzysta lub 3n + 1 w przeciwnym wypadku. 3.3. (+20) Napisz program, który pobierze od użytkownika liczb˛e, a nast˛epnie wypisze na ekran słowo „tak”, jeżeli jej wartość bezwzgl˛edna jest wi˛eksza od 5, „nie” w przeciwnym wypadku. 3.4. (+20) Napisz program, który pobierze od użytkownika trzy liczby: a, b i c, a nast˛epnie znajdzie i wypisze na ekran miejsca zerowe funkcji f (x) = ax2 + bx + c lub słowa „Brak miejsc zerowych”, jeżeli żadne zera nie istnieja.˛ 22 3.5. (+30) Napisz program, który określi czy podany przez użytkownika rok jest przest˛epny czy nie. Rok jest przest˛epny, jeżeli dzieli si˛e na 4, ale nie na 100, chyba że dzieli si˛e na 400. 3.6. (+30) Napisz program, który pobierze od użytkownika dwie liczby, a nast˛epnie da użytkownikowi możliwość obliczenia sumy lub iloczynu tych liczb. Po pobraniu dwóch liczb program powinien wypisać na ekran list˛e możliwości i poprosić użytkownika o podanie liczby odpowiadajacej ˛ wybranej przez niego opcji. 3.7. (+40) Napisz program, który pobierze od użytkownika dwie liczby, a nast˛epnie da użytkownikowi możliwość obliczenia różnicy lub ilorazu tych liczb. Program powinien wypisać na ekran komunikat o bł˛edzie, jeżeli wybrany zostanie iloraz, a druga z liczb b˛edzie zerem. 3.8. (+40) Napisz program, który b˛edzie wystawiał oceny za klasówki. Powinien pobrać od użytkownika maksymalna˛ liczb˛e punktów, a nast˛epnie liczb˛e punktów zdobyta˛ przez ucznia. Skala ocen wyglada ˛ nast˛epujaco: ˛ od 90% włacznie ˛ ocena bardzo dobra, od 75% włacznie ˛ ocena dobra, od 50% włacznie ˛ ocena dostateczna, od 40% ocena dopuszczajaca, ˛ a poniżej 40% niedostateczna. 3.9. (+60) Napisz program, który pobierze od użytkownika 6 liczb: współrz˛edne lewego górnego rogu prostokata, ˛ wysokość i szerokość prostokata ˛ oraz współrz˛edne punktu. Program powinien nast˛epnie wypisać na ekran słowo „wierzchołek”, „kraw˛edź”, „wewnatrz” ˛ lub „poza”, w zależności od tego gdzie, wzgl˛edem prostokata, ˛ znajduje si˛e dany punkt. 3.10. (+60) Napisz program, który pobierze od użytkownika 6 liczb, opisujacych ˛ współrz˛edne wierzchołków pewnego trójkata. ˛ Program powinien wypisać na ekran słowo „ostrokatny”, „prostokatny” lub „rozwartokatny” w zależności od tego czy opisany trójkat ˛ jest ostrokatny, ˛ prostokatny ˛ czy rozwartokatny. ˛ 3.11. (+80) Napisz program, który pobierze od użytkownika osiem liczb, opisujacych ˛ współrz˛edne punktów A, B, C i D, a nast˛epnie sprawdzi czy odcinki AB i CD przecinaja˛ si˛e. Rozszerzenie 3.1. (+80) Napisz program, który znajdzie miejsca zerowe danego wielomianu trzeciego stopnia. 3.2. (+130) Napisz program, który sprawdzi czy dane słowo jest palindromem (czyta si˛e je tak samo od poczatku ˛ jak od końca). 3.3. (+210) Napisz program, który określi czy dana liczba naturalna jest kwadratem innej liczby naturalnej czy też nie. 23 4. Funkcje Przykłady 4.1. Napisz funkcj˛e kwadrat, która przyjmuje jeden argument: długość boku kwadratu i zwraca pole jego powierzchni. Używajac ˛ tej funkcji napisz program, który obliczy pole powierzchni kwadratu o boku 5. 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std; int kwadrat (int n) { return n*n; } int main() { cout << kwadrat(5) << endl; return 0; } W pierwszym rozdziale nauczyliśmy si˛e wykorzystywać wbudowane w j˛ezyk C++ funkcje matematyczne znajdujace ˛ si˛e w pliku cmath, takie jak sqrt czy pow. W tym rozdziale nauczymy si˛e definiować własne funkcje i wykorzystywać je w programach. W liniach 4-6 umieszczona została definicja funkcji kwadrat, która przyjmuje jeden argument (liczb˛e całkowita˛ n), a nast˛epnie oblicza i zwraca wartość kwadratu tej liczby. Definicja funkcji składa si˛e z 4 elementów, kolejno: typu zwracanego, nazwy funkcji, listy argumentów i ciała funkcji. Typ zwracany (tutaj int) określa typ wartości, która jest wynikiem działania naszej funkcji. W tym przykładzie operujemy na liczbach całkowitych, stad ˛ typem zwracanym jest int. Nast˛epnie nazwa funkcji (kwadrat), której b˛edziemy później używać do wywoływania danej funkcji. Ograniczenia nazw funkcji sa˛ takie same jak dla zmiennych: małe i wielkie litery alfabetu angielskiego, cyfry i znaki podkreślenia, przy czym nazwa nie może zaczynać si˛e od cyfry. Istotne jest także to, że nie możemy mieć jednocześnie zmiennej i funkcji o tej samej nazwie. 24 Po nazwie, w nawiasach okragłych, ˛ znajduje si˛e lista argumentów. Każdy argument posiada typ oraz nazw˛e, a kolejne argumenty oddzielone sa˛ przecinkami. Tutaj posiadamy tylko jeden argument: n, który posiada typ int. W końcu w nawiasach klamrowych umieszczamy ciało funkcji, czyli kod, który zostanie wykonany w momencie wywołania naszej funkcji. Istotne sa˛ tutaj dwie rzeczy. Po pierwsze, ciało funkcji powinno zawierać wyrażenie return (linia 5), które zwraca wynik działania funkcji i kończy jej działanie. Po drugie, argumenty funkcji możemy wykorzystywać wewnatrz ˛ ciała funkcji jak zmienne. Ich poczatkowe ˛ wartości b˛eda˛ równe tym, które przekazane zostały w wywołaniu funkcji. Przykład wywołania funkcji kwadrat znajduje si˛e w linii 9. Tak jak w przypadku wbudowanych funkcji matematycznych, podajemy najpierw nazw˛e funkcji (kwadrat), a nast˛epnie w nawiasach okragłych ˛ wartości przekazywanych argumentów. Tutaj przekazujemy tylko jedna˛ wartość 5, która zostanie przypisana argumentowi n wewnatrz ˛ funkcji kwadrat. 4.2. Napisz funkcj˛e wartosc_bezwzgledna, która przyjmuje jeden argument: liczb˛e całkowita,˛ a zwraca wartość bezwzgl˛edna˛ tej liczby. Napisz program, który obliczy wartość bezwzgl˛edna˛ liczby -42 używajac ˛ tej funkcji. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std; int wartosc_bezwzgledna(int n) { if (n > 0) { return n; } else { return -n; } } int main() { cout << wartosc_bezwzgledna(-42) << endl; return 0; } Funkcje zawierać moga˛ nie tylko wyrażenia return, ale dowolny inny kod. Możemy w nich tworzyć nowe zmienne, wypisywać i pobierać dane, sprawdzać warunki oraz wykorzystywać wszystkie inne konstrukcje j˛ezyka C++, jak p˛etle (o których dowiemy si˛e nieco później). Dodatkowo funkcja może zawierać wi˛ecej niż jedno wyrażenie return. Istotne jest jedynie to, że zawsze wykonane zostanie tylko jedno z nich: to, do którego 25 program dotrze w pierwszej kolejności. Nast˛epnie funkcja zakończy swoje działanie, a jakikolwiek inny kod znajdujacy ˛ si˛e wewnatrz ˛ funkcji zostanie zignorowany. W ten sposób, jeżeli do funkcji wartosc_bezwzgledna przekażemy liczb˛e dodatnia,˛ warunek z linii 5 b˛edzie spełniony i wykona si˛e wyrażenie return z linii 6, które zwróci oryginalna˛ wartość argumentu, po czym działanie funkcji zakończy si˛e. W przeciwnym wypadku, jeżeli argument b˛edzie ujemny (lub zero), wykona si˛e wyrażenie z linii 8, zwrócona zostanie wartość przeciwna do danej i działanie funkcji zakończy si˛e. Uwaga! W C++ umieszczenie kolejnych linii kodu bezpośrednio po wyrażeniu return nie jest bł˛edem – taki program uruchomi si˛e. Jednakże kod znajdujacy ˛ si˛e za tym wyrażeniem nigdy nie zostanie wykonany, ponieważ wyrażenie return kończy działanie funkcji. 4.3. Napisz funkcj˛e delta, która przyjmuje trzy argumenty: współczynniki a, b, c równania kwadratowego, a zwraca wartość delty tego równania. Napisz program, który obliczy delt˛e równania x2 − 2x + 1 = 0 używajac ˛ tej funkcji. 1 2 3 #include <iostream> using namespace std; 4 5 6 7 8 9 double delta(double a, double b, double c) { return b*b - 4*a*c; } 10 11 12 int main() { cout << delta(1, -2, 1) << endl; return 0; } Funkcja może zwracać każdy z możliwych typów zmiennej, w tym także typ double. Argumenty także moga˛ przyjmować dowolny typ zmiennej. Jeżeli chcemy, aby funkcja przyjmowała wi˛ecej niż jeden argument, w definicji funkcji umieszczamy je wszystkie, każdy z własnym typem i nazwa,˛ oddzielone przecinkami. Ich kolejność jest istotna: wartości przekazywane do funkcji b˛eda˛ przypisywane do argumentów w takiej kolejności, w jakiej argumenty były podane w definicji funkcji. W ten sposób wywołanie funkcji delta w linii 9 przypisze wartość 1 argumentowi a, wartość -2 argumentowi b i wartość 1 argumentowi c. 26 4.4. Napisz funkcj˛e czy_wieksza, która sprawdzi czy pierwsza z dwóch pobranych liczb jest wi˛eksza od drugiej. Napisz program, który przy jej użyciu sprawdzi, czy liczba 5 jest wi˛eksza od 6. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std; bool czy_wieksza(int a, int b) { return a > b; } int main() { if (czy_wieksza(5, 6)) { cout << "Jest wieksza" << endl; } else { cout << "Nie jest wieksza" << endl; } return 0; } Funkcje moga˛ zwracać również wartości logiczne, co pozwala na łatwe sprawdzenie prawdziwości skomplikowanych warunków. Aby uzyskać taki efekt, potrzebny jest nam nowy typ zmiennej: bool. Jest to typ logiczny, który przyjmuje jedna˛ z dwóch wartości: true (prawda) lub false (fałsz). Każdy warunek w rzeczywistości jest operatorem (takim jak + czy -), którego wynikiem działania jest jedna z dwóch wartości logicznych. Możemy wynik takiego działania przechowywać w zmiennej (typu bool), przekazywać jako argument czy zwracać jako wynik działania funkcji (linia 5). Wyrażenie warunkowe tak naprawd˛e przyjmuje wartość logiczna,˛ czyli oprócz warunków przy użyciu operatorów porównania możemy użyć także wartości true i false, zmiennych typu bool czy wywołań funkcji o typie zwracanym bool (linia 9). Uwaga! W miejscu warunku w wyrażeniu warunkowym if możemy także użyć liczby. Każda liczba różna od zera ma wartość logiczna˛ true, a zero posiada wartość false. Podobnie w druga˛ stron˛e, użycie wartości logicznej jako liczby również jest dopuszczalne. Wartość false da nam 0, natomiast true zostanie zamienione na 1. 27 4.5. Napisz funkcj˛e hello_world, która wypisze na ekran słowa „Hello, world!”. Napisz program, który wypisze na ekran te słowa używajac ˛ tej funkcji. 1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std; void hello_world() { cout << "Hello, world!" << endl; } int main() { hello_world(); return 0; } Możliwe jest napisanie funkcji, która nie zwraca żadnej wartości ani nie przyjmuje żadnych argumentów. Jeżeli nie chcemy, żeby nasza funkcja przyjmowała jakiekolwiek argumenty, w definicji pozostawiamy jej list˛e argumentów pusta˛ (linia 4). Nawiasy okragłe ˛ musza˛ jednak pozostać na swoim miejscu. Podobnie wywołanie funkcji wcia˛ż zachowuje okragłe ˛ nawiasy po nazwie funkcji, jednak nie umieszczamy mi˛edzy nimi żadnej wartości. Jeżeli nie chcemy zwracać żadnej wartości, typ zwracany zast˛epujemy w definicji słowem void. W tym wypadku możemy pominać ˛ w ciele funkcji wyrażenie return, jako że żadna wartość nie b˛edzie zwracana. Działanie funkcji zakończy si˛e w momencie dotarcia do ostatniej linii ciała funkcji. Umieszczenie wyrażenia return w funkcji typu void jest jednak dopuszczalne w celu wymuszenia wcześniejszego zakończenia jej działania. W takim wypadku jednak pomijamy wartość zwracana˛ po słowie return i umieszczamy bezpośrednio po nim średnik. Pytania 4.1. Co wypisze na ekran wywołanie f(5)? 1 2 3 4 int f(int n) { return 3*n-5; cout << "f(n) = " << 3*n-5 << endl; } 28 4.2. Co wypisze na ekran wywołanie g(-5)? 1 2 3 4 5 6 7 8 int g(int n) { if (n > 0) { return n; } cout << "Dana liczba jest ujemna" << endl; return -n; } 4.3. Jak różnia˛ si˛e w zachowaniu funkcje oblicz_kwadrat i wypisz_kwadrat zdefiniowane poniżej? 1 2 3 4 int oblicz_kwadrat(int n) { return n*n; } 5 6 7 void wypisz_kwadrat(int n) { cout << n*n << endl; } 4.4. Gdybyśmy chcieli napisać programy obliczajace ˛ kwadrat danej liczby, każdy wykorzystujacy ˛ inna˛ funkcj˛e z poprzedniego pytania, czym różniłyby si˛e te programy? 4.5. Co wypisze na ekran nast˛epujacy ˛ fragment kodu? 1 2 3 4 5 if (false) { cout << "A" << endl; } else { cout << "B" << endl; } Zadania 4.1. (+30) Napisz funkcj˛e, która przeliczy dana˛ odległość w kilometrach na mile. Napisz program, który b˛edzie dokonywać przeliczenia przy użyciu tej funkcji. 4.2. (+30) Napisz funkcj˛e, która przeliczy dana˛ temperatur˛e w stopniach Fahrenheita na stopnie Celsjusza (C = 59 (F − 32)). Napisz program, który przeliczać b˛edzie dana˛ przez użytkownika temperatur˛e przy użyciu tej funkcji. 29 4.3. (+30) Napisz funkcj˛e, która obliczy i zwróci pole koła o zadanym promieniu. 4.4. (+40) Napisz funkcj˛e, która obliczy pole powierzchni stożka o zadanej wysokości i promieniu podstawy. 4.5. (+40) Napisz funkcj˛e, która dla danej liczby całkowitej zwróci połow˛e jej wartości (jeżeli dana liczba jest parzysta) lub jej trzykrotność zwi˛ekszona˛ o 1 (jeżeli jest nieparzysta). 4.6. (+30) Napisz funkcj˛e, która zwróci mniejsza˛ z dwóch danych liczb. 4.7. (+40) Napisz funkcj˛e, która zwróci najmniejsza˛ z trzech danych liczb. 4.8. (+40) Napisz funkcj˛e signum, która zwracać b˛edzie wartość funkcji signum danej liczby. Napisz program, który wykorzysta t˛e funkcj˛e do obliczenia wartości funkcji signum dla danej przez użytkownika liczby. 4.9. (+30) Napisz funkcj˛e, która sprawdzi, czy dana liczba jest dzielnikiem drugiej. Napisz program, który przy jej użyciu b˛edzie dla danych przez użytkownika liczb b˛edzie wypisywać słowo „tak” lub „nie”, w zależności od tego, czy pierwsza z liczb dzieli druga˛ czy nie. 4.10. (+40) Napisz funkcj˛e, który sprawdzi, czy z odcinków o danych długościach boków da si˛e zbudować trójkat. ˛ Napisz program, który przy użyciu tej funkcji sprawdzi, czy z odcinków o długościach 1, 2 i 3 da si˛e zbudować trójkat. ˛ 4.11. (+40) Napisz funkcj˛e, która sprawdzi, czy trójkat ˛ o zadanych bokach jest prostokatny. ˛ Napisz program, który przy użyciu tej funkcji sprawdzi, czy trójkat ˛ o długościach boków 12, 13 i 5 jest prostokatny. ˛ 4.12. Rozwia˛ż zadania z poprzednich rozdziałów, tym razem piszac ˛ odpowiednie funkcje, a nast˛epnie programy wykorzystujace ˛ te funkcje. Rozszerzenie ˛ 4.1. (+130) Napisz funkcj˛e suma, która obliczy i zwróci sum˛e liczb znajdujacych si˛e w danej tablicy (lub wektorze). 4.2. (+210) Napisz funkcj˛e licznik, która nie przyjmuje argumentów, a każde kolejne jej wywołanie zwraca kolejne liczby naturalne. Pierwsze wywołanie tej funkcji powinno zwrócić 0, kolejne 1, nast˛epne 2 itd. 4.3. (+340) Napisz funkcj˛e zlicz, która przyjmie od użytkownika pewien ciag ˛ liczb całkowitych (w postaci tablicy lub wektora) oraz wskaźnik do funkcji, która przyjmuje liczb˛e całkowita,˛ a zwraca wartość logiczna.˛ Funkcja ta ma policzyć dla ilu elementów danego ciagu ˛ dana funkcja zwraca prawd˛e. 30 5. Rekurencja Uwaga! W tym rozdziale nie sa˛ omówione żadne nowe konstrukcje j˛ezyka C++. Omówiona jest za to technika wykorzystujaca ˛ funkcje, która pozwala na rozwiazanie ˛ pewnych nowych rodzajów zadań. Przed rozpocz˛eciem pracy nad tym rozdziałem upewnij si˛e, że rozumiesz funkcje i instrukcje warunkowe. Bez tej wiedzy b˛edziesz mieć problemy ze zrozumieniem materiału zawartego w tym rozdziale. Jeżeli potrzebujesz, rozwia˛ż wi˛ecej zadań z poprzedniego rozdziału. Przykłady 5.1. Napisz program, który b˛edzie w nieskończoność wypisywać kolejne liczby naturalne, poczawszy ˛ od 0, po jednej liczbie na lini˛e. 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std; void wypiszNaturalneOd(int n) { cout << n << endl; wypiszNaturalneOd(n+1); } int main() { wypiszNaturalneOd(0); return 0; } Uwaga! Program ten nigdy sam nie zakończy swojego działania. Aby zakończyć jego działanie wciśnij kombinacj˛e klawiszy Ctrl+C. Rozwiazanie ˛ tego zadania może na pierwszy rzut oka wygladać ˛ bardzo nietypowo. Wykorzystuje ono bardzo popularna˛ technik˛e zwana˛ rekurencja.˛ W celu wypisania wszystkich kolejnych liczb naturalnych tworzymy funkcj˛e, która pozwoli wypisać nam wszystkie liczby naturalne poczawszy ˛ od pewnej 31 danej liczby. Funkcja ta, wypiszNaturalneOd nie zwraca żadnej wartości (wypisuje jedynie liczby), a jako argument przyjmuje poczatkow ˛ a˛ liczb˛e, od której chcemy zaczać ˛ wypisywanie. W ciele tej funkcji znajduja˛ si˛e jedynie 2 linie kodu. Pierwsza z nich odpowiada za wypisanie pierwszej z liczb (czyli n, od którego mieliśmy zaczać). ˛ Druga linia wywołuje zaś t˛e sama˛ funkcj˛e, jednak z argumentem o jeden wi˛ekszym, co wypisze na ekran wszystkie pozostałe liczby naturalne, które mamy do wypisania. Rekurencja polega na wywoływaniu funkcji wewnatrz ˛ jej własnej definicji. Pozwala to na wielokrotne wykonanie tego samego fragmentu kodu. Funkcj˛e, która wywołuje sama siebie nazywamy rekurencyjna.˛ Funkcje rekurencyjne z reguły poda˛żaja˛ za utartym schematem: jeżeli potrafi˛e znaleźć rozwiazanie ˛ dla n, to jakie b˛edzie rozwiazanie ˛ dla n + 1? 5.2. Napisz program, który b˛edzie odliczać w dół do 0 od pewnej zadanej liczby. Program ma pobrać od użytkownika liczb˛e n, a nast˛epnie wypisać na ekran liczby od n do 0 włacznie, ˛ w kolejności malejacej, ˛ po jednej liczbie na lini˛e. 1 2 #include <iostream> using namespace std; 3 4 5 6 7 8 void odliczOd(int n) { if (n < 0) { return; } 9 10 11 12 13 14 15 16 17 18 19 20 cout << n << endl; odliczOd(n-1); } int main() { int n; cin >> n; odliczOd(n); return 0; } Program z poprzedniego przykładu nie był zbyt przydatny, jako że nigdy nie kończył swojego działania. Nie posiadał on tzw. podstawy rekurencji, a jedynie krok rekurencyjny. 32 W tym przykładzie chcemy wypisywać kolejno coraz mniejsze liczby, stad ˛ wywołanie w linii 10 zawiera n-1, a nie n+1 jak w poprzednim przykładzie. Tym razem jednak chcemy zakończyć wypisywanie liczb, jeżeli n przekroczy 0. Dlatego oprócz zmiany plusa na minus, do funkcji dodane zostały jeszcze dodatkowe linie kodu (5-7), które sprawdzaja˛ czy argument funkcji nie jest ujemny. Jeżeli jest, działanie funkcji jest przerywane, a co za tym idzie, kolejne liczby nie sa˛ wypisywane na ekran. Taki warunek nazywamy podstawa˛ rekurencji i z reguły umieszczamy go na samym poczatku ˛ funkcji rekurencyjnej. Uwaga! Warunek w linii 5 moglibyśmy zastapić ˛ przez n == -1 i program działałby wcia˛ż poprawnie. Jednak gdyby ktoś poprosił ci˛e o zmian˛e tego kodu tak, aby wypisywał co druga˛ liczb˛e, konieczna byłaby modyfikacja tego warunku na n == -1 || n == -2. Unikaj używania warunków z == lub != w podstawie rekurencji (a później także w p˛etlach). Czasem jest to niezb˛edne, jednak w wi˛ekszości przypadków lepiej jest taki warunek zastapić ˛ przez jedna˛ z nierówności. Cała˛ reszt˛e kodu funkcji rekurencyjnej nazywamy krokiem rekurencji. ˛ w 5.3. Napisz program, który wypisze kolejne liczby naturalne od a do b włacznie, kolejności rosnacej, ˛ gdzie a i b sa˛ dane przez użytkownika. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std; void wypiszNaturalne(int start, int koniec) { if (start > koniec) { return; } cout << start << endl; wypiszNaturalne(start+1, koniec); } int main() { int start, koniec; cin >> start >> koniec; wypiszNaturalne(start, koniec); return 0; } 33 Tym razem nasza funkcja rekurencyjna przyjmuje dwa argumenty: granice przedziału, którego elementy mamy wypisać. Zacznijmy od omówienia kroku rekurencji (linie 9-10). Przypomina on rozwiaza˛ nie przykładu 5.1. Wypisujemy pierwsza˛ liczb˛e przedziału, a potem rekurencyjnie wypisujemy cała˛ reszt˛e. Tym razem musimy jednak pami˛etać o przekazaniu również drugiego argumentu koniec, który jednak nie zmienia si˛e. Wypisywanie powinno zakończyć si˛e w momencie, kiedy kolejna liczba do wypisania (tj. start) jest wi˛eksza niż ostatnia liczba, która miała być wypisana (czyli koniec). Stad ˛ podstawa rekurencji (linie 5-7) sprawdza czy start jest wi˛eksze od koniec i jeżeli tak, to kończy działanie funkcji bez wypisywania żadnej wartości na ekran. Głowna funkcja programu sprowadza si˛e do pobrania od użytkownika dwóch liczb, a nast˛epnie przekazania ich do zdefiniowanej już funkcji wypiszNaturalne. 5.4. Dana jest plansza o wymiarach 2n × 2n , gdzie n > 0, z wyci˛etym jednym z pól, oraz dowolna liczba klocków w kształcie litery L złożonej z 3 kwadratów. W jaki sposób dokładnie przykryć plansz˛e klockami tak, aby żaden klocek nie zakrywał wyci˛etego pola ani nie wystawał poza plansz˛e? Klocki nie moga˛ na siebie nachodzić, ale można dowolnie je obracać. Uwaga! Ten przykład nie zawiera kodu, jako że zadanie nie prosi o jego napisanie. Jeżeli czujesz si˛e na siłach, możesz spróbować napisać program, który b˛edzie konstruował i prezentował odpowiedni układ klocków dla danej planszy. Wymaga to jednak nieco wiedzy wykraczajacej ˛ poza dotychczasowy materiał. Zaczniemy od rozwiazania ˛ najprostszego przypadku, gdzie n = 1, czyli plansza ma rozmiar 2 × 2. Nast˛epnie na podstawie prostszych rozwiazań ˛ b˛edziemy konstruować rozwiazania ˛ dla coraz wi˛ekszych plansz. Przypadek bazowy jest trywialnie prosty. Mamy plansz˛e 2 × 2 z jednym z pól wyci˛etym. Cała reszta planszy ma wi˛ec dokładnie taki sam kształt jak nasz klocek, a wi˛ec wystarczy go tam położyć. Nast˛epnie załóżmy, że mamy rozwiazanie ˛ dla n i spróbujmy znaleźć rozwiazanie ˛ dla n+1. Dzielimy plansz˛e na 4 cz˛eści: na pół w pionie i w poziomie, otrzymujac ˛ cztery ćwiartki. 34 Dostajemy wi˛ec 4 plansze, każda o rozmiarach 2n ×2n , a taka˛ potrafimy zapełnić klockami tak, aby dowolne pole pozostało wolne. W jednej z ćwiartek znajduje si˛e pole, które musi pozostać puste, wi˛ec zapełniamy ja˛ odpowiednio. Na pozostałych 3 ćwiartkach nie mamy żadnych pól, które powinny pozostać puste. Możemy je sobie wybrać tak, aby w pozostała˛ luk˛e dało si˛e umieścić jeszcze jeden klocek. W każdej z tych 3 ćwiartek zostawiamy wolne pole przy samym środku planszy, co da nam luk˛e w kształcie klocka, która˛ możemy w prosty sposób zapełnić. Wiemy, że dla dowolnego n da si˛e wypełnić plansz˛e, jeżeli da si˛e to zrobić dla n − 1. Wiemy także, że jest to możliwe dla n = 1. Stad ˛ wiemy, że da si˛e to zrobić dla n = 2 (bo da si˛e dla 2 − 1 = 1), a wi˛ec także dla n = 3 (bo da si˛e dla 3 − 1 = 2) itd. W ten sposób nie tylko udowodniliśmy (przez indukcj˛e), że żadany ˛ układ istnieje dla każdego n > 0, ale także znaleźliśmy rekurencyjny sposób na jego skonstruowanie. 35 5.5. Napisz program, który obliczy wartość silni z danej liczby. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std; int silnia(int n) { if (n == 0) { return 1; } return n * silnia(n-1); } int main() { int n; cin >> n; cout << silnia(n); return 0; } Rekurencj˛e spotykamy nie tylko w programowaniu, ale i w matematyce. Niektóre funkcje, jak silnia, zdefiniowane sa˛ rekurencyjnie. To znaczy że definicja takiej funkcji odwołuje si˛e do tej właśnie funkcji. Uwaga! Silnia z n (zapisujemy n!) to iloczyn wszystkich kolejnych liczb naturalnych od 1 do n włacznie. ˛ Silnia zdefiniowana jest nast˛epujaco: ˛ ( 1 n! = n · (n − 1)! gdy n = 0 gdy n 6= 0 Definicj˛e t˛e możemy zapisać w C++ w niemal niezmienionej formie, jak widać na przykładzie powyżej. Sprawdzamy najpierw czy n == 0. Jeżeli tak, zwracamy 1, jeżeli nie, zwracamy iloczyn n i silni z n-1. Uwaga! Drugie z wyrażeń return moglibyśmy umieścić w bloku else, jednak nie jest to konieczne. Jeżeli warunek podstawy rekurencji jest spełniony, to i tak program nie dotrze nigdy do kodu kroku rekurencji. 36 5.6. Ile jest liczb naturalnych nie wi˛ekszych od n, które podzielne sa˛ przez 5, ale nie sa˛ podzielne przez 3? Napisz program, który obliczy wynik dla n danego przez użytkownika. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std; int ileNieWiekszych(int n) { if (n < 0) { return 0; } if (n % 5 == 0 && n % 3 != 0) { return 1 + ileNieWiekszych(n-1); } else { return ileNieWiekszych(n-1); } } int main() { int n; cin >> n; cout << ileNieWiekszych(n) << endl; return 0; } Przy użyciu rekurencji możemy w łatwy sposób policzyć ile liczb w danym przedziale spełnia pewien warunek. Wystarczy rekurencyjnie przejść kolejno po wszystkich liczbach w przedziale (jak w przykładach 5.1, 5.2 i 5.3) i dla każdej z nich sprawdzić, czy warunek jest spełniony. Jeżeli tak, do ostatecznego wyniku dodajemy 1. Jeżeli nie, nie dodajemy nic. Dokładnie w ten sposób działa krok rekurencyjny funkcji w tym rozwiazaniu. ˛ Sprawdzamy czy obecnie rozpatrywana liczba spełnia warunek (linia 9) i dodajemy 1 do wyniku dla liczby o jeden mniejszej (linia 10) lub po prostu zwracamy wynik dla n − 1 (linia 12). W podstawie rekurencji sprawdzamy, czy dana liczba jest mniejsza zero. Jeżeli tak, to nie musimy szukać już dalej, jako że interesuja˛ nas wyłacznie ˛ liczby naturalne, a poniżej 0 ich nie ma, stad ˛ wynik wynosi 0. 37 Pytania 5.1. Co stanie si˛e, jeżeli wywołamy odliczOd(-1)? 5.2. Co stanie si˛e, jeżeli wywołamy silnia(-1)? 5.3. Z czego wynika różnica w odpowiedziach na poprzednie dwa pytania? 5.4. Co wypisze na ekran wywołanie silnia(20)? Czy jest to prawidłowy wynik? Jaka jest najwi˛eksza wartość n dla której funkcja z przykładu 5.5 zwróci prawidłowy wynik? Dlaczego dla wartości wi˛ekszych otrzymujemy bł˛edny wynik? 5.5. Czy zmiana typu zwracanego z int na double w funkcji silnia jest dobrym rozwiazaniem ˛ problemu z poprzedniego pytania? 5.6. Przeczytaj w internecie o wieżach Hanoi, na przykład na Wikipedii. Opisz własnymi słowami, ale zwi˛eźle, w jaki sposób rekurencyjnie rozwiazać ˛ ten problem dla n kra˛żków. Narysuj kolejne kroki dla n = 3. Zadania 5.1. (+50) Napisz program, który wypisze liczby od danego n do 1 włacznie, ˛ przy czym każda liczba podzielna przez 3 powinna zostać zastapiona ˛ słowem „fizz”. 5.2. (+60) Napisz program, który rekurencyjnie obliczy sum˛e liczb naturalnych od 1 ). do danego n. Nie używaj wzoru Gaussa (tj. n(n+1) 2 5.3. (+60) Napisz program, który obliczy wartość k-tej pot˛egi 2, gdzie k jest liczba˛ naturalna˛ dana˛ jako argument funkcji. Nie używaj cmath. 5.4. (+70) Napisz program, który obliczy wartość k-tej pot˛egi n, gdzie k i n sa˛ danymi liczbami naturalnymi. Nie używaj cmath. 5.5. (+75) Napisz program, który obliczy wartość funkcji Ackermanna A(m, n) zdefiniowanej nast˛epujaco: ˛ gdy m = 0 n + 1 A(m, n) = A(m − 1, 1) gdy m > 0 i n = 0 A(m − 1, A(m, n − 1)) gdy m > 0 i n > 0 5.6. (+70) Napisz program, który pobierze od użytkownika liczb˛e całkowita,˛ a nast˛epnie wypisze jej cyfry, zaczynajac ˛ od jedności, po jednej cyfrze na lini˛e. 38 5.7. (+70) Algorytm Euklidesa. Najwi˛ekszy wspólny dzielnik (NWD) dwóch liczb to najwi˛eksza taka liczba naturalna, która dzieli bez reszty obie te liczby. Wiemy, że NWD(a, 0) = a oraz że NWD(a, b) = NWD(b, a mod b), gdzie a mod b to reszta z dzielenia a przez b. Napisz program, który znajdzie NWD dwóch danych liczb. ˛ Collatza zdefiniowany jest nast˛epujaco: ˛ pierwsza liczba ciagu ˛ jest 5.8. (+80) Ciag dowolna˛ liczba˛ naturalna.˛ Nast˛epnie każda kolejna wartość ciagu ˛ obliczana jest na podstawie poprzedniej wedle nast˛epujacej ˛ reguły: jeżeli poprzednia wartość jest parzysta, nowa wartość stanowi jej połow˛e, w przeciwnym razie jej trzykrotność powi˛ekszona˛ o 1. Przykład. Jeżeli rozpoczniemy od 6 uzyskamy: 6, 3, 10, 5, 16, 8, 4, 2, 1. Napisz program, który dla danego pierwszego elementu ciagu ˛ wypisze kolejne elementy tego ciagu ˛ aż do napotkania pierwszej jedynki. 5.9. (+60) Napisz funkcj˛e, która policzy wartość n-tej liczby Fibonacciego (Fn ). Dla uściślenia: F0 = 0, F1 = 1, Fn = Fn−1 + Fn−2 . ˛ 5.10. (+60) Napisz program, który wypisze kolejne liczby naturalne od a do b włacznie, w kolejności malejacej, ˛ gdzie a i b to dane liczby naturalne. 5.11. (+80) Napisz program, który wypisze wszystkie dzielniki danej liczby naturalnej. Podpowiedź: konieczne może być napisanie dwóch funkcji. Jedna z nich przyjmuje dwa argumenty: wartość dzielnej i potencjalnego dzielnika, która rekurencyjnie sprawdza kolejne dzielniki. Druga funkcja przyjmuje tylko jeden argument i wywołuje pierwsza˛ funkcj˛e z danym argumentem i wartościa˛ pierwszego potencjalnego dzielnika. 5.12. (+100) Pot˛eg˛e o wykładniku naturalnym możemy zdefiniować nast˛epujaco: ˛ gdy k = 0 1 k k k 2 2 n = n ·n gdy 2 | k k−1 k−1 n 2 · n 2 · n gdy 2 6 | k Napisz funkcj˛e, która obliczać b˛edzie pot˛egi o wykładnikach naturalnych używajac ˛ tej definicji. Upewnij si˛e, że w każdym przypadku wykonujesz tylko jedno wywołanie rekurencyjne (np. przez zapisanie wyniku w zmiennej). Nie używaj cmath. 5.13. (+60) Ile jest liczb naturalnych mniejszych od n, które podzielne sa˛ przez 5, ale nie sa˛ podzielne przez 3? Napisz program, który obliczy wynik dla n danego przez użytkownika. 39 5.14. (+80) Jeżeli wypiszemy wszystkie liczby naturalne mniejsze od 10, które sa˛ wielokrotnościami 3 lub 5, otrzymamy 3, 5, 6 i 9. Suma tych liczb wynosi 23. Napisz program, który znajdzie sum˛e wszystkich wielokrotności 3 i 5 poniżej 1000. (Project Euler, zadanie 1) 5.15. (+100) Suma kwadratów pierwszych 10 liczb naturalnych wynosi 12 + 22 + . . . + 102 = 385 Natomiast kwadrat sumy pierwszych 10 liczb naturalnych wynosi (1 + 2 + . . . + 10)2 = 3025 Stad ˛ różnica mi˛edzy suma˛ kwadratów pierwszych 10 liczb naturalnych a kwadratem ich sumy wynosi 3025 − 385 = 2640. Znajdź różnic˛e mi˛edzy suma˛ kwadratów a kwadratem sumy dla pierwszych 100 liczb naturalnych. Podpowiedź: przydatne b˛edzie tu napisanie wi˛ecej niż jednej funkcji. (Project Euler, zadanie 6) 5.16. (+120) Mamy do dyspozycji monety o nominałach 5zł i 2zł. Napisz program, który obliczy minimalna˛ liczb˛e monet jaka jest niezb˛edna do wydania reszty w kwocie danej przez użytkownika. Program powinien wypisać słowa „nie da si˛e”, jeżeli danej kwoty nie da si˛e wydać przy użyciu danych nominałów. Upewnij si˛e, że twój program zwraca prawidłowy wynik dla kwot takich jak 6zł (3 monety) czy 21zł (6 monet). 5.17. (+120) Napisz program, który wypisze na ekran tabliczk˛e mnożenia do 100. Rozszerzenie 5.1. (+210) Napisz funkcj˛e, która przybliży wartość liczby π ze wzoru Leibniza: P∞ (−1)n π n=0 2n+1 . 4 = 5.2. (+340) Napisz funkcj˛e, która obliczy przybliżona˛ wartość logarytmu naturalnego z danej liczby zmiennoprzecinkowej (double). 5.3. (+550) Zaimplementuj algorytm sortowania przez scalanie. 40 6. P˛etle while Przykłady 6.1. Napisz program, który, bez użycia rekurencji, wypisze na ekran liczby naturalne od pewnego danego n do 0 włacznie, ˛ w kolejności malejacej, ˛ po jednej liczbie na lini˛e. Uwaga! Kod rozwiazania ˛ znajduje si˛e niżej. Funkcje rekurencyjne w j˛ezykach takich jak C++, Java czy Python, zajmuja˛ bardzo dużo pami˛eci. Dodatkowo ich zapotrzebowanie na pami˛eć cz˛esto rośnie wraz ze wzrostem wartości przekazywanych argumentów. Na przykład funkcja odliczOd zdefiniowana w poprzednim rozdziale, gdy wywołana z argumentem 1000, wywoła sama˛ siebie z argumentem 999, nast˛epnie 998, 997 itd. aż do 0. Oznacza to 1001 wywołań tej samej funkcji, a każde wywołanie zajmuje pewna˛ ilość miejsca w pami˛eci (zależnie od liczby i typów argumentów). Uwaga! Używanie rekurencji wymaga pewnego stopnia ostrożności. Wywołanie funkcji rekurencyjnej z wieloma argumentami może prowadzić do tzw. przepełnienia stosu. Nie oznacza to jednak, że rekurencja nie jest przydatna. Istnieja˛ programy, które o wiele łatwiej napisać jest rekurencyjnie niż iteracyjnie (tj. przy użyciu p˛etli). Istnieja˛ wr˛ecz obliczenia, których bez rekurencji wykonać si˛e nie da (np. funkcja Ackermanna). Dlatego zamiast mnożyć wywołania, z których każde posiada swój zestaw argumentów, możemy użyć zmiennej, której wartość b˛edziemy wielokrotnie zmieniać i za każdym razem wykorzystywać jej wartość do naszych obliczeń. Do takiego wielokrotnego wykonywania tego samego fragmentu kodu służa˛ p˛etle. W programie powyżej wykorzystana jest p˛etla while, która powtarza wykonywanie danego jej fragmentu kodu tak długo, jak długo spełniony jest pewien dany warunek. P˛etla while wygladem ˛ przypomina wyrażenie if. Najpierw umieszczamy słowo kluczowe while, nast˛epnie w nawiasach okragłych ˛ warunek, a na końcu w nawiasach klamrowych fragment kodu do powtarzania. 41 Uwaga! P˛etla while przypomina wyrażenie if nie tylko wygladem, ˛ ale i zachowaniem. Wyrażenie if sprawdza warunek, po czym wykonuje fragment kodu, jeżeli warunek był spełniony lub omija go w przeciwnym wypadku. Podobnie p˛etla while sprawdza warunek, po czym albo wykonuje kod (warunek jest spełniony) albo go pomija. Różnica polega na tym, że po wykonaniu przypisanego fragmentu kodu, p˛etla while ponownie sprawdza warunek i decyduje, czy wykonać kod itd. aż warunek nie b˛edzie spełniony. Spójrzmy teraz na rozwiazanie ˛ przykładu: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> using namespace std; int main() { int n; cin >> n; while (n >= 0) { cout << n << endl; n = n-1; } return 0; } W liniach 5-6 pobieramy od użytkownika wartość liczby n i przechowujemy ja˛ w zmiennej n. Mimo, że kod ten znajduje si˛e poza p˛etla,˛ to dokonuje on inicjalizacji p˛etli, czyli ustawia wartości zmiennych, którymi p˛etla b˛edzie si˛e posiłkować. Nast˛epnie w linii 8 znajduje si˛e warunek. P˛etla b˛edzie wykonywana tak długo, jak warunek jest spełniony. Chcemy wypisywać kolejne liczby aż zejdziemy poniżej 0, stad ˛ warunek n >= 0. Nast˛epnie w linii 9 wypisujemy obecna˛ wartość n, a w linii 10 zmniejszamy t˛e wartość o 1. W ten sposób nast˛epna iteracja (powtórzenie p˛etli) wykonywana b˛edzie dla liczby o jeden mniejszej. Nazywamy to krokiem p˛etli. Kod przy p˛etli wykonywany b˛edzie po kolei dla coraz mniejszych wartości n, aż dojdzie do −1, dla którego warunek nie b˛edzie spełniony i wykonywanie p˛etli zakończy si˛e. 42 6.2. Napisz program, który wypisze na ekran kolejne liczby naturalne od 0 do pewnego danego n, bez użycia rekurencji. 1 2 3 4 5 6 #include <iostream> using namespace std; int main() { int n; cin >> n; 7 8 9 10 11 12 13 14 15 int i = 0; while (i <= n) { cout << i << endl; i = i+1; } return 0; } Jeżeli chcemy wypisać liczby w przeciwnym kierunku, tj. od 0 do n, nie wystarczy nam już tylko jedna zmienna n. Musimy dołożyć do niej kolejna˛ zmienna˛ i, która zapami˛etuje jaka nast˛epna liczba powinna zostać wypisana. Zmienna ta pełni rol˛e tzw. iteratora, czyli zmiennej opisujacej ˛ obecne położenie w pewnym zestawie liczb (tutaj kolejne liczby naturalne od 0 do n). Pozwala to zrobić bez wprowadzania zmian do tego zestawu (tutaj n pozostaje bez zmian, w odróżnieniu od poprzedniego zadania). 6.3. Napisz program, który obliczy sum˛e liczb od 1 do n włacznie, ˛ bez użycia n(n+1) rekurencji ani wzoru Gaussa (tj. 2 ). 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std; int main() { int n; cin >> n; int i = 1; int suma = 0; while (i <= n) { suma = suma + i; i = i+1; } 43 14 15 16 17 cout << suma << endl; return 0; } Program ten jest bardzo podobny do programu z poprzedniego przykładu. Dodana została tu zmienna suma, która przechowuje dotychczasowa˛ sum˛e liczb. Zmienna,˛ która zapami˛etuje dotychczasowy wynik działań nazywamy akumulatorem. Poczatkowa ˛ wartość tej zmiennej (linia 9) wynosi 0, jako że nie zsumowaliśmy jeszcze żadnych liczb, a suma niczego wynosi 0. Nast˛epnie w p˛etli (linie 10-13) kolejno przechodzimy po wszystkich liczbach od 1 do n włacznie. ˛ Robimy to tak, jak w poprzednim zadaniu, z ta˛ różnica,˛ że tym razem zamiast wypisywać każda˛ kolejna˛ liczb˛e, to dodajemy jej wartość do zmiennej suma (linia 11). Stad ˛ po zakończeniu działania p˛etli, zmienna suma zawierać b˛edzie sum˛e wszystkich liczb od 1 do n włacznie. ˛ Jej poczatkowa ˛ wartość, zero, powi˛ekszona została kolejno o wartość każdej kolejnej liczby naturalnej. To daje nam końcowa˛ wartość 0 + 1 + 2 + . . . + (n − 1) + n, czyli wymagana˛ sum˛e. Zostaje nam tylko wypisać wynik (linia 15). 6.4. Napisz program, który b˛edzie pobierał od użytkownika liczby aż do napotkania zera, a nast˛epnie wypisze na ekran ile liczb pobrał (wliczajac ˛ końcowe zero). 1 2 3 #include <iostream> using namespace std; 4 5 6 7 8 9 int main() { int ile = 0; int n = 1; while (n != 0) { cin >> n; ile = ile + 1; } 10 11 12 13 14 15 16 cout << ile << endl; return 0; } W tym programie wewnatrz ˛ p˛etli pobieramy kolejne wartości do zmiennej n i zwi˛ekszamy wartość zmiennej ile. 44 Ta druga wartość jest naszym akumulatorem, jako że zapami˛etuje ile liczb pobraliśmy do tej pory. Poczatkowo ˛ jest to 0 (linia 5). Nast˛epnie z każdym pobraniem kolejnej liczby wartość ta zwi˛eksza si˛e o 1 (linia 10), jako że pobraliśmy o jedna˛ liczb˛e wi˛ecej niż dotychczas (linia 9). Poczatkowa ˛ wartość n wynosi 1. Dokładna wartość nie ma znaczenia, jako że i tak zostanie nadpisana. Ważne jest jedynie to, żeby była różna od 0, aby warunek p˛etli przy pierwszej iteracji był spełniony. Wewnatrz ˛ p˛etli pobieramy kolejne wartości i zwi˛ekszamy wartość akumulatora. Uwaga! Warunek p˛etli NIE jest magicznym strażnikiem, który przerwie działanie p˛etli w momencie, w którym warunek nie b˛edzie dłużej spełniony. Warunek p˛etli jest sprawdzany tylko i wyłacznie ˛ na poczatku ˛ iteracji! Stad ˛ po pobraniu 0 w linii 9, linia 10 wcia˛ż zostanie wykonana. Dopiero wtedy zakończy si˛e obecna iteracja, a na poczatku ˛ kolejnej sprawdzony zostanie warunek. 6.5. Napisz program, który wypisze k pierwszych pot˛eg 2, zaczynajac ˛ od 20 , gdzie k jest dane przez użytkownika. Uwaga! Sa˛ dwa sposoby na wykonanie tego zadania. Pierwszy z nich, z użyciem cmath (p˛etla˛ przechodzac ˛ po wykładnikach i obliczajac ˛ pot˛egi przy użyciu pow) spróbuj napisać samodzielnie. Drugi sposób pokazany jest poniżej. 1 2 3 4 5 6 7 #include <iostream> using namespace std; int main() { int k; cin >> k; int potega = 1; while (k > 0) { cout << potega << endl; potega = potega*2; k = k-1; } 8 9 10 11 12 13 14 15 16 return 0; } 45 Akumulator nie musi być za każdym razem obliczany przez sumowanie, możemy używać również mnożenia. W przypadku tego programu, akumulatorem jest zmienna potega, która zapami˛etuje wartość kolejnej pot˛egi do wypisania. Za każda˛ iteracja˛ zwi˛ekszana jest dwukrotnie, co daje nam wartość kolejnej pot˛egi. Z kolei zmienna k opisuje ile jeszcze pot˛eg pozostało nam do wypisania. Jeżeli osiagnie ˛ ona 0, oznacza to, że nie zostało nam już nic i możemy zakończyć działanie p˛etli, a nast˛epnie programu. Zwróć też uwag˛e, że zmienna ta jest za każda˛ iteracja˛ zmniejszana, a nie zwi˛ekszana, jako że liczba pot˛eg pozostałych do wypisania zmniejsza si˛e za każdym razem. 6.6. Napisz program, który obliczy sum˛e n danych liczb. Program powinien najpierw pobrać liczb˛e n, a nast˛epnie n liczb, po czym wypisać ich sum˛e. 1 2 3 4 5 6 7 8 #include <iostream> using namespace std; int main() { int n; cin >> n; int i = 0; int suma = 0; while (i < n) { int k; cin >> k; 9 10 11 12 13 14 15 16 17 18 19 20 suma = suma + k; i = i + 1; } cout << suma; return 0; } Cz˛esto zdarzać si˛e b˛edzie, że b˛edziemy potrzebowali pobrać od użytkownika pewien ciag ˛ liczb o określonej przez użytkownika długości. Co prawda w nast˛epnym rozdziale poznamy krótszy sposób zapisania programu powyżej, jednak warto już teraz zrozumieć i zapami˛etać ogólny schemat „pobierania n liczb”. Najpierw musimy oczywiście wiedzieć, ile liczb musimy pobrać. Stad ˛ zaczynamy od stworzenia zmiennej n i pobrania jej wartości od użytkownika (linie 5-6). Nast˛epnie tworzymy zmienna˛ i pełniac ˛ a˛ funkcj˛e iteratora, która zapami˛etuje ile liczb zostało już pobranych (linia 8). Poczatkowo ˛ jest to oczywiście 0. W tym przypadku tworzymy 46 również akumulator suma do przechowywania sumy pobranych już liczb, ale nie jest on cz˛eścia˛ ogólnego wzorca. P˛etla b˛edzie działać tak długo, jak liczba pobranych liczb (i) jest mniejsza od liczby wszystkich liczb, jakie mieliśmy pobrać (n), stad ˛ warunkiem p˛etli (linia 10) jest i < n. Wewnatrz ˛ p˛etli, za każda˛ iteracja˛ tworzymy zmienna˛ i pobieramy do niej liczb˛e (linie 11-12). Uwaga! Zmienna˛ do której pobieramy liczb˛e w p˛etli możemy stworzyć przed rozpocz˛eciem p˛etli, a nie w środku. W ten sposób tworzymy tylko jedna˛ zmienna,˛ a nie n zmiennych, z których każda istnieje tylko w czasie swojej iteracji. Łamie to jednak zasad˛e „zmienne tworzymy tak późno jak tylko możliwe”. Teoretycznie stworzenie zmiennej przed p˛etla˛ prowadzi do zmniejszonego zużycia pami˛eci. W praktyce nowoczesne kompilatory i tak potrafia˛ wykryć przypadek taki jak w kodzie wyżej i użyć dla każdej z tych n zmiennych tego samego miejsca w pami˛eci. W końcu musimy wykonać jakieś działanie na pobranej liczbie, tutaj dodać ja˛ do dotychczasowej sumy (linia 14), a nast˛epnie zwi˛ekszyć wartość iteratora (linia 15). Po zakończeniu działania p˛etli, zmienna suma zawierać b˛edzie wartość sumy n pobranych od użytkownika liczb, dlatego wypisujemy ja˛ (linia 18) i kończymy działanie programu. Pytania 6.1. Porównaj kod z przykładu 6.1 z funkcja˛ rekurencyjna˛ odliczOd z poprzedniego rozdziału, wykonujac ˛ a˛ to samo zadanie. Zwróć uwag˛e na podobieństwa i różnice mi˛edzy nimi. 6.2. Skad ˛ bierze si˛e różnica w warunkach znajdujacych ˛ si˛e w obu tych programach (n < 0 w funkcji rekurencyjnej, n >= 0 w p˛etli)? 6.3. Dlaczego zmienna n w przykładzie 6.4 ma przypisana˛ poczatkow ˛ a˛ wartość, jeżeli jest ona niemal natychmiast nadpisywana w p˛etli? Czy możemy t˛e wartość dowolnie zmienić? 6.4. W poprzednim rozdziale zwrócona była uwaga, aby unikać == i != w warunkach p˛etli, a w zamian używać nierówności tam, gdzie tylko jest to możliwe. Dlaczego wi˛ec w przykładzie 6.4 warunek to n != 0? Czy możemy go zamienić na jakaś ˛ nierówność? Jeżeli tak, jaka? ˛ Jeżeli nie, dlaczego? 47 6.5. Który z dwóch poniższych fragmentów kodu skompiluje si˛e? Jeżeli któryś z nich spowoduje bład ˛ kompilacji – jaki i dlaczego? Jeżeli oba si˛e kompiluja,˛ jaka jest różnica w ich działaniu? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Fragment 1 int n = 5; int i = 0; int k; while (i < n) { cin >> k; i = i + 1; } cout << k; // Fragment 2 int n = 5; int i = 0; while (i < n) { int k; cin >> k; i = i + 1; } cout << k; Zadania Uwaga! W rozwiazaniach ˛ zadań poniżej, o ile nie zaznaczono inaczej, nie używaj cmath ani rekurencji. 6.1. Napisz program, który wypisze na ekran liczby nieparzyste od danego n do 1 włacznie, ˛ w kolejności malejacej. ˛ Zwróć uwag˛e, że n może być parzyste – musisz to sprawdzić przed wykonaniem p˛etli i odpowiednio zmienić wartość n, aby było nieparzyste. 6.2. Napisz program, który wypisze k pierwszych wielokrotności 103, gdzie k jest dane przez użytkownika. Za pierwsza˛ wielokrotność 103 uznajemy 103. 6.3. Napisz program, który wypisze reszty z dzielenia liczby n danej przez użytkownika, przez wszystkie kolejne liczby naturalne od 1 do n włacznie. ˛ 48 6.4. Napisz funkcj˛e, który znajdzie liczb˛e dzielników danej liczby całkowitej (jeżeli jest to dla ciebie zbyt trudne, rozwia˛ż najpierw poprzednie zadanie). Nast˛epnie napisz program, który przy użyciu tej funkcji b˛edzie w nieskończoność wypisywał kolejne liczby pierwsze, zaczynajac ˛ od 2. 6.5. Napisz program, który obliczy wartość silni z danej przez użytkownika liczby n. Dla przypomnienia: silnia to iloczyn kolejnych liczb naturalnych od 1 do n włacznie. ˛ 6.6. Zmodyfikuj program z przykładu 6.5 tak, aby obliczał i wypisywał wyłacznie ˛ wartość k-tej pot˛egi 2. Wymaga to wyłacznie ˛ zmiany położenia jednej linii kodu. 6.7. Napisz program, który obliczy nk , gdzie n i k sa˛ dane przez użytkownika. ˛ Liczba trój6.8. Napisz program, który policzy sum˛e n pierwszych liczb trójkatnych. katna ˛ to liczba kul, które potrzebne sa˛ do zbudowania trójkata ˛ równobocznego o danej podstawie. Kolejne liczby trójkatne ˛ to 1, 3, 6, 10, 15, 21, 28 itd. 6.9. Napisz program, który pobierze od użytkownika n liczb (gdzie n jest dane), a nast˛epnie wypisze na ekran ich średnia˛ arytmetyczna.˛ 6.10. Napisz program, który pobierze od użytkownika n liczb, a nat˛epnie wypisze na ekran najmniejsza˛ z nich. Podpowiedź: potrzebujesz zmiennej, która zapami˛etywać b˛edzie wartość najmniejszej z dotychczas pobranych liczb. Jej wartość powinna być zmieniana tylko wtedy, gdy nowo pobrana liczba jest mniejsza od dotychczasowego minimum. Rozszerzenie 6.1. Napisz program, który b˛edzie znajdował i wypisywał na ekran rozkład danej liczby na czynniki pierwsze. Postaraj si˛e, aby twój program działał jak najszybciej. Na przykład rozkład na czynniki pierwsze liczby 999 999 937 (która jest pierwsza) powinien pojawiać si˛e na ekranie niemal natychmiast (kilka sekund to za długo). 6.2. Regresja liniowa. Napisz program, który dla danego zestawu punktów na płaszczyznie, znajdzie lini˛e prosta,˛ której sumaryczna odległość od każdego z punktów jest jak najmniejsza. 6.3. Klastrowanie algorytmem k-średnich. Napisz program, który dany zestaw punktów na płaszczyznie podzieli na dwa zestawy takie, że sumaryczna odległość wszystkich punktów od środka ich zestawu jest jak najmniejsza. 49 7. P˛etle for Przykłady 7.1. Bez użycia p˛etli while ani rekurencji, napisz program, który wypisze na ekran kolejne liczby naturalne od 0 do pewnego danego n. 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std; int main() { int n; cin >> n; for (int i = 0; i <= n; i = i+1) { cout << i << endl; } return 0; } Wiele p˛etli, które b˛edziemy chcieli napisać, zawiera w sobie trzy elementy: inicjalizacj˛e, warunek i krok p˛etli. Żaden z tych elementów nie jest wymagany, jednak z reguły w p˛etli pojawia˛ si˛e wszystkie 3. W poprzednim rozdziale nauczyliśmy si˛e wykorzystywać p˛etle while, w których jedynie warunek jest elementem stałym. Inicjalizacja i krok p˛etli umieszczaliśmy poza właściwa˛ konstrukcja˛ p˛etli, w trzech różnych miejscach. Dotycza˛ one jednak tego samego: zachowania iteratora, dlaczego wi˛ec rozrzucać je po całym programie? P˛etle for pozwalaja˛ nam zebrać te trzy elementy w jedno miejsce. Ich konstrukcja wyglada ˛ nast˛epujaco: ˛ najpierw słowo kluczowe for, nast˛epnie w nawiasach okragłych, ˛ oddzielone średnikami, kolejno: inicjalizacja, warunek i krok p˛etli. W końcu w nawiasach klamrowych, jak zwykle, umieszczamy fragment kodu, który chcemy wielokrotnie wykonać. W liniach 8-10 powyższego programu widzimy przykład takiej właśnie p˛etli. Najpierw słowo for, nast˛epnie definicja i przypisanie wartości zmiennej i, która pełni funkcj˛e iteratora. Potem, po średniku, warunek, a nast˛epnie, po kolejnym średniku, zwi˛ekszamy wartość iteratora. 50 W porównaniu do przykładu 6.2, program ten nie zawiera niemal żadnego nowego kodu. Zmienia si˛e jedynie położenie elementów p˛etli, a słowo while zast˛epujemy słowem for. Cały program zachowuje si˛e przy tym identycznie do programu z przykładu 6.2. Uwaga! Wykonanie p˛etli for ma nast˛epujacy ˛ przebieg: najpierw wykonywana jest inicjalizacja, nast˛epnie sprawdzany jest warunek. Jeżeli warunek jest spełniony, to wykonujemy najpierw ciało p˛etli (czyli fragment kodu w klamerkach), a nast˛epnie krok p˛etli i wracamy do sprawdzania warunku. A wi˛ec: inicjalizacja wykonywana jest tylko raz, przed pierwszym sprawdzeniem warunku. Warunek jest sprawdzany przed każdym wykonaniem p˛etli, a krok po każdym wykonaniu p˛etli. 7.2. Napisz program, który pobierze od użytkownika n liczb, a nast˛epnie wypisze na ekran ich sum˛e. 1 2 3 #include <iostream> using namespace std; 4 5 6 7 8 9 int main() { int n; cin >> n; int suma = 0; for (int i = 0; i < n; i = i+1) { int k; cin >> k; 10 11 12 13 14 15 16 17 18 suma = suma + k; } cout << suma << endl; return 0; } W tym zadaniu widzimy znany już schemat „pobierz n liczb”, jednak przy użyciu p˛etli for. Zaczynamy od pobrania liczby n w liniach 5-6. Nast˛epnie w p˛etli w liniach 9-14 n razy pobieramy liczb˛e i dodajemy jej wartość do akumulatora suma. Znów, w porównaniu do przykładu 6.6, powyższy program nie zawiera żadnego nowego kodu, a jedynie zmienia układ kodu już istniejacego ˛ (no i słowo while zast˛epuje słowem for). 51 7.3. Napisz program, który b˛edzie pobierać od użytkownika liczby aż do napotkania 0, a nast˛epnie wypisze na ekran ile liczb pobrał. 1 2 3 4 5 6 #include <iostream> using namespace std; int main() { int ile; int n = 1; 7 8 9 10 11 12 13 14 for (ile = 0; n != 0; ile = ile+1) { cin >> n; } cout << ile << endl; return 0; } Znów mamy do czynienia z przepisaniem przykładu z poprzedniego rozdziału (tym razem przykład 6.4) przy użyciu p˛etli for. Nie wymaga on raczej wi˛ekszego komentarza, z wyjatkiem ˛ zmiennej ile, która tworzona jest przed p˛etla˛ (w linii 5), zamiast wewnatrz ˛ inicjalizacji (linia 8). Dzieje si˛e tak dlatego, że zmienne tworzone wewnatrz ˛ p˛etli, w tym wewnatrz ˛ inicjalizacji, istnieja˛ jedynie wewnatrz ˛ p˛etli. Po zakończeniu jej działania, zmienne te nie sa˛ już dla nas dost˛epne. W tym programie jednak interesuje nas wypisanie wartości zmiennej ile po zakończeniu działania p˛etli, stad ˛ musimy stworzyć ja˛ przed rozpocz˛eciem p˛etli. Uwaga! W tym przykładzie tworzymy zmienna˛ poza p˛etla,˛ a w inicjalizacji przypisujemy jej jedynie poczatkow ˛ a˛ wartość. Jest jednak dopuszczalne całkowite pomini˛ecie inicjalizacji, na przykład: 1 2 3 4 5 6 int ile = 0; int n = 1; for (; n != 0; ile = ile+1) { cin >> n; } Jest to jedyna różnica w działaniu mi˛edzy p˛etla˛ while, a p˛etla˛ for. Jest to zdecydowanie pożadane ˛ zachowanie, jako że zmienne powinny istnieć wyłacznie ˛ 52 tak długo, jak sa˛ potrzebne. Stad ˛ tworzymy je tak późno jak si˛e da. Od teraz możemy również pozbywać si˛e ich tak szybko, jak to tylko możliwe. Uwaga! Znamy teraz dwie, niemal równoważne, metody wielokrotnego wykonywania tego samego fragmentu kodu. Której z nich należy używać? W j˛ezyku C++ najcz˛eściej stosować b˛edziemy p˛etl˛e for, chyba że istnieja˛ powody, aby użyć p˛etli while. Takim powodem może być na przykład skomplikowana inicjalizacja i krok p˛etli, których nie chcemy (lub nie możemy) umieszczać w jednej linii. 7.4. Napisz program, który wypisze na ekran k kolejnych pot˛eg 2, zaczynajac ˛ od 20 , gdzie k jest dane przez użytkownika. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> using namespace std; int main() { int k; cin >> k; for (int i = 0, n = 1; i < k; i = i+1) { cout << n << endl; n = n*2; } return 0; } Ponownie, przykład ten jest kopia˛ przykładu 6.5. Jedna rzecz warta uwagi to stworzenie wi˛ecej niż jednej zmiennej w inicjalizacji (linia 8). Możemy stworzyć wiele zmiennych jednego i każdej z nich przypisać inna˛ wartość, oddzielajac ˛ je przecinkami. Istotne jest to, że typ podajemy tylko raz, przed pierwsza˛ ze zmiennych. Pytania 7.1. Porównaj programy z przykładów 6.2 i 7.1. Zwróć uwag˛e na podobieństwa i różnice mi˛edzy nimi. Czy w którymś z przykładów znajduje si˛e kod, którego nie ma w drugim z nich? 7.2. Dokonaj podobnego porównania dla pozostałych programów z tego i odpowiednich przykładów z poprzedniego rozdziału. 53 7.3. Który z dwóch poniższych fragmentów kodu skompiluje si˛e? Jeżeli któryś z nich spowoduje bład ˛ kompilacji – jaki i dlaczego? Jeżeli oba si˛e kompiluja,˛ jaka jest różnica w ich działaniu? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Fragment 1 int n = 10; int i = 0; int suma = 0; while (i < n) { suma = suma + i; i = i + 1; } cout << i << " " << suma; // Fragment 2 int n = 10; int suma = 0; for (int i = 0; i < 10; i = i+1) { suma = suma + i; } cout << i << " " << suma; 7.4. Co wypisze poniższy fragment kodu? Dlaczego zmienna n tworzona jest tutaj przed p˛etla,˛ a nie w inicjalizacji? 1 2 3 int n; for (n = 0; n < 10; n = n+1) { } cout << n << endl; 7.5. Co wypisze poniższy fragment kodu? 1 2 3 4 5 int n = 10; for (n = 0; 1 < 0; n = n+1) { cout << n << endl; } cout << n << endl; Zadania Rozwia˛ż zadania z poprzedniego rozdziału, tym razem używajac ˛ p˛etli for. 54 8. Wektory Przykłady 8.1. Napisz program, który pobierze od użytkownika 10 liczb, a nast˛epnie wypisze je w kolejności odwrotnej niż podana. Uwaga! Kod poniżej. To zadanie można rozwiazać ˛ przy użyciu wiedzy przedstawionej dotychczas w tym podr˛eczniku. Można stworzyć 10 zmiennych, od a1 do a10, potem do każdej z nich kolejno pobrać wartość, a w końcu każda˛ z nich po kolei wypisać, zaczynajac ˛ od a10, a kończac ˛ na a1. Taki program b˛edzie działać. B˛edzie prosty do napisania. Co jednak gdyby liczb miało być nie 10, a 100 czy 1000? Sprytniejsza osoba może sobie napisać program, który wygeneruje taki program dla dowolnej, z góry określonej liczby. Co jednak, jeżeli ta liczba nie jest z góry określona? Uwaga! O rozwiazaniu ˛ takim jak to powyżej mówimy, że si˛e nie skaluje. Oznacza to, że rozwiazanie ˛ takie działa bardzo dobrze dla małych liczb, jednak bardzo szybko staje si˛e niewystarczajace ˛ jeżeli te liczby zaczniemy zwi˛ekszać. Rozwiazanie, ˛ które si˛e nie skaluje niekoniecznie jest złe. Jeżeli wiemy, że pewne liczby zawsze pozostana˛ małe, użycie szybkiego rozwiazania, ˛ które si˛e nie skaluje może być dobrym pomysłem. Na przykład w zadaniu powyżej, gdyby liczb miało być 3, stworzenie 3 osobnych zmiennych byłoby świetnym rozwiazaniem. ˛ Problem polega na tym, że w rzeczywistości niemal nigdy nie możemy być pewni, że liczby zawsze pozostana˛ małe. Możemy na przykład użyć wektorów. Wektor pozwala na przechowanie w jednej zmiennej zestawu wartości o określonym typie. Możemy na przykład napisać vector<int> ns(10);, co stworzy wektor liczb całkowitych o nazwie ns i 10 wartościach: od ns[0] do ns[9]. Każda˛ z tych wartości możemy wykorzystywać jako osobna˛ zmienna˛ typu int. asdflkj 55 Uwaga! W informatyce liczymy zawsze od 0. Stad ˛ pierwsza˛ wartościa˛ w wektorze vector<int> ns(10) jest ns[0], a ostatnia˛ ns[9], zaś wartość ns[10] nie istnieje. W ten sposób możemy szybko stworzyć duża˛ liczb˛e zmiennych, jednak nadal mamy problem z pobraniem i wypisaniem ich wszystkich. Tutaj też jednak jest szybkie rozwiazanie. ˛ W nawiasie kwadratowym (np. ns[3]) nie musimy umieszczać konkretnej liczby. Może być to wyrażenie matematyczne, zmienne, a nawet wywołania funkcji. Cokolwiek, co ostatecznie da nam liczb˛e całkowita˛ si˛e nada. Dzi˛eki temu te 10 liczb możemy pobrać przy użyciu p˛etli: 1 2 3 for (int i = 0; i < 10; i++) { cin >> ns[i]; } a nast˛epnie wypisać je przy użyciu kolejnej p˛etli, idacej ˛ w druga˛ stron˛e: 1 2 3 for (int i = 9; i >= 0; i = i-1) { cout << ns[i] << endl; } Należy także pami˛etać, że aby wykorzystywać w programie wektory, musimy na poczatku ˛ programu dopisać lini˛e #include <vector>. W całości rozwiazanie ˛ tego zadania wyglada ˛ tak: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <vector> #include <iostream> using namespace std; int main() { vector<int> ns(10); for (int i = 0; i < 10; i = i+1) { cin >> ns[i]; } for (int i = 9; i >= 0; i = i-1) { cout << ns[i] << endl; } return 0; } 56 8.2. Napisz program, który pobierze od użytkownika n liczb, a nast˛epnie wypisze je w kolejności odwrotnej niż podana. 1 2 3 4 5 6 7 #include <vector> #include <iostream> using namespace std; int main() { int n; cin >> n; 8 9 10 11 12 13 14 15 16 17 18 19 vector<int> ns(n); for (int i = 0; i < n; i = i+1) { cin >> ns[i]; } for (int i = n-1; i >= 0; i = i-1) { cout << ns[i] << endl; } return 0; } Dzi˛eki wektorom możemy również stworzyć zestaw wartości o wielkości nieznanej w momencie pisania kodu. Nie tylko indeks (wartość w nawiasie kwadratowym) może być wyrażeniem matematycznym czy zmienna,˛ ale także rozmiar wektora. Dzi˛eki temu możemy pobrać od użytkownika liczb˛e n (linie 6-7), a nast˛epnie stworzyć wektor o n wartościach (linia 9). W końcu pobieramy te wartości w p˛etli (linie 10-12) i wypisujemy je na ekran w kolejności odwrotnej (linie 14-16). Program ten nie różni si˛e bardzo od rozwiazania ˛ poprzedniego przykładu, jedynie stała wartość 10 została zamieniona na zmienna˛ n. Warto zwrócić uwag˛e na to, jak ta zamiana si˛e dokonała, szczególnie w inicjalizacji i warunkach p˛etli. W pierwszej p˛etli poczatkowa ˛ wartość nie zmienia si˛e (jest to wcia˛ż 0). Jednak warunek zależał od liczby 10 w poprzednim zadaniu, stad ˛ tutaj zależy od n. Nasz warunek to i < n. Nie chcemy, aby zmienna i osiagn˛ ˛ eła wartość n, ponieważ taki indeks nie istnieje w wektorze ns. Moglibyśmy co prawda napisać i <= n-1, jednak zapis i < n jest krótszy i zdecydowanie cz˛eściej stosowany w takiej sytuacji. Podobnie w drugiej z p˛etli, zaczynamy od wartości n-1, ponieważ jest to ostatni indeks znajdujacy ˛ si˛e w wektorze ns. Chcemy natomiast dojść do wartości 0, stad ˛ warunek to i >= 0, a nie po prostu i > 0. 57 8.3. Napisz program, który pobierze od użytkownika n liczb, a nast˛epnie dodatkowa˛ liczb˛e a i sprawdzi, ile razy a znajduje si˛e w danym zestawie liczb. 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <vector> #include <iostream> using namespace std; int main() { // pobierz n liczb do wektora int n; cin >> n; vector<int> ns(n); for (int i = 0; i < n; i = i+1) { cin >> ns[i]; } 14 15 16 17 18 19 // pobierz dodatkowa liczbe i sprawdz // ile razy znajduje sie w wektorze int a; cin >> a; int count = 0; for (int i = 0; i < n; i = i+1) { if (ns[i] == a) { count = count+1; } } 20 21 22 23 24 25 26 27 28 29 30 // wypisz wynik na ekran cout << count << endl; return 0; } Linie 7-13 tego programu odpowiadaja˛ za pobranie n liczb od użytkownika i przechowanie ich w wektorze. Nast˛epnie w liniach 17-18 pobieramy dodatkowa˛ liczb˛e, której b˛edziemy wyszukiwać pośród pobranych wcześniej wartości. Dopiero potem zaczyna si˛e właściwe poszukiwanie. Tworzymy zmienna˛ count (linia 19), która pełnić b˛edzie funkcj˛e akumulatora – zapami˛etywać b˛edzie ile razy dotychczas napotkaliśmy wartość a. Poczatkowo ˛ jest to 0, jako że jeszcze nie zacz˛eliśmy szukać. Nast˛epnie p˛etla˛ przechodzimy po wszystkich indeksach wektora ns, od 0 włacz˛ nie do n wyłacznie ˛ (linia 20). Wartość pod każdym indeksem przyrównujemy 58 do a (linia 21) i jeżeli taka równość zachodzi, zwi˛ekszamy count o 1 (linia 22). Po zakończeniu działania p˛etli zmienna count zawiera nasz wynik, stad ˛ wypisujemy ja˛ na ekran (linia 27). 8.4. Napisz funkcj˛e contains, która przyjmować b˛edzie jako argumenty wektor i liczb˛e, a nast˛epnie sprawdzi, czy dana liczba znajduje si˛e w danym wektorze. Napisz przykładowy program, który b˛edzie wykorzystywał t˛e funkcj˛e. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <vector> #include <iostream> using namespace std; bool contains(vector<int> ns, int n) { for (int i = 0; i < ns.size(); i = i+1) { if (ns[i] == n) { return true; } } return false; } int main() { int n; cin >> n; vector<int> ns(n); for (int i = 0; i < n; i = i+1) { cin >> ns[i]; } 20 21 22 23 24 25 int a; cin >> a; if (contains(ns, a)) { cout << "tak" << endl; } else { cout << "nie" << endl; } 26 27 28 29 30 31 32 33 34 return 0; } 59 Wektory możemy przekazywać jako argumenty funkcji, jak każda˛ inna˛ zmienna.˛ Przy definicji funkcji możemy umieścić wektor jako argument (linia 5), a nast˛epnie przekazać wartość do funkcji podajac ˛ jej nazw˛e (linia 26). Należy jednak zwrócić uwag˛e na kwesti˛e rozmiaru wektora. Jak zapewne pami˛etasz, przy tworzeniu funkcji deklarujemy jej argumenty podajac ˛ dla każdego z nich typ i nazw˛e. Rozmiar wektora nie jest cz˛eścia˛ ani jego typu ani nazwy, stad ˛ nie możemy narzucić, że wektor przekazany do funkcji b˛edzie mieć dokładna,˛ określona˛ przez nas, liczb˛e elementów. Musimy jednak w jakiś sposób poznać rozmiar wektora, który otrzymujemy wewnatrz ˛ funkcji. Służy do tego metoda size. Podajemy najpierw nazw˛e wektora, którego rozmiar chcemy poznać, nast˛epnie kropk˛e i słowo size, za którym umieszczamy par˛e okragłych ˛ nawiasów (linia 6). Uwaga! Metoda to pewien specjalny rodzaj funkcji. Każde jej wywołanie musi być przywiazane ˛ do zmiennej (podanej przed kropka) ˛ określonego typu. Zmienna ta jest przekazywana jako argument do metody, jednak w nieco inny sposób niż inne argumenty. Jak to dokładnie działa dowiesz si˛e w nast˛epnych dwóch rozdziałach. Oczywiście metod˛e size możemy wykorzystywać dla każdego wektora i w każdym miejscu, nie tylko dla wektorów liczb całkowitych wewnatrz ˛ funkcji. Sama funkcja contains jest dość prosta. Zgodnie z treścia˛ zadania przyjmuje dwa argumenty: wektor liczb ns oraz poszukiwana˛ liczb˛e n. Zwraca typ bool: prawd˛e, jeżeli ns zawiera wartość n lub fałsz w przeciwnym wypadku. Wewnatrz ˛ funkcji znajduje si˛e p˛etla, która przechodzi kolejno po wszystkich indeksach, od 0 do ns.size()-1 włacznie. ˛ Dla każdego z nich sprawdza, czy znajdujaca ˛ si˛e pod nim wartość jest równa n. Jeżeli tak, zwraca prawd˛e, co kończy działanie funkcji. Sprawdzanie kolejnych indeksów nie ma sensu, jako że już znamy odpowiedź. Jeżeli p˛etla zakończy swoje działanie i dotrzemy do linii 12, oznacza to, że wartość n nie znajduje si˛e w wektorze. Zwracamy wtedy fałsz kończac ˛ działanie funkcji. Funkcj˛e t˛e można napisać wykorzystujac ˛ akumulator typu bool, który przechowuje informacj˛e o tym, czy widzieliśmy już wartość n w wektorze. Poczatkowo ˛ ma wartość false, jako że nie mogliśmy widzieć n jeżeli nie zacz˛eliśmy szukać. Nast˛epnie p˛etla˛ podobna˛ jak w programie wyżej sprawdzamy każdy element wektora i ustawiamy akumulator na true jeżeli znajdziemy n. Na końcu zwracamy wartość akumulatora. Rozwiazanie ˛ podane na poczatku ˛ tego przykładu ma jednak t˛e zalet˛e, że w wielu przypadkach nie musimy sprawdzać wszystkich wartości w wektorze. 60 8.5. Napisz funkcj˛e, która przyjmować b˛edzie jako argument wektor zawierajacy ˛ liczby całkowite, a zwróci wektor zawierajacy ˛ tylko liczby parzyste, w tej samej liczbie i kolejności, w jakiej wyst˛epowały w danym wektorze. Napisz przykładowy program, który b˛edzie wykorzystywać t˛e funkcj˛e. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <vector> using namespace std; vector<int> even_filter(vector<int> ns) { vector<int> result; for (int i = 0; i < ns.size(); i = i+1) { if (ns[i] % 2 == 0) { result.push_back(ns[i]); } } return result; } // przykladowy program int main() { int n; cin >> n; vector<int> ns(n); for (int i = 0; i < n; i = i+1) { cin >> ns[i]; } 25 26 27 28 29 30 31 32 vector<int> evens = even_filter(ns); for (int i = 0; i < evens.size(); i = i+1) { cout << evens[i] << endl; } return 0; } Wektor możemy przekazywać jako argument funkcji, ale możemy go także zwracać jako wynik. Tutaj funkcja even_filter przyjmuje jako argument wektor zawierajacy ˛ pewne liczby, a jako wynik zwraca inny wektor, zawierajacy ˛ wyłacznie ˛ liczby parzyste. Jedyny problem polega na tym, że nie wiemy jaki b˛edzie rozmiar wynikowego 61 wektora. Moglibyśmy najpierw jedna˛ p˛etla˛ policzyć ile liczb parzystych znajduje si˛e w danym zestawie, a nast˛epnie stworzyć wektor o pożadanym ˛ rozmiarze. Istnieje jednak prostsze rozwiazanie. ˛ Wektory można dowolnie rozszerzać. Dzi˛eki temu możemy stworzyć pusty wektor, do którego dodawać b˛edziemy kolejne wartości. W linii 6 pomini˛ety został rozmiar tworzonego wektora, co oznacza, że b˛edzie on pusty. Nast˛epnie w p˛etli wykorzystujemy metod˛e push_back, która dodaje na końcu wektora wartość przekazana˛ jako argument (linia 9). Przechodzimy wi˛ec p˛etla˛ po wszystkich elementach ns i sprawdzamy ich parzystość. W przypadku gdy sprawdzana wartość jest parzysta, przekazujemy ja˛ do metody push_back. Na końcu wektora result tworzony jest wtedy nowy element, który otrzymuje wartość przekazana˛ do push_back. Wywołanie funkcji zwracajacej ˛ wektor nie różni si˛e niczym od wywołania innych funkcji. Podajemy najpierw nazw˛e funkcji, a nast˛epnie w nawiasach jej argumenty. Przykład widzimy w linii 26. Tutaj wynik funkcji umieszczany jest w nowej zmiennej, co pozwoli na jego wykorzystanie w późniejszych obliczeniach. Uwaga! Poniższy kod prezentuje sposób wykorzystania funkcji zwracajacej ˛ wektor, którego lepiej jest unikać, mimo że jest technicznie poprawny i zwróci ten sam wynik co linie 26-29 kodu z przykładu powyżej. 1 2 3 for (int i = 0; i < even_filter(ns).size(); i++) { cout << even_filter(ns)[i] << endl; } Problem z tym fragmentem kodu polega na tym, że w każdym obrocie p˛etli funkcja even_filter wywoływana jest dwa razy, tworzac ˛ za każdym razem dwa nowe, identyczne wektory. Nie dosyć, że dla dużych wektorów operacja ta może trwać dłuższy czas, to do tego zajać ˛ może dużo pami˛eci. Dużo lepiej jest wywołać t˛e funkcj˛e raz i jej wynik przypisać do zmiennej. Pytania 8.1. Spójrz na kod z przykładu 8.2. W jaki sposób zmieni si˛e jego zachowanie, jeżeli warunek p˛etli w linii 14 zmienimy na i > 0? 8.2. Co si˛e stanie, jeżeli zmienimy warunek w linii 10 na i <= n? 62 Zadania Uwaga! W zadaniach, w których należy napisać funkcj˛e, napisz również przykładowy program, który wykorzystuje t˛e funkcj˛e i pozwala przetestować poprawność jej działania. 8.1. Napisz program, który pobierze od użytkownika zestaw liczb a nast˛epnie pewna˛ liczb˛e całkowita˛ k, a nast˛epnie wypisze na ekran ile liczb w danym zestawie jest podzielnych przez k. 8.2. Napisz funkcj˛e, która z danego wektora liczb całkowitych wybierze i zwróci najmniejsza.˛ 8.3. Napisz funkcj˛e, która policzy ile razy dana liczba wyst˛epuje w danym zestawie liczb. Napisz program, który b˛edzie wykorzystywać t˛e funkcj˛e do policzenia ile razy liczba 0 wyst˛epuje w danym przez użytkownika zestawie. 8.4. Napisz program, który pobierze od użytkownika pewien zestaw liczb całkowitych, a nast˛epnie znajdzie i wypisze najmniejsza˛ liczb˛e naturalna,˛ która si˛e w nim nie znajduje. 8.5. Napisz funkcj˛e, która dla danej liczby naturalnej znajdzie i zwróci wektor zawierajacy ˛ wszystkie jej dzielniki w kolejności rosnacej. ˛ Postaraj si˛e, aby twoja funkcja zwracała wynik natychmiast nawet dla liczb o wartościach bliskich miliarda. 8.6. Napisz funkcj˛e, która przyjmie jako argument liczb˛e n, a nast˛epnie pobierze od użytkownika n liczb i zwróci je w formie wektora. Możesz tej funkcji używać w swoich późniejszych programach dla szybszego pobierania wartości do wektora: 1 2 3 int n; cin >> n; vector<int> ns = pobierz(n); 8.7. Napisz funkcj˛e, która przyjmować b˛edzie jako argument wektor zawierajacy ˛ liczby całkowite, a zwróci wektor zawierajacy ˛ tylko liczby dodatnie, w tej samej liczbie i kolejności, w jakiej wyst˛epowały w danym wektorze. 8.8. Napisz funkcj˛e, która przyjmie jako argumenty wektor oraz indeks i zwróci wektor, który posiada te same elementy, za wyjatkiem ˛ elementu pod danym indeksem. Kolejność elementów ma pozostać taka sama. 63 8.9. Napisz funkcj˛e, która przyjmie jako argumenty wektor i dwa indeksy: i oraz j. Funkcja ma zwrócić wektor zawierajacy ˛ elementy znajdujace ˛ si˛e na indeksach od i do j włacznie ˛ w wektorze danym jako argument. Jeżeli i > j, wynikiem powinien być pusty wektor. Wyjatkiem ˛ jest sytuacja, gdy j = −1, w której funkcja powinna zwrócić elementy od indeksu i aż do końca danego wektora. 8.10. Na przyj˛eciu zorganizowanym przez Bajtazara obecnych b˛edzie n osób. Każda osoba ma jasno określone preferencje na temat tego kto ma siedzieć po jej prawej stronie. Każda osoba posiada tylko jedna˛ taka˛ preferencj˛e, inna˛ od wszystkich pozostałych. Ile stołów b˛edzie potrzebnych, aby dogodzić wszystkim preferencjom gości? Najpierw podana jest liczba n, a nast˛epnie n liczb ai (0 6 i ≤ n). Liczba ai określa numer osoby, która ma siedzieć po prawej stronie osoby z numerem i. Jako wynik podaj jedna˛ liczb˛e: minimalna˛ liczb˛e wymaganych stołów. Dodatkowo, w kolejnej linii, możesz podać liczby opisujace ˛ ile osób powinno siedzieć przy każdym ze stołów. Rozszerzenie 8.1. Napisz funkcj˛e reverse, która odwróci kolejność elementów wektora danego jako argument w miejscu. To znaczy, poniższy fragment kodu powinien wypisać 9 8 7 6 5 4 3 2 1 0. 1 2 3 4 5 6 7 8 9 10 vector<int> ns; for (int i = 0; i < 10; i++) { ns.push_back(i); } reverse(ns); // ewentualnie reverse(&ns); for (int i = 0; i < ns.size(); i++) { cout << ns[i] << " "; } 8.2. Napisz przeładowania operatorów << i >> dla wektorów i odpowiednich strumieni. Pozwoli ci to na pobieranie i wypisywanie wektorów przy użyciu strumieni cin i cout, ale także strumieni plików. 8.3. W jaskini smoka znajduje si˛e n cennych przedmiotów. Każdy z nich ma swoja˛ własna˛ obj˛etość oi i wartość wi . Plecak pomieści co najwyżej K jednostek obj˛etości. Napisz program, który wybierze skarby o najwi˛ekszej sumarycznej wartości tak, aby wszystkie zmieściły si˛e w plecaku. 64 9. Znaki i łańcuchy 65 10. Obsługa plików 66 11. Wskaźniki 67 12. Struktury 68 A. Biblioteka Allegro 69