Zaznacz stronę

Podstawy języka C++ – część 2 – operacje

paź 13, 2024 | Język C++

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.

C++
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:

C++
#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.

C++
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.
C++
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ę.

C++
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:

C++
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

C++
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

C++
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.

C++
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.