Typy zmiennych i ich zastosowanie w C++

Czym są zmienne w C++?

Zmienne stanowią fundament każdego języka programowania, w tym C++. Można je porównać do pojemników, w których przechowujemy dane podczas działania programu. Każda zmienna w C++ ma określony typ, nazwę oraz wartość. Typ zmiennej określa rodzaj danych, jakie może ona przechowywać, a także ilość pamięci, jaką zajmuje w systemie.

Podstawowa składnia deklaracji zmiennej w C++ wygląda następująco:

typ_zmiennej nazwa_zmiennej = wartość_początkowa;

Na przykład:

int liczba = 10;
double pi = 3.14159;
char znak = 'A';

Podstawowe typy zmiennych w C++

C++ oferuje szereg wbudowanych typów danych, które można podzielić na kilka głównych kategorii:

Kategoria Typy Zastosowanie
Całkowitoliczbowe int, short, long, long long Liczby całkowite (bez części ułamkowej)
Zmiennoprzecinkowe float, double, long double Liczby rzeczywiste (z częścią ułamkową)
Znakowe char, wchar_t, char16_t, char32_t Pojedyncze znaki
Logiczne bool Wartości logiczne (prawda/fałsz)
Void void Brak typu (używany głównie dla funkcji)

Typy całkowitoliczbowe

Typy całkowitoliczbowe służą do przechowywania liczb całkowitych (bez części ułamkowej). W C++ mamy do dyspozycji następujące typy całkowitoliczbowe:

1. int

Podstawowy typ całkowitoliczbowy, najczęściej używany w praktyce programistycznej. Typowo zajmuje 4 bajty pamięci (32 bity), co pozwala na przechowywanie wartości w zakresie od \(-2^{31}\) do \(2^{31}-1\), czyli od -2,147,483,648 do 2,147,483,647.

int liczba = 42;
int ujemna_liczba = -100;

2. short

Zajmuje mniej pamięci niż int (zwykle 2 bajty), co ogranicza zakres wartości do \(-2^{15}\) do \(2^{15}-1\), czyli od -32,768 do 32,767. Przydatny, gdy chcemy oszczędzać pamięć, a wiemy, że wartości będą niewielkie.

short mala_liczba = 1000;

3. long

Na większości systemów 32-bitowych ma taki sam rozmiar jak int (4 bajty), ale na systemach 64-bitowych może zajmować 8 bajtów, co zwiększa zakres wartości.

long duza_liczba = 1000000L; // L na końcu oznacza literal typu long

4. long long

Wprowadzony w standardzie C++11, gwarantuje co najmniej 8 bajtów pamięci, co daje zakres od \(-2^{63}\) do \(2^{63}-1\), czyli od -9,223,372,036,854,775,808 do 9,223,372,036,854,775,807.

long long bardzo_duza_liczba = 9000000000000000000LL; // LL oznacza literal typu long long

Modyfikatory typów całkowitoliczbowych

Każdy z powyższych typów może być dodatkowo zmodyfikowany przez słowa kluczowe:

  • signed – pozwala na przechowywanie wartości ujemnych i dodatnich (domyślne zachowanie)
  • unsigned – pozwala na przechowywanie tylko wartości nieujemnych, ale podwaja górny zakres

Na przykład:

unsigned int tylko_dodatnie = 4000000000; // wartość poza zakresem zwykłego int
signed short z_minusem = -200;

Poniższa tabela przedstawia typowe zakresy dla różnych typów całkowitoliczbowych:

Typ Typowy rozmiar Minimalny zakres Maksymalny zakres
short 2 bajty -32,768 32,767
unsigned short 2 bajty 0 65,535
int 4 bajty -2,147,483,648 2,147,483,647
unsigned int 4 bajty 0 4,294,967,295
long 4 bajty (32-bit)
8 bajtów (64-bit)
-2,147,483,648 lub mniej 2,147,483,647 lub więcej
long long 8 bajtów -9,223,372,036,854,775,808 9,223,372,036,854,775,807

Typy zmiennoprzecinkowe

Typy zmiennoprzecinkowe (rzeczywiste) służą do przechowywania liczb z częścią ułamkową. W C++ mamy trzy podstawowe typy zmiennoprzecinkowe:

1. float

Typ o pojedynczej precyzji, zajmujący 4 bajty pamięci. Zapewnia precyzję około 7 cyfr dziesiętnych. Zakres wartości wynosi w przybliżeniu od \(1.2 \times 10^{-38}\) do \(3.4 \times 10^{38}\).

float pi_przyblizenie = 3.14159f; // f na końcu oznacza literal typu float

2. double

Typ o podwójnej precyzji, zajmujący 8 bajtów pamięci. Zapewnia precyzję około 15 cyfr dziesiętnych. Zakres wartości wynosi w przybliżeniu od \(2.3 \times 10^{-308}\) do \(1.7 \times 10^{308}\). Jest to najczęściej używany typ zmiennoprzecinkowy ze względu na dobry kompromis między precyzją a wydajnością.

double pi_dokladniej = 3.141592653589793;

3. long double

Typ o rozszerzonej precyzji, zajmujący zwykle 10 lub 16 bajtów pamięci (zależnie od kompilatora i systemu). Zapewnia jeszcze większą precyzję niż double, ale kosztem wydajności.

long double pi_bardzo_dokladnie = 3.1415926535897932384626433832795L; // L oznacza literal typu long double

Poniższy wykres przedstawia porównanie precyzji typów zmiennoprzecinkowych:

Typy znakowe

Typy znakowe służą do przechowywania pojedynczych znaków. W C++ mamy następujące typy znakowe:

1. char

Podstawowy typ znakowy, zajmujący 1 bajt pamięci. Może przechowywać znaki ASCII oraz wartości liczbowe od -128 do 127 (dla signed char) lub od 0 do 255 (dla unsigned char).

char litera = 'A';
char cyfra = '7';
char znak_specjalny = '@';

Warto pamiętać, że w C++ typ char może być traktowany zarówno jako typ znakowy, jak i jako mały typ całkowitoliczbowy:

char litera = 'A';
int kod_ascii = litera; // kod_ascii będzie miał wartość 65 (kod ASCII dla 'A')

2. wchar_t, char16_t, char32_t

Te typy zostały wprowadzone do obsługi znaków Unicode i innych rozszerzonych zestawów znaków:

  • wchar_t – szeroki znak, zwykle 2 lub 4 bajty (zależnie od systemu)
  • char16_t – 2-bajtowy znak (UTF-16)
  • char32_t – 4-bajtowy znak (UTF-32)
wchar_t szeroki_znak = L'ł'; // L oznacza literal typu wchar_t
char16_t znak_utf16 = u'ę'; // u oznacza literal typu char16_t
char32_t znak_utf32 = U'🙂'; // U oznacza literal typu char32_t

Typ logiczny (boolean)

Typ bool służy do przechowywania wartości logicznych: prawda (true) lub fałsz (false). Zajmuje zwykle 1 bajt pamięci, choć teoretycznie potrzebuje tylko 1 bitu.

bool prawda = true;
bool falsz = false;

bool wynik = (10 > 5); // wynik będzie miał wartość true
bool inny_wynik = (3 == 7); // inny_wynik będzie miał wartość false

Typ bool jest szczególnie przydatny w instrukcjach warunkowych:

bool czy_pelnoletni = (wiek >= 18);

if (czy_pelnoletni) {
    cout << "Możesz wejść na stronę" << endl;
} else {
    cout << "Dostęp zabroniony" << endl;
}

Typ void

Typ void oznacza "brak typu" i jest używany głównie w trzech kontekstach:

1. Funkcje bez wartości zwracanej

void powitaj() {
    cout << "Witaj świecie!" << endl;
    // Funkcja nic nie zwraca
}

2. Funkcje bez parametrów

int pobierz_liczbe(void) {
    int x;
    cin >> x;
    return x;
}

3. Wskaźniki ogólnego przeznaczenia

void* wskaznik;
// Wskaźnik void* może przechowywać adres dowolnego typu danych
// ale wymaga jawnego rzutowania przed użyciem

Typy pochodne

Oprócz typów podstawowych, C++ oferuje również typy pochodne, które są tworzone na bazie typów podstawowych:

1. Tablice

Tablice przechowują wiele wartości tego samego typu w ciągłym obszarze pamięci.

int liczby[5] = {1, 2, 3, 4, 5};
char napis[10] = "Hello"; // Tablica znaków

2. Wskaźniki

Wskaźniki przechowują adresy pamięci innych zmiennych lub obiektów.

int liczba = 42;
int* wskaznik = &liczba; // wskaznik przechowuje adres zmiennej liczba

cout << *wskaznik; // Wyświetli 42 (wartość pod adresem przechowywanym przez wskaznik)

3. Referencje

Referencje są aliasami (innymi nazwami) dla istniejących zmiennych.

int liczba = 42;
int& referencja = liczba; // referencja odnosi się do zmiennej liczba

referencja = 100; // Zmienia wartość zmiennej liczba na 100

Typy zdefiniowane przez użytkownika

C++ pozwala na tworzenie własnych typów danych za pomocą:

1. Struktury (struct)

struct Osoba {
    string imie;
    string nazwisko;
    int wiek;
};

Osoba jan = {"Jan", "Kowalski", 30};

2. Klasy (class)

class Punkt {
private:
    double x, y;
public:
    Punkt(double x_val, double y_val) : x(x_val), y(y_val) {}
    double odleglosc() {
        return sqrt(x*x + y*y);
    }
};

Punkt p(3.0, 4.0);

3. Unie (union)

union Wartosc {
    int i;
    float f;
    char c;
};

Wartosc w;
w.i = 10; // Teraz w.i ma wartość 10, ale w.f i w.c mają nieokreślone wartości

4. Wyliczenia (enum)

enum Kolor {CZERWONY, ZIELONY, NIEBIESKI};
Kolor wybrany = ZIELONY;

5. Wyliczenia z zakresem (enum class) - C++11

enum class Status {OK, BLAD, OSTRZEZENIE};
Status wynik = Status::OK;

Aliasy typów

C++ umożliwia tworzenie aliasów (alternatywnych nazw) dla istniejących typów:

1. Słowo kluczowe typedef

typedef unsigned long ulong;
ulong duza_liczba = 1000000UL;

2. Słowo kluczowe using (C++11)

using liczba_calkowita = int;
liczba_calkowita x = 42;

Automatyczne wnioskowanie typu (C++11)

Od C++11 można używać słowa kluczowego auto, aby kompilator sam wywnioskował typ zmiennej na podstawie przypisanej wartości:

auto liczba = 42; // int
auto pi = 3.14159; // double
auto znak = 'A'; // char
auto tekst = "Hello"; // const char*

Jest to szczególnie przydatne przy złożonych typach:

std::vector liczby = {1, 2, 3, 4, 5};
auto iterator = liczby.begin(); // std::vector::iterator

Zmienne globalne i lokalne

W C++ zmienne mogą mieć różny zakres widoczności i czas życia:

1. Zmienne lokalne

Zmienne zadeklarowane wewnątrz funkcji lub bloku kodu są widoczne tylko w tym bloku i istnieją tylko podczas jego wykonywania:

void funkcja() {
    int x = 10; // zmienna lokalna
    // x jest widoczna tylko wewnątrz tej funkcji
}

// Tutaj x nie jest już dostępna

2. Zmienne globalne

Zmienne zadeklarowane poza wszystkimi funkcjami są widoczne w całym programie i istnieją przez cały czas jego działania:

int globalna = 100; // zmienna globalna

void funkcja() {
    cout << globalna; // Można używać zmiennej globalnej
    globalna = 200; // Można modyfikować zmienną globalną
}

Należy jednak pamiętać, że nadużywanie zmiennych globalnych może prowadzić do trudnych do wykrycia błędów i utrudniać utrzymanie kodu.

3. Zmienne statyczne

Zmienne statyczne zachowują swoją wartość między wywołaniami funkcji:

void licznik() {
    static int liczba_wywolan = 0; // Inicjalizowana tylko raz
    liczba_wywolan++;
    cout << "Funkcja została wywołana " << liczba_wywolan << " razy" << endl;
}

Stałe w C++

Stałe to zmienne, których wartości nie można zmodyfikować po inicjalizacji:

1. Słowo kluczowe const

const double PI = 3.14159265359;
const int MAX_UZYTKOWNIKOW = 100;

2. Stałe w czasie kompilacji (C++11)

constexpr int KWADRAT(int x) {
    return x * x;
}

constexpr int wynik = KWADRAT(5); // Obliczane podczas kompilacji

Konwersje typów

W C++ można konwertować wartości między różnymi typami danych:

1. Konwersje niejawne

Kompilator automatycznie konwertuje niektóre typy:

int i = 42;
double d = i; // Niejawna konwersja z int na double (bezpieczna)

double pi = 3.14159;
int zaokraglone = pi; // Niejawna konwersja z double na int (potencjalna utrata danych)

2. Konwersje jawne (rzutowanie)

C++ oferuje kilka sposobów jawnej konwersji typów:

Styl C (stary):

double pi = 3.14159;
int zaokraglone = (int)pi; // Rzutowanie w stylu C

Funkcje rzutowania (nowoczesny C++):

double pi = 3.14159;
int zaokraglone = static_cast(pi); // Preferowany sposób w C++

Inne operatory rzutowania w C++:

  • dynamic_cast - bezpieczne rzutowanie w hierarchii klas
  • const_cast - usuwanie kwalifikatorów const/volatile
  • reinterpret_cast - niskopoziomowe konwersje (niebezpieczne)

Podsumowanie

Typy zmiennych w C++ stanowią fundament języka, pozwalając na precyzyjne określenie, jakie dane będą przechowywane i jak będą przetwarzane. Zrozumienie typów danych i ich właściwości jest kluczowe dla tworzenia efektywnego, bezpiecznego i poprawnego kodu.

Wybór odpowiedniego typu zmiennej powinien uwzględniać:

  • Zakres wartości, jakie zmienna będzie przechowywać
  • Wymaganą precyzję (dla typów zmiennoprzecinkowych)
  • Zużycie pamięci
  • Wydajność operacji na danym typie
  • Czytelność i intencje kodu

Nowoczesny C++ oferuje wiele narzędzi ułatwiających pracę z typami, takich jak auto, using, czy constexpr, które warto wykorzystywać dla zwiększenia czytelności i bezpieczeństwa kodu.