Přeskočit na obsah

Ukazatele: adresy, dereferencování a aritmetika

Pochopte ukazatele jako adresy paměti, naučte se operátory adres a dereferencí, aritmetiku ukazatelů v polích, konstantní správnost s ukazateli a to, kdy jsou v moderním C++ stále vhodné nezpracované ukazatele.

Co je ukazatel?

ukazatel je proměnná, která ukládá adresu paměti. Každý objekt v C++ žije na nějaké adrese v paměti a ukazatel vám umožňuje odkazovat na tento objekt nepřímo. Ukazatele jsou základem C++ – umožňují dynamické datové struktury, polymorfismus a efektivní předávání parametrů.

Typ ukazatele je deklarován pomocí*:int* p;deklaruje ukazatel naint. Samotný ukazatel zabírá paměť (obvykle 8 bajtů na 64bitových systémech) a jeho hodnota je adresa, nikoli data, na která ukazuje.

Address-of & Dereference

Dva základní operátory ukazatele jsou&(adresa) a*(dereference). Operátor&získá adresu proměnné. Operátor*sleduje adresu pro přístup k objektu, na který ukazuje.

address_deref.cpp
#include <iostream>

int main() {
    int value = 42;
    int* ptr = &value;  // ptr holds the address of value

    std::cout << "value:  " << value << '\n';        // 42
    std::cout << "&value: " << &value << '\n';       // e.g. 0x7ffd5a3c
    std::cout << "ptr:    " << ptr << '\n';           // same address
    std::cout << "*ptr:   " << *ptr << '\n';          // 42 (dereference)

    *ptr = 100;  // modify value through the pointer
    std::cout << "value after *ptr = 100: " << value << '\n'; // 100

    int* null_ptr = nullptr;  // points to nothing — safe sentinel
    // *null_ptr = 5;  // UNDEFINED BEHAVIOR — crash on most systems
}

Aritmetika ukazatele a útlum pole

Aritmetika ukazatele je platná pouze v rámci pole (nebo jeden za koncem). Kompilátor škáluje aritmetiku podlesizeof(T). Název pole se ve většině kontextů rozkládá na ukazatel na svůj první prvek – to je důvod, proč pole ve stylu C při předávání funkcím ztrácejí informace o velikosti.

pointer_arithmetic.cpp
#include <iostream>
#include <cstddef>  // std::ptrdiff_t

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* p = arr;  // array decays to pointer to first element

    std::cout << *p << '\n';        // 10
    std::cout << *(p + 2) << '\n';  // 30  (moves 2*sizeof(int) bytes)
    std::cout << p[3] << '\n';      // 40  (p[i] is *(p + i))

    // Iterating via pointer
    for (int* it = arr; it != arr + 5; ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';  // 10 20 30 40 50

    // Pointer difference
    std::ptrdiff_t diff = (arr + 4) - arr;  // 4 (elements, not bytes)
    std::cout << "diff: " << diff << '\n';

    // WARNING: arithmetic outside the array is UNDEFINED BEHAVIOR
    // int* bad = arr + 10;  // UB — don't do this
}

const Ukazatele vs Ukazatel na konst

Umístěníconstvzhledem k*určuje, co je neměnné:

- const int* p (neboint const* p) — ukazatel na const: nemůžete upravit hodnotu, na kterou ukazujete, pomocíp, ale můžete znovu usaditptak, aby ukazoval jinam.
- int* const p — const pointer: samotný ukazatel je neměnný (nelze znovu usadit), ale můžete upravit hodnotu, na kterou ukazuje.
- const int* const p — ukazatel i hodnota, na kterou ukazuje, jsou neměnné.

Číst deklarace zprava doleva:int* const pse čte jako "pje konstantní ukazatel na int."

Ukazatele prázdnoty (void*) mohou obsahovat libovolnou adresu, ale nelze je dereferencovat bez přetypování. Používají se především v API a alokátorech ve stylu C.

Osvědčený postup

V moderním C++ by měly být raw ukazatele používány výhradně jako nevlastnící, pozorovatelské ukazatele. Říkají: "Vidím tento předmět, ale nejsem zodpovědný za jeho životnost." Běžná legitimní použití:

- Implementace datových struktur (uzly stromu směřující k nadřazenému prvku)
- Parametry funkcí, které pozorují, ale nevlastní (ačkoli odkazy jsou často lepší)
- Propojení s C API
- Iterátory ve vlastních kontejnerech

Pokud ukazatel vlastní zdroj, zabalte jej dostd::unique_ptrnebostd::shared_ptr. Pokud zjistíte, že píšetedelete, téměř jistě to v moderním C++ děláte špatně.

Úskalí

Vícející se ukazatele jsou chybou ukazatele č. 1. Ukazatel se začne houpat, když je objekt, na který ukazuje, zničen:

- Vrácení ukazatele na lokální proměnnou
- Odstranění objektu, zatímco na něj stále odkazuje jiný ukazatel
- Ukazatel nastd::vector, který je znovu přidělen popush_back

Neinicializované ukazatele obsahují nesmyslné adresy. Vždy inicializujte nanullptr, pokud ještě nemáte cíl. Kompilátor NEBUDE upozorňovat na všechna neinicializovaná použití ukazatelů.

Ukazatel vs reference: Kdy použít který

Reference a ukazatele poskytují nepřímé informace, ale v důležitých věcech se liší. Reference nemohou být nulové, nemohou být znovu umístěny a nevyžadují explicitní dereferencování. Upřednostňujte odkazy pro parametry funkcí; použijte ukazatele, když je potřeba nullable nebo re-seating.

ptr_vs_ref.cpp
#include <iostream>

void increment_ref(int& val) { ++val; }         // cannot be null
void increment_ptr(int* val) { if (val) ++(*val); } // must null-check

int main() {
    int x = 10;
    increment_ref(x);        // clean call syntax
    increment_ptr(&x);       // caller must take address
    std::cout << x << '\n';  // 12

    int* p = &x;
    p = nullptr;  // OK — pointers can be re-seated and nulled

    int& r = x;
    // r = ???;    // references cannot be re-seated
    // int& bad;   // ERROR — references must be initialized
}
Klíčové poznatky
  • &gets an address,*follows it — these are inverse operations
  • Pointer arithmetic is only valid within arrays and scales bysizeof(T)
  • Array names decay to pointers, losing size information
  • Readconstplacement right-to-left:const int*vsint* const
  • V moderním C++ by nezpracované ukazatele měly být pouze nevlastními pozorovateli
  • Prefer references over pointers unless you need nullability or re-seating

Kvíz — Otestujte své znalosti

(15 XP)

1. Co znamená`const int* p`?

2. What happens when an array is passed to a function expecting a pointer parameter?

3. Kdy platí ukazatelová aritmetika v C++?