Sok más nyelvhez hasonlóan a C++ is ismeri a mutatókat. Már tudod, hogy a referenciák és a mutatók hasonlítanak egymásra, de gondolj 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ímét 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 ismerkedsz a mutatókkal, elsőre kissé rejtélyesnek tűnhetnek, de ha hozzászoksz, 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 buta mutatónak vagy nyers mutatónak is szokás nevezni. A modern C++-ban okos mutatók is léteznek, a legalaptípus viszont 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 használjuk a mutatókat. A mutatók révén megoszthatjuk egy objektum címét a programunk más részeivel, ez pedig két fő okból hasznos:
A C++-ban egy mutató deklarációjakor meg kell adnod azt az adattípust, amelyre 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 határozatlan értéket tárol, amit gyakran „szemétcímnek” neveznek.
Bár bizonyos fordítók nullptr-re inicializálhatják a mutatókat, ez a viselkedés nem garantált minden fordítónál, ezért fontos, hogy ne számíts rá.
A legjobb gyakorlat, ha a nyers mutatókat 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.
Egy mutató visszafejtése 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 visszafejtést explicit módon kell elvégezni, a referenciák pedig egyszerűen aliasként viselkedtek.
A mutatóaritmetika lehetővé teszi, hogy aritmetikai műveleteket végezz a 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);
A C++-ban a mutatóaritmetika könnyen nem definiált viselkedéshez vezethet, ha nem bánsz vele óvatosan. A nem definiált viselkedés váratlan programeredményekben, összeomlásokban vagy akár biztonsági résekben is megnyilvánulhat. A nem definiált viselkedés következményeire híres példa az Ariane 5 rakéta felrobbanása 1996-ban, amikor egy 64 bites lebegőpontos szám 16 bites előjeles egésszé alakításából eredő szoftveres kivétel katasztrofális meghibásodáshoz vezetett.
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 a ptr->x írható.
Ez az operátor javítja a kód olvashatóságát és csökkenti a terjengőssé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;
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 objektumra, és null értéket is kaphatnak. Ez a rugalmasság azonban kockázatokkal jár, például null mutatók visszafejtésével 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 objektumhoz 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.
Üdvözlünk, mérnök! Te vagy a Speedywagon Alapítvány egyik utolsó veteránja, egy titkos szervezeté, amely évtizedek óta olyan ősi fenyegetések ellen harcol, mint a Pillar Men. Ennek során éveket töltöttél az Alapítvány technológiai rendszereinek karbantartásával, amelyek a legmodernebb technológia és elöregedett könyvtárak keverékéből épültek fel.
Az utóbbi időben azonban meghibásodnak a Pillar Men tevékenységét követő szenzorok. Az Alapítvány rendszerei régiek, és a kód egy olyan legacy C++ könyvtárral lép kapcsolatba, amelyet nem lehet frissíteni. A feladatod, hogy megvalósíts négy alapvető függvényt, amelyek egy régimódi, mutatóalapú könyvtár segítségével figyelik a Pillar Men szenzorok aktivitását.
Modern C++ mérnökként szívesebben használnál okos mutatókat, de sajnos a legacy kód tiszteletet követel a régi módszerek iránt. Az emberiség sorsa ezeken a mutatókon állhat vagy bukhat, szóval óvatosan haladj, és vezéreljen a Hamon-energia.
Mivel a szenzorok mérései hatalmasak lehetnek, adunk egy minta struct-ot, amelyet a valódi könyvtár használ. A kódot már megvalósítottuk neked a fejlécfájlban.
struct pillar_men_sensor {
int activity{};
std::string location{};
std::vector<int> data{};
};
connection_check)Az első feladatod, hogy megbizonyosodj róla, hogy a Pillar Men szenzor megfelelően csatlakozik.
Nem engedhetjük meg, hogy leválasztott szenzorok téves riasztásokat váltsanak ki.
Írj egy connection_check függvényt, amely egy nullptr-ellenőrzéssel megvizsgálja, hogy a szenzor mutatója érvényes-e.
pillar_men_sensor struct-ra mutató mutató.true-t adjon vissza, ha a szenzor mutatója nem null, egyébként pedig false-t.pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false
activity_counter)A Pillar Men az árnyékban ólálkodnak, és tudnunk kell, hogy a szenzorok észleltek-e bármilyen aktivitást.
Írd meg az activity_counter függvényt, amely egy szenzortömböt és egy kapacitást vár, utóbbi a tömbben lévő szenzorok számát adja meg.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123
alarm_control)Nem minden szenzor válthat ki riasztást, hacsak nincs valódi veszély.
Az alarm_control függvény gondoskodik róla, hogy egy szenzor csak akkor váltson ki riasztást, ha az aktivitásszintje nagyobb 0-nál.
A függvénynek emellett ellenőriznie kell a null szenzorokat is, hogy elkerülje a rendszer összeomlását.
pillar_men_sensor-ra mutató mutatót.nullptr-e a szenzor. Ha a szenzor nullptr, adjon vissza false-t.true-t, egyébként false-t.pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true
uv_alarm függvénnyelEbben a feladatban a uv_alarm függvényt valósítod meg, amely az UV-fénynek való kitettség és a szenzor aktivitása alapján dönti el, hogy ki kell-e váltani egy riasztást.
A uv_alarm függvény használja a rendelkezésre álló uv_light_heuristic függvényt, amely egy adatvektoron dolgozik, és bizonyos küszöbértékek alapján ad vissza egy értéket.
Ez egy minta verziója annak az összetett kódnak, amely éles környezetben fut majd, kérlek, ne változtasd meg az interfészt.
Definiáld a uv_alarm függvényt a speedywagon névtérben. A függvény:
pillar_men_sensor struct-ra mutató mutatót.false-t, ha a szenzor mutatója null.uv_light_heuristic függvényt, átadva a szenzor data tömbjének címét.true-t, ha a uv_light_heuristic által visszaadott érték nagyobb, mint a sensor->activity szint, egyébként false-t.Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) C++ nyelvet 19 fogalom100 feladat segítségével, valódi emberi mentorálással, mindez ingyen.