Kurzusok
/
C++
C++
/
Tanterv
/
Mutatók
Mu

Mutatók ebben a kurzusban: C++

1 feladat

A(z) Mutatók fogalomról

Ahogy sok más nyelvben, a C++-ban is vannak mutatók. Már tudod, hogy a referenciák és a mutatók hasonlítanak egymáshoz, de tekints rájuk úgy, mint ami egy szinttel közelebb van a számítógéped belső működéséhez. A mutatók olyan változók, amelyek objektumok címeit tárolják. Segítségükkel közvetlenül dolgozhatsz az objektumokkal, ami lehetővé teszi a dinamikus memóriafoglalást és a hatékony adatkezelést a C++-ban.

Ha most találkozol először a mutatókkal, kissé rejtélyesnek tűnhetnek, de ha egyszer megszoktad őket, egészen egyszerűek.

A C++ kulcsfontosságú részét képezik, ezért szánj rá időt, hogy igazán megértsd őket. Az ebben a fogalomban bemutatott csupasz változatot dumb pointernek vagy raw pointernek is nevezik. A modern C++-ban okos mutatók is vannak, de az alap típus egyáltalán nem okos, és minden munkát neked kell kézzel elvégezned.

Mielőtt belemennénk a részletekbe, érdemes megérteni, mire is használjuk a mutatókat. A mutatók segítségével megoszthatod egy objektum címét a programunk más részeivel, ami két fő ok miatt hasznos:

  1. A referenciákhoz hasonlóan a mutatók elkerülik a másolást, és segítenek csökkenteni a program erőforrásigényét.
  2. A referenciákkal ellentétben a mutatók átirányíthatók más objektumokra.
  3. A mutatók ezenkívül mutathatnak null értékre is, jelezve, hogy éppen semmilyen objektumra nem mutatnak.

Általános szintaxis

A mutatódeklaráció a C++-ban abból áll, hogy megadod az adattípust, amire a mutató mutat, majd egy csillagot (*) és a mutató nevét. A mutatók deklarálásakor azok nem kapnak automatikusan kezdőértéket. Explicit értékadás nélkül a mutató jellemzően meghatározatlan értéket tartalmaz, amit gyakran „szemétcímnek” neveznek. Bár bizonyos fordítók inicializálhatják a mutatókat nullptr-ra, ez a viselkedés nem garantált minden fordítónál, ezért fontos, hogy ne számíts rá. A legjobb gyakorlat az, hogy a raw pointereket explicit módon inicializálod, és használat előtt ellenőrzöd, hogy nem null értékűek, így elkerülheted a lehetséges problémákat.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

Ahhoz, hogy egy változó címét egy mutatóhoz rendeld, a címképző operátort (&) használod. A mutató dereferálása az indirekciós operátorral (*) történik.

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

Figyelem: a dereferálást explicit módon kell elvégezni, míg a referenciák egyszerűen aliasként működtek.

Mutatóaritmetika

A mutatóaritmetika segítségével aritmetikai műveleteket végezhetsz mutatókon, ami különösen tömbökkel dolgozva hasznos. Ha egy egész számot adsz egy mutatóhoz, az egy másik elemre fog mutatni.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

A mutatóaritmetika a C++-ban könnyen meghatározatlan viselkedéshez vezethet, ha nem bánsz vele óvatosan. A meghatározatlan viselkedés váratlan programkimenetekben, összeomlásokban vagy akár biztonsági résekben is megnyilvánulhat. A meghatározatlan viselkedés következményeinek egy hírhedt példája az Ariane 5 rakéta felrobbanása volt 1996-ban, ahol egy 64 bites lebegőpontos szám 16 bites előjeles egésszé alakítása miatti szoftverkivétel vezetett katasztrofális hibához.

Tagváltozók elérése

A C++-ban a -> operátorral érheted el egy objektum tagjait egy rá mutató mutatón keresztül. Ez egy rövidítés, amely leegyszerűsíti a mutatók által mutatott objektumok tagjainak elérését. Ha például ptr egy olyan objektumra mutat, amelynek van egy x tagváltozója, akkor a (*ptr).x helyett közvetlenül használhatod a ptr->x kifejezést. Ez az operátor javítja a kód olvashatóságát és csökkenti a bőbeszédűséget, amikor objektumokra mutató mutatókkal dolgozol.

Íme egy rövid példa: egy Superhero struct, amelynek van egy superpower tagváltozója. A main függvény létrehoz egy dianaPrince mutatót egy Superhero objektumra (amely Wonder Womant képviseli). A -> operátorral érjük el a superpower tagváltozót, bemutatva Wonder Woman ikonikus „Lasso of Truth” fegyverét.

struct Superhero {
    std::string superpower;
};

Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";

// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

// Memory cleanup:
delete dianaPrince;

Mutatók vs. referenciák

A mutatók és a referenciák egyaránt lehetővé teszik az objektumok közvetett elérését, de képességeikben és biztonsági szempontjaikban különböznek. A mutatók rugalmasan átirányíthatók más célobjektumra, és null értéket is kaphatnak. Ez a rugalmasság azonban kockázatokkal jár, például null mutatók dereferálásával vagy lógó mutatók létrehozásával. A referenciák ezzel szemben nem lehetnek null értékűek, és létrehozásukkor érvényes objektumokhoz kötődnek, így elkerülik ezeket a kockázatokat. Mivel biztonságosabbak, a referenciákat érdemes előnyben részesíteni a mutatókkal szemben, kivéve, ha szükség van a mutatók nyújtotta többletfunkciókra.

Advanced

Dinamikus memóriafoglalás és null mutatók

A C++-ban a new operátorral futásidőben dinamikusan foglalhatsz memóriát objektumoknak a heapen. Ez egy mutatót ad vissza a lefoglalt memóriacímre, így az objektumok élettartama nem kötődik egy adott hatókörhöz. Fontos azonban, hogy a new-hoz mindig társítsd a delete-et, hogy megfelelően felszabadítsd a memóriát. A dinamikus memóriafoglalást óvatosan kell használni, hogy elkerüld a gyakori problémákat, például a memóriaszivárgást és a felszabadított memória elérését.

A mutatók beállíthatók egy null mutató nevű speciális értékre is, ami azt jelzi, hogy nem mutatnak érvényes címre. A null mutatókat gyakran arra használjuk, hogy inicializáljuk a mutatókat, mielőtt érvényes értéket adnánk nekik.

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

Egy mutató vagy egy érvényes objektum címét tartalmazza, vagy nullptr-ra van állítva. Így csak azt kell ellenőrizned, hogy a mutató null-e, és feltételezheted, hogy minden nem null mutató érvényes. Amikor egy objektum megsemmisül, a rá mutató mutatók lógóvá válnak (nem állítódnak automatikusan nullptr-ra). A te felelősséged felismerni ezeket az eseteket, és gondoskodni arról, hogy az ilyen mutatók azután nullptr-ra legyenek állítva.

Régi kódokban a nullptr két alternatívájával találkozhatsz. Először is, a 0 literál kifejezetten null értékként értelmeződik mutatók esetében, bár ez az egyetlen eset, amikor egész literált rendelhetsz egy mutatóhoz. Másodszor, a C-ből örökölt, a <cstddef> fejlécben definiált preprocessor macro NULL a null mutató egy másik reprezentációja, bár a modern C++ kódban ritkábban használják.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Mutatók fogalmat

A gyakorlás zárolva

Oldj fel még 1 feladatot, hogy gyakorolhasd a(z) Mutatók fogalmat