Operácie s ukazovateľmi. Čo to znamená v C: čo je ukazovateľ Čo je uložené v premennej typu ukazovateľ

Ukazovateľ je odvodený typ, ktorý predstavuje adresu nejakej hodnoty. Jazyk C++ používa koncept premenných adries. Prácu s adresami zdedil C++ z jazyka C. Predpokladajme, že v programe je definovaná premenná typu int:

int x;

Môžete definovať premennú ukazovateľa na celé číslo:

int* xptr;

a priraďte premennej xptr adresu premennej x:

xptr =

Treba si zapamätať:

  • Operácia & použitá na premennú je operácia adresy.
  • Operácia * použitá na adresu (inými slovami, aplikovaná na ukazovateľ) je operácia adresy.

Pre predchádzajúce definície x a y sú teda tieto dva operátory ekvivalentné:

// priraďte premennej y hodnotu x

int y = x;

// priraďte premennej y hodnotu

// nachádza sa na xptr

int y = *xptr;

Pomocou operácie adresy môžete zapisovať hodnoty:

// napíšte číslo 10 na adresu xptr

*xptr = 10;

Po vykonaní tohto príkazu bude hodnota premennej x 10, pretože xptr ukazuje na premennú x.

Ukazovateľ nie je len adresa, ale adresa hodnoty určitého typu. Ukazovateľ xptr je adresa celočíselnej hodnoty. Adresy veličín iných typov môžete definovať takto:

// ukazovateľ na celé číslo bez znamienka

nepodpísaný dlhý* lPtr;

// ukazovateľ na bajt

char* cp;

// ukazovateľ na objekt triedy Complex

Komplexné* p;

Ak ukazovateľ odkazuje na objekt nejakej triedy, potom operácia prístupu k atribútu triedy namiesto bodky je označená „->“, napríklad p->real. Ak si spomeniete na jeden z predchádzajúcich príkladov:

void Complex::Add(Complex x)

This->real = this->real + x.real;

Toto->imaginárne = toto->imaginárne +

X.imaginárny;

potom je to ukazovateľ na aktuálny objekt, t.j. objekt, ktorý vykonáva metódu Add. Záznam this-> znamená prístup k atribútu aktuálneho objektu.

Môžete definovať ukazovateľ na akýkoľvek typ, vrátane funkcie alebo metódy triedy. Ak existuje niekoľko funkcií rovnakého typu:

int foo(long x);

int bar (dlhá x);

Môžete definovať premennú typu ukazovateľ na funkciu a volať tieto funkcie nie priamo, ale nepriamo cez ukazovateľ:

int (*functptr)(dlhé x);

functionptr =

(*funkptr)(2);

functionptr =

(*funkptr)(4);

Na čo slúžia ukazovatele? Ukazovatele sa objavili predovšetkým pre potreby programovania systému. Keďže jazyk C bol určený pre „nízkoúrovňové“ programovanie, bolo potrebné pristupovať napríklad k registrom zariadení. Tieto registre zariadení majú veľmi špecifické adresy, t.j. bolo potrebné prečítať alebo zapísať hodnotu na konkrétnej adrese. Vďaka mechanizmu ukazovateľov takéto operácie nevyžadujú žiadne ďalšie jazykové nástroje.

int* hardwareRegiste =0x80000;

*registrácia hardvéru =12;

Použitie ukazovateľov sa však neobmedzuje len na potreby programovania systému. Ukazovatele môžu výrazne zjednodušiť a urýchliť množstvo operácií. Predpokladajme, že program má pamäťovú oblasť na ukladanie medzivýsledkov výpočtov. Túto oblasť pamäte využívajú rôzne programové moduly. Namiesto kopírovania tejto oblasti pamäte zakaždým, keď pristupujeme k modulu, môžeme odovzdať ukazovateľ ako argument volania funkcie, čím zjednodušíme a zrýchlime výpočty.

Posledná aktualizácia: 27.05.2017

Ukazovatele v jazyku C podporujú množstvo operácií: priradenie, získanie adresy ukazovateľa, získanie hodnoty z ukazovateľa, niektoré aritmetické operácie a porovnávacie operácie.

Pridelenie

Ukazovateľ môže byť priradený buď adrese objektu rovnakého typu, hodnote iného ukazovateľa alebo konštante NULL.

Priradenie adresy k ukazovateľu už bolo diskutované v predchádzajúcej téme. Ak chcete získať adresu objektu, použite operáciu &:

Int a = 10; int *pa = // ukazovateľ pa ukladá adresu premennej a

Okrem toho ukazovateľ a premenná musia mať rovnaký typ, in v tomto prípade int.

Priradenie ukazovateľa k inému ukazovateľu:

#include int main(void) ( int a = 10; int b = 2; int *pa = int *pb = printf("Premenna a: adresa=%p \t hodnota=%d \n", pa, *pa); printf("Premenná b: adresa=%p \t hodnota=%d \n", pb, *pb); pa = pb; // teraz ukazovateľ pa ukladá adresu premennej b printf("Premenná b: adresa= %p \ t hodnota=%d \n", pa, *pa); návrat 0; )

Keď je ukazovateľ priradený k inému ukazovateľu, prvý ukazovateľ v skutočnosti začne tiež ukazovať na rovnakú adresu, na ktorú ukazuje druhý ukazovateľ.

Ak nechceme, aby ukazovateľ ukazoval na konkrétnu adresu, môžeme mu priradiť podmienenú hodnotu null pomocou konštanty NULL, ktorá je definovaná v hlavičkovom súbore stdio.h:

Int *pa = NULL;

Dereferencia ukazovateľa

Operácia dereferencovania ukazovateľa v tvare *názov_ukazateľa vám umožňuje získať objekt na adrese, ktorá je uložená v ukazovateli.

#include int main(void) ( int a = 10; int *pa = int *pb = pa; *pa = 25; printf("Hodnota na ukazovateli pa: %d \n", *pa); // 25 printf(" Hodnota na ukazovateli pb: %d \n", *pb); // 25 printf("Hodnota premennej a: %d \n", a); // 25 návrat 0; )

Cez výraz *pa môžeme získať hodnotu na adrese, ktorá je uložená v ukazovateli pa a cez výraz ako *pa = hodnota môžeme na túto adresu vložiť novú hodnotu.

A keďže v tomto prípade ukazovateľ pa ukazuje na premennú a, tak keď sa zmení hodnota na adrese, na ktorú ukazuje ukazovateľ, zmení sa aj hodnota premennej a.

Adresa ukazovateľa

Ukazovateľ ukladá adresu premennej a z tejto adresy môžeme získať hodnotu tejto premennej. Okrem toho má ukazovateľ, ako každá premenná, adresu, kde sa nachádza v pamäti. Túto adresu je možné získať aj pomocou operácie &:

Int a = 10; int *pa = printf("adresa ukazovatela=%p \n", &pa); // adresa ukazovateľa printf("adresa uložená v ukazovateli=%p \n", pa); // adresa, ktorá je uložená v ukazovateli je adresa premennej a printf("hodnota na ukazovateli=%d \n", *pa); // hodnota na adrese v ukazovateli - hodnota premennej a

Porovnávacie operácie

Na ukazovatele možno použiť porovnávacie operácie > , >=< , <= ,== , != . Операции сравнения применяются только к указателям одного типа и константе NULL . Для сравнения используются номера адресов:

Int a = 10; int b = 20; int *pa = int *pb = if(pa > pb) printf("pa (%p) je väčšie ako pb (%p) \n", pa, pb); else printf("pa (%p) je menšie alebo rovné pb (%p) \n", pa, pb);

Výstup konzoly v mojom prípade:

Pa (0060FEA4) je väčšie ako pb (0060FEA0)

Obsadenie

Niekedy je potrebné priradiť ukazovateľ jedného typu k hodnote ukazovateľa iného typu. V tomto prípade by ste mali vykonať operáciu konverzie typu:

Char c = "N"; char *pc = int *pd = (int *)pc; printf("pc=%p \n", pc); printf("pd=%p\n", pd);

Pri učení C majú začiatočníci často otázky týkajúce sa ukazovateľov, myslím, že každý má približne rovnaké otázky, preto popíšem tie, ktoré mi vyvstali.

Na čo slúži ukazovateľ?

Prečo sa vždy píše „ukazovateľ typu“ a čo je ukazovateľ typu uint16_t odlišný od typu ukazovateľ uint8_t?

A kto vlastne prišiel s indexom?

Pred zodpovedaním týchto otázok si pripomeňme, čo je ukazovateľ.
Ukazovateľ je premenná, ktorá obsahuje adresu nejakého dátového prvku (premennej, konštanty, funkcie, štruktúry).

Ak chcete deklarovať premennú ako ukazovateľ, musíte pred jej názvom uviesť s * a na získanie adresy premennej sa používa & (unárny operátor adresy).
char a = "a"; char *p =
V tomto prípade p obsahuje adresu premennej a. Čo je však zaujímavé pre ďalšiu prácu s ukazovateľom netreba písať hviezdičku, je potrebná len pri deklarovaní.
char a = "a"; char b = "b"; char *p = p =
V tomto prípade p obsahuje adresu premennej b, ale ak chceme získať hodnotu nachádzajúcu sa na tejto adrese, tak musíme použiť operátor dereference, rovnaká hviezdička *.
char new_simbol = 0; char a = "a"; char *p = new_simbol = *p;
Premenná new_simbol teda bude obsahovať ascii kód symbolu "a".

Teraz prejdime priamo k otázkam, na čo je ukazovateľ potrebný. Predstavte si, že máme pole, s ktorým chceme vo funkcii pracovať. Aby ste mohli pole odovzdať funkcii, musíte ho skopírovať, teda plytvať pamäťou, ktorej už MK má málo, takže správnejšie riešenie by bolo pole nekopírovať, ale odovzdať adresu jeho prvý prvok a veľkosť.
m = (1,2,3...);
Môžete to urobiť takto
void foo(char *m, veľkosť uint8_t) ( )
alebo tak
void foo(char m, uint8_t size) ( )
Keďže názov poľa obsahuje adresu jeho prvého prvku, nie je to nič iné ako ukazovateľ. V poli sa môžete pohybovať pomocou najjednoduchšieho aritmetické operácie, napríklad ak chcete získať hodnotu piateho prvku poľa, musíte k adrese poľa pridať 4 (adresa prvého prvku) a použiť operátor dereference.
m = * (m + 4);

A hneď sa natíska otázka, prečo všade píšu typ pred ukazovateľ? Všetko je jednoduché, odovzdaním adresy prvého prvku poľa a veľkosti poľa hovoríme: Odtiaľto (ukazovateľ) vykopte 10 dier (veľkosť poľa), prídeme o dve hodiny a tí, ktorí mali kopať jamy nazývané traktor a kopať jamu. Aby sme sa nedostali do tejto situácie, bolo potrebné špecifikovať veľkosť diery, v našej analógii je typ ukazovateľa rovnaký ako typ určuje, koľko bajtov premenná zaberie v pamäti.

Zadaním typu ukazovateľa teda povieme kompilátoru, tu je adresa začiatku poľa, jeden prvok poľa zaberá 2 bajty, takýchto prvkov je v poli 10, takže koľko pamäte máme alokovať na toto pole? 20 bajtov - kompilátor odpovie. Pre prehľadnosť si zoberme ukazovateľ typu void, nie je definované, koľko miesta zaberá- toto je len adresa, preveďme ju na ukazovatele odlišné typy a vykonajte operáciu deaddressing.


Funkcii môžete tiež odovzdať ukazovateľ na štruktúru. Keďže označenie štruktúry je známe, stačí nám odovzdať adresu jej začiatku a kompilátor ju sám rozdelí na polia.

No a posledná otázka je, kto prišiel s týmto ukazovateľom. Aby sme porozumeli tejto problematike, musíme sa obrátiť na assembler, napríklad AVR, a tam nájdeme návod
st X, r1 ;ulož obsah r1 do SRAM na adresu X, kde X je dvojica registrov r26, r27 ld r1,X ; načítať obsah SRAM do r1 na adrese X, kde X je dvojica registrov r26, r27
Je zrejmé, že X obsahuje ukazovateľ(adresa) a ukázalo sa, že neexistuje žiadny zlý človek, ktorý prišiel s ukazovateľom, aby všetkých oklamal, práca s ukazovateľmi (adresami) je podporovaná na úrovni jadra MK.

Ukazovatele sú extrémne mocný nástroj v programovaní. Pomocou ukazovateľov sa dajú niektoré veci v programovaní značne zjednodušiť a zároveňÚčinnosť vášho programu sa výrazne zvýši. Ukazovatele dokonca umožňujú spracovávať neobmedzené množstvo údajov. Napríklad môžete použiť ukazovatele na zmenu hodnôt premenných v rámci funkcie, pričom premenné sa do funkcie odovzdajú ako parametre. Okrem toho je možné použiť ukazovatele na dynamickú alokáciu pamäte, čo znamená, že môžete písať programy, ktoré dokážu za chodu spracovať prakticky neobmedzené množstvo údajov – pri písaní programu nemusíte vedieť, koľko pamäte vopred alokovať. Toto je možno najsilnejšia funkcia ukazovateľov. Najprv získajme všeobecný prehľad o ukazovateľoch, naučíme sa, ako ich deklarovať a používať.

Čo sú ukazovatele a prečo sú potrebné?

Ukazovatele sú ako štítky, ktoré odkazujú na miesta v pamäti. Predstavte si trezor s rôznymi veľkosťami schránok vo vašej miestnej banke. Každá bunka má číslo, jedinečné číslo, ktoré je spojené iba s touto bunkou, takže môžete rýchlo identifikovať požadovanú bunku. Tieto čísla sú podobné adresám počítačových pamäťových buniek. Napríklad máte bohatého strýka, ktorý má všetky svoje cennosti vo svojom trezore. A aby si zabezpečil všetky svoje úspory, rozhodol sa mať menší trezor, do ktorého by vložil kartičku, ktorá ukazovala umiestnenie veľkého trezoru a uvádzala 16. heslo pre veľký trezor, v ktorom boli uložené skutočné šperky. Trezor s mapou v podstate uloží polohu ďalšieho trezoru. Celá táto organizácia na šetrenie šperkov je ekvivalentná ukazovateľom v C. V počítači sú ukazovatele jednoducho premenné, ktoré ukladajú adresy pamäte, zvyčajne adresy iných premenných.

Myšlienka je taká, že ak poznáte adresu premennej, môžete prejsť na túto adresu a získať v nej uložené údaje. Ak potrebujete do funkcie odovzdať obrovský kus údajov, je oveľa jednoduchšie odovzdať adresu pamäte, kde sú tieto údaje uložené, než kopírovať každý kus údajov! Okrem toho, ak program potrebuje viac pamäte, môžete požiadať systém o viac pamäte. Ako to funguje? Systém jednoducho vráti adresu miesta v pamäti a túto adresu musíme uložiť do premennej ukazovateľa. Takto môžeme interagovať s dátami zo zadaného pamäťového miesta.

Syntax ukazovateľa

Ak máme ukazovateľ, môžeme získať jeho adresu v pamäti a údaje, na ktoré odkazuje, z tohto dôvodu majú ukazovatele trochu nezvyčajnú syntax, ktorá sa líši od jednoduchých deklarácií premenných. Navyše, keďže ukazovatele nie sú obyčajné premenné, musíte kompilátoru povedať, že premenná je ukazovateľ, a povedať kompilátoru typ údajov, na ktoré ukazovateľ odkazuje. Takže ukazovateľ je deklarovaný takto:

Data_type *pointerName;

kde data_type je typ údajov, pointerName je názov ukazovateľa.

Napríklad deklarujme ukazovateľ, ktorý ukladá adresu pamäťovej bunky obsahujúcej celé číslo:

Int *integerPointer;

Všimnite si použitie symbolu * pri deklarovaní ukazovateľa. Tento znak je kľúčovým znakom v deklarácii ukazovateľa. Ak pridáte tento symbol do deklarácie premennej, bezprostredne pred názov premennej, premenná bude deklarovaná ako ukazovateľ. Okrem toho, ak deklarujete viacero ukazovateľov na rovnakom riadku, pred každým z nich musí byť hviezdička. Pozrime sa na niekoľko príkladov:

// Deklarácia ukazovateľa a jednoduchej premennej v jednom riadku int *pointer1, // toto je premenná ukazovateľa; // toto je regulárna premenná typu int // Deklarácia dvoch ukazovateľov v jednom riadku int *pointer1, // toto je pointer s názvom pointer1 *pointer2; // toto je ukazovateľ s názvom pointer2

Ako som povedal, ak pred názvom premennej nie je znak *, potom je to riadna premenná, inak je to ukazovateľ. To je presne to, čo ukazuje príklad deklarácií ukazovateľov vyššie.

Existujú dva spôsoby použitia ukazovateľa:

  1. Použite názov ukazovateľa bez symbolu *, týmto spôsobom môžete získať skutočnú adresu miesta v pamäti, na ktoré ukazovateľ odkazuje.
  2. Ak chcete získať hodnotu uloženú v pamäti, použite názov ukazovateľa so symbolom *. V kontexte ukazovateľov má symbol * technický názov: operácia dereferencie. V podstate berieme odkaz na nejakú adresu pamäte, aby sme získali skutočnú hodnotu. Možno to bude ťažké pochopiť, ale v budúcnosti sa to všetko pokúsime pochopiť.

Deklarácia ukazovateľa, získanie adresy premennej

Aby ste mohli deklarovať ukazovateľ, ktorý bude odkazovať na premennú, musíte najprv získať adresu tejto premennej. Ak chcete získať pamäťovú adresu premennej (jej umiestnenie v pamäti), musíte pred názvom premennej použiť znak &. To umožňuje zistiť adresu pamäťovej bunky, v ktorej je uložená hodnota premennej. Táto operácia sa nazýva operácia prijatia adresy a vyzerá takto:

Int var = 5; // jednoduchá deklarácia premennej s predbežnou inicializáciou int *ptrVar; // deklaroval ukazovateľ, ale zatiaľ na nič neukazuje ptrVar = // teraz náš ukazovateľ odkazuje na adresu v pamäti, kde je uložené číslo 5

IN riadok 3 bola použitá operácia odoberania adresy, zobrali sme adresu premennej var a priradili ju k ukazovateľu ptrVar. Pozrime sa na program, ktorý názorne ukáže silu ukazovateľov. Takže tu je zdroj:

#include int main() ( int var; // bežná celočíselná premenná int *ptrVar; // ukazovateľ na celé číslo (ptrVar musí byť typu int, pretože sa bude odvolávať na premennú typu int) ptrVar = // priradil ukazovateľ adresu miesto v pamäti, kde leží hodnota premennej var scanf("%d", &var); // premenná var obsahuje hodnotu zadanú z klávesnice printf("%d\n", *ptrVar); // výstup hodnotu cez ukazovateľ getchar(); )

Výsledok programu:

IN riadok 10, printf() vypíše hodnotu uloženú v premennej var. Prečo sa to deje? Nuž, pozrime sa na kód. IN riadok 5 deklarovali sme premennú var typu int. IN riadok 6— ukazovateľ ptrVar na celočíselnú hodnotu. Potom bola smerníku ptrVar priradená adresa premennej var, na to sme použili operátor priradenia adresy. Používateľ potom zadá číslo, ktoré je uložené v premennej var, nezabudnite, že ide o rovnaké miesto, na ktoré ukazuje ptrVar. Pretože ampersand používame na priradenie hodnoty premennej var in funkcie scanf(), malo by byť jasné, že scanf() inicializuje premennú var prostredníctvom adresy. Ukazovateľ ptrVar ukazuje na rovnakú adresu.

Potom v riadok 10, vykoná sa operácia „dereferencovania“ - *ptrVar. Program cez ukazovateľ ptrVar načíta adresu, ktorá je uložená v ukazovateli, prejde do požadovanej pamäťovej bunky na adrese a vráti hodnotu, ktorá je tam uložená.

Všimnite si, že vo vyššie uvedenom príklade sa pred použitím ukazovateľa najprv inicializuje, aby sa zabezpečilo, že ukazovateľ ukazuje na špecifickú adresu pamäte. Ak by sme začali používať ukazovateľ bez jeho inicializácie, odkazoval by na ľubovoľné miesto v pamäti. A to by mohlo viesť k mimoriadne nepríjemným následkom. Operačný systém napríklad pravdepodobne zabráni vášmu programu v prístupe k neznámemu pamäťovému umiestneniu, pretože operačný systém vie, že váš program nevykonáva inicializáciu ukazovateľa. V podstate to spôsobí zlyhanie programu.

Ak by boli takéto triky povolené v OS, mohli by ste pristupovať k ľubovoľnému miestu pamäte. A to znamená pre kohokoľvek spustený program môžete vykonať vlastné zmeny, napríklad ak máte dokument otvorený vo Worde, môžete programovo zmeniť ľubovoľný text. Našťastie Windows a iné moderné OS vám zabráni v prístupe k tejto pamäti a predčasne ukončí váš program.

Preto si pamätajte, že aby ste predišli zlyhaniu vášho programu, mali by ste vždy inicializovať ukazovatele pred ich použitím.

P.S.: Ak na telefóne nemáte peniaze a nie je možné ich dobiť, ale potrebujete súrne zavolať, vždy môžete použiť platbu dôvery Beeline. Sum dôvera platba môže byť veľmi rôznorodá, od 50 do 300 rubľov.

Aj keď väčšina programátorov chápe rozdiel medzi objektmi a ukazovateľmi na ne, niekedy nie je úplne jasné, aký spôsob prístupu k objektu by sa mal zvoliť. Nižšie sme sa pokúsili odpovedať na túto otázku.

Otázka

Všimol som si, že často programátori, ktorých kód som videl, používajú ukazovatele na objekty častejšie ako tieto objekty samotné, t.j. používajú napríklad nasledujúcu konštrukciu:

Object *myObject = nový objekt;

Objekt myObject;

To isté s metódami. Prečo namiesto toho:

MyObject.testFunc();

mali by sme napísať toto:

MyObject->testFunc();

Ako tomu rozumiem, zvyšuje to rýchlosť, pretože... pristupujeme priamo k pamäti. Správny? P.S. Prešiel som z Javy.

Odpoveď

Všimnite si, mimochodom, že v Java sa ukazovatele nepoužívajú explicitne, t.j. Programátor nemôže pristupovať k objektu v kóde cez ukazovateľ naň. V skutočnosti sú však v Jave všetky typy, okrem základných, referenčnými typmi: pristupuje sa k nim odkazom, hoci nie je možné explicitne odovzdať parameter odkazom. A tiež, všimnite si, nové v C++ a v Jave alebo C# sú úplne odlišné veci.

Aby ste mali predstavu o tom, aké sú ukazovatele v C++, tu sú dva podobné fragmenty kódu:

Objekt objekt1 = nový Objekt(); // Nový objekt Object object2 = new Object(); // Ďalší nový objekt objekt1 = objekt2; // Obe premenné odkazujú na objekt, na ktorý predtým odkazoval objekt2 // Ak sa objekt, na ktorý odkazuje objekt1, zmení, // objekt2 sa tiež zmení, pretože ide o ten istý objekt

Najbližší ekvivalent v C++ je:

Objekt * objekt1 = nový Objekt(); // Pamäť je alokovaná pre nový objekt // Na túto pamäť odkazuje objekt Object1 Object * object2 = new Object(); // To isté s druhým objektom delete object1; // C++ nemá systém na zber odpadu, takže ak sa tak nestane, // program už nebude mať prístup k tejto pamäti, // aspoň do reštartovania programu // Toto sa nazýva objekt úniku pamäte1 = objekt2; // Rovnako ako v Jave, objekt1 ukazuje na rovnaké miesto ako objekt2

Toto je však úplne iná vec (C++):

Objekt objekt1; // Nový objekt Objekt objekt2; // Iný objekt1 = objekt2; // Úplné skopírovanie objektu2 do objektu1, // namiesto predefinovania ukazovateľa je veľmi nákladná operácia

Získame však rýchlosť priamym prístupom k pamäti?

Presne povedané, táto otázka spája dve rôzne problémy. Po prvé: kedy by ste mali použiť dynamické prideľovanie pamäte? Po druhé: kedy by ste mali používať ukazovatele? Prirodzene, tu sa nezaobídeme bez všeobecných slov, že vždy je potrebné zvoliť najvhodnejší nástroj pre danú prácu. Takmer vždy existuje lepšia implementácia ako použitie manuálneho dynamického prideľovania (dynamická alokácia) a/alebo nespracované ukazovatele.

Dynamická distribúcia

Znenie otázky predstavuje dva spôsoby vytvorenia objektu. A hlavným rozdielom je ich životnosť (doba uloženia) v pamäti programu. Použitie objektu myObject; , spoliehate sa na automatickú celoživotnú detekciu a objekt bude zničený hneď, ako opustí svoj rozsah. Ale Object *myObject = nový objekt; udržiava objekt nažive, kým ho manuálne nevymažete z pamäte príkazom delete. Poslednú možnosť použite len vtedy, keď je to naozaj nevyhnutné. A preto Vždy zvoliť automatické určenie doby použiteľnosti predmetu, ak je to možné.

Vynútené určenie životnosti sa zvyčajne používa v nasledujúcich situáciách:

  • Potrebujete, aby objekt existoval aj po opustení jeho rozsahu- presne tento objekt, presne v tejto pamäťovej oblasti, a nie jeho kópia. Ak to pre vás nie je dôležité (vo väčšine prípadov áno), spoľahnite sa na automatické určenie životnosti. Tu je však príklad situácie, keď možno budete potrebovať prístup k objektu mimo jeho rozsahu, ale môžete to urobiť bez jeho explicitného uloženia: Zapísaním objektu do vektora môžete „prerušiť spojenie“ so samotným objektom - v skutočnosti bude k dispozícii (a nie jeho kópia) pri volaní z vektora.
  • Musíte použiť veľa pamäte, ktorý môže pretiecť zásobník. Je skvelé, ak takýto problém nemusíte riešiť (a stretávate sa s ním len zriedka), pretože je to „mimo kompetencie“ C++, no žiaľ, niekedy musíte vyriešiť aj tento problém.
  • Napríklad neviete presne veľkosť poľa, ktoré budete musieť použiť. Ako viete, v C++ majú polia pri definovaní pevnú veľkosť. To môže spôsobiť problémy napríklad pri čítaní vstupov používateľa. Ukazovateľ definuje iba oblasť v pamäti, kde bude zapísaný začiatok poľa, zhruba povedané, bez obmedzenia jeho veľkosti.

Ak je potrebné použiť dynamickú alokáciu, mali by ste ju zapuzdreť pomocou inteligentného ukazovateľa (môžete si prečítať v našom článku) alebo iného typu, ktorý podporuje frázu „Získanie zdroja sa inicializuje“ (podporujú to štandardné kontajnery - toto je fráza podľa ktorý zdroj: bloková pamäť, súbor, sieťové pripojenie a tak ďalej. - po prijatí sa inicializuje v konštruktore a potom sa deštruktorom opatrne zničí). Inteligentné ukazovatele sú napríklad std::unique_ptr a std::shared_ptr .

Smerovky

Existujú však prípady, kedy je použitie ukazovateľov opodstatnené nielen z hľadiska dynamickej alokácie pamäte, ale takmer vždy existuje alternatívny spôsob bez použitia ukazovateľov, ktorý by ste si mali zvoliť. Ako predtým, povedzme: vždy sa rozhodnite pre alternatívu, pokiaľ neexistuje konkrétna potreba použiť ukazovatele.

Medzi prípady, kedy možno zvážiť použitie ukazovateľov ako možnú možnosť, patria:

  • Referenčná sémantika. Niekedy môže byť potrebné pristupovať k objektu (bez ohľadu na to, ako je preň alokovaná pamäť), pretože chcete pristupovať k funkciám v tomto objekte, a nie k jeho kópii – t.j. keď potrebujete implementovať pass by reference. Vo väčšine prípadov tu však stačí použiť odkaz a nie ukazovateľ, pretože na to sú odkazy vytvorené. Všimnite si, že ide o mierne odlišné veci od toho, čo bolo opísané v bode 1 vyššie. Ale ak máte prístup ku kópii objektu, potom nie je potrebné použiť odkaz (ale uvedomte si, že kopírovanie objektu je nákladná operácia).
  • Polymorfizmus. Volanie funkcií v rámci polymorfizmu (trieda dynamických objektov) je možné pomocou odkazu alebo ukazovateľa. Opäť je výhodné použiť odkazy.
  • Voliteľný objekt. V tomto prípade môžete použiť nullptr na označenie, že objekt je vynechaný. Ak je to argument funkcie, potom je lepšie ho implementovať s predvolenými argumentmi alebo preťažením. Prípadne môžete použiť typ, ktorý toto správanie zapuzdruje, ako napríklad boost::voliteľné (upravené v C++14 std::voliteľné).
  • Zlepšenie rýchlosti kompilácie. Možno budete musieť oddeliť kompilačné jednotky (kompilačné jednotky). Jedným z účinných spôsobov použitia ukazovateľov je predbežná deklarácia (pretože ak chcete použiť objekt, musíte ho najskôr definovať). To vám umožní rozmiestniť kompilačné jednotky, čo môže mať pozitívny vplyv na zrýchlenie času kompilácie, čím sa výrazne zníži čas strávený týmto procesom.
  • Interakcia s knižnicouC alebo C-ako. Tu budete musieť použiť neupravené ukazovatele a uvoľniť z nich pamäť na poslednú chvíľu. Surový ukazovateľ môžete získať z inteligentného ukazovateľa, napríklad pomocou operácie get. Ak knižnica používa pamäť, ktorá sa musí neskôr uvoľniť manuálne, môžete deštruktor umiestniť do inteligentného ukazovateľa.