W części pierwszej „podstawy języka c++ – narzędzia i typy danych” poznaliśmy jakich narzędzi używać do kompilacji, jak wygląda struktura programu oraz jakie mamy typy danych. W drugiej części dowiemy się, jakie operacje można wykonać na zmiennych w programie.
Operacje matematyczne w C++
Operacje matematyczne na zmiennych w C++ to podstawowe działania, które pozwalają na manipulowanie danymi liczbowymi. Do najczęściej używanych operatorów należą: dodawanie (+
), odejmowanie (-
), mnożenie (*
), dzielenie (/
) oraz reszta z dzielenia (%
). Dzięki tym operatorom można wykonywać obliczenia na zmiennych typu int
, float
i double
. Zapis oraz reguły są identyczne do tych, które znamy z lekcji matematyki.
int a = 5;
int b = 3;
int roznica = a - b;
float srednia = (a + b) / 2.0;
int a_przez_b = a / b;
int reszta_z_dzielenia = a % b;
int licznik = 0;
licznik++; // post-inkrementacja
++licznik; // pre-inkrementacja
Wspomnę tu jeszcze o pre i post inkrementacji, bo może sie wydawać nietypowe, a w C++ jest bardzo powszechne. Zapis ++i
i i++
oznacza po prostu zwiekszenie wartości o 1. Z punktu widzenia efektu końcowego są równoważne. Różnica natomiast pojawia się, gdy na przykład przekazemy tę wartość do funkcji.
- post-inkrementacja (
i++
) najpierw zwraca aktualną wartość a następnie zwiększa ją o 1 - pre-inkrementacje (
++i
) najpierw zwiększa o 1 i dopiero wtedy zwraca nową wartość
Najlepiej widać to na przykładzie:
#include <iostream>
void funkcja(int v)
{
std::cout << v << std::endl;
}
int main()
{
int i = 0;
funkcja(++i); // wyswietli 1
// teraz i jest rowne 1
funkcja(i++); // wyswietli 1
// choć teraz i jest rowne 2
}
Operacje logiczne
Logika to serce podejmowania decyzji w programach. Operacje logiczne w C++ pozwalają na tworzenie warunków, które kontrolują przepływ programu. Główne operatory logiczne to AND (&&
), OR (||
) oraz NOT (!
).
- Operator && zwraca wartość prawda (true), jeśli oba warunki są spełnione,
- Operator || zwraca prawdę, gdy przynajmniej jeden z warunków jest prawdziwy,
- Operator ! neguje wartość logiczną – zmienia true na false i odwrotnie
Operatory te są często stosowane w instrukcjach sterujących, takich jak if
, do sprawdzania złożonych warunków i podejmowania decyzji w programie, o czym będzie jeszcze w dalszej części.
Operacje bitowe
Operacje bitowe w C++ pozwalają na manipulowanie poszczególnymi bitami zmiennych, co jest szczególnie przydatne w programowaniu niskopoziomowym i embedded. Główne operatory bitowe to AND (&
), OR (|
), XOR (^
), NOT (~
), a także przesunięcia bitowe w lewo (<<
) i w prawo (>>
). Dzięki tym operatorom można efektywnie zarządzać flagami, sterować sprzętem, a także optymalizować pamięć, operując bezpośrednio na bitach.
0011 & 0101 => 0001
0011 | 0101 => 0111
0011 ^ 0101 => 0110
~0011 => 1100
0011 >> 1 => 0001
0011 << 1 => 0110
Warto tu zwrócic uwagą na operacje przesunięć bitowych
- przesunięcie o 1 w lewo odpowiada mnożeniu liczby całkowitej przez 2
- przesunięcie o 1 w prawo odpowiada dzieleniu liczby całkowitej przez 2
ta własiciwość jest wykorzystywana często w optymalizacji algorytmów, głównie dlatego, że operacje mnożenia i dzielenia są bardzo kosztowne w porównaniu do innych instrukcji procesora.
Rzutowanie typów zmiennych
Rzutowanie zmiennych w C++ pozwala na konwersję jednego typu danych na inny. Jest to przydatne, gdy chcemy np. zamienić wartość typu float
na int
lub wykorzystać różne typy w jednym wyrażeniu. W C++ mamy kilka sposobów na wykonanie rzutowania.
- rzutowanie jawne, polega na poprzedzeniu zmiennej nowym typem w nawiasie
(int)zmienna
, lub(float)wartosc
, kompilator wówczas doda odpowiedni kod konwertujący zmienną na nowy typ. Należy pamietać, że przy konwersji typów w niektórch kierunkach możemy utracić część informacji, np.: gdy zamieniamy typ float na int, tracimy część ułamkową, lub gdy konwertujemy int na short, możemy utracić 16 starszych bitów informacji. - rzutowanie statyczne
static_cast<int>(zmienna)
– działa niemal tak samo jak rzutowanie jawne, jest bezpieczniejszą i rekomendowaną metodą w c++ - rzutowanie dynamiczne
dynamic_cast<TypBazowy*>(wskaznik)
– stosowane przy typach polimorficznych w programowaniu obiektowym - rzutowanie „reinterpretacyjne” –
reinterpret_cast<int*>(data)
– szczególny typ rzutowania, który nie zmienia binarnej reprezentacji wartości zmiennej, należy używac z rozwagą - rzutowanie stałej wartości
const_cast<int>(value)
– również szczególny typrzutowania, pozwala „pominąć” atrybut „const” zmiennej i wykonać na niej operacje modyfikującą, tu także wymagana jest rozwaga, bo łatwo można wprowadzić kompilator w błąd.
float fivef = 5.1;
int fivei = (int)fivef; // wynik: 5
fivei = static_cast<int>(fivef); // wynik: 5, bezpieczniej
Nalezy tu także wspomnieć o rzutowaniu niejawnym. Zachodzi ono automatycznie, gdy kompilator konwertuje jedną wartość na inny typ danych bez wyraźnej instrukcji od programisty. Dzieje się to na przykład, gdy typ, taki jak int
, jest używany w operacji z innym typem, jak float
. Kompilator wówcza automatycznie konwertuje int
na float
, aby zachować precyzję.
int a = 5;
float b = 2.5;
float wynik = a + b; // Niejawne rzutowanie 'a' z int na float
Wyrażenia
Wyrażenie w C++ to kombinacja zmiennych, operatorów i wartości, która po wykonaniu zwraca jakiś wynik. Wyrażenia mogą pełnić różne funkcje, takie jak przypisanie wartości, wykonywanie obliczeń matematycznych czy ocenianie warunków logicznych.
Oto przykład wyrażenia w C++ z użyciem kilku zmiennych:
int a = 5;
int b = 10;
int c = 2;
int wynik = (a + b) * c;
// ^^^^^^^^^^^^
// wyrażenie
Może nie wydaje się to użyteczne, ale jedno należy o tym wiedzieć, to wyrażenie ma swój typ oraz wartość. zwłaszcza umiejętność ceny jaki to jest typ bedzie przydatna w dalszej pracy z kodem.
Instrukcje sterujące
I tu dochodzimy do clue programowania, czyli podejmowania decyzji. W C++ mamy do tego instrukcje sterujące, takie jak if
, else
i switch
. Pozwalają one programowi wykonywać różne akcje w zależności od różnych uwarunowań.
Przykład
int x = 10;
if (x > 5) {
std::cout << "x jest większe od 5" << std::endl;
} else {
std::cout << "x jest mniejsze lub równe 5" << std::endl;
}
switch(x) {
case 0:
wylacz();
break;
case 1:
wlacz();
break;
default:
tegoSieNieSpodziewalem(x);
}
Pętle
Czaasem zachodzi potrzeba wykonania jakiejś funkcji albo fragmentu kodu wielokrotnie. A nie chcemy tego samego fragmentu kodu pisać wielokrotnie. Nawet jest to niewskazane, o czy mówi reguła DRY (Don’t Repeat Yourself). Dodatkowo sprawa sie komplikuje, gdy nie wiemy ile razy mamy go wykonać, bo to dopiero zostanie obliczone. Tutaj z pomoca przychodzą nam pętle. Do dyspozycji mamy trzy główne rodzaje: for
, while
oraz do-while
. Zobaczmy to na przykładach
int ile = 5;
// for( inicjalizacja ; warunek kontynuacji ; aktualizacja)
for (int i = 0; i < ile; i++) {
std::cout << "i wynosi: " << i << std::endl;
}
int x = 0; // inicjalizacja
while (x < ile) { // warunek kontynuacji
std::cout << "x wynosi: " << x << std::endl;
x++; // aktualizacja
}
int y = 0;
do {
std::cout << "y wynosi: " << y << std::endl;
y++;
} while (y < 5);
warto odnotować, że w tym przykładzie petla for
i while
są równoważne.
Poza wymienionym wyżej petlami mamy także pętle „zakresową”. Została ona wprowadzona stosunkowo późno, bo dopiero w wersji C++11 standardu, a służy ona głównie do iterowania po kolekcjach lub tablicach.
int liczby[] = { 1, 2, 3, 4 };
for(auto liczba: liczby) {
std::cout << liczba << std::endl;
}
Podsumowanie
Operacje arytmetyczne, logiczne i bitowe umożliwiają precyzyjne zarządzanie wartościami, zarówno wysokopoziomowo, jak i niskopoziomowo, co jest kluczowe w aplikacjach embedded. Wyrażenia i rzutowanie pozwalają elastycznie kontrolować typy danych oraz przekształcać je w zależności od potrzeb.