Podsumowanie
Gratulacje, ukończyłeś pierwszy etap nauki 🎉. Tematy podstawowe wprowadziły Cię w świat programowania oraz zaznajomiły Cię z podstawowymi zagadnieniami i algorytmami. Jeżeli programowanie Cię zainteresowało, zachęcamy zagłębić się w tajniki C++, czytając tematy zaawansowane. Zanim to, uporządkujmy sobie jednak świeżo poznane pojęcia.
Słowniczek
Kompilator - program, który tłumaczy język C++ (zrozumiały dla ludzi), na język zrozumiały dla maszyn (np. komputera);
Biblioteka - plik / program, który tłumaczy kompilatorowi co ma zrobić, gdy natrafi na pewne obiekty, takie jak
std::cout
,std::string
albostd::vector
. Jej nazwę podajemy między<...>
, czyli zarównoiostream
, jak i<iostream>
, odnoszą się do tej samej biblioteki<iostream>
;Przestrzeń nazw - “folder”, dzięki któremu C++ unika ryzyka konfliktu nazw (czyli dwóch różnych obiektów o tej samej nazwie). Możemy “wyrzucić na zewnątrz” rzeczy z “folderu” poprzez
using namespace nazwa_przestrzeni
;Wypisywanie - umieszczanie tekstu na ekranie;
Wczytywanie - ustawianie wartości zmiennych (w czasie wykonania), na podstawie tego, co użytkownik wpisze po uruchomieniu programu;
Zmienna - komórka pamięci, która przechowuje wartość o pewnym typie. Jej działanie można porównać do pudełka;
Typ danych - określaja typ wartości oraz sposób jej przechowania w pamięci komputera;
Operator - znak specjalny, który wykonuje operacje (np.: dodaje, odejmuje, łączy warunki), lub pełni funkcje pomocniczą (np. oddziela wyrażenia);
Konstrukcja warunkowa - wykonuje określony kod, jeżeli warunek jest prawdziwy. Może również wykonać inny kod, w przeciwnym wypadku;
Pętla - wykonuje wielokrotnie ten sam kod, dopóki warunek jest spełniony;
Iteracja - (również obrót pętli) powtórzenie kodu pętli - np. jeśli pętla wykonała kod pięć razy, powiemy, że wykonała pięć iteracji lub pięć obrotów;
Iterowanie się po obiekcie - (rówież przechodzenie po obiekcie) uzyskiwanie kolejnych elementów obiektu, przechowywującego wiele wartości, np. tablica;
Iterator - zmienna wskazująca na jakiś element np. wektora. Iteratory są dostępne tylko na wybranych typach danych, nie ma ich na tablicach. Na razie, aby uzyskać element tablicy/wektora wystarczy odwołać się do indeksu, lecz niedługo poznasz struktury, w których nie będzie się dało tak zrobić - wtedy iteratory się nam przydadzą;
Tablica - struktura, który przechowywuje wiele wartości;
Funkcja - kod, który może zostać wygodnie wykonany z kilku różnych miejsc w kodzie, poprzez
nazwa()
, oraz przyjmować różne parametry;Metoda - funkcja, która uruchamiana jest na obiekcie, za pomocą
obiekt.metoda()
. Często wiąże się to z modyfikacją obiektu;Zwracanie wartości - (dotyczy funkcji i metody) obliczanie wartości przez funkcje i oddawanie/podstawianie jej w miejsce, gdzie funkcja została uruchomiona;
Wywoływanie funkcji/metody - inne określenie na uruchamianie funkcji/metody. Można używać zamiennie;
Wektor - usprawniona tablica. Zawiera szereg przydatnych metod. Nie ma związku z wektorami z lekcji fizyki;
Sortowanie - porządkowanie elementów, według pewnej reguły - najczęściej rosnąco lub malejąco;
Zaawansowane zagadnienia C++
Zanim rozpoczniesz czytanie zaawansowanych artykułów, poznajmy kilka nowych zagadnień i nową składnię języka - sposób, w jaki można pewne rzeczy zapisać.
Usprawnionione przechodzenie po pętli
Jeżeli mamy wektor, o rozmiarze $n$, możemy się po nim iterować za pomocą następującej składni.
for (typ zmienna : wektor) {
}
Wypiszmy w ten sposób elementy wektora.
vector<int> liczby = {5, 4, 3, 2, 1};
for (int liczba : liczby) {
cout << liczba << ' ';
}
W ten sposób zamiast indeksów, jak przy tradycyjnym przechodzeniu, będziemy uzyskiwać wartości.
Wskaźniki
Załóżmy, że mam zmienną a
o typie int
. Ma ona swoje położenie w pamięci komputera, które określa jej adres. Adres uzyskujemy za pomocą operatora &
. C++ chciałby jednak wiedzieć, jaki typ ma wartość znajdująca się pod tym adresem. Do tego celu używamy gwiazdki *
, np. adres liczby całkowitej to int*
.
int liczba = 5;
int* adres_liczby = &liczba;
Jeżeli chcemy uzyskać wartość spod adresu, ponownie używamy gwiazdki. Można powiedzieć, że ładujemy adres, czyli uzyskujemy to pole w pamięci, które opisuje.
int liczba = 5;
int* adres_liczby = &liczba;
*adres_liczby += 5;
cout << liczba << endl;
Co tu się zadziało?
Wartość zmiennej
liczba
to5
(przyjmijmy, że jej adres to0x15
);Wartość zmiennej
adres_liczby
to0x15
;Ładujemy wartość zmiennej
adres_liczby
i zwiększamy ją o5
;- W ten sposób zwiększyliśmy wartość, która znajdowała się pod adresem
0x15
, czyli wartość zmiennejliczba
;
- W ten sposób zwiększyliśmy wartość, która znajdowała się pod adresem
Wypisujemy wartość zmiennej
liczba
, która wynosi10
;
Zastosowanie wskaźników
Po co nam wskaźniki? Wyobraźmy sobie, że chcemy zrobić funkcje, która zwiększa wartość danej zmiennej o ile
. Na pierwszy rzut oka wydawałoby się, że poniższa funkcja wykona to, co oczekiwaliśmy. Niestety, czeka nas rozczarowanie.
void zwieksz(int liczba, int ile)
{
liczba += o_ile;
}
Jeżeli wywołamy tę funkcję zwieksz(jakas_zmienna, 5)
, to wartość jakas_zmienna
nie zostanie zwiększona o 5
- funkcja zwieksz
skopiuje jej wartość do parametru i utworzy swoją własną, nową zmienną, o takiej samej wartości, jak jakas_zmienna
. Następnie zwiększy ją o 5
, jednak nie zmieni to wartości jakas_zmienna
, a jedynie jej kopie, utworzoną na potrzeby funkcji zwieksz
.
Rozwiązanie? Wskaźniki! Utwórzmy sobie funkcję, która przyjmuje adres zmiennej typu int
. Dzięki temu zwiększy się wartość spod adresu parametru, czyli wartość zmiennej, którą podaliśmy (a nie jej kopii).
#include <iostream>
using namespace std;
void zwieksz(int* liczba, int ile)
{
*liczba += ile;
}
// Oto sposób, w jaki należy wywołać tę funkcję
int main()
{
int liczba = 5;
zwieksz(&liczba, 5);
cout << liczba << endl; // wpisze "10"
// zwieksz(liczba, 5);
// ^^ BŁĄD ^^
// Program oczekuje adresu do liczby całkowitej, a nie liczby całkowitej
}
Trzeba tylko uważać, aby nie napisać
liczba += ile
. Wtedy bowiem zmieni się wartość parametru, czyli adresu, a nie tego, co się pod nim znajduje.
Wygodne wskaźniki - referencja
Jeżeli nie chcemy pisać gwiazdeczek, możemy utworzyć referencję. Referencja to nic innego jak zmienna, która odnosi się do dokładnie tej samej wartości, o tym samym położeniu w pamięci. Innymi słowy, dzięki referencji możemy mieć dwie zmienne, które są dokładnie tym samym, a zmiana jednej z nich modyfikuje drugą.
#include <iostream>
using namespace std;
int main()
{
int liczba = 5;
int& tez_liczba = liczba;
liczba++;
cout << tez_liczba << endl; // wypisze "6"
tez_liczba++;
cout << liczba << endl; // wypisze "7"
}
Zmienne liczba
i tez_liczba
są powiązane - odnoszą się do tego samego adresu w pamięci, a co za tym idzie, możemy zmieniać jedną wartość, a druga też się zmieni.
Referencja jest szczególnie przydatna w przypadku parametrów funkcji, kiedy przekazujemy funkcji np. vector
, bez jego kopiowania, tak, aby funkcja mogła edytować jego wartości, a po wyjściu z niej, zmienna oryginalna też była zmieniona.
#include <iostream>
#include <vector>
using namespace std;
void zwieksz_kazdy_element_wektora_o_1(vector<int>& wektor)
{
for (int i = 0; i < wektor.size(); i++) {
wektor[i]++;
}
}
int main()
{
vector<int> fajny_wektor = {2, 6, 1, 3, 2, 7};
zwieksz_kazdy_element_wektora_o_1(fajny_wektor);
for (int element : fajny_wektor) {
cout << element << ' ';
}
cout << endl;
// wypisze "3 7 2 4 3 8"
}