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