Podręcznik
Strona: | SEZAM - System Edukacyjnych Zasobów Akademickich i Multimedialnych |
Kurs: | 3. Wyrażenia i pętle |
Książka: | Podręcznik |
Wydrukowane przez użytkownika: | Gość |
Data: | poniedziałek, 7 lipca 2025, 08:14 |
1. Wyrażenia, operatory i funkcje matematyczne
Wyrażenia są formułami, które zawsze dają jakiś wynik, w zasadzie wszystko co staje się wartością, jest w C++ uważane za wyrażenie. Do budowy wyrażeń wykorzystuje się następujące elementy języka:
- stałe (np. liczby),
- zmienne,
- nawiasy okrągłe,
- operatory arytmetyczne,
- operatory relacji,
- operatory logiczne,
- funkcje, takie jak np. sin(x).
Tak więc stałe i zmienne są w C++ wyrażeniami i jeśli gdzieś dalej powiemy, że w danej konstrukcji występuje wyrażenie, to trzeba pamiętać, że może ono być na przykład liczbą, napisem czy nazwą zmiennej.
- Operatory arytmetyczne. Są to między innymi dobrze Wam znane operatory dodawania, odejmowania, itp. Dostępne w C++ operatory arytmetyczne opiszemy w następnym podrozdziale.
- Operatory logiczne, czyli operatory koniunkcji, alternatywy, negacji itp. Operatory logiczne zawsze zwracają wartość logiczną (prawdę lub fałsz).
- Operatory relacji, podobnie jak operatory logiczne, zwracają zawsze wartość logiczną.
- Operatory bitowe. Pozwalają one na wykonywanie operacji bezpośrednio na bitach, czyli najmniejszych niepodzielnych jednostkach pamięci. Każda liczba, tekst, obraz czy dźwięk są pamiętane jako zestawy bitów pogrupowane w bajty. Język C i wywodzące się z niego C++ są często wykorzystywane do bezpośredniego programowania sprzętu, mają więc dość silnie rozwinięte metody operacji na bitach i liczbach zapisanych dwójkowo.
- Operator przecinka. Tak - to nie pomyłka. W C++ przecinek jest także operatorem, a nie tylko służy do oddzielania od siebie zmiennych tego samego typu czy też oddzielania argumentów funkcji. Przecinek może być także wykorzystany do oddzielania od siebie wyrażeń, co powoduje, że wszystkie zostaną obliczone, natomiast tylko ostatnie z nich zostanie wykorzystane jako wartość. I znów stwierdzimy, że jest to skomplikowany i charakterystyczny dla C++ operator, więc nie będziemy się nim dalej zajmowali.
- Operatory rzutowania, które stosuje się, by dokonać jawnej konwersji wartości. Operatorów rzutowania w C++ jest kilka, większość z nich nie będzie wykorzystywana w trakcie tego kursu. Najczęściej będziemy stosować konwersję typu.
1.1. Operator podstawienia
Jedną z podstawowych operacji w każdym języku programowania jest instrukcja podstawienia, a więc przypisania wartości. Instrukcja podstawienia w języku C++ to operacja, która przypisuje wartość jednej zmiennej do innej zmiennej lub wyrażenia. W języku C++, instrukcja podstawienia wykorzystuje operator przypisania =
. Ogólna składnia instrukcji podstawienia jest następująca:
zmienna = wyrażenie;
Poniżej w kodzie znajdują się przykładowe operacje przypisania wartości dla różnego typu zmiennych:
zmienna_calkowita = 8;
zmienna_rzeczywista = 1.25;
druga_zmienna_rzeczywista = .25;
zmienna_znakowa = 'A';
napis = "Ala ma kota.";
prawda = true;
int zmienna;
int liczba;
liczba = 6;
int suma, licznik;
licznik = 4;
suma = 5;
int liczba = 6;
double suma = 0, iloczyn = 1, dane;
{}
. Inicjalizacja zmiennych przy pomocy klamer została wprowadzona do języka C++ wraz ze standardem C++11, który został formalnie zatwierdzony przez ISO (ang. International Organization for Standardization) w sierpniu 2011 roku. Standard C++11 przyniósł wiele nowych funkcji i udoskonaleń do języka, a jednym z nich była właśnie jednolita inicjalizacja (ang. uniform initialization) przy użyciu nawiasów klamrowych {}
.int licznik = {0}; // notacja ze znakiem przypisania =
double iloczyn {1}; // notacja bez znaku = (częściej stosowana)
double suma {}; // puste klamry oznaczają inicjalizację wartością zerową
- Zapobieganie niezamierzonym konwersjom: Inicjalizacja za pomocą klamer zapobiega niebezpiecznym konwersjom typów, które mogą prowadzić do utraty danych.
int x = 3.14; // Dozwolone, x staje się 3 int y {3.14}; // Błąd kompilacji: konwersja z double na int może powodować utratę danych
- Jednoznaczna inicjalizacja: Inicjalizacja za pomocą klamer jednoznacznie określa, że zmienna powinna być zainicjalizowana, a nie przypisana.
- Ujednolicona składnia: Ta forma inicjalizacji zapewnia spójny sposób inicjalizacji zarówno typów podstawowych, jak i złożonych.
1.2. Operatory arytmetyczne
- dodawanie (+),
- odejmowanie (-),
- mnożenie (*),
- dzielenie (/),
- reszta z dzielenia (%).
int x = 10;
int y = 3;
int sum = x + y; // Dodawanie: sum = 13
int difference = x - y; // Odejmowanie: difference = 7
int product = x * y; // Mnożenie: product = 30
int quotient = x / y; // Dzielenie: quotient = 3 (dzielenie całkowite)
int remainder = x % y; // Reszta z dzielenia: remainder = 1Przy omawianiu operacji podstawiania warto podać zasadę na jakiej wyznaczona jest wartość wyrażenia w podanych operacjach.
int a = 1;
int b = 3;
double wynik = a/b;
int
) jest również liczbą całkowitą, nawet jeśli wynik matematyczny jest liczbą zmiennoprzecinkową. To zachowanie jest związane z regułami typu danych używanych w operacji. Kiedy obie operandy dzielenia są typu całkowitego, wynik również będzie typu całkowitego, a ewentualna część ułamkowa zostanie odrzucona (obcięta). W tym przypadku 1 / 3
daje 0
(część ułamkowa jest odrzucona). Aby uzyskać wynik dzielenia jako liczbę zmiennoprzecinkową, co spowoduje poprawne obliczenie 1 / 3
jako 0.33333
, co najmniej jeden z operandów musi być typu zmiennoprzecinkowego (float
, double
, long double
). - Rzutowanie operandów za pomocą nawiasów (ang.C-style cast)
double x = (double) a / b;
- Rzutowanie za pomocą static_cast
double x = static_cast<double>(a) / b; // lub double x = a / static_cast<double>(b);
Rzutowanie przy pomocy
static_cast
jest preferowaną metodą rzutowania w C++, ponieważ jest bardziej bezpieczne i czytelne w porównaniu do rzutowania w stylu C. C-style cast jest bardziej ryzykowne, ponieważ pozwala na konwersję między praktycznie dowolnymi typami, co może prowadzić do błędów, które są trudne do wykrycia.W trakcie implementacji aplikacji często istnieje konieczność zamiany miejscami dwóch zmiennych. Zamianę wartości pomiędzy dwiema zmiennymi można wykonać przy pomocy zmiennej pomocniczej.
W celu zamiany wartości pomiędzy zmiennymi nalezy wykonać następujące operacje:
int a = 5;
int b = 3;
int schowek = b; // zapamiętanie wartości b w zmiennej schowek (1)
b = a; // przypisanie wartości zmiennej a do zmiennej b (2)
a = schowek; // przypisanie wartości zmiennej schowek do zmiennej a (3)
Po wykonaniu operacji wartości w zmiennych zostaną zamienione wzajemnie.
Na liście operatorów istnieje jeden operator który może być mniej znany. Jest to operacja zwracająca resztę z dzielenia tzw. dzielenie modulo (%). Przykładowo 5 % 2 jest równe 1, bo reszta z dzielenia 5 przez 2 jest równa 1, zaś -11 % 3 jest równe -2. Operator na zajęciach będzie głownie wykorzystywany w celu sprawdzenia do sprawdzania podzielności argumentów całkowitych.
Operator | Przykład zastosowania | Klasyczny zapis operacji |
---|---|---|
+= |
a += b |
a = a + b |
-= |
a -= b |
a = a - b |
*= |
a *= b |
a = a * b |
/= |
a /= b |
a = a / b |
1.3. Operatory inkrementacji i dekrementacji
x = x + 1 ;
możemy napisać krócej
x++ ; (wersja przyrostkowa)
lub:
++x ; (wersja przedrostkowa)
i analogicznie zamiast:
x = x - 1 ;
możemy użyć operatora dekrementacji
x-- ; (wersja przyrostkowa)
lub:
--x ; (wersja przedrostkowa)
int a = 5;
int b = ++a; // a zostaje zwiększone do 6, następnie b przyjmuje wartość 6
int c = --a; // a zostaje zmniejszone do 5, następnie c przyjmuje wartość 5
int a = 5;
int b = a++; // b przyjmuje wartość 5, następnie a zostaje zwiększone do 6
int c = a--; // c przyjmuje wartość 6, następnie a zostaje zmniejszone do 5<br>
1.4. Operatory logiczne oraz relacji
W języku C++ możemy także na zmiennych wykonywać operacje logiczne.bool a = true;
bool b = false;
bool resultAnd = a && b; // AND logiczny: resultAnd = false
bool resultOr = a || b; // OR logiczny: resultOr = true
bool resultNot = !a; // NOT logiczny: resultNot = false
int x = 10;
int y = 5;
bool isEqual = (x == y); // Równość: isEqual = false
bool isNotEqual = (x != y); // Nierówność: isNotEqual = true
bool isGreater = (x > y); // Większe: isGreater = true
bool isLess = (x < y); // Mniejsze: isLess = false
bool isGreaterOrEqual = (x >= y); // Większe lub równe: isGreaterOrEqual = true
bool isLessOrEqual = (x <= y); // Mniejsze lub równe: isLessOrEqual = falseCzasmi istnieje także konieczność wykonania operacji bitowych na poszczególnych zmiennych.
unsigned int a = 5; // binarnie: 0101
unsigned int b = 3; // binarnie: 0011
unsigned int resultAnd = a & b; // AND bitowy: resultAnd = 1 (binarnie: 0001)
unsigned int resultOr = a | b; // OR bitowy: resultOr = 7 (binarnie: 0111)
unsigned int resultXor = a ^ b; // XOR bitowy: resultXor = 6 (binarnie: 0110)
unsigned int resultNot = ~a; // NOT bitowy: resultNot = 4294967290 (binarnie: 11111111111111111111111111111010)
unsigned int resultShiftLeft = a << 1; // Przesunięcie w lewo: resultShiftLeft = 10 (binarnie: 1010)
unsigned int resultShiftRight = a >> 1; // Przesunięcie w prawo: resultShiftRight = 2 (binarnie: 0010)
1.5. Priorytety operatorów
W C++ priorytety operatorów określają kolejność, w jakiej operacje są wykonywane w wyrażeniu. Operatorzy o wyższym priorytecie są oceniani przed operatorami o niższym priorytecie. Jeśli operatorzy mają ten sam priorytet, ich kolejność jest określana przez ich łączność (lewo- lub prawostronną).
Priorytet | Operator | Opis | Łączność |
---|---|---|---|
1 | :: | operator zakresu, przestrzeń nazw | Lewostronna |
2 | a++ a-- () [] . -> |
postinkrementacja postdekrementacja wywołanie funkcji indeksowanie dostęp do członka klasy |
Lewostronna |
3 | ++a --a ~ ! +a -a & * new delete sizeof (typ) |
preinkrementacja predekrementacja negacja bitowa negacja logiczna tożsamość wartość przeciwna operator adresu operator wartości wskazywanej przez wskaźnik operator tworzenia obiektu w pamięci operator usuwania obiektu z pamięci operator rozmiaru zmiennej, stałej, wyrażenia lub typu operator rzutowania typów w stylu języka C |
Prawostronna |
4 | .* ->* |
operator dostępu do składowej operator dostęp do składowej |
Lewostronna |
5 | * / % |
operator mnożenia operator dzielenia operator dzielenia modulo (reszta z dzielenia) |
Lewostronna |
6 | + - |
operator dodawania operator odejmowania |
Lewostronna |
7 | << >> |
przesunięcie bitowe w lewo przesunięcie bitowe w prawo |
Lewostronna |
8 | <=> | operator porównania trójwartościowego | Lewostronna |
9 | < > <= >= |
mniejsze większe mniejsze równe większe równe |
Lewostronna |
10 | == != |
równe różne |
Lewostronna |
11 | & | operator bitowy AND | Lewostronna |
12 | ^ | operator bitowy XOR | Lewostronna |
13 | | | operator bitowy OR | Lewostronna |
14 | && | operator logiczny AND | Lewostronna |
15 | || | operator logiczny OR | Lewostronna |
16 | a?b:c = += -= *= /= %= <<= >>= &= ^= |= |
operator przetwarzania warunkowego operator przypisania operator skróconego przypisania przez dodawanie operator skróconego przypisania przez odejmowanie operator skróconego przypisania przez mnożenie operator skróconego przypisania przez dzielenie operator skróconego przypisania przez dzielenie modulo operator skróconego przypisania przez przesunięcie bitowe w lewo operator skróconego przypisania przez przesunięcie bitowe w prawo operator skróconego przypisania przez AND operator skróconego przypisania przez XOR operator skróconego przypisania przez OR |
Prawostronna |
17 | , | operator przecinek | Lewostronna |
W pierwszej kolejności wykonywane są operacje w nawiasach:
Następnie wykonywane jest mnożenie i dzielenie w głównym wyrażeniu:
Ostatnie jest sprawdzenie OR:
true.
1.6. Funkcje matematyczne
W przypadku gdy nie wystarczają nam podstawowe operatory możemy skorzystać z dodatkowej biblioteki do obliczeń matematycznych. Biblioteka<cmath>
w C++ jest standardową biblioteką. Zawiera szeroki zakres funkcji do wykonywania operacji matematycznych, podobnych do tych dostępnych w standardowej bibliotece C <math.h>
. Poniżej znajdują się kluczowe funkcje i ich opis:- Funkcje trygonometryczne:
sin(double x)
- sinus z wartości x,cos(double x)
- cosinus z wartości x,tan(double x)
- tangens z wartości x,asin(double x)
- arcus sinus z wartości x,acos(double x)
- arcus cosinus z wartości x,atan(double x)
- arcus tangens z wartości x.
- Funkcje hiperboliczne:
sinh(double x)
- sinus hiperboliczny z wartości x,cosh(double x)
- cosinus hiperboliczny z wartości x,tanh(double x)
- tangens hiperboliczny z wartości x.
- Funkcje wykładnicze i logarytmiczne:
exp(double x)
- wartość e podniesiona do potęgi x,log(double x)
- logarytm naturalny z wartości x,log10(double x)
- logarytm o podstawie 10 z wartości x,sqrt(double x)
- pierwiastek kwadratowy z wartości x.
- Funkcje potęgowe:
pow(double base, double exp)
- podniesienie wartości base do potęgi exp,
- Funkcje zaokrąglające:
ceil(double x)
- wartość x jest zaokrąglana w górę do najbliższej liczby całkowitej nie mniejszej niż x,floor(double x)
- wartość x jest zaokrąglana w dół do najbliższej liczby całkowitej nie większej niż x,round(double x)
- wartość x jest zaokrąglana do najbliższej liczby całkowitej.
- Wartość bezwzględna:
fabs(double x)
- wartość bezwzględna z liczby x.
- Funkcje minimum, maksimum i różnicy dodatniej:
fmin(double x, double y)
- zwracana jest mniejsza z wartości x oraz y,fmax(double x, double y)
- zwracana jest większa z wartości x oraz y,fdim(double x, double y)
- zwracana jest dodania różnica pomiędzy x i y.
2. Instrukcja wielokrotnego wyboru
W przypadku gdy istnieje konieczność wyboru z kilku możliwości możemy skorzystać z instrukcji warunkowej if. W przypadku kilku możliwych przypadków do wyboru jest to wystarczające. Jeżeli jednak chcemy dokonać wyboru z kilkudziesięciu przypadków wielokrotne wykorzystanie instrukcji warunkowej if może nie być zbyt wygodnym rozwiązaniem. Aby ułatwić zapis takiego i podobnych przypadków, kiedy wybieramy jedną opcję z wielu, powstała instrukcja wielokrotnego wyboru, tzw. instrukcja switch. Instrukcja switch jest kolejną, po if-ach, instrukcją strukturalną, jaką poznajecie. Ale jest to pojedyncza instrukcja i wszędzie tam, gdzie składnia języka dopuszcza jedną instrukcję, możemy ją wstawić.switch (selektor)
{
case wart_11 : case wart_12 : ... : case wart_1i : Instrukcja_1; break;
case wart_21 : ... : case wart_2j : Instrukcja_2; break;
...
case wart_n1 : ... : case wart_nm : Instrukcja_n; break;
default : Instrukcja_inna;
}
Poniżej przedstawiono prostą symulację, która demonstruje działanie instrukcji wielokrotnego wyboru switch.
Wybierz wartość zmiennej n
i zobacz, jak działa instrukcja switch
w C++ z animacją:
switch (n) { case 1: cout << "Case 1"; break; case 2: cout << "Case 2"; break; case 3: cout << "Case 3"; break; case 4: cout << "Case 4"; break; default: cout << "Default case"; }
Na podsatwie symulacji możemy omówić działanie instrukcji switch. W zależności od wartości selektora, który jest umieszczony po instrukcji case zostaje wywołana dana grupa instrukcji występująca po dwukropku. Jeżeli wartość nie występuje po żadnym case to zostanie wykonany ciąg instrukcji, oddzielonych średnikami, znajdujący się po słowie default. Należy zaznaczyć, że w symulacji w każdym bloku instrukcji przypisanych do danej wartości selektora występuje instrukcja break. Instrukcja break powoduje przerwanie operacji switch oraz przejście do nawiasu zamykającego instrukcję wielokrotnego wyboru switch. Instrukcja break nie jest obligatoryjna. W przypadku gdy słowo break występuje, wykonana zostanie jedynie instrukcja przypisana do danej listy wartości. W przypadku gdy break nie będzie, wykonana zostanie instrukcja przypisana do danej wartości i wszystkie następne instrukcje, włącznie z tą przypisaną do default - aż do napotkania break lub końca instrukcji.
3. Pętle while
W trakcie projektowania oraz implementacji aplikacji często pojawia się potrzeba powtarzania grupy tych samych instrukcji. W tym przypadku konieczne jest zastosowanie pętli. W języku C++ istnieje do wyboru kilka rodzajów pętli, które są bardziej odpowiednie w różnych zadaniach. Pętle są więc instrukcjami iteracyjnymi, pozwalającymi na cykliczne wykonywanie kolejnych iteracji (cykli).Każda pętla składa się z dwóch części:
- treści, czyli jednej bądź więcej instrukcji, które są wykonywane podczas każdego przejścia pętli,
- warunku, czyli wyrażenia logicznego, które steruje pracą pętli.
Wybierz wartość zmiennej n
i zobacz, jak działa pętla while
w C++ z animacją:
while (warunek)
instrukcja;
Analogicznie jak w przypadku instrukcji warunkowej if w przypadku pętli while wykonuje się jedna instrukcja. Jeżeli chcemy, aby w pętli wykonywało się kilka instrukcji musimy zastosować nawiasy klamrowe { }
, aby uczynić z nich jedną instrukcję złożoną:
while (warunek)
{
instrukcja_1;
instrukcja_2;
...
instrukcja_n;
} ;
Poniżej przedstawiono prostą symulację pokazującą w jaki sposób działa pętla while.
int n = ...; // Aktualna wartość: ... while (n > 0) { cout << "n = " << n << endl; n--; }
int n = 1;
while (n!=0){
cin >> n;
cout << n;
}
n
w taki sposób, aby przynajmniej raz warunek był spełniony. Później już użytkownik bedzie podawał kolejne wartości n
i od niego zależy czy warunek będzie spełniony, czy nie.
4. Pętla do while
W celu powtórzenia bloku instrukcji możemy wykorzystać także pętlę do-while. Instrukcja do-while składa się z dwu części: znacznika początku pętli, którym jest właśnie słowo do oraz znacznika jej końca, którym jest słowo while. Dodatkowo, za słowem while występuje warunek wykonywania pętli.do
instrukcja;
while (warunek);
do
{
instrukcja_1;
instrukcja_2;
...
instrukcja_n;
} while (warunek);
Główna różnica pomiędzy pętlą while, a do-while jest moment w którym sprawdzany jest warunek. W pętli while warunek jest sprawdzany na początku, natomiast w przypadku pętli do-while warunek sprawdzany jest na końcu. Oznacza to, że pętla do-while wykona się przynajmniej raz.
Wybierz wartość zmiennej n
i zobacz, jak działa pętla do-while
w C++ z animacją:
int n = ...; // Aktualna wartość: ... do { cout << "n = " << n << endl; n--; } while (n > 0); // Warunek pętli: n > 0
5. Pętla for
Kolejnym rodzajem pętli w języku C++ jest pętla for. Omówione w poprzednich rozdziałach pętle while oraz do-while są wykorzystywane w sytuacjach gdy nie jest znana (z góry) liczba cykli. Pętla for jest wykorzystywana głównie w sytuacjach, gdy wiemy ile chcemy wykonać iteracji pętli. Instrukcja for należy do grupy instrukcji strukturalnych. Aby dokładnie zilustrować w jakim celu możemy wykorzystać pętlę for przedstawimy prosty przykład.int n = 5;
do{
cout << n << endl;
n--;
}while(n=>0);
for (inicjalizacja; warunek końca; krok)
instrukcja;
- Inicjalizacja to instrukcja, która jest wykonywana w momencie rozpoczynania pętli.
- Warunek to wyrażenie, które jest sprawdzane przed wykonaniem każdego przebiegu pętli.
- Instrukcja to właściwa treść pętli. Jest ona wykonywana w każdym przebiegu pętli.
- Krok jest instrukcją wykonywaną po wykonaniu treści pętli.
Wszystkie części pętli for mogą, ale nie muszą wystąpić. Konieczne jest tylko postawienie średników, które oddzielają od siebie poszczególne części. Jeśli nie występuje warunek, to traktowane jest to jak wyrażenie zawsze prawdziwe . Tak więc zupełnie prawidłowy jest zapis:
for (;;)
{
...
}
{}
. Poniżej przedstawiono symulację prostej pętli for.
Wybierz wartość zmiennej n
i zobacz, jak działa pętla for
w C++ z animacją:
for (int i = 0; i < ...; i++) { cout << "i = " << i << endl; }