Olimpijskie Kółko Informatyczne dla Początkujących w

Transkrypt

Olimpijskie Kółko Informatyczne dla Początkujących w
Olimpijskie Kółko Informatyczne
dla Początkujących w Radomiu
grupa średniozaawansowana
prowadzący i autor podręcznika: Tomasz Roda
Spis treści
1.Wstęp.................................................................................................................................................3
1.1.Informacje o notatkach...............................................................................................................3
1.2.Informacje o zajęciach...............................................................................................................3
1.3.Struktura "podręcznika".............................................................................................................3
1.4.Prawa autorskie, kontakt z autorem...........................................................................................3
1.5.Historia zmian............................................................................................................................3
2.Wprowadzenie do programowania i podstawowych narzędzi...........................................................4
2.1.Czym jest program?...................................................................................................................4
2.2.Programy działające w trybie tekstowym..................................................................................4
2.3.Konsola w systemie Windows...................................................................................................5
2.4.Przekierowywanie standardowego wejścia i wyjścia*...............................................................5
2.5.Środowisko Dev­C++.................................................................................................................6
2.6.Kompilator GNU C++ w systemie Linuks (g++)*....................................................................7
3.Podstawy języka C++........................................................................................................................7
3.1.Budowa programu......................................................................................................................7
3.2.Dołączanie bibliotek, wprowadzenie do funkcji........................................................................8
3.3.Komentarze..............................................................................................................................10
3.4.Wypisywanie danych................................................................................................................10
3.5.Sztuczne zatrzymywanie pracy programu................................................................................11
3.6.Zmienne całkowitoliczbowe (int).............................................................................................11
3.7.Specyfikatory zmiennych całkowitoliczbowych (int)...............................................................12
3.8.Wczytywanie danych...............................................................................................................13
3.9.Instrukcje warunkowe if...........................................................................................................14
3.10.Operatory porównania............................................................................................................15
3.11.Instrukcja warunkowa switch*................................................................................................15
3.12.Bloki poleceń i widoczność zmiennych..................................................................................17
3.13.Operatory przypisania i podstawowe operacje arytmetyczne.................................................18
3.14.Wykorzystanie operatorów post­ i preinkrementacji*............................................................21
3.15.Operatory logiczne.................................................................................................................21
3.16.Pętla while..............................................................................................................................22
3.17.Pętla for...................................................................................................................................23
3.18.Zmienne tekstowe (string)......................................................................................................24
3.19.Zaawansowane operacje na zmiennych typu string*..............................................................25
3.20.Zmienne znakowe (char)........................................................................................................27
1
3.21.Kody ASCII............................................................................................................................27
3.22.Zmienne liczbowe zmiennoprzecinkowe (float i double)......................................................27
3.23.Podsumowanie typów zmiennych..........................................................................................28
3.24.Podstawowe funkcje matematyczne.......................................................................................29
3.25.Zaawansowane funkcje matematyczne*................................................................................30
3.26.Tablice....................................................................................................................................30
3.27.Konwersja między różnymi typami danych*..........................................................................31
3.28.Funkcje...................................................................................................................................31
3.29.Rekurencja*............................................................................................................................31
3.30.Formatowanie kodu źródłowego*..........................................................................................31
4.Zaawansowane programowanie w C++...........................................................................................32
4.1.Debugowanie, czyli odnajdywanie błędów..............................................................................32
4.2.Wstęp do obiektów i zaawansowanych struktur danych..........................................................32
4.3.Vector – coś więcej niż tablica.................................................................................................32
4.4.Vector – dla chcących wiedzieć więcej*..................................................................................33
4.5.Sortowanie danych...................................................................................................................34
5.Wstęp do algorytmiki......................................................................................................................35
5.1.Obliczanie złożoności pamięciowej, czyli ile pamięci zajmuję?..............................................35
5.2.Obliczanie złożoności obliczeniowej, czyli jak szybko działam?............................................35
5.3.Znajdywanie minimum i maksimum.......................................................................................35
6.Inne..................................................................................................................................................35
6.1.Adresy serwisów z zadaniami algorytmicznymi......................................................................35
6.2.Informacje o OI i OIG.............................................................................................................35
6.3.Obozy przygotowawcze do Olimpiady Informatycznej...........................................................35
6.4.Inne konkursy informatyczne...................................................................................................35
7.Przykładowe zadania wraz z rozwiązaniami....................................................................................35
8.Dalszy rozwój..................................................................................................................................36
8.1.Co po podstawach C++ i algorytmiki?.....................................................................................36
8.2.Wybór uczelni wyższej............................................................................................................36
2
1. Wstęp
1.1.
Informacje o notatkach
Niniejszy "podręcznik" jest zbiorem notatek powstałych w celu zebrania w jednym miejscu materiałów związanych z Olimpijskim Kółkiem Informatycznym w Radomiu. Notatki dedykowane są dla kółka dla początkujących, dla grupy średniozaawansowanej.
Omawianym językiem programowania w niniejszych notatkach jest C++.
1.2. Informacje o zajęciach
Zajęcia odbywają w roku szkolnym 2009/2010 w Radomiu. Organizowane są w ramach programu "Mazowieckie Talenty". Przeznaczone są dla gimnazjalistów i uczniów szkół ponadgimnazjalnych chcących zgłębić tajniki programowania i nauczyć się rozwiązywania problemów algorytmicznych stawianych w Olimpiadzie Informatycznej i Olimpiadzie Informatycznej Gimnazjalistów, później na studiach oraz w późniejszej pracy zawodowej i życiu (tak, tak!).
1.3. Struktura "podręcznika"
Kolejne rozdziały ułożone są w kolejności chronologicznej (w takiej, w jakiej pojawiają się na zajęciach). Po każdych zajęciach notatki będą aktualizowane – gdy tak się stanie, będziecie powiadomieni przez maila.
W "podręczniku" będą pojawiać się również rozdziały dodatkowe oznaczane gwiazdką i tekstem na szarym tle. Rozdziały polecam osobom szczególnie łaknącym wiedzy z dziedziny informatki i tym, dla których podstawowy materiał jest nudny lub bardzo szybko wchodzi do głowy.
Dodatkowo ważne informacje będą wyróżnione przez otoczenie ramką. Ale dla Was, jako ludzi rządnych wiedzy, każda informacja jest ważna...
Kody źródłowe też mają swój wyróżnik (w końcu są bardzo ważnym elementem naszych zajęć, więc nie mogą czuć się odrzucone) – szara belka z lewej strony.
1.4. Prawa autorskie, kontakt z autorem
Podręcznik można wykorzystywać w dowolnych celach niekomercyjnych, zachowując jednak w każdym wykorzystanym fragmencie informację o autorze.
W przypadku znalezienia błędu w notatkach czy po prostu pytań, proszę o kontakt pod adresem: [email protected] bądź [email protected].
1.5. Historia zmian
•
2010.03.25
zaawanoswane operacje na zmiennych typu string, podsumowanie typów
zmiennych, vector, sortowanie
3
•
2009.12.06
switch, operacje arytmetyczne, operatory logiczne, while, for, char, kody
ASCII, zmienne liczbowe zmiennoprzecinkowe, podstawowe funkcje
matematyczne, tablice
•
2009.11.23
if, bloki, operatory porównania, string, formatowanie kodu
•
2009.11.22
pierwsza wersja "podręcznika" – do podrozdziału "wczytywanie danych"
2. Wprowadzenie do programowania i podstawowych narzędzi
2.1. Czym jest program?
W Windowsie mamy pełno różnych programów rozsianych po folderach. Prawie każdy z nich ma nazwę kończącą się w ten sam sposób ­ ".exe". Pliki nazwane w ten sposób są to tzw. pliki wykonywalne, które powstały w wyniku kompilacji (i późniejszej konsolidacji – ale będziemy używać skrótu myślowego i pozostaniemy przy określeniu: kompilacja) kodu źródłowego.
Czym jest kod źródłowy? Jest to plik tekstowy (z rozszerzeniem ".cpp" w przypadku języka C++ ­ a takim będziemy się zajmować) zawierający logicznie ułożony zestaw instrukcji, który jest zrozumiały dla kompilatora języka C++.
Wróćmy do programu. Każdy program posiada miejsce, w którym zaczyna się jego wykonywanie (uruchamianie). Z tego początkowego miejsca wykonywane są kolejne fragmenty programu lub inne, zewnętrzne, znajdujące się poza programem funkcje systemowe. Te kolejne fragmenty programu mogę wywoływać jeszcze kolejne, i kolejne, i kolejne... aż do momentu zakończenia działania programu.
2.2. Programy działające w trybie tekstowym
Jak wiemy z własnego doświadczenia, programy wyglądają różnie. Te najczęściej używane przez nas posiadają ładną grafikę, wyskakujące okienka, nie mówiąc już o grach... Ale to wszystko stanowi tylko opakowanie używanych programów (może poza kilkoma wyjątkami), prawdziwa logika aplikacji jest ukryta za interfejsem (czyli za tym wszystkimi okienkami, którymi obsługujemy program). My będziemy skupiać się na budowie tego wnętrza programu, a jako interfejsu będziemy używać tzw. standardowego wejścia i standardowego wyjścia programu. Te pierwsze możemy wyobrazić sobie jako tekst wpisywany z klawiatury, a drugie jako wyświetlanie tekstu na ekranie monitora. To w pełni wystarczy nam do komunikacji z tworzonymi przez nas programami.
Czyli uwaga warta zapamiętania płynąca z tego rozdziału: ilekroć w zadaniach będzie mowa o standardowym wejściu i wyjściu, wiemy, że program ma działać w trybie tekstowym (i dane ma odczytywać tak, jakby odczytywał dane z klawiatury, a wypisywać je tak, jak wypisuje się informacje "na ekran" w trybie tekstowym).
W konkursach algorytmicznych inny tryb odczytywania i wypisywania danych niż tryb tekstowy nie będzie się pojawiać.
4
2.3. Konsola w systemie Windows
W systemie Windows programy uruchamiane w trybie tekstowym obsługiwane są przez konsolę (linię poleceń) cmd. W tym rozdziale zaznajomimy się z jej obsługą.
W celu uruchomienia konsoli, wybierz z menu Start polecenie Uruchom... i wpisz cmd, zatwierdzając enterem bądź przyciskiem OK. W systemach Vista i, bodajże, w najnowszej siódemce wystarczy wpisać cmd bezpośredniu w polu widocznym w menu start.
Po uruchomieniu widzimy najczęściej okno konsoli posiadające czarne tło i tzw. znak zachęty mówiący nam, że możemy zacząć wydawać polecenia.
W konsoli zawsze operujemy na konkretnym katalogu, w którym się znajdujemy. Ścieżka do niego podana jest na początku linii, przed znakiem zachęty.
Aby przejść do kolejnego podkatalogu korzystamy z polecenia cd, podając po spacji nazwę katalogu (pamiętając, że każde wystąpienie spacji poprzedzamy znakiem "\"), np. chcąc przejść do podkatalogu "Zadania", wpisujemy:
cd Zadania
Aby przejść do katalogu o poziom wyżej, wpisujemy:
cd ..
W celu przejścia do innego dysku, np. na D:, wpisujemy po prostu jego nazwę – dla naszego przykładu:
D:
Jeśli chcemy wyświetlić listę wszystkich plików znajdujących się w folderze, w którym przebywamy, wpisujemy:
dir
A gdy znajdziemy się już w folderze, w którym mamy program do uruchomienia, po prostu wpisujemy jego nazwę, np. chcąc uruchomić program o nazwie "kasztany.exe", wpisujemy:
kasztany.exe
Pamiętajmy, by każde polecenie zatwierdzać klawiszem enter!
W przypadku uruchamiania programów, nawet, jeśli program się zakończył, efekty jego pracy pozostają widoczne na ekranie! Nie trzeba więc w tym wypadku uciekać się do trików powodujących zatrzymanie działania programu.
Dodam, że konsola w systemie Linux jest równie prosta w obsłudze – różni się kilkoma szczegółami, ale ogólna idea jest praktycznie ta sama.
2.4. Przekierowywanie standardowego wejścia i wyjścia*
Podczas procesu rozwiązywania zadań zachodzi konieczność testowania rozwiązań. Czasami dane wejściowe, dla których ma działać nasz program, są długie i każdorazowe ich wpisywanie z klawiatury może być bardzo męczące. Rozwiązaniem jest przekierowanie standardowego wejścia i wyjścia!
W praktyce wygląda to tak, że zamiast wpisywać z klawiatury tekst za każdym razem, wpisujemy go do pliku tekstowego w takiej formie, w jakiej chcemy go podać do programu. Czyli 5
tworzymy dla przykładu plik o nazwie "test01.txt" i przy użyciu edytora (np. standardowy Notatnik) wprowadzamy do niego nasz test, czyli dla przykładowego zadania (Kasztany z pierwszej pracy domowej) może on wyglądać tak:
2
5 1 2 3 2 1
3 2 1 98
Zwróćcie szczególną uwagę na nową, pustą linię na końcu – jest to po prostu wstawienie znaku nowej linii po liczbie 98 mówiące programowi to samo, co wciśnięcie klawisza enter po ręcznym wpisywaniu tych danych do programu – czyli "zatwierdzam linię, wczytaj ją".
I wówczas, będąc w folderze z naszym programem i mając w tym samym folderze utworzony plik tekstowy "test01.txt" możemy uruchomić program z automatycznym wpisaniem tych danych do programu za pomocą polecenia:
kasztany.exe < test01.txt
Polecenie spowoduje uruchomienie programu z wpisanymi przez nas danymi testowymi – program, jeśli został poprawnie napisany, powinien wypisać na ekran rozwiązanie i zakończyć działanie (o ile sztucznie nie zatrzymujemy jego wykonania na końcu).
Możemy też przekierować wszystkie dane wypisywane przez nasz program do pliku (np. w celu późniejszego porównywania czy przeanalizowania na spokojnie, gdy wypisywanych informacji przez program jest cała masa). W celu przekierowania wypisywanych przez program danych do pliku korzystamy z odwrotnego znaku, np. w celu przekierowania wyniku działania aplikacji "kasztany.exe" do pliku "out01.txt" wpisujemy:
kaszatany.exe > out01.txt
Spowoduje to utworzenie pliku tekstowego ze wszystkimi informacjami, które wypisałby program standardowo "na ekran".
Oba przekierowania można połączyć, pisząc (dla naszego przykładu):
kasztany.exe < test01.txt > out01.txt
2.5. Środowisko Dev­C++
Pod systemem Windows najprostszym i stosunkowo dobrym sposobem na tworzenie aplikacji w C++ jest korzystanie z edytora i kompilatora (takie 2 w 1) Dev­C++.
Aby zainstalować go na naszym komputerze, należy udać się pod adres:
http://sourceforge.net/project/downloading.php?groupname=dev­cpp&filename=devcpp­4.9.9.2_setup.exe&use_mirror=sunet
Po ściągnięciu zwyczajnie uruchamiamy instalator i przechodzimy przez proces instalacji.
Przy uruchomieniu wybieramy wersję językową (dostępny jest język polski), możliwa jest też późniejsza zmiana wersji językowej w ustawieniach.
Na początek tworzymy nowy kod źródłowy i zapisujemy go w pliku o rozszerzeniu ".cpp", np. "kasztany.cpp". Po uzupełnieniu pliku sensowną treścią, możemy skompilować i uruchomić program. Do tego celu używamy menu "Uruchom...". Warto używać skrótów klawiszowych podanych obok poleceń tego menu.
W szczególności po skompilowaniu programu (czyli po przetłumaczeniu naszego kodu 6
źródłowego do pliku wykonywalnego ".exe") możemy uruchomić program bezpośrednio z katalogu, w którym program się znajduje (bez użycia Dev­C++). Można do tego celu użyć również konsoli systemu Windows (polecam 2 poprzednie podrozdziały w celu zaznajomienia się z konsolą).
Pamiętaj, że w przypadku wysyłania rozwiązań w systemach automatycznego sprawdzania czy na wszelkiego rodzaju konkursy i olimpiady, zawsze wysyłaj kod źródłowy z rozszerzeniem *.cpp (a nie np. pliki wykonywalne *.exe).
Często będzie się zdarzać, że proces kompilacji nie zostanie przeprowadzony poprawnie – "wyskoczy błąd". Wówczas należy w dolnej części Dev­C++ zobaczyć w tabelce, jaki błąd wyskoczył oraz w której linii się to stało. Należy się do tej linii udać i spojrzeć uważnie, gdzie jest błąd. Po poprawieniu można spróbować powtórnie skompilować program. Uwaga: często błąd występuje w poprzedniej linii, niż podana (lub jeszcze w jednej z powyższych).
2.6. Kompilator GNU C++ w systemie Linuks (g++)*
Dla osób korzystających z Linuksa przyda się niewątpliwie garść informacji z tego podrozdziału.
W Linuksie, w celu skompilowania kodu źródłowego do postaci pliku wykonywalnego, należy skorzystać z kompilatora GNU C++ (program kryje się pod nazwą g++). Jeśli polecenie g++ nie jest wykrywane (polecenie wpisujemy w konsoli systemu Linuks), należy ten pakiet doinstalować – jeśli nie wiesz jak, skorzystaj z Google lub skontaktuj się z autorem tych notatek.
Po pomyślnym zainstalowaniu jesteśmy gotowi do kompilacji. W tym celu za pomocą konsoli przechodzimy do katalogu, w którym kryje się gotowy do kompilacji plik z kodem źródłowym i używamy polecenia, dla przykładowego pliku "kasztany.cpp":
g++ kasztany.cpp -o kasztany
Dzięki temu uzyskamy plik wykonywalny o nazwie "kasztany", który uruchamiamy poleceniem z konsoli:
./kasztany
Dodam, że plik z kodem źródłowym można edytować praktycznie dowolnym edytorem tekstu, np. Gedit lub Kate (które to posiadają kolorowanie składni C++).
Większość systemów oceniających zadania w ramach konkursów i olimpiad używa właśnie systemu Linuks i kompilatora GNU C++. Na cele Olimpiady Informatycznej i Olimpiady Informatycznej Gimnazjalistów system oceniający korzysta z następującego polecenia do kompilacji kodów źródłowych (na "kasztanowym" przykładzie):
g++ -O2 -static kasztany.cpp -lm -o kasztany
3. Podstawy języka C++
3.1. Budowa programu
Pamiętamy z wprowadzenia, że program posiada miejsce, w którym się zaczyna. Takim miejscem, w którym zaczyna się program w C++ jest "główny fragment programu", który nazwany 7
jest po prostu main (albo funkcja main – więcej o tym, czym jest funkcja, będzie w przyszłości). Jak on wygląda? Zobacz:
int main
{
}
Taki fragment jest obowiązkowo musi znaleźć się w każdym programie, bo to od niego wszystko się zaczyna.
Wewnątrz głównego bloku (główny blok zaczyna i kończy się klamerką) umieszczamy kolejne instrukcje, które chcemy w ramach programu wykonać. Umieszczamy je i umieszczamy, powstaje nam ich cały szereg, ale w końcu nadejdzie moment, w którym program musi się zakończyć. System operacyjny musi wiedzieć, czy nasz program zakończył się poprawnie (czyli nie wyskoczył żaden błąd) czy jednak coś się stało. Często się zdarza, że "coś się stanie" – wtedy nasz program automatycznie poinformuje system operacyjny, że zakończył się błędem – o to się martwić nie musimy. Jednak jak napisaliśmy poprawny program, to chcemy, by na końcu poinformował on system operacyjny, że zakończył się poprawnie. Poinformowanie odbywa się poprzez wysłanie kodu liczbowego w głównym bloku programu. Kodem liczbowym informującym o tym, że wszystko przebiegło bez zarzutu, jest 0 (zero). Wysyłanie kodu liczbowego (nazywanego paradoksalnie kodem błędu) odbywa się poprzez przekazanie do funkcji main liczby za pomocą instrukcji return z podaną po spacji liczbą 0.
Pamiętaj! Zawsze kończ program instrukcją return 0;
Instrukcję możesz wywołać w dowolnym miejscu funkcji main – po jej wywołaniu następuje zawsze zakończenie działania programu, więc instrukcje znajdujące się dalej w kolejności po return 0 nie zostaną wykonane!
Każdy program rozpoczynamy więc od konstrukcji głównej funkcji:
int main
{
return 0;
}
Kolejna ważna uwaga: w C++ każdą instrukcję (poza jasno określonymi wyjątkami) należy zakończyć średnikiem ";".
3.2. Dołączanie bibliotek, wprowadzenie do funkcji
Konstrukcja programu przedstawiona w poprzednim podrozdziale nic sensownego nie robi. Aby zaczęła coś robić, możemy skorzystać z instrukcji zwanymi funkcjami, które dostępne są w różnych grupach funkcji, które są nazywane bibliotekami. Funkcja jest instrukcją, która po wywołaniu wykonuje z góry określoną czynność. Podczas wykonywania tej czynności może przyjmować dodatkowe parametry, które nazywane są argumentami funkcji. Każda funkcja posiada swoją nazwę – poprzez taką nazwę funkcja jest właśnie wywoływana do działania. Możemy 8
więc np. mieć funkcję, która dla danej liczby potrafi wyciągnąć z niej pierwiastek. Czyli "na chłopski rozum" w funkcji main możemy kazać dokonać następującej czynności: "hej, funkcjo o nazwie pierwiastek – proszę, daję tobie jako argument jedną liczbę 9, a Ty mi powiedz, jaki jest pierwiastek z tej liczby" – po takim wywołaniu funkcja zwraca liczbę, mówiąc: "proszę, pierwiastek z podanej liczby to 9".
Jak wspomniałem, funkcje wywołujemy poprzez nazwę oraz możemy dołączyć do tej funkcji argumenty. Argumenty podajemy w nawiasach "(" i ")" po nazwie funkcji. Jeśli podajemy więcej, niż jeden argumenty, to kolejne argumenty rozdzielamy od siebie przecinkami. Pamiętaj, że nawet, jeśli nie podajesz żadnego argumentu, musisz wpisać nawiasy po nazwie funkcji (bez zawartości wewnątrz). Wracając do przykładu z pierwiastkiem – funkcja ta nazywa się "sqrt" i jako argument przyjmuje jedną liczbę, więc jej wywołanie może wyglądać tak:
sqrt(9);
Funkcja obliczy pierwiastek równy 3, ale nic z tą wartością nie zrobimy póki co. Ponadto powyższa funkcja nie będzie działać bez dołączenia odpowiedniej biblioteki! Więc nie próbuj na razie wpisywać powyższego fragmentu do swojego programu – więcej o funkcji pierwiastek w dalszych podrozdziałach. Ważne, by zacząć się oswajać z funkcjami i ich wykorzystaniem w niedalekiej przyszłości.
Wspomniałem, że funkcje oraz inne ważne elementy programu (jak np. nazwa symbolizująca standardowe wyjście lub wejście – która funkcją nie jest, więc nie kończy się nawiasami) znajdują się w bibliotekach. Dla przykładu biblioteka, w której znajdują się podstawowe elementy umożliwiające wypisywanie danych na ekran i wczytywanie znajdują się w bibliotece o nazwie "iostream". Aby dołączyć taką bibliotekę, należy na samym początku programu podać (podając w "trójkątnych" nawiasach – znakach mniejszości/większości – nazwę potrzebnej biblioteki – dla przykładu "iostream"):
#include <iostream>
Chcąc dołączyć kolejną bibliotekę, należy w kolejnej linii podać instrukcję taką, jak wyżej, ale z inną nazwą.
W C++ możemy korzystać z tzw. biblioteki standardowej (std) – w rzeczywistości jest to zbiór bibliotek o różnych nazwach i możliwościach – będziemy je stopniowo poznawać w ramach zajęć (i w tym podręczniku). Każdy element z tych bibliotek należy do tzw. przestrzeni nazw std, co oznacza, że przed każdym elementem musimy podawać "std::" ­ ale bez sensu (dla naszych celów) się z tym męczyć. Aby zaradzić temu zjawisku, tuż po dołączeniu bibliotek a przed rozpoczęciem funkcji main podajemy:
using namespace std;
Po dołączeniu 3 przykładowych bibliotek nasz program może wyglądać następująco:
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
int main()
{
9
return 0;
}
3.3. Komentarze
W tworzonym przez nas kodzie źródłowym możemy dodawać informacje nieprzeznaczone dla kompilatora, a dla osoby przeglądającej kod źródłowy (lub po prostu informacje dla siebie). Możemy też zakomentowywać fragmenty programu, które w danej chwili nie chcemy uwzględniać w programie (czyli jakby unieważniamy je).
Informacje takie nazywane są komentarzami. Generalnie istnieją 2 główne sposoby na komentowanie kodu (więcej nie potrzebujemy).
Pierwszym sposobem jest prosty, jednolinijkowy komentarz, który zaczyna się dwoma znakami "/", a kończy na końcu linii.
Drugim sposobem jest otwarcie komentarza za pomocą "/*" i jego zakończenie przy użyciu "*/". Taki komentarz nie kończy się z końcem linii – bywa przydatny, gdy okaże się, że kod, który napisaliśmy, zawiera jakieś błędy, ale nie chcemy się go pozbywać na zawsze – wówczas umieszczamy go w komentarzu i "jedziemy dalej". Przydatny bywa też w sytuacji, gdy chcemy opisać, jak działa dany fragment kodu.
Użycie dwóch rodzajów komentarza widoczne jest w poniższym przykładzie:
int main()
{
//tutaj komentarz jednolinijkowy
/* a tutaj komentarz,
który jest trochę
dłuższy */
}
3.4. Wypisywanie danych
Dane będziemy wypisywać zawsze na standardowe wyjście (spójrz do jednego z poprzednich rozdziałów, by więcej się o nim dowiedzieć), czyli w domyśle "na ekran". Ten "ekran" w C++ symbolizowany jest nazwą cout z biblioteki iostream (by więc móc wypisać coś na ekran, musimy tą bibliotekę dołączyć do programu).
Wypisywanie "na ekran" odbywa się poprzez kierowanie do cout kolejnych rzeczy, które chcemy wypisać. Odbywa się to poprzez operator "<<" (możemy zapamiętać to jako strzałeczki skierowane w stronę naszego cout, który symbolizuje "ekran"). Po tym operatorze podajemy element, który chcemy wypisać. Elementy możemy podawać jeden za drugim, rozdzielając je za każdym razem kolejnymi operatorami "<<".
Pamiętać należy, że tekst wypisywany na ekran należy otoczyć cudzysłowami!
Przykładowe wypisanie na ekran zdania "Kocham C++!" wygląda następująco:
cout << "Kocham C++!";
Możemy podawać też argumenty jeden za drugim:
10
cout << "Kocham C++ i mam " << 15 << " lat";
Jak widać powyżej, liczby możemy wypisywać bez otaczania cudzysłowami.
Aby wstawić nową linię ("enter"), należy skorzystać z elementu endl. Np.:
cout << "To jest pierwsza linia." << endl;
cout << "A to jest druga linia." << endl << "I trzecia!";
Pamiętaj o dołączeniu do programu biblioteki iostream, gdy korzystasz z wypisywania na ekran!
3.5. Sztuczne zatrzymywanie pracy programu
Gdy korzystamy ze środowiska Dev­C++ i uruchamiamy programy bezpośrednio z niego, bez użycia konsoli, to po skończeniu program znika. Możemy chcieć go "utrzymać przy życiu", by zobaczyć, co nam wypisał, jak się zakończył. W tym celu możemy skorzystać z drobnej sztuczki – instrukcji zatrzymującej pracę programu (metoda działa w systemie Windows):
system("PAUSE");
Najlepiej taki fragment umieszczać tuż przed zakończeniem programu, czyli tuż przed instrukcją return.
Pamiętaj, by przy wysyłaniu kodu źródłowego do automatycznych systemów oceny lub na konkursy koniecznie zakomentować lub usunąć linię z funkcją system("PAUSE"); lub inną, oczekującą na końcu programu na reakcję użytkownika. Jeśli tego nie zrobisz, skutkować może to oceną rozwiązania na 0 punktów!
3.6. Zmienne całkowitoliczbowe (int)
Bardzo ważnym elementem każdego programu są zmienne. Są to elementy potrafiące zapamiętać wprowadzone do nich wartości. Każda zmienna posiada w dodatku swoją nazwę. Możemy więc kazać dla przykładu: "zmienno o nazwie xyz zapamiętaj liczbę 42" i w jednej z kolejnych linii: "zmienno o nazwie xyz, powiedz, jaką wartość przechowujesz" i wtedy zmienna xyz odpowie: "przechowuję liczbę 42". Takie żądania mogą zachodzić dowolną ilość razy w czasie działania programu. Dzięki zmiennym możemy przechowywać dane w pamięci komputera.
Na początku zajmiemy się liczbami (całkowitymi). Każdą zmienną w C++ przed pierwszym użyciem należy zadeklarować. Czyli zgłosić chęć jej używania w programie. Czynimy to, podając typ zmiennej i jej nazwę. Typem zmiennej całkowitoliczbowej jest int. Zgłoszenie chęci użycia zmiennej całkowitoliczbowej o nazwie xyz wygląda więc następująco:
int xyz;
Ten typ w C++ przechowuje liczby od około ­32 000 do około 32 000 (przechowywane są więc też liczby ujemne). W praktyce okazuje się, że przechowuje liczby od około ­2 000 000 000 do około 2 000 000 000 – ale nie możemy tego zakładać!
Jak już zadeklarujemy zmienną, możemy przypisać jej jakąś wartość. Czynimy to poprzez operator przypisania "=", podając z lewej strony nazwę zmiennej, a z prawej wartość, którą 11
chcemy przypisać. Wartością może być liczba, może być rozbudowane wyrażenie (o czym później) lub zmienna (lub połączenie każdej z tych rzeczy).
Możemy dokonać również przypisania w tej samej linii, co deklaracja zmiennej.
Przykładowe przypisanie liczby oraz zmiennej do innej zmiennej i użycie przypisania wraz z deklaracją wygląda następująco:
int xyz;
xyz = 42;
int liczba_inna;
liczba_inna = 55;
liczba_inna = xyz;
int jeszcze_inna_liczba = 0;
Pamiętaj, że zmienna, dopóki nic jej nie zostanie przypisane, posiada nieokreśloną wartość! W szczególności może to być dowolna, "śmieciowa" liczba, np. 1234155. Dlatego, gdy Twój program niespodziewanie zacznie wypisywać takie wartości, to prawdopodobnie nie zainicjowałeś zmiennej, czyli nie przypisałeś jej żadnej wartości przed wypisaniem lub przed operacjami na niej. Takie wypisywanie "śmieci" na ekran może oznaczać też to, że próbowałeś przypisać zmiennej wartość większą (bądź mniejszą – "bardzo ujemną") niż jest w stanie pomieścić – wówczas dochodzi do tzw. zjawiska "przekręcenia zmiennej".
Zmienne możemy wypisywać bezpośrednio na ekran, podając jej nazwę jako jeden z argumentów po operatorze "<<".
Możemy też zadeklarować wiele zmiennych tego samego typu, wymieniając je po przecinku, np.:
int x, y, z, a, b, c, jeszcze_inna, i_jeszcze_inna;
3.7. Specyfikatory zmiennych całkowitoliczbowych (int)
Z poprzedniego podrozdziału pamiętamy, że typ int to liczba od około ­32 000 do około 32 000. Co w przypadku, gdy chcemy większą liczbę? Musimy skorzystać ze specyfikatorów zmiennych. Taki specfyfikator to po prostu dodatkowe słowo dodane przed typem zmiennej.
W przypadku chęci przechowywania liczb od około ­2 000 000 000 do około 2 000 000 000, należy skorzystać ze specyfikatora o nazwie long. Aby więc zadeklarować taką zmienną, należy napisać:
long int xyz;
Albo skrótowo (kompilator domyślnie uznaje, że chodzi o zmienną typu int):
long xyz;
A co, gdy chcemy jeszcze więcej? Możemy dopisać kolejne long przed zmienną – wówczas zmienna pomieści od około ­9*10^18 do około 9*10^18 (10^18 oznacza 10 do potęgi 18, czyli wychodzi 9 i osiemnaście zer). Przykładowo:
long long int xyz;
lub:
12
long long xyz;
W systemie Windows może wystąpić problem z użyciem zmiennych typu long long. Może objawić się to błędem przy kompilacji lub po prostu "pojemnością" zmiennej takiej, jak w typie long. Jest to spory problem, który w przypadku udziału w konkursach rozwiązać najlepiej przetestowaniem rozwiązania na systemie Linuks.
A co, gdy mamy ograniczoną pamięć (im większą liczbę może przechowywać zmienna, tym więcej pamięci komputera zajmuje, a w zadaniach często mamy limit pamięci, z której chcemy skorzystać)? Możemy chcieć ograniczyć "pojemność" zmiennej. Jak pisałem wyżej, int gwarantuje, że pomieści liczbę od ­32 000 do 32 000, jednak w praktyce najczęściej mieści tyle, co long int – a więc zajmuje więcej pamięci. Aby ograniczyć "pojemność" zmiennej do ­32 000 ... 32 000, należy skorzystać ze specyfikatora short, np.:
short int xyz;
lub:
short xyz;
Domyślnie każda zmienna liczbowa przechowuje liczby ujemne. Ale możemy zrezygnować z nich na rzecz podwojenia górnego limitu i ograniczenia dolnego limitu do 0. Możemy uczynić to poprzez dopisanie na początku specyfikatora unsigned. Spowoduje to, że z dopisanym unsigned typ short mieści od 0 do około 64 000, typ long od 0 do około 4 000 000 000, typ long long od 0 do około 18*10^18. Przykłady użycia tego specyfikatora widoczne są poniżej:
unsigned
unsigned
unsigned
unsigned
unsigned
int a;
short int b;
long c;
long long int d;
long long e;
3.8. Wczytywanie danych
Do tej pory nauczyliśmy się wypisywać tekst, liczby na ekran. Niemal analogicznie możemy odczytywać dane "z klawiatury". Aby to uczynić, musimy skorzystać z elementu o nazwie cin zawartego w bibliotece iostream. Odczytywane dane muszą "wędrować" do zmiennych, czyli przed wczytaniem danych musimy zadeklarować zmienną – np. liczbę typu int o nazwie xyz. Wówczas wczytywanie odbywa się poprzez przekierowanie danych z klawiatury, czyli z cin do zmiennej przy użyciu operatora ">>" ("strzałka" od cin do zmiennej). Tak samo, jak przy wypisywaniu, możemy wczytywać dane jedne za drugimi, rozdzielając kolejne zmienne, do których dane trafią, za pomocą operatora ">>".
Dane wprowadzane "z klawiatury" są dzielone w miejscach wystąpienia tzw. białych znaków, czyli głównie spacji i "entera" (znaku nowej linii). Oddzielone dane trafią do kolejnych zmiennych. Czyli, gdy wpiszemy z klawiatury tekst: "123 4 55", to dane trafią do 3 osobnych zmiennych – do pierwszej trafi 123, do drugiej 4 i do trzeciej 155. (Tak samo rozdzielany będzie tekst – o zmiennych tekstowych jest mowa w jednym z dalszych podrozdziałów.)
Przykładowe wczytanie 3 liczb może wyglądać następująco:
int x, y, z;
cin >> x >> y >> z;
13
lub:
int
cin
cin
cin
x,
>>
>>
>>
y, z;
x;
y;
z;
3.9. Instrukcje warunkowe if
W naszym programie możemy uzależniać wykonanie fragmentów kodu w zależności od zachodzenia pewnych warunków. Do tego celu służy instrukcja warunkowa if.
Wyobraźmy sobie, że chcemy wczytać liczbę i napisać na ekranie słowo "duża liczba", jeśli jest większa niż 100, a w przeciwnym wypadku "liczba mała". Do tego celu musimy wykorzystać wspomnianą instrukcję warunkową if.
Składnia instrukcji wygląda następująco: na początku znajduje się słowo if, po nim w nawiasie podany jest warunek, który ma zachodzić. W kolejnej linii podajemy instrukcję, która ma zostać wykonana w przypadku, gdy zachodzi warunek z nawiasu. Można też podać więcej niż jedną instrukcję, ale należy je objąć wówczas nawiasami klamrowymi "{", "}"! Po instrukcji bądź bloku instrukcji możemy (ale nie musimy) podać instrukcje, które zostaną wykonane, jeśli warunek nie zachodzi. Możemy to uczynić za pomocą pojedynczego słowa kluczowego else, podając po nim instrukcję lub blok instrukcji otoczony nawiasami klamrowymi.
W szczególności możemy podać cały szereg instrukcji warunkowych. Np. "jeśli liczba jest większa niż 100, to wypisz duża liczba, w przeciwnym wypadku jeśli liczba jest większa niż 10, to wypisz średnia liczba, a w przeciwnym wypadku wypisz mała liczba". Aby podać instrukcję "w przeciwnym wypadku, jeżeli..." należy do else dopisać po odstępie kolejną instrukcję if wraz z podanym w nawiasie warunkiem.
Opisywany w przykładzie zapis programu wygląda następująco:
int x;
cin >> x;
if (x > 100)
cout << "duza liczba";
else if (x > 10)
cout << "srednia liczba";
else
cout << "mala liczba";
Jeśli chcemy wykonać więcej, niż jedną instrukcję, kod może wyglądać tak:
if (x > 100)
{
cout << "duza liczba" << endl;
cout << "naprawde duza";
}
else
cout << "mala liczba";
cout << "koniec!";
Ostatnia instrukcja, wypisująca "koniec!", wykona się zawsze, gdyż else (oraz if) obejmuje tylko jedną, kolejną instrukcję – chyba, że pod nim znajduje się blok instrukcji otoczony nawiasami 14
klamrowymi.
O konstrukcji warunków dowiemy się więcej w kolejnym podrozdziale.
3.10. Operatory porównania
W poprzednim podrozdziale dowiedzieliśmy się o konstrukcji instrukcji warunkowych. W tym nauczymy się, jak wycisnąć z nich więcej.
Możemy konstruować warunki tak, by wykonywać instrukcję w zależności od zachodzenia jednej z poniższych relacji:
•
wartość a jest równa wartości b (==)
•
wartość a jest różna od wartości b (!=)
•
wartość a jest większa od wartości b (>)
•
wartość a jest większa od wartości b lub jest jej równa (>=)
•
wartość a jest mniejsza od wartości b (<)
•
wartość a jest mniejsza od wartości b lub jest jej równa (<=)
W nawiasach na końcu każdego punktu podano operatory odpowiadające podanym opisom relacji. Używamy ich, podając po lewej stronie operatora jedną z wartości, zmiennych bądź wyrażenie (nawet bardzo długie i skomplikowane), itp. a po prawej drugą.
Przykład użycia operatorów porównania obrazuje poniższy kod:
int x;
cin >> x;
if (x >= 10)
cout << "x
if (x == 10)
cout << "x
if (x != 5)
cout << "x
if (x < 4)
cout << "x
jest wieksze lub rowne 10" << endl;
jest rowne 10" << endl;
jest rozne od 5" << endl;
jest mniejsze od 4" << endl;
3.11. Instrukcja warunkowa switch*
Jak już wiemy, możemy tworzyć cały ciąg instrukcji warunkowych. Istnieje jednak pewna instrukcja, która pozwala skrócić i lepiej zorganizować zapis w sytuacji, gdy uzależniamy wykonanie instrukcji od przyrównania zmiennej do wielu wybranych wartości. Tą instrukcją jest switch.
Jak działa switch? Switch bada jedną zmienną, sprawdzając jej równość z kolejno podawanymi wartościami. Jeśli któraś z wartości zachodzi, wykonywane są instrukcje znajdujące się pod nią. Dodatkowo jest też sekcja wykonywana w przypadku, gdy żadna z podanych wartości nie jest równa badanej zmiennej.
15
A jak jest zbudowany? Po słowie kluczowym switch w nawiasie podajemy nazwę badanej zmiennej. Następnie w bloku (w nawiasach klamrowych "{", "}") podajemy opisy kolejnych przypadków. Opis przypadku składa się ze słowa kluczowego case oraz z wartości (lub wyrażenia) podanej po spacji, po wartości podajemy dwukropek i wypisujemy instrukcję bądź jej ciąg, które mają zostać wykonane dla tego przypadku. Wykonanie instrukcji kończymy instrukcją break, która powoduje przerwanie wykonywania całej instrukcji switch. Jeśli polecenia break nie podamy, wykonywane będą wszystkie kolejne instrukcje, nawet, jeśli podany tuż nad nimi opis przypadku (case) nie jest spełniony. Switch działa po prostu tak, że od momentu pasującego przypadku wykonywane są wszystkie instrukcje do momentu wystąpienia break lub końca bloku switch.
Struktura instrukcji switch wygląda następująco:
switch (zmienna)
{
case WARTOSC_1:
//zestaw instrukcji
break;
case WARTOSC_2:
//kolejny zestaw instrukcji
break;
case WARTOSC_3:
//jeszcze inny zestaw
break;
default:
//instrukcje wykonywane, gdy nie jest spełniony żaden
inny przypadek
}
Przykładowy kod może wyglądać tak:
int x;
cin >> x; //wczytanie liczby
switch (x)
{
case 2:
cout << "wczytano liczbe dwa" << endl;
break;
case 3:
cout << " wczytano liczbe trzy" << endl;
break;
default:
cout << "wczytano liczbe inna niz dwa i trzy" << endl;
}
Powyższy kod wypisuje stosowny komunikat, gdy wczytano liczbę 2 lub 3 albo inną niż obie podane. Ale przeanalizujmy, co się stanie, jeśli nie podamy break:
int x;
cin >> x; //wczytanie liczby
switch (x)
{
case 2:
16
cout << "wczytano liczbe dwa" << endl;
case 3:
cout << " wczytano liczbe trzy" << endl;
default:
cout << "wczytano liczbe inna niz dwa i trzy" << endl;
}
Pamiętajmy – od momentu pasującego przypadku wykonywane są wszystkie instrukcje, dopóki nie pojawi się break lub nie skończy się blok switch.
Co więc wypisane zostanie dla wczytanej liczby 3? Wypisane zostanie:
wczytano liczbe trzy
wczytano liczbe inna niz dwa i trzy
A dla wczytanej liczby 2? Na ekranie pojawi nam sie:
wczytano liczbe dwa
wczytano liczbe trzy
wczytano liczbe inna niz dwa i trzy
Należy więc pamiętać o stosowaniu instrukcji break.
Ale może okazać się, że chcemy wykorzystać zaobserwowane wyżej zjawisko. Dajmy na to, że chcemy wypisać "mala liczba nieparzysta", gdy podamy 1, 3, 5 lub 7, chcemy wypisać "bardzo mala liczba parzysta", gdy podamy 2 lub 4 i chcemy wypisać "inna liczba", gdy nie zachodzi żadna równość z tymi przypadkami. Wówczas możemy napisać:
int x;
cin >> x;
switch (x)
{
case 1:
case 3:
case 5:
case 7:
cout << "mala liczba nieparzysta";
break;
case 2:
case 4:
cout << "bardzo mala liczba parzysta";
break;
default:
cout << "inna liczba";
}
Jeśli np. wczytana zostanie liczba 3, to drugi z kolei przypadek (case 3) zostanie spełniony – wykonywane więc będą wszystkie instrukcje od tego miejsca do momentu instrukcji break przerywającej wykonywanie lub do momentu zakończenia bloku switch. Najbliższą instrukcją od case 3 jest wypisanie na ekran wyrażenia "mala liczba nieparzysta" – zostanie więc wykonana. Kolejną instrukcją jest break, więc cała instrukcja switch zostaje zakończona.
3.12. Bloki poleceń i widoczność zmiennych
W podrozdziale traktującym o instrukcjach warunkowych była mowa o grupowanie 17
instrukcji w bloki. Czynimy to za pomocą nawiasów klamrowych "{" i "}". Kod otoczony w bloki stanowi spójną całość (i jest wykonywany cały blok instrukcji, zamiast pojedynczej, jeśli taki blok znajdzie się "pod" instrukcjami warunkowymi bądź "pod" pętlami czy funkcjami (o których będzie mowa w kolejnych podrozdziałach).
Należy pamiętać, że zmienne zadeklarowane wewnątrz bloku nie są widoczne poza nim! Oznacza to, że nie są w ogóle rozpoznawane i próba kompilacji kończy się wówczas niepowodzeniem.
W poniższym przykładzie nastąpi błąd kompilacji, gdyż dla instrukcji próbującej wypisać zmienną zmienna xyz jest niewidoczna (bo jest "zamknięta" w bloku powyżej):
if (x > 100)
{
int xyz = 1234;
}
cout << "Zmienna xyz jest rowna " << xyz;
3.13. Operatory przypisania i podstawowe operacje arytmetyczne
Zacznijmy od przypomnienia sobie najprostszego przypisania wartości do zmiennej. Korzystamy z niego przy pomocy operatora = (znak równości), umieszczając po lewej stronie nazwę zmiennej, do której przypisujemy wartość, a po prawej wartość lub wyrażenie. Najprostsze przypisanie może wyglądać tak:
int x;
x = 4;
lub krócej, razem z deklaracją zmiennej:
int x = 4;
Ale możemy również podstawiać pod zmienną wartość innej zmiennej. Przykładowo:
int x = 4;
int y;
y = x;
W ostatniej linii następuje podstawienie do zmiennej o nazwie y wartości znajdującej się w zmiennej x, czyli 4. Po tej instrukcji zmienna y również zawiera wartość 4.
W tym miejscu nauczymy się przydatnej umiejętności. Załóżmy, że mamy zmienną x równą 4 i zmienną y równą 5. Chcemy zamienić wartości miejscami. Nie możemy tego zrobić w ten sposób:
x = y;
y = x;
Nie możemy tego tak zrobić, gdyż po pierwszej linii w zmiennej x będziemy mieć tą samą wartość, co w y, czyli 5. Po przypisaniu do x nowej wartości 5 zatracimy zupełnie informację o poprzedniej wartości 4. I kolejna linia spowoduje przypisanie do y wartości x, która po pierwszej linii wynosi 5. Po wykonaniu całego kodu obie zmienne zawierać więc będą wartości 5. A tego chcemy uniknąć!
Rozwiązaniem powyższego problemu jest skorzystanie z tzw. zmiennej pomocniczej. 18
Polega to na użyciu nowej zmiennej, w której zapamiętamy wartość x przed przypisaniem do x nowej wartości i potem odczytamy tą poprzednią wartość x ze zmiennej pomocniczej i przypiszemy do y. Ostatecznie użycie wygląda tak:
int pomocnicza;
pomocnicza = x;
x = y;
y = pomocnicza;
Ale zabawa dopiero się zaczyna. Z prawej strony mogą być znacznie bardziej skomplikowane wyrażenia, zawierające operacje sumowania, mnożenia zmiennych i wartości liczbowych, itp.
Jednym z najprostszych operacji jest operacja sumowania, której dokonuje się tak, jak nas uczono w szkole podstawowej – za pomocą operatora dodawania "+". Jest to operator, który stosujemy tak samo, jak zawsze – czyli po prawej i lewej stronie od znaku plusa umieszczamy liczby – lub w naszym przypadku zmienne. Przykłady użycia przypisania z użyciem wyrażenia z operacją sumowania poniżej:
int
int
int
int
int
int
int
d =
x = 3;
y = 3 + 2
z = 4;
a = x + z;
b = z + 5;
c = 3 + 2 + 1 + z + y;
d = 4;
d + 1;
Przeanalizujmy dokładnie powyższy przykład. W pierwszej linii mamy zwykłe przypisanie wartości do zmiennej x. W drugiej przypisujemy do zmiennej y wartość sumowania 3 + 2, czyli pod zmienną y mamy 5. W czwartej linii do zmiennej a przypisujemy wynik sumowania zmiennej x i z – pod x kryje się 3, a pod z 4, więc ostatecznie pod a podstawiona zostanie wartość 7. W piątej linii pod zmienną b podstawiamy wynik sumowania zmiennej z (pod którą kryje się 4) i liczby 5, więc pod zmienną b będziemy mieć wartość 9. Linia szósta zawiera cały ciąg operacji sumowania zmiennych i liczb – ostatecznie pod c podstawiona zostanie wartość 15. W ostatniej, bardzo ważnej (z merytorycznego punktu widzenia) linii mamy podstawienie pod zmienną d wartości powstałej przez sumowanie dotychczasowej wartości zmiennej d, czyli 4 oraz wartości 1, co ostatecznie daje podstawienie pod zmienną d liczby 5. Instrukcja ta zwiększyła więc wartość zmiennej d o 1. Jest to często spotykany zabieg w procesie tworzenia programów, więc warto go zapamiętać!
Analogicznie do sumowania, mamy też operatory odejmowania ­, mnożenia *. Dla przykładu:
int x = 3 – 1;
int y = x * 2;
Mamy też dzielenie. Ale dla liczb typu int (czyli dla liczb całkowitych) jest to dzielenie z odrzuceniem reszty z dzielenia. Operatorem dzielenia jest znak /. Dla przykładu mamy więc: 14 / 3 jest równe 4 r. 2 (cztery z resztą z dzielenia wynoszącą 2). Resztę z dzielenia odrzucamy i zostaje nam 4. Tak więc dla przykładu:
int x = 14 / 3;
int a = 16;
a = a / 10;
19
Po tym fragmencie kodu pod zmienną x kryje się wartość 4, a pod zmienną a mamy 1 (bo następuje przypisanie do zmiennej a wartości dzielenia dotychczasowej wartości zmiennej a, czyli 16 przez 10 – reszta jest odrzucana, więc zostaje sama jedynka).
Ale mamy też operację modulo, czyli wyciąganie samej reszty z dzielenia. Czynimy to przy użyciu operatora %. Aby więc wyciągną resztę z dzielenia 14 przez 3 piszemy: 14 % 3 i uzyskujemy resztę o wartości 2. Przykładowe użycie:
int x = 14 / 3;
int a = 16;
a = a / 10;
Po wykonaniu powyższego kodu pod zmienną znajdziemy wartość 2, a pod zmienną a wartość 6.
Operacje możemy ze sobą łączyć, pamiętając o kolejności wykonywania działań.
Pamiętaj! Operacje mnożenia, dzielenia i modulo wykonywane są przed operacjami dodawania i odejmowania.
Jeśli chcemy wymusić inną kolejność wykonywania działań, możemy wykorzystać (nawet wielokrotnie w jednym wyrażeniu) nawiasy "(" i ")". Grupują one operacje ze sobą dokładnie na tej samej zasadzie, jak w matematyce. Przykładowe wykorzystanie nawiasów:
int x = 4;
x = (4 – 2) * x / (4 – (2 + 1));
Po wykonaniu powyższego fragmentu kodu pod zmienną x będziemy mieć wartość 8.
Możemy również skracać zapis, stosując skrócone instrukcje przypisania. Rozważymy ich działanie na przykładzie dodawania – reszta działa analogicznie.
Popatrzmy zatem na poniższy fragment kodu:
x = x + 5;
Powoduje on zwiększenie dotychczasowej wartości zmiennej x o 5. Po zastosowaniu skróconej instrukcji przypisania += powyższy kod wygląda tak:
x += 5;
Co należy odczytywać jako "zwiększ zmienną x o wartość podaną po znaku równości, czyli o 5". Po znaku równości może oczywiście znaleźć się bardziej skomplikowane wyrażenie, np.:
x += 5 * 2 – 1;
Ostatecznie wartość x w powyższym kodzie zostanie zwiększona o 9.
Analogicznie wygląda użycie operatorów: ­=, *=, /= i %=.
Istnieją również bardzo przydatne instrukcje zwiększania i zmniejszania (tzw. instrukcje postinkrementacji i postdekrementacji). Powodują one zwiększenie zmiennej o 1 lub jej zmniejszenie o 1. Symbolem instrukcji są: "++" i "­­". Poniżej przykład użycia instrukcji:
x--; //zmniejszenie x o 1
y++; //zwiększenie y o 1
Powyższe linie działają dokładnie tak samo, jak:
20
x = x – 1;
y = y + 1;
lub dokładnie tak samo, jak:
x -= 1;
y += 1;
ale za to zapis jest krótszy (w dodatku często stosowany w praktyce, więc warto go zapamiętać).
3.14. Wykorzystanie operatorów post­ i preinkrementacji*
Ten rozdział nie będzie bardzo pouczający, ale wskaże za to wykorzystanie operatorów postinkrementacji oraz wprowadzi operatory preinkrementacji.
Wiemy, jak działają operatory postinkrementacji. Możemy do tych zastosowań użyć też operatorów preinkrementacji, które w zastosowaniach takich, jak w poprzednim zadaniu, działają analogicznie do operatorów postinkrementacji. Dla przykładu:
--x;
--y;
[TODO]
3.15. Operatory logiczne
Wiemy, jak tworzyć instrukcje warunkowe. Jednakże proste warunki nie wyczerpują mocy możliwości, jakie w nich tkwią. Warunki możemy ze sobą łączyć, rozbudowywać, tworząc naprawdę rozbudowane wyrażenia. Możemy chcieć wykonać instrukcję pod warunkiem, że zajdzie (przykładowy) warunek A lub jednocześnie B i C (A, B, C to przykładowe wyrażenia logiczne, np. a == 3, b < 4, itd.). W tym celu przydatne będą operatory logiczne.
Pierwszym z nich jest operator koniunkcji && ­ operator, który łączy ze sobą dwa wyrażenia – połączone wyrażenie jest spełnione tylko wtedy, kiedy zajdzie warunek przed symbolem && i po symbolu &&. Np.:
int a = 9;
if (a > 5 && a / 3 == 3)
cout << "warunek spełniony";
Powyższy warunek zapisany w instrukcji if zostanie spełniony: liczba a jest większa od 5 i jej wynik dzielenia przez 3 daje w rezultacie 3. Gdyby za zmienną a podstawić np. 12, warunek nie będzie spełniony i w rezultacie nic na ekran nie zostanie wypisane (bo co prawda jest spełniony warunek a > 5, ale wynik dzielenia 12 przez 3 wynosi 4, więc a / 3 != 3).
Kolejny operator to operator alternatywy || ­ połączone wyrażenie jest spełnione wtedy, gdy spełnione jest przynajmniej jedno z połączonych podwyrażeń. Np.:
int a = 12;
if (a > 5 || a / 3 == 3)
cout << "warunek spełniony";
Powyższy warunek jest spełniony, bo spełnione jest pierwsze z podwyrażeń (a > 5), choć drugie jest fałszywe.
21
Przydatny bywa również operator negacji !, który odwraca wartość wyrażenia. Czyli, gdy dodamy przed wyrażeniem wykrzyknik !, symbol negacji, to wyrażenie zostanie spełnione wtedy, gdy wyrażenie, przed którym dodawaliśmy wykrzyknik... nie jest spełnione. I odwrotnie – jeśli wyrażenie nie jest spełnione, to z uwagi na dodany wykrzyknik staje się prawdą. Np.:
int a = 3;
if (!(a > 5 && a / 3 == 3))
cout << "warunek spełniony";
Gdyby nie wykrzyknik, wyrażenie z instrukcji if pozostałoby niespełnione. Jak pamiętamy, wykrzyknik odwraca nam wyliczoną wartość wyrażenia i fałsz staje się prawdą. Zwróć uwagę na to, że wyrażenie, przed którym został dodany wykrzyknik, zostało otoczone nawiasami – dzięki czemu dajemy kompilatorowi sygnał, co jest wyrażeniem, które negujemy (czyli do którego dokładamy wykrzyknik).
Należy pamiętać o tym, że operator && ma wyższy priorytet, niż operator || (czyli zachowuje się tak samo, jak w matematyce mnożenie w stosunku do dodawania – czyli wykonujemy je w pierwszej kolejności). Aby zapobiec niechcianej kolejności wykonywania, stosujemy nawiasy ( i ) ­ tak samo, jak w działaniach arytmetycznych, grupując wyrażenia w grupy, które wykonujemy przed innymi.
3.16. Pętla while
Przypomnij sobie, jak wygląda konstrukcja instrukcji warunkowej if. Instrukcja bądź blok instrukcji zapisany w niej był jednokrotnie wykonywany w momencie spełnienia warunku. A jest możliwe wykonanie takiej konstrukcji, by instrukcje wewnątrz były wykonywane tak długo, jak długo spełniony jest warunek? Tak! Do tego służą pętle.
Podstawową odmianą pętli jest pętla while. Konstrukcja jest bardzo prosta:
while (WARUNEK)
INSTRUKCJA_LUB_BLOK_INSTRUKCJI;
Zapisujemy w nawiasie po słowie kluczowym "while" warunek, po spełnieniu którego zostanie wykonana instrukcja (bądź blok instrukcji) i po wykonaniu zostanie ponownie spełniony warunek – jeśli warunek jest spełniony, następuje ponownie taka sama procedura (aż do momentu, gdy warunek nie będzie spełniony bądź do momentu, w którym pętla zostanie przerwana – ale o tym później), a jeśli nie jest, wykonywanie pętli zostaje zakończone. Np.:
int a = 1;
while (a <= 3)
{
cout << "Przebieg petli nr " << a << endl;
a++;
}
Przeanalizujmy powyższy przykład:
•
deklarujemy zmienną a i przypisujemy jej wartość 1
•
sprawdzamy, czy zachodzi warunek a <= 3 – zachodzi, bo 1 <= 3
22
•
wykonujemy więc blok instrukcji: wypisujemy informację o pierwszym przebiegu i zwiększamy wartość a o 1 – pod zmienną a znajduje się więc wartość 2
•
ponownie sprawdzamy warunek a <= 3 – warunek zachodzi, bo 2 <= 3
•
i znów wykonywany jest blok instrukcji, po którym a jest równe 3 (bo nastąpiło a++)
•
sprawdzamy warunek a <= 3 – zachodzi, bo 3 <= 3
•
ponowne wykonanie bloku instrukcji (po nim a wynosi 4)
•
sprawdzamy warunek a <= 3 – nie zachodzi, bo 4 jest większe od 3
•
kończymy więc wykonywanie pętli
Zapisując warunek możemy posiłkować się, tak samo, jak w instrukcji warunkowej if, operatorami logicznymi, nawiasowaniem, itp.
Pamiętaj! Często zdarza się sytuacja, że pętla nie zostanie w ogóle wykonana – dzieje się tak, gdy od razu warunek nie jest spełniony.
3.17. Pętla for
Istnieje jeszcze jeden rodzaj pętli – pętla for. Jest bardzo podobna do pętli while, posiada tylko nieco inną formę zapisu. Deklaracja pętli for składa się z trzech części wewnątrz nawiasu po słowie kluczowym for:
•
instrukcja początkowa
•
zapis warunku
•
instrukcja wykonywana PO każdym wykonaniu pętli
Zapis wygląda tak:
for (INSTRUKCJA_POCZĄTKOWA; WARUNEK; INSTRUKCJA_PO_WYKONANIU)
INSTRUKCJA_LUB_BLOK_INSTRUKCJI;
Czym jest instrukcja początkowa? Jest to instrukcja, która zostanie jednokrotnie wykonana przed rozpoczęciem wykonywania pętli. Czyli jest tym samym, co deklaracja zmiennej a w przykładzie z pętlą while.
A czym jest warunek? Jest dokładnie tym samym, co warunek przy pętli while. Czyli tak długo, jak warunek jest spełniony, tak długo pętla będzie się wykonywać.
Ostatni element to instrukcja, która jest wykonywana zawsze po skończeniu wykonywania każdego bloku instrukcji (bądź instrukcji, jeśli podana jest zamiast bloku). Wróćmy jeszcze raz do przykładu w pętli while – możemy np. wyciągnąć z bloku instrukcji a++ i wrzucić do ostatniego elementu zapisu w nawiasie pętli for – wówczas instrukcja ta zostanie wykonana po każdym kroku dokładnie tak samo, jakby była zapisana na końcu w bloku instrukcji.
Pamiętaj – element pierwszy i ostatni zapisu pętli for nie jest obowiązkowy – możesz go pominąć w zapisie (ale średnik rozdzielający elementy musi zostać!).
Zapiszmy teraz przykład z pętli while przy użyciu pętli for – efekt działania będzie 23
dokładnie taki sam, jak w przykładzie while:
for (int a = 1; a <= 3; a++)
{
cout << "Przebieg petli nr " << a << endl;
}
3.18. Zmienne tekstowe (string)
W C++ wprowadzona została tzw. klasa o nazwie string – jest to typ zmiennej, który służy do przechowywania tekstu. Tekstem może być dowolny ciąg znaków, łącznie ze spacjami czy znakami nowej linii.
Należy jednak pamiętać, że tak, jak w przypadku liczb, tekst pobierany z cin do zmiennych typu string jest dzielony w miejscach wystąpienia białych znaków (np. odstępów, czyli "spacji"). Więc gdy wpiszemy "jakis taki tekst", to słowo "jakis" trafi do pierwszej zmiennej, "taki" do drugiej, a "tekst" do trzeciej.
Aby skorzystać ze zmiennej typu string, należałoby na początku pliku dołączyć bibliotekę o tej samej nazwie (string). Jednakże jest ona już dołączona w iostream, więc gdy korzystamy z iostream, dołączanie biblioteki string nie jest konieczne.
Deklaracja zmiennej typu string odbywa się tak samo, jak każdej innej, np.:
string zmienna_tekstowa;
Aby przypisać zmiennej jakiś tekst, wystarczy skorzystać z operatora przypisania (=) podając po prawej stronie tekst w cudzysłowie, np.:
string zmienna_tekstowa;
zmienna_tekstowa = "przykladowy tekst";
Albo bezpośrednio przy deklaracji:
string zmienna_tekstowa = "przykladowy tekst";
Możemy też, mając zmienną tekstową z przypisaną wartością, dokleić na jej końcu kolejny tekst za pomocą operatora +=. Np.:
string tekst;
tekst = "Ile masz lat?";
tekst += " Mam 15 lat.";
cout << tekst;
Powyższy kod spowoduje wypisanie "Ile masz lat? Mam 15 lat.".
Aby sprawdzić długość tekstu w zmiennej typu string, należy odwołać się do funkcji size (lub length) na tej zmiennej – oznacza to, że podajemy ją po kropce po nazwie zmiennej (dzieje się tak dlatego, że zmienna jest obiektem klasy string – ale tego na razie nie musimy wiedzieć i rozumieć – wystarczy zapamiętać, że wywołujemy funkcje po kropce po nazwie zmiennej). Dla przykładu:
string tekst = "Donald Tusk";
cout << tekst.size(); //albo tekst.length()
Powyższy przykład wypisze 11 (odstępy też wliczają się do długości tekstu).
24
Możemy też chcieć porównać, która ze zmiennych zawiera tekst występujący wcześniej w alfabecie. Np. dla zmiennej zawierającej "zamek" i zmiennej zawierającej "zadatek", to druga występuje w alfabecie wcześniej. Aby sprawdzić, czy dany tekst lub zmienna jest wcześniej przed inną zmienną, należy na tej innej zmiennej (czyli po kropce) wywołać funkcję compare, podając jako argument zmienną porównywaną z nią. Funkcja ta zwróci nam 0, jeśli obie wartości są takie same, zwróci wartość ujemną (prawdopodobnie ­1, ale bezpieczniej jest sprawdzać, czy zwrócona wartość jest mniejsza od 0) jeśli tekst porównywany jest wcześniej w alfabecie niż tekst podany jako argument funkcji compare i wartość dodatnią, jeśli występuje po tekście podanym jako argument.
Przykład ilustrujący działanie tej funkcji:
string a = "zamek";
string b = "zadatek";
if (a.compare(b) > 0)
cout << "zamek wystepuje po zadatek" << endl;
if (b.compare("zadatek") == 0)
cout << "b jest rowne tekstowi zadatek" << endl;
W powyższym przykładzie spełnione będą oba warunki. W pierwszy "zamek" występuje w kolejności alfabetycznej (nazywanej inaczej leksykograficzną) po "zadatek", więc funkcja compare wywołana na zmiennej a zwróci wartość dodatnią. W drugim przypadku porównujemy zmienną b zawierającą "zadatek" z tekstem "zadatek" – oba teksty są takie same, więc funkcja compare zwróci nam wartość 0 i warunek zostanie spełniony.
Możemy też odwołać się do konkretnej litery w tekście. Mając daną zmienną, np. 5 literową, chcąc się dowiedzieć, jaka litera znajduje się na pozycji nr 3, korzystamy z operatora [], podając wewnątrz numer pozycji nas interesującej.
Ważna uwaga – w C++ numerujemy pozycje od 0! Tak więc chcąc się odwołać do litery trzeciej z kolei, odwołujemy się do pozycji nr 2, a chcąc odwołać się do pierwszej, odwołujemy się do pozycji nr 0, itd.
Dla przykładu:
string s;
cin >> s;
cout << "Trzecia litera wczytanego slowa to " << s[2];
Korzystając ze zmiennych tekstowych możemy mieć problemy z polskimi znakami (ą, ę, Ś, itp.), które mogą być porównywane nie tak, jak oczekujemy, mogą zachodzić też inne problemy. Dlatego w konkursach i wszelakich zadaniach polskie znaki praktycznie nie występują.
Pamiętaj również, że w C++ małe i wielkie litery są od siebie różne! Czyli litera "A" jest inna niż "a".
3.19. Zaawansowane operacje na zmiennych typu string*
Pamiętamy uwagę z poprzedniego rozdziału – wczytywanie tekstu do wskazanej zmiennej typu string przerywane jest po napotkaniu spacji. Ale co w sytuacji, w której chcemy wczytać całą linię do takiej zmiennej – łącznie ze spacjami i wszelkimi innymi znakami? Wówczas z pomocą 25
przychodzi nam funkcja getline. Jako parametry funkcja przyjmuje wejście (standardowe) – czyli po prostu cin oraz zmienną typu string. Po wykonaniu funkcji do podanej jako drugi parametr zmiennej przekierowana zostanie jedna linia ze standardowego wejścia (czyli najczęściej "z klawiatury"). Przykład użycia:
string zmienna_tekstowa;
getline(cin, zmienna_tekstowa);
Możemy też chcieć szybko sprawdzać, czy dana zmienna typu string jest pusta. Do tego celu służy funkcja empty() wykonywana na zmiennej typu string, czyli dla przykładu:
if (zmienna_tekstowa.empty())
cout << "zmienna pusta";
else
cout << "zmienna nie jest pusta";
Istnieje również prosta w użyciu funkcja umożliwiająca wyszukanie w zmiennej typu string określonego fragmentu tekstu (czy po prostu znaku). Aby tego dokonać, należy skorzystać z funkcji find na zmiennej typu string, jako parametr podając fragment tekstu, który chcemy znaleźć. Dla przykładu, gdy chcemy wyszukać w zmiennej wyrażenia "ma":
string tekst = "Ala ma psa. Jan ma dom.";
cout << "wynik: " << tekst.find("ma"); //zwróci: wynik: 4
Uwaga! Funkcja zwróci tylko pierwsze wystąpienie danego fragmentu. Jeśli dany fragment nie zostanie odnaleziony, funkcja zwróci wartość ­1. Pamiętaj też, że zwracana pozycja jest indeksem, pod którym znajduje się pierwsza litera szukanego fragmentu. I pamiętaj, że numerujemy od zera!
A co, gdy chcemy znaleźć kolejne wystąpienie? Albo po prostu wszystkie? Jeśli znaleźliśmy już jedno wystąpienie, to szukamy kolejnych od pozycji, na której znaleziono ostatnie wystąpienie. Jest to możliwe poprzez dodanie drugiego parametru do funkcji find – liczby, która jest przesunięciem, od którego mamy szukać. Czyli gdy znaleźliśmy w porpzednim przykładzie tekst na 4. pozycji, to szukamy dalej od pozycji numer 5. Wygląda to tak:
string tekst = "Ala ma psa. Jan ma dom.";
cout << "wynik: " << tekst.find("ma", 5); //zwróci: wynik: 16
Jest też funkcja działająca podobnie – z różnicą kierunku szukania. Funkcja rfind przeszukuje zmienną typu string w kolejności od końca do początku. Przy czym zwracany indeks jest standardowy – numer znaku (od zera) licząc od początku. Przykład:
string tekst = "Ala ma psa. Jan ma dom.";
cout << "wynik: " << tekst.rfind("ma"); //zwróci: wynik: 16
Powyższa funkcja, przeszukując od tyłu, natknęła się na "ma" na pozycji numer 16.
W łatwy sposób możemy też pobierać fragmenty zmiennej typu string (od podanej pozycji określoną ilość znaków). Trzymając się powyższej deklaracji zmiennej tekst, chcąc pobrać fragment tekstu od pozycji numer 12 liczący 3 znaki, otrzymamy "Jan". Używamy do tego funkcji substr, jako pierwszy parametr podając numer pozycji, a jako drugi długość pobieranego fragmentu:
string tekst = "Ala ma psa. Jan ma dom.";
cout << tekst.substr(12, 3); //zwróci: Jan
26
3.20. Zmienne znakowe (char)
Istnieje również zmienna do przechowywania pojedynczego znaku tekstu, jest to zmienna char. Może przechowywać takie wartości jak: 'a', 'b', 'X', '%', '2', itp.. Ale tekstu "ab" już nie przechowa – zmienna tego typu przechowuje tylko pojedynczy znak, nie więcej.
Ale w C++ możemy podstawiać pod zmienną typu char również małe liczby całkowite (od ­128 do 127) – zamiennie do znaków.
Poniżej kilka przykładów użycia zmiennej typu char:
char x = 'a';
x = x + 1;
//zwiekszamy zmienna o 1
cout << x;
char y = 65 – x;
string s;
cin >> s;
//wczytanie tekstu
char a = s[0]; //podstawienie pierwszego znaku tekstu do zm. a
Pamiętaj, że pojedyncze znaki, które nie są tekstem (czyli których nie mamy zamiaru przypisać do zmiennej typu string) otaczamy apostrofami, np. 'a'.
3.21. Kody ASCII
Po zapoznaniu się z definicją zmiennej char wiemy już, że istnieje zależność pomiędzy "małymi" liczbami a znakami (literami, znakami cyfr, znakami interpunkcyjnymi, itp.). Każdy z podstawowych znaków posiada unikalny kod ASCII. Dla przykładu mała litera "a" posiada kod 97. Każdy ze znaków ma przypisaną wartość od 0 do 127.
Aby poznać dokładne kody poszczególnych znaków, odsyłam do:
http://pl.wikipedia.org/wiki/ASCII
Wymienię tylko często używane zakresy znaków:
•
małe litery od 'a' do 'z' mają kod ASCII kolejno od 97 do 122
•
wielkie litery od 'A' do 'Z' mają kod ASCII kolejno od 65 do 90
•
znaki cyfr od '0' do '9' mają kod ASCII kolejno od 48 do 57
Możliwe, że niektórzy z Was zauważyli, że w tablicy kodów ASCII brakuje polskich znaków (nie mówiąc już o chińskich znakach). Nie występują one w tablicy kodów ASCII – zapisujemy je w różnoraki sposób, zależny od tzw. kodowania – ale na razie nie musimy tego wiedzieć (w zadaniach typowo olimpijskich nie jest wymagana umiejętność operowania na znakach spoza tablicy kodów ASCII).
3.22. Zmienne liczbowe zmiennoprzecinkowe (float i double)
Dotychczas (z liczb) poznaliśmy zmienną do przechowywania liczb całkowitych. W przypadku chęci przechowywania liczb zawierających ułamek dziesiętny należy skorzystać z typu float lub double. Dzięki temu będziemy mogli przechowywać liczby z częścią "po przecinku" 27
(który w C++ zapisujemy zawsze kropką). Zmienna double jest trochę bardziej precyzyjna niż float. Dokładne różnice omówiono w rozdziale z podsumowaniem typów zmiennych.
Przykłady użycia zmiennych to:
float x = 5;
x = x / 2;
cout << "Wynik działania 5 / 2 to: " << x << endl;
double a = 4.5673454;
Warto również zapoznać się z metodami wypisywania liczb zmiennoprzecinkowych z określoną precyzją (czyli np. do określonego miejsca po przecinku). Aby wypisać dokładnie N cyfr liczby, należy skorzystać z instrukcji setprecision(N) w połączeniu z cout (setprecision znajduje się w bibliotece iomanip, stąd należy ją dołączyć poleceniem #include <iomanip> na początku pliku z kodem źródłowym w przypadku chęci skorzystania z funkcji), w przykładzie dla N wynoszącego 4 – czyli wypiszemy dokładnie 4 cyfr:
float x = 3.14159;
cout << setprecision(4) << x;
skutkiem będzie wypisanie na ekran (zwóć uwagę na zaokrąglenie wartości):
3.142
jeśli natomiast chcemy wypisać określoną ilość znaków po przecinku, korzystamy z dodatkowego parametru fixed, np.:
float x = 3.14159;
cout << fixed << setprecision(4) << x;
co skutkuje wypisaniem:
3.1416
3.23. Podsumowanie typów zmiennych
Poniżej tabelka podsumowująca poznane typy zmiennych (oraz dodatkowe). Kilka podanych nazw typów w jednej komórce oznacza dokładnie to samo (mogą być używane zamiennie – choć warto dla przejrzystości trzymać się jednego wybranego sposobu nazewnictwa):
Typ
Ograniczenia
Uwagi
char
1 znak
lub liczba 0 – 127
znak podajemy w apostrofach (np. 'A')
signed char
1 znak
lub liczba ­127 – 127
jw.
unsigned char
1 znak
lub liczba 0 – 255
jw.
short
short int
signed short
signed short int
liczba ­32767 – 32767
używana, gdy deklarujemy sporo liczb (np. tablicę), a chcemy oszczędzić pamięć (należy uważać, by nie przekroczyć dopuszczalnych zakresów)
unsigned short
unsigned short int
liczba 0 – 65535
jw.
28
int
signed
signed int
liczba ­32767 – 32767
podane ograniczenie jest minimalnym ograniczeniem, jakie może wystąpić – w praktyce zdarza się często większe, więc i zajmowana pamięć jest większa (ale nie można zakładać większego ograniczenia – na niektórych systemach występuje dokładnie takie, jak podane)
unsigned int
unsigned
liczba 0 – 65535
jw.
long
long int
signed long
singed long int
liczba ­2147483647 – 2147483647
unsgined long
unsigned long int
liczba 0 – 4294967295
bool
wartość logiczna true / false
float
liczba zmiennoprzecinkowa
double
liczba zmiennoprzecinkowa
równa dokładności float lub większa (częściej jest większa dokładność)
long double
liczba zmiennoprzecinkowa
największa dokładność (i największa zajmowana pamięć!)
3.24. Podstawowe funkcje matematyczne
Zapoznaliśmy się już z operatorami arytmetycznymi. Jednakże matematyka to rozległa dziedzina z wieloma rodzajami funkcji. Twórcy języka C++ pomyśleli o tym i dodali obsługę wielu z takich funkcji.
sqrt to funkcja obliczająca pierwiastek (drugiego stopnia – czyli "standardowy"). Funkcja znajduje się w bibliotece cmath – stąd należy pamiętać o jej dołączeniu w przypadku chęci skorzystania z funkcji pierwiastka. Funkcja w nawiasie jako jedyny parametr przyjmuje liczbę, której pierwiastek chcemy obliczyć. Przykład użycia poniżej:
#include <cmath>
#include <iostream>
int main()
{
float x = 2;
cout << "Pierwiastek z liczby 2 to około " << sqrt(x);
return 0;
}
abs to funkcja zwracająca wartość bezwzględną z liczby. Jej użycie wygląda podobnie, jak 29
użycie pierwiastka – w nawiasie jako jedyny parametr podajemy liczbę, której wartość bezwzględną chcemy otrzymać. Funkcja znajduje się również w bibliotece cmath. Przykład użycia:
#include <cmath>
#include <iostream>
int main()
{
float x = -3.45;
cout << "Wartość bezwzględna liczby -3.45 to " << abs(x);
return 0;
}
Mamy do dyspozycji również funkcje zaokrąglające liczby w dół (funkcja floor) i w górę (funkcja ceil). Użycie wygląda analogicznie, jak funkcji pierwiastka i wartości bezwzględnej. Obie znajdują się w bibliotece cmath.
3.25. Zaawansowane funkcje matematyczne*
[TODO]
3.26. Tablice
Na tym etapie powinieneś umieć deklarować zmienne. Wyobraź sobie, że musisz wczytać do pamięci komputera 100 albo nawet 10 000 liczb. Możesz w tym celu zadeklarować 100 czy 10 000 zmiennych... Ale jest to pracochłonna operacja – do tego celu stworzono tablice. Tablica jest to zmienna, która zawiera kolejno numerowane komórki do przechowywania danych. Możemy więc utworzyć zmienną o nazwie tab, która będzie tablicą przechowującą 100 liczb. Taka deklaracja wygląda tak:
int tab[100];
Tablice w C++ tworzymy przez dopisanie w kwadratowym nawiasie do nazwy zmiennej ilości elementów, którą ma posiadać deklarowana zmienna. Na tej zasadzie możemy tworzyć tablice różnych typów zmiennych.
Do tak utworzonej tablicy możemy przypisywać wartości pod wskazanymi "komórkami". Do "komórek" odwołujemy się poprzez podanie numeru elementu w nawiasie kwadratowym po nazwie zmiennej. I tu ważna uwaga:
Pamiętaj, że w C++ numerujemy elementy od 0. W szczególności numerujemy od 0 elementy tablicy. Tak więc jeśli mamy tablicę 100 elementów, to jej elementy mają numery od 0 do 99 – elementu numer 100 nie ma wówczas!
W ten sam sposób odwołujemy się do elementów przy wypisywaniu. Numer podany w nawiasie nie musi być liczbą wpisaną "na sztywno" do programu – jako indeks (czyli numer elementu) może wystąpić również zmienna liczbowa. Przykłady użycia tablic poniżej:
int tab[100]; //deklaracja tablicy 100-elementowej
tab[4] = 55; //przypisanie liczby 55 do elementu nr 4
cout << tab[4] << endl; //wypisanie lementu nr 4
int x = 10;
30
tab[x] = 100; //przypisanie liczby 100 do elementu nr 10
for (int i = 0; i < 100; i++)
cin >> tab[i]; //kolejne wczytywanie wszystkich elementow
3.27. Konwersja między różnymi typami danych*
[TODO]
3.28. Funkcje
[TODO]
3.29. Rekurencja*
[TODO]
3.30. Formatowanie kodu źródłowego*
Pisząc kod źródłowy niejednokrotnie zdarza się, że rozrasta się on do sporych rozmiarów. Jeśli pisząc taki kod, będziemy czynić to chaotycznie, jego odczytywanie może stać się bardzo utrudnione. Dlatego należy stosować się do wybranych reguł formatowania (pisania) kodu.
Podstawową zasadą podczas pisania kodu jest stosowanie wcięć w każdym nowoutworzonym bloku instrukcji. Wcięcie stosujemy również przy pojedynczej instrukcji (nieotoczonej w nawiasy klamrowe) znajdującej się linię poniżej instrukcji warunkowej, pętli, itp. Każdy nowy blok to zwiększone wcięcie. Wcięciem najczęściej jest znak tabulacji (klawisz "Tab") bądź podwójny bądź poczwórny znak odstępu ("spacji").
Kolejną uwagą wartą zapamiętania jest pisanie każdej instrukcji w nowej linii – w szczególności, jeśli mamy instrukcję warunkową if, nawet, jeśli po niej wykonujemy tylko jedną instrukcję, piszemy ją w nowej linii (z wcięciem).
Dodatkowo przed i za każdym operatorem logicznym, arytmetycznym, porównania stosujemy pojedynczy znak odstępu (nie licząc operatorów jednoargumentowych – np. ++, ­­, !) – dzięki temu kod staje się bardziej przejrzysty i jesteśmy w stanie szybko wychwycić wartości czy zmienne z wyrażenia.
Pojedynczy odstęp należy stawiać również po przecinkach (np. podając argumenty w funkcji) lub po średnikach (np. kolejne sekcje w nawiasach pętli for).
Poniżej kilka przykładów, jak należy poprawnie formatować kod.
Nie rób tak:
if (x == 2) cout << "x jest rowne 2";
ale rób tak:
if (x == 2)
cout << "x jest rowne 2";
Nie rób tak:
31
if (x > 100)
cout << "x jest wieksze niz 100";
else
{
cout << "x nie jest wieksze od 100" << endl;
cout << "i jeszcze jakis tekst";
}
ale rób tak:
if (x > 100)
cout << "x jest wieksze niz 100";
else
{
cout << "x nie jest wieksze od 100" << endl;
cout << "i jeszcze jakis tekst";
}
Nie rób tak:
int x=7*3-1;
ale rób tak:
int x = 7 * 3 – 1;
Nie rób tak:
for (int i=4;i<10;i++)
ale rób tak:
for (int i = 4; i < 10; i++)
4. Zaawansowane programowanie w C++
4.1. Debugowanie, czyli odnajdywanie błędów
[TODO]
4.2. Wstęp do obiektów i zaawansowanych struktur danych
[TODO]
4.3. Vector – coś więcej niż tablica
Do tej pory gdy korzystaliśmy z tablic, z góry musieliśmy znać maksymalną dopuszczalną ilość elementów, które chcemy przechowywać w tablicy. W dodatku tablice poza przechowywaniem kolejnych wartości nic ponadto "nie potrafią". Ale w C++ możemy skorzystać z dobrodziejstw klasy Vector, która jest czymś na kształt tablicy, ale z możliwością powiększania i zmniejszania ilości elementów "w locie" (taka "tablica w wersji 2.0") oraz z dodatkowymi funkcjami, o których dalej.
Aby zadeklarować zmienną typu vector, należy napierw dołączyć do pliku bibliotekę vector (#include <vector>). Podczas deklaracji wektora musimy już wiedzieć, jakiego typu dane będziemy przechowywać (czy będzie to tekst albo małe liczy czy po prostu int). Typ ten podajemy w 32
nawiasach strzałkowych "<" i ">" po nazwie typu "vector". Podajemy też nazwę zmiennej i... gotowe. Początkowo wektora jest zerowej długości, nie posiada żadnego elementu. Przykład deklaracji wektora do przechowywnia liczb całkowitych typu int:
#include <vector>
using namespace std;
int main()
{
vector<int> zmienna;
// […]
}
Jedną z głównych operacji na strukturze wektor jest operacja dodawania elementu na koniec "tablicy". Do tego celu służy funkcja push_back(WSTAWIANY_ELEMENT) wykonywana na obiekcie, czyli funkcję wykonujemy po kropce po nazwie zmiennej typu vector. Po jednokrotnej takiej operacji mamy już wektor jednoelementowy, po 10 operacjach wektor 10­
elementowy. W nawiasie po nazwie funkcji podajemy wartość do wstawienia. Czyli, gdy deklarowaliśmy vector przechowujący dane typu int, to jako parametr podajemy liczbę, wyrażenie bądź zmienną typu int.
Do każdego z wstawionego i istniejącego w wektorze elementu możemy odwoływać się dokładnie w ten sam sposób, co do elementów tablicy. Pamiętaj, że element o tym indeksie musi istnieć, inaczej program zakończy się błędem. Czyli aby odczytać element siódmy z kolei, odwołujemy się nazwa_zmiennej[6] (bo numerujemy elementy od 0). W ten sposób możemy odczytywać wartość elementu, ale też i modyfikować. Ale wstawiać już w ten sposób nie możemy – wstawianie odbywa się zawsze poprzez push_back() ­ poza kilkoma wyjątkami, ale są one małoistotne w tym momencie.
Możemy też pobrać ilość elementów obecnych w zmiennej typu vector. Do tego celu używamy funkcji size() na obiekcie – czyli po kropce po nazwie zmiennej.
Podsumowując dotychczas poznane właściwości typu vector, posłużymy się poniższym przykładem:
vector<int> t;
//deklaracja wektora
for (int i = 0; i < 10; i++) //wczytanie 10 liczb
{
int x;
cin >> x;
//wczytanie liczby...
t.push_back(x); //...i dodanie jej do wektora
}
for (int i = t.size() - 1; i >= 0; i--) //wypisanie elementów t
{
cout << t[i] << endl;
}
4.4. Vector – dla chcących wiedzieć więcej*
[TODO]
33
4.5. Sortowanie danych
W momencie, gdy używamy zmiennej typu vector do przechowywania danych, sortowanie jest bajecznie proste. Wystarczy dołączyć bibliotekę o nazwie algorithm (porpzez dyrektywę #include <algorithm> na początku pliku) i skorzystać z funkcji sort(zmienna.begin(), zmienna.end()). Jako parametry podajemy tzw. iterator początkowy i końcowy vectora (czyli obrazowo: początek i koniec zmiennej typu vector – dokładnie tak, jak podałem).
Wyobraźmy sobie, że wczytujemy 10 liczb do vectora (por. rozdział 4.3): 5, 3, 3, 6, 2, 7, 8, 1, 4, 3. Wówczas wywołujemy:
sort(t.begin(), t.end());
I już! Po wypisaniu od elementu 0 do elementu 9 uzyskamy: 1, 2, 3, 3, 3, 4, 5, 6, 7, 8.
Ale co w sytuacji, gdy chcemy sortować w odwrotnej kolejności? Albo gdy chcemy najpierw mieć liczby parzyste od najmniejszej do największej, a potem nieparzyste – również od najmniejszej do największej? Rozważmy prostszy, pierwszy przykład.
Możemy zadeklarować własną funkcję porównującą. Tworzymy funkcję (gdzieś przed sekcją main), która przyjmuje 2 parametry – zmienne o typie takim, jaki mamy w vectorze – czyli w naszym przykładzie zmienne typu int. Funkcja zwraca wartość true, jeśli pierwszy argument ma się znaleźć przed drugim, a false w przeciwnym wypadku. Jeśli parametry mają być sobie równe, zwracamy albo true albo false – nie ma znaczenia (ale jeśli zwracamy true, to zwracajmy go zawsze w takiej sytuacji – i odwrotnie). Przykładowa funkcja porównująca do sortowania elementów w kolejności odwrotnej:
bool porownanie(int a, int b)
{
if (a > b)
//jeśli a jest większe od b...
return true; //... to a ma się znaleźć PRZED b
else
return false;
}
Tak utworzoną funkcję aplikujemy jako trzeci argument funkcji sort i... już! Funkcja sama posortuje elementy dokładnie tak, jak określimy. I zrobi to (względnie) szybko, co bardzo ważne:
sort(t.begin(), t.end(), porownanie);
Funkcję aplikujemy podając po prostu jej nazwę.
A jak by wyglądała funkcja porównująca dla drugiego przypadku (najpierw parzyszte, potem nieparzyste)? Tak:
bool porownanie(int a, int b)
{
if (a % 2 == 0)
//jeśli a jest parzyste...
{
if (b % 2 != 0)
//...i b nie jest parzyste...
return true; //...to a jest przed b
else if (a < b)
//a jeśli b jest parzyste i większe
return true; //...to też a jest przed b
else
return false;
34
}
else
{
//jeśli a jest nieparzyste...
if (b % 2 == 0)
return false;
else if (a < b)
return true;
else
return false;
//...oraz
//...to a
//a jeśli
//...to a
b jest parzyste...
NIE JEST przed b
b też nieparzyste i większe
jest przed b
}
}
5. Wstęp do algorytmiki
5.1. Obliczanie złożoności pamięciowej, czyli ile pamięci zajmuję?
[TODO]
5.2. Obliczanie złożoności obliczeniowej, czyli jak szybko działam?
[TODO]
5.3. Znajdywanie minimum i maksimum
[TODO]
6. Inne
6.1. Adresy serwisów z zadaniami algorytmicznymi
[TODO]
6.2. Informacje o OI i OIG
[TODO]
6.3. Obozy przygotowawcze do Olimpiady Informatycznej
[TODO]
6.4. Inne konkursy informatyczne
[TODO]
7. Przykładowe zadania wraz z rozwiązaniami
[TODO]
35
8. Dalszy rozwój
8.1. Co po podstawach C++ i algorytmiki?
[TODO]
8.2. Wybór uczelni wyższej
[TODO]
36