Tehted osutitega. Tehted osutitega C-s eristatakse järgmist tüüpi viiteid:

Sildid: C osuti. Osuti osutile. Osuti tüüp. Osuti aritmeetika. Osutajate võrdlus.

Viidad

See on võib-olla kogu kursuse kõige raskem ja kõige olulisem teema. Ilma näpunäidete mõistmiseta on C edasine uurimine mõttetu. Viidad on väga lihtne kontseptsioon, väga loogiline, kuid nõuavad tähelepanu detailidele.

Definitsioon

Indeks on muutuja, mis salvestab mäluala aadressi. Osutil, nagu ka muutujal, on tüüp. Osutajate deklareerimise süntaks

<тип> *<имя>;

Näiteks
ujuk *a;
pikk pikk *b;
Kaks peamist osutioperaatorit on &-operaator ja *-de viitamise operaator. Vaatame lihtsat näidet.

#kaasa #kaasa void main() ( int A = 100; int *p; //Hangi muutuja A aadressi p = //Väljastab muutuja A aadressi printf("%p\n", p); //Väljustab muutuja A aadressi muutuja A printf("% d\n", *p //Muuda muutuja A sisu *p = 200 printf("%d\n", A); getch();

Vaatame koodi uuesti hoolikalt

Int A = 100;

Muutuja nimega A. See asub mälus mingil aadressil. Väärtus 100 salvestatakse sellele aadressile.

Loodud tüübikursor int.

Nüüd muutuja lk salvestab muutuja aadressi A. Operaatori * abil pääseme juurde muutuja sisule A.
Sisu muutmiseks kirjuta

*p = 200;

Pärast seda väärtus A samuti muutunud, kuna see osutab samale mälupiirkonnale. Ei midagi keerulist.
Nüüd veel üks oluline näide

#kaasa #kaasa void main() ( int A = 100; int *a = double B = 2,3; double *b = printf("%d\n", suurus(A)); printf("%d\n", sizeof(a )); printf("%d\n", printf("%d\n", sizeof(b));

Kuvatakse
4
4
8
4
Kuigi muutujatel on erinev tüüp ja suurus, on neile osutavad osutid sama suurusega. Tõepoolest, kui osutid salvestavad aadresse, peavad need olema täisarvu tüüpi. See on õige, kursor ise on salvestatud muutujasse nagu suurus_t(ja ka ptrdiff_t), see on tüüp, mis käitub nagu täisarv, kuid selle suurus sõltub süsteemi bitivõimsusest. Enamikul juhtudel pole nende vahel vahet. Miks siis kursoril tüüpi on vaja?

Osuti aritmeetika

Esiteks vajab kursor tüüpi, et viitamise eemaldamise toiming (sisu vastuvõtmine aadressi järgi) õigesti töötaks. Kui osuti salvestab muutuja aadressi, peate teadma, mitu baiti peate kogu muutuja saamiseks võtma alates sellest aadressist.
Teiseks toetavad osutid aritmeetilisi tehteid. Nende tegemiseks peate teadma suurust.
Toiming +N liigutab kursorit N*sizeof(type) baidi võrra edasi.
Näiteks kui kursor int *p; salvestab aadressi CC02, siis pärast p += 10; see salvestab aadressi CC02 + sizeof(int)*10 = CC02 + 28 = CC2A (kõik toimingud tehakse kuueteistkümnendsüsteemis). Oletame, et oleme loonud osuti massiivi algusesse. Seejärel saame selle massiivi kaudu "liikuda", pääsedes juurde üksikutele elementidele.

#kaasa #kaasa void main() ( int A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int *p; p = A; printf("%d\n", *p) p++; printf("%d\n", *p = p + 4;

Pange tähele, kuidas saime massiivi esimese elemendi aadressi

Massiiv on sisuliselt ise osuti, nii et te ei pea kasutama &-operaatorit. Saame näite ümber kirjutada erinevalt

Hankige esimese elemendi aadress ja liikuge selle suhtes massiivi kaudu.
Lisaks operaatoritele + ja - toetavad võrdlustoiminguid osutajad. Kui meil on kaks osutit a ja b, siis a > b, kui aadress, mis salvestab a, on suurem kui aadress, mis salvestab b.

#kaasa #kaasa void main() ( int A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int *a, *b; a = b = printf("&A == %p\ n", a); printf("&A == %p\n", b); if (a< b) { printf("a < b"); } else { printf("b < a"); } getch(); }

Kui osutid on võrdsed, osutavad need samale mälualale.

Kursor kursorile

Indeks salvestab mäluala aadressi. Saate luua kursori kursori, seejärel salvestab see kursori aadressi ja pääseb juurde selle sisule. Osuti osutile on määratletud kui

<тип> **<имя>;

Ilmselgelt ei takista miski loomast kursorit kursorile kursorile ja kursorit kursorile kursorile ja nii edasi. Vajame seda kahe- ja mitmemõõtmeliste massiividega töötamisel. Siin on lihtne näide sellest, kuidas saate kursoriga kursorile töötada.

#kaasa #kaasa #define SIZE 10 void main() ( int A; int B; int *p; int **pp; A = 10; B = 111; p = pp = printf("A = %d\n", A); *p = 20 printf("A = %d\n", A *(*pp) = 30 //siin pole sulgusid printf("A = %d\n", A = printf); ("B = %d\n", *p **pp = 333; printf("B = %d", B);

Osutajad ja tüübivalu

Kuna kursor salvestab aadressi, saate selle üle kanda teisele tüübile. Seda võib vaja minna näiteks siis, kui tahame muutujast osa võtta või kui teame, et muutuja salvestab meile vajaliku tüübi.

#kaasa #kaasa #define SIZE 10 void main() ( int A = 10; int *intPtr; char *charPtr; intPtr = printf("%d\n", *intPtr); printf("---------- ----------\n"); charPtr = (char*)intPtr; printf("%d", *charPtr); charPtr++; printf("%d", *charPtr); charPtr++; printf ("%d ", *charPtr) printf("%d ", *charPtr);

Selles näites kasutame ära asjaolu, et suuruse tüüp int on 4 baiti ja char 1 bait. Tänu sellele saate pärast esimese baidi aadressi saamist läbida numbri ülejäänud baidid ja kuvada nende sisu.

NULL osuti - null osuti

Enne lähtestamist salvestab indeks prügi, nagu iga muu muutuja. Kuid samal ajal võib see "prügi" osutuda kehtivaks aadressiks. Olgu meil näiteks osuti. Kuidas ma saan teada, kas see on lähtestatud või mitte? Üldiselt mitte mingil juhul. Selle probleemi lahendamiseks võeti kasutusele stdlib teegi NULL makro.
Kui osuti määratlemisel ei ole konkreetse väärtusega initsialiseeritud, on tavaks muuta see võrdseks väärtusega NULL.

Int *ptr = NULL;

Standardi järgi on garanteeritud, et sel juhul on osuti võrdne NULL, ja on null ning seda saab kasutada tõeväärtusena vale. Kuigi olenevalt teostusest NULL ei pruugi olla võrdne 0-ga (selles mõttes, et see ei ole bitipõhises esituses võrdne nulliga, näiteks int või ujuk).
See tähendab, et antud juhul

Int *ptr = NULL; kui (ptr == 0) ( ... )

üsna korrektne toimimine ja juhul

Int a = 0; kui (a == NULL) ( ... )

käitumine pole määratletud. See tähendab, et kursorit saab võrrelda nulliga või nulliga NULL, aga sa ei saa NULL võrrelda täisarvu või ujukoma tüüpi muutujaga.

#kaasa #kaasa #kaasa void main() ( int *a = NULL; märgita pikkus, i; printf("Sisesta massiivi pikkus: "); scanf("%d", &length); if (pikkus > 0) ( //Kui mälu on eraldatud , tagastab osuti //Kui mälu ei eraldatud, tagastatakse NULL if ((a = (int*) malloc(length * sizeof(int))) != NULL) ( for (i = 0; i).< length; i++) { a[i] = i * i; } } else { printf("Error: can"t allocate memory"); } } //Если переменая была инициализирована, то очищаем её if (a != NULL) { free(a); } getch(); }

Näited

Nüüd mõned näited osutitega töötamise kohta
1. Käime läbi massiivi ja leiame kõik paariselemendid.

#kaasa #kaasa void main() ( int A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int paaris; int paarisloendur = 0; int *iter, *end; //iter salvestab aadress massiivi esimene element //end salvestab massiivi järgmise "elemendi" aadressi pärast viimast massiivi jaoks (iter = A, end = iter< end; iter++) { if (*iter % 2 == 0) { even = *iter; } } //Выводим задом наперёд чётные числа for (--evenCounter; evenCounter >= 0; evenCounter--) ( printf("%d ", paaris); ) getch(); )

2. Kui sorteerime elemente, peame neid sageli teisaldama. Kui objekt võtab palju ruumi, on kahe elemendi vahetamine kulukas. Selle asemel saate luua algsetele elementidele viitavate massiivi ja seda sorteerida. Kuna osutite suurus on väiksem kui sihtmassiivi elementide suurus, on sortimine kiirem. Lisaks ei muudeta massiivi, mis on sageli oluline.

#kaasa #kaasa #define SIZE 10 void main() ( topelt sorteerimata = (1.0, 3.0, 2.0, 4.0, 5.0, 6.0, 8.0, 7.0, 9.0, 0.0); double *p; topelt *tmp; märgilipp = 1; märgita i; printf("sortimata massiiv\n"; jaoks (i = 0; i< SIZE; i++) { printf("%.2f ", unsorted[i]); } printf("\n"); //Сохраняем в массив p адреса элементов for (i = 0; i < SIZE; i++) { p[i] = &unsorted[i]; } do { flag = 0; for (i = 1; i

3. Huvitavam näide. Kuna tähetüübi suurus on alati 1 bait, siis saab sellega realiseerida vahetusoperatsiooni – kahe muutuja sisu vahetamist.

Peatage sellel saidil AdBlock.

Osuti on muutuja, mis salvestab arvuti mällu objekti aadressi, näiteks teise muutuja. Oleme muutujaaadressidega juba varem kokku puutunud, kui uurisime funktsiooni scanf.

Niisiis, lähme järjekorras. Osuti deklaratsioon.

Osuti deklaratsioon erineb muutuja deklaratsioonist ainult selle poolest, et lisab tüübinime järele sümboli *. Näited:

Nimekiri 1.

int * p_g; // osuti int tüüpi muutujale double * p_f; // osuti double tüüpi muutujale

Saate määrata kursorile aadressi määramisoperaatori abil. Näited:

Nimekiri 2.

int n = 100; kahekordne PI = 3,1415926; int * p_k; // osuti int tüüpi muutujale double * p_pi; // osuti muutujale tüüp double p_k = // saada muutuja n aadress ja määrata see osutile p_k p_pi = // saada muutuja PI aadress ja määrata see osutile p_pi

Osuti väärtuse kuvamiseks ekraanil tuleb kasutada %p modifikaatorit funktsioonis printf. Näide:

Nimekiri 3.

printf("adress peremennoi PI %p\n", p_pi);

Kasutades muutuja aadressi, mis on salvestatud osutisse, saate muuta selle muutuja väärtust. Selleks kasutage viitamise tühistamise toimingut *. Siin on näide:

Nimekiri 4.

#kaasa int main(void) ( int a = 100; int * p_a = // salvestab muutuja a aadressi osutisse printf("a = %d\n", a); // standardne viis muutuja väärtuse saamiseks a printf("a = %d\n", *p_a // saada muutuja a väärtus sellele osutiga // kasutades kursorit p_a, kirjutada muutujasse a *p_a = 50; a = %d\n", *p_a ); tagasta 0; )

Joon.1 Juurdepääs muutujale kursori kaudu

Kokku kasutatakse tähist * osutajate puhul kahel juhul:

  • osuti deklareerimisel näidata, et see on osuti;
  • kui tahame pääseda juurde muutujale, millele kursor osutas.

Samuti on olemas nn null pointerNULL. Null osuti ei viita kuhugi. Seda kasutatakse osutite nullimiseks. Vaata näidet.

Nimekiri 5.

#kaasa int main(void) ( int a = 100; int * p_a = // salvestab muutuja a aadressi osutisse printf("a = %d\n", a); // standardne viis muutuja väärtuse saamiseks a printf("a = %d\n", *p_a // saada muutuja a väärtus sellele osutiga // kasutades kursorit p_a, kirjutada muutujasse a *p_a = 50; a = %d\n", *p_a ); printf("%p\n", p_a); p_a = NULL; printf("%p\n", p_a);

Joon.2 Kursori nullimine

Kodu

C-keel näidetega

Funktsioonid C-s

Milliseid funktsioone kasutatakse C-s?

C-i funktsioone kasutatakse konkreetsete toimingute tegemiseks üldprogrammis. Programmeerija ise otsustab, milliseid toiminguid funktsioonides kuvada. Eriti mugav on kasutada funktsioone korduvate toimingute jaoks.

Lihtne näide funktsioonist C-s

Funktsiooni näide C-s:

See on väga lihtne C-programm. See lihtsalt prindib rea "Functions in C". Programmil on üks funktsioon nimega main. Vaatame seda funktsiooni üksikasjalikumalt. Funktsiooni päises, st. rivis

int on funktsiooni tagastustüüp;

main on funktsiooni nimi;

(void) on funktsiooni argumentide loend. Sõna void näitab, et funktsioonil pole argumente;

return on lause, mis lõpetab funktsiooni täitmise ja tagastab funktsiooni tulemuse punkti, kus funktsioon kutsuti;

EXIT_SUCCESS on väärtus, mis võrdub nulliga. See on määratletud failis stdlib.h;

osa funktsioonist pärast päist, ümbritsetud lokkis sulgudega

{
puts("Funktsioonid C-s");
tagasta EXIT_SUCCESS;
}

nimetatakse funktsiooni kehaks.

Seega, kui töötame funktsiooniga, peame märkima funktsiooni nime, meie jaoks on see main, funktsiooni poolt tagastatava väärtuse tüübi, meie jaoks on see int, esitage pärast funktsiooni sulgudes argumentide loend. funktsiooni nimi, meil pole argumente, seega kirjutame void, täidame funktsiooni kehasse mõned toimingud (mille huvides funktsioon loodi) ja tagastame funktsiooni tulemuse, kasutades return-lauset. Siin on põhitõed, mida peate C-i funktsioonide kohta teadma.

Kuidas kutsuda C-s ühest funktsioonist teist funktsiooni?

Vaatame näidet C-i funktsioonide kutsumisest:

Käitame selle ja saame:

See näide loob summafunktsiooni, mis liidab kaks täisarvu ja tagastab tulemuse. Vaatame üksikasjalikult selle funktsiooni ülesehitust.

summa funktsiooni päis:

int summa(int a, int b)

siin int on funktsiooni tagastustüüp;

summa on funktsiooni nimi;

(int a, int b) - funktsiooni nime järel sulgudes on selle argumentide loend: esimene argument on int a, teine ​​argument on int b. Argumentide nimetused on formaalsed, s.t. Funktsiooni kutsumisel ei pea me selle funktsiooni argumentidena saatma muutujate a ja b väärtusi. Põhifunktsioonis kutsume summafunktsiooni järgmiselt: summa(d, e);. Kuid on oluline, et funktsioonile edastatavad argumendid vastaksid funktsioonis deklareeritud tüübile.

Summafunktsiooni kehas, s.o. Funktsiooni päise järel olevate lokkis sulgude sees loome kohaliku muutuja int c, omistame sellele a pluss b summa väärtuse ja tagastame selle funktsiooni tulemuseks koos return-lausega.

Nüüd vaatame, kuidas põhifunktsioonist summafunktsiooni kutsutakse.

Siin on põhifunktsioon:

Kõigepealt loome kaks int muutujat

int d = 1; int e = 2;

Edastame need argumendi väärtustena summafunktsioonile.

int f = summa(d, e);

selle väärtus saab olema summafunktsiooni tulemus, st. kutsume funktsiooniks summa, mis tagastab int väärtuse, mille omistame muutujale f. Argumentidena edastame d ja f. Aga funktsiooni päise summas

int summa(int a, int b)

argumente nimetatakse a ja b, miks me siis d ja f läbime? Sest formaalsed argumendid kirjutatakse funktsiooni päisesse, st. Argumentide nimed EI OLE olulised, kuid nende tüübid on olulised. Summafunktsioonil on mõlemad int tüüpi argumendid, mis tähendab, et selle funktsiooni kutsumisel tuleb edastada kaks int tüüpi argumenti mis tahes nimedega.

Veel üks peensus. Funktsioon tuleb enne selle esmakordset väljakutsumist deklareerida. Meie näites juhtus nii: kõigepealt deklareeritakse summafunktsioon ja alles siis kutsume seda põhifunktsioonist välja. Kui funktsioon deklareeritakse pärast seda, kus seda kutsutakse, tuleks kasutada funktsiooni prototüüpi.

Funktsiooni prototüüp C-s

Vaatame funktsiooni näidet C-s:

Selles näites on summafunktsioon defineeritud allpool, kus seda põhifunktsioonis kutsutakse.

Sel juhul peate kasutama summafunktsiooni prototüüpi. Meie prototüüp on deklareeritud põhifunktsiooni kohal:

int summa(int a, int b);

Prototüüp on funktsiooni päis, mis lõpeb semikooloniga. Prototüüp on funktsiooni deklaratsioon, mis määratletakse allpool. Täpselt seda me tegime: kuulutasime funktsiooni prototüübi

int f = summa(d, e);

ja põhifunktsiooni all määratleme summafunktsiooni, mis oli eelnevalt prototüübis deklareeritud:

Mille poolest erineb funktsiooni deklaratsioon C-s funktsiooni definitsioonist C-s?

Kui kirjutame funktsiooni prototüübi, näiteks järgmiselt:

int summa(int a, int b);

siis deklareerime funktsiooni.

Ja kui me rakendame funktsiooni, st. Kirjutame üles mitte ainult pealkirja, vaid ka funktsiooni keha, näiteks:

siis defineerime funktsiooni.

tagastusavaldus

Tagastuslause lõpetab funktsiooni C-s ja tagastab selle operatsiooni tulemuse kõnepunkti. Näide:

Seda funktsiooni saab lihtsustada:

siin tagastab return lause summa a + b väärtuse.

Ühes funktsioonis võib olla mitu tagastuslauset. Näide:

Kui näites on argumendi a väärtus suurem kui kaks, tagastab funktsioon nulli (esimene juhtum) ja kõik, mis jääb kommentaarile “// Esimene juhtum;” ei hukata.

Osutajad C-keeles

Kui a on väiksem kui kaks, kuid b on väiksem kui null, siis funktsioon lõpetab oma töö ja kõik, mis jääb kommentaarile “// Teine juhtum;” ei hukata.

Ja ainult siis, kui mõlemad eelnevad tingimused ei ole täidetud, jõuab programmi täitmine viimase tagastamislauseni ja tagastatakse summa a + b.

Funktsiooni argumentide edastamine väärtuse järgi

Argumente saab C-funktsioonile edastada väärtuse järgi. Näide:

Näites loome põhifunktsioonis muutuja int d = 10. Anname selle muutuja väärtuse järgi edasi funktsioonile sum(d). Summafunktsiooni sees suurendatakse muutuja väärtust 5 võrra. Põhifunktsioonis aga d väärtus ei muutu, kuna see läks väärtusega edasi. See tähendab, et edasi anti muutuja väärtus, mitte muutuja ise. Seda tõendab programmi tulemus:

need. pärast summafunktsioonist naasmist d väärtus ei muutunud, samas kui summafunktsiooni sees see muutus.

C-funktsiooni osutite edastamine

Kui annate sellele muutujale osuti funktsiooni argumendina muutuja väärtuse asemel, võib selle muutuja väärtus muutuda. Näiteks võtame programmi eelmisest jaotisest, muutes seda veidi:

Selles programmi versioonis lülitusin argumendi edastamiselt väärtuste järgi kursori edastamisele muutujale. Vaatame seda punkti lähemalt.

printf("summa = %d\n", summa(&d));

Summafunktsioonile ei edastata mitte muutuja d väärtus, mis on võrdne 10-ga, vaid selle muutuja aadress, näiteks:

Vaatame nüüd summafunktsiooni:

Selle argument on viide int. Teame, et osuti on muutuja, mille väärtus on mõne objekti aadress. Muutuja d aadress saadetakse summafunktsioonile:

Summa sees on osuti int *a viide tühistatud. See võimaldab meil liikuda kursori juurest muutuja enda juurde, millele meie kursor osutab. Ja meie puhul on selleks muutuja d, st. väljendus

on samaväärne väljendiga

Tulemus: summafunktsioon muudab muutuja d väärtust:

Seekord muutub d väärtus pärast summast naasmist, mida eelmises lõigus argumendi väärtuse järgi edastamisel ei täheldatud.

C/C++ programmis Eclipse

Tegin kõik selle artikli näited Eclipse'is. Siit leiate teavet selle kohta, kuidas Eclipse'is C/C++-ga töötada. Kui töötate teises keskkonnas, toimivad näited ka seal.

Sildid: C osuti. Osuti osutile. Osuti tüüp. Osuti aritmeetika. Osutajate võrdlus.

Viidad

See on võib-olla kogu kursuse kõige raskem ja kõige olulisem teema. Ilma näpunäidete mõistmiseta on C edasine uurimine mõttetu. Viidad on väga lihtne kontseptsioon, väga loogiline, kuid nõuavad tähelepanu detailidele.

Definitsioon

Osuti on muutuja, mis salvestab mälukoha aadressi.

Teema 7. Osutajad C-s.

Osutil, nagu ka muutujal, on tüüp. Osutajate deklareerimise süntaks

<тип> *<имя>;

Näiteks
Kaks peamist osutioperaatorit on &-operaator ja *-de viitamise operaator. Vaatame lihtsat näidet.

#kaasa #kaasa void main() ( int A = 100; int *p; //Hangi muutuja A aadressi p = //Väljastab muutuja A aadressi printf("%p\n", p); //Väljustab muutuja A aadressi muutuja A printf("% d\n", *p //Muuda muutuja A sisu *p = 200 printf("%d", *p);

Vaatame koodi uuesti hoolikalt

Muutuja nimega A. See asub mälus mingil aadressil. Väärtus 100 salvestatakse sellele aadressile.

Loodud tüübikursor int.

Nüüd muutuja lk salvestab muutuja aadressi A. Operaatori * abil pääseme juurde muutuja sisule A.
Sisu muutmiseks kirjuta

Pärast seda väärtus A samuti muutunud, kuna see osutab samale mälupiirkonnale. Ei midagi keerulist.
Nüüd veel üks oluline näide

#kaasa #kaasa void main() ( int A = 100; int *a = double B = 2,3; double *b = printf("%d\n", suurus(A)); printf("%d\n", sizeof(a )); printf("%d\n", printf("%d\n", sizeof(b));

Kuvatakse
Kuigi muutujatel on erinev tüüp ja suurus, on neile osutavad osutid sama suurusega. Tõepoolest, kui osutid salvestavad aadresse, peavad need olema täisarvu tüüpi. See on õige, osuti ise salvestatakse muutujasse nagu suurus_t(ja ka ptrdiff_t), see on tüüp, mis käitub nagu täisarv, kuid selle suurus sõltub süsteemi bitivõimsusest. Enamikul juhtudel pole nende vahel vahet. Miks siis osutil tüüpi on vaja?

Osuti aritmeetika

Esiteks vajab kursor tüüpi, et viitamise eemaldamise toiming (sisu saamine aadressi järgi) õigesti töötaks. Kui osuti salvestab muutuja aadressi, peate teadma, mitu baiti peate kogu muutuja saamiseks võtma alates sellest aadressist.
Teiseks toetavad osutid aritmeetilisi tehteid.

Nende tegemiseks peate teadma suurust.
Toiming liigutab kursorit baidi kaupa edasi.
Näiteks kui kursor int *p; salvestab aadressi CC02, siis pärast p += 10; see salvestab aadressi CC02 + sizeof(int)*10 = CC02 + 28 = CC2A (kõik toimingud tehakse kuueteistkümnendsüsteemis). Oletame, et oleme loonud osuti massiivi algusesse. Seejärel saame selle massiivi kaudu liikuda, pääsedes juurde üksikutele elementidele.

#kaasa #kaasa void main() ( int A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int *p; p = A; printf("%d\n", *p) p++ printf("%d\n", *p = p + 4;"%d\n";

Pange tähele, kuidas saime massiivi esimese elemendi aadressi

Massiiv on sisuliselt ise osuti, nii et te ei pea kasutama &-operaatorit. Saame näite ümber kirjutada erinevalt

Hankige esimese elemendi aadress ja liikuge selle suhtes massiivi kaudu.
Lisaks operaatoritele + ja - toetavad võrdlustoiminguid osutajad. Kui meil on kaks osutit a ja b, siis a > b, kui aadress, mis salvestab a, on suurem kui aadress, mis salvestab b.

#kaasa #kaasa void main() ( int A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int *a, *b; a = b = printf(“&A == %p\ n", a); printf("&A == %p\n", b);< b) { printf(«a < b»); } else { printf(«b < a»); } getch(); }

Kui osutid on võrdsed, osutavad need samale mälualale.

Kursor kursorile

Kursor salvestab mäluala aadressi. Saate luua kursori kursori, seejärel salvestab see kursori aadressi ja pääseb juurde selle sisule. Osuti osutile on määratletud kui

<тип> **<имя>;

Ilmselgelt ei takista miski loomast kursorit kursorile kursorile ja kursorit kursorile kursorile ja nii edasi. Vajame seda kahe- ja mitmemõõtmeliste massiividega töötamisel. Siin on lihtne näide sellest, kuidas saate kursoriga kursorile töötada.

#kaasa #kaasa #define SIZE 10 void main() ( int A; int B; int *p; int **pp; A = 10; B = 111; p = pp = printf("A = %d\n", A); *p = 20 printf("A = %d\n", A = printf("B = %d\n", *p = 333); B);

Osutajad ja tüübivalu

Kuna kursor salvestab aadressi, saate selle üle kanda teisele tüübile.

Seda võib vaja minna näiteks siis, kui tahame muutujast osa võtta või kui teame, et muutuja salvestab meile vajaliku tüübi.

#kaasa #kaasa #define SIZE 10 void main() ( int A = 10; int *intPtr; char *charPtr; intPtr = printf("%d\n", *intPtr); printf("————————\n" charPtr = (char*)intPtr ("%d ", *charPtr++);

Selles näites kasutame ära asjaolu, et suuruse tüüp int on 4 baiti ja char 1 bait. Tänu sellele saate pärast esimese baidi aadressi saamist läbida numbri ülejäänud baidid ja kuvada nende sisu.

NULL osuti - null osuti

Enne lähtestamist salvestab kursor prügi, nagu iga muu muutuja. Kuid samal ajal võib see "prügi" osutuda kehtivaks aadressiks. Olgu meil näiteks osuti. Kuidas ma saan teada, kas see on lähtestatud või mitte? Üldiselt mitte mingil juhul. Selle probleemi lahendamiseks võeti kasutusele stdlib teegi NULL makro.
Kui osuti määratlemisel ei ole konkreetse väärtusega initsialiseeritud, on tavaks muuta see võrdseks väärtusega NULL.

int *ptr = NULL;

Standardi järgi on garanteeritud, et sel juhul on osuti võrdne NULL, ja on null ning seda saab kasutada tõeväärtusena vale. Kuigi olenevalt teostusest NULL ei pruugi olla võrdne 0-ga (selles mõttes, et see ei ole bitipõhises esituses võrdne nulliga, näiteks int või ujuk).
See tähendab, et antud juhul

int *ptr = NULL; kui (ptr == 0) ( … )

üsna korrektne toimimine ja juhul

int a = 0; kui (a == NULL) ( … )

käitumine pole määratletud. See tähendab, et kursorit saab võrrelda nulliga või nulliga NULL, aga sa ei saa NULL võrrelda täisarvu või ujukoma tüüpi muutujaga.

#kaasa #kaasa #kaasa void main() ( int *a = NULL; märgita pikkus, i; printf("Sisesta massiivi pikkus: "); scanf("%d", &length); if (pikkus > 0) ( //Kui mälu on eraldatud , tagastab osuti //Kui mälu ei eraldatud, tagastatakse NULL if ((a = (int*) malloc(length * sizeof(int))) != NULL) ( for (i = 0; i).< length; i++) { a[i] = i * i; } } else { printf(«Error: can’t allocate memory»); } } //Если переменая была инициализирована, то очищаем её if (a != NULL) { free(a); } getch(); }

Näited

Nüüd mõned näited osutitega töötamise kohta
1. Käime läbi massiivi ja leiame kõik paariselemendid.

#kaasa #kaasa void main() ( int A = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int paaris; int paarisloendur = 0; int *iter, *end; //iter salvestab aadress massiivi esimene element //end salvestab massiivi järgmise "elemendi" aadressi pärast viimast massiivi jaoks (iter = A, end = iter< end; iter++) { if (*iter % 2 == 0) { even = *iter; } } //Выводим задом наперёд чётные числа for (—evenCounter; evenCounter >= 0; evenCounter—) ( printf("%d ", paaris); ) getch(); )

2. Kui sorteerime elemente, peame neid sageli teisaldama. Kui objekt võtab palju ruumi, on kahe elemendi vahetamine kulukas. Selle asemel saate luua algsetele elementidele viitavate massiivi ja seda sorteerida. Kuna osutite suurus on väiksem kui sihtmassiivi elementide suurus, on sortimine kiirem. Lisaks ei muudeta massiivi, mis on sageli oluline.

#kaasa #kaasa #define SIZE 10 void main() ( topelt sorteerimata = (1.0, 3.0, 2.0, 4.0, 5.0, 6.0, 8.0, 7.0, 9.0, 0.0); double *p; topelt *tmp; märgilipp = 1; märgita i; printf("sortimata massiiv\n"; jaoks (i = 0; i< SIZE; i++) { printf(«%.2f «, unsorted[i]); } printf(«\n»); //Сохраняем в массив p адреса элементов for (i = 0; i < SIZE; i++) { p[i] = &unsorted[i]; } do { flag = 0; for (i = 1; i

3. Huvitavam näide. Kuna tähetüübi suurus on alati 1 bait, siis saab sellega realiseerida vahetusoperatsiooni – kahe muutuja sisu vahetamist.

#kaasa #kaasa #kaasa void main() ( int pikkus; char *p1, *p2; char tmp; float a = 5.0f; float b = 3.0f; printf("a = %.3f\n", a); printf("b = %.3f\n", b); p1 = (char*) p2 = (char*) // Uurige, mitu baiti liigutada long = sizeof(float) ( // Muutujate sisu vahetamine byte byte = * p1 = *p2 //ära unusta edasi liikuda p1++ printf(“a = %.3f\n”, a;

Selles näites saate muuta muutujate tüüpi a Ja b sisse kahekordne või mõni muu (koos vastava muudatusega väljundis ja kõnes suurus), vahetame ikkagi kahe muutuja baite.

4. Otsige üles kasutaja sisestatud stringi pikkus, kasutades kursorit #include #kaasa void main() ( char puhver; char *p; märgita pikkus = 0; scanf("%127s", puhver); p = puhver; while (*p != '\0') ( p++; pikkus++; ) printf( "pikkus = %d", pikkus();

Pöörake tähelepanu koodi jaotisele

while (*p != ‘\0’) ( p++; pikkus++; )

seda saab ümber kirjutada

while (*p != 0) ( p++; pikkus++; ) või while (*p) ( p++; pikkus++; )

või eemaldades tingimuse juurdekasvu

while (*p++) ( pikkus++; )

ru-Cyrl18-õpetusSypachev [e-postiga kaitstud]

Osuti on spetsiaalne muutuja, mis salvestab teise muutuja aadressi. Osuti deklareeritakse järgmiselt: tüüp* muutuja; Kus tüüp- mis tahes kehtiv liht- või liitpõhiosutitüüp.

Oletame näiteks, et deklareeritakse tavaline muutuja int t; Deklareerimine ja initsialiseerimine int* p= tähenda järgmist. Muutuvas lk Salvestatakse mitte programmi poolt töödeldud täisarv (õpilase hinne, toodetud toodete arv jne), vaid lahtri aadress, mis sisaldab määratud tüüpi teavet (täisarv). Aadressi all peame silmas muutujale eraldatud RAM-i sektsiooni esimese baidi numbrit. Ilma osutita muutujate puhul jätab süsteem ilma täiendava deklaratsioonita ka aadressi meelde ja selle saab kasutada kasutades ja operatsioonid (aadressi eemaldamine), Näiteks , &t. See ühekordne operatsioon, mida mõnikord nimetatakse ka aadressivõtuks, ei tee muutuja väärtusega midagi. t.

Enne esimest kasutamist peab osuti muutuja olema initsialiseeritud. Kuni me ei määratle kursori väärtust, viitab see millelegi juhuslikule mälus ja selle kasutamine võib viia ettearvamatute tulemusteni.

Üks meetoditest on näidatud ülal ja tähendab, et muutujas lk raku aadress on paigutatud t. Oluline on sellest aru saada int* p= samaväärne int* p; p=&t; ja mitte *p=&t; See on üks näpunäidete õppimise algetapi raskusi. Selle teema muudab veelgi keerulisemaks asjaolu, et sama sümbol " & ” kasutatakse viitetüüpi muutuja deklareerimisel.

Osutajad C-s.

Siin see sümbol määratleb aadressivõtu operatsioon muutuja jaoks ja sellel pole viitetüübiga mingit pistmist.

Pange tähele, et tühikute paigutus osutite deklareerimisel on vaba. Vastuvõetavad on ka järgmised kanded: int * p= &t; int *p= Eelistada tuleks lõigu alguses olevaid sissekandeid, millest on lihtsam aru saada indeksi tähendusest. Muutuja deklareeritakse lk, mitte *lk, ja lisaks tüüp on int*, mitte int.

Kui korraga deklareeritakse mitu osutit, tuleb iga muutuja ette kirjutada sümbol “*”: ujuki* q1, *q2;

Lahtri sisu, mille aadress asub lk, on programmi tekstis tähistatud tähisega operatsioonid viitamise mahavõtmine . See kasutab sama "*" sümbolit nagu kursori muutuja deklareerimisel. See ühekordne tehe tagastab määratud aadressil asuva muutuja väärtuse. Sellepärast *lk on programmi poolt töödeldud täisarv, mis asub lahtris, mille aadress on osuti muutujas lk. Võttes arvesse initsialiseerimist ( p = &t) *lk Ja t- see on sama tähendus. See tähendab, et kui kasutate cin>>t; sisestame näiteks numbri 2 ja käivitame *p*=5; või *p=*p*5; siis väärtus muutub t, kuigi ilmset muutust ei paistnud olevat. Seetõttu operaator cout<< t; väljastab numbri 10 (2*5). Ja vastupidi, muutudes t(Näiteks t++;), muudame seeläbi väärtust *lk. Kasutades cout<<(*p); trükime 11.

Tähistame ülaltoodut järgmiselt:

lk(või &t) *lk(või t)

"Vasak ristkülik" (mälulahter) sisaldab aadressi ja "parem" lahter sisaldab töödeldavat täisarvu.

Siin käsitletavad toimingud "&" ja "*" on unaarsed ja neil on kõrgem prioriteet kui sarnased kahendtehted "bit ja" ning aritmeetiline korrutamine.

Sest *lk Määratletakse samad toimingud, mis määratud tüüpi muutuja puhul, kuid täisarvude puhul. Seetõttu on vastuvõetavad näiteks järgmised operaatorid: a) cin>>(*p); b) int r; r=*p*2; c) kui (*p%2)…; d) cout<<(*p);.

Saate kuvada ka osuti muutuja väärtuse. cout< Kuvab aadressi kuueteistkümnendsüsteemis. Samas ei pruugi see olla sama, kui sama programmi käitatakse korduvalt.

⇐ Eelmine567891011121314Järgmine ⇒

Avaldamise kuupäev: 2015-02-18; Loetud: 526 | Lehe autoriõiguste rikkumine

Studopedia.org – Studopedia.Org – 2014-2018 (0,001 s)…

— osutaja Töötajale. Sellele osutile saate määrata ühe või teie puhul mitu (massiivi süntaksiga) eraldatud objekti. Seega viitab see töötajate hulgale.

Olete sellele osutile viitanud.

Märkused ja oletused

Kuna see osutab (mitmele) töötajale, osutab see ka esimesele kirjele. Seejärel pääsete juurde täisarvulise liikme muutujale, mis on siiski võimalik. Kuid siis proovite kasutada massiivi indeksi operaatorit () täisarvulise väärtuse puhul, mis pole võimalik.

Tõenäoliselt tahtsite juurdepääsu oma eraldatud massiivi sisestuse liikme muutujale. Seega peate seda pöörama: esmalt kasutage massiiviindeksi operaatorit, seejärel pääsete juurde selle konkreetse töötaja liikmele.

madala taseme sõnadega tähendab: võtke kursor, lisage määratud tüübi suurus (nii et see osutaks -ndale kirjele) ja viige sellele aadressile. See tähendab, mis on tegelikult töötaja indeksis (kuid mitte osuti).

Seejärel soovite juurdepääsu selle töötaja liikmele.

Kui see oli ikka kursor, peaksite kasutama nooleoperaatorit, kuid kuna kasutasite massiivi indeksi operaatorit (), olete sellele juba viidanud, siis on punktioperaator õige:

C-d õppides tekivad algajatel sageli osutitega seotud küsimused, arvan, et kõigil on ligikaudu samad küsimused, seega kirjeldan neid, mis mul tekkisid.

Mille jaoks on osuti?

Miks on alati kirjutatud “tüüp pointer” ja mis on tüübiosutaja uint16_t erineb tüüpi osutist uint8_t?

Ja kes üldse selle indeksi välja mõtles?

Enne nendele küsimustele vastamist tuletagem meelde, mis on osuti.
Osuti on muutuja, mis sisaldab mõne andmeelemendi (muutuja, konstant, funktsioon, struktuur) aadressi.

Muutuja kursina deklareerimiseks peate selle nime ette eelnema * , ja muutuja aadressi saamiseks kasutatakse seda & (üheaadressi operaator).
char a = "a"; char *p =
Sel juhul sisaldab p muutuja a aadressi. Aga huvitav on see kursoriga edasiseks tööks ei pea te tärni kirjutama, seda on vaja ainult deklareerimisel.
char a = "a"; char b = "b"; char *p = p =
Sel juhul sisaldab p muutuja b aadressi, kuid kui tahame saada sellel aadressil asuvat väärtust, peame kasutama viiteoperaatorit, sama tärn *.
char uus_sümbol = 0; char a = "a"; char *p = uus_sümbol = *p;
Seega sisaldab muutuja new_simbol sümboli "a" ascii koodi.

Liigume nüüd otse küsimuste juurde, milleks osutit vaja on. Kujutage ette, et meil on massiiv, millega tahame funktsioonis töötada. Et massiivi funktsioonile üle anda, tuleb see kopeerida ehk kulutada mälu, mida MK-l niigi vähe on, seega õigem lahendus oleks massiivi mitte kopeerida, vaid anda edasi selle esimese aadress element ja suurus.
m =(1,2,3...);
Saate seda teha nii
void foo(char *m, suurus uint8_t) ( )
või nii
void foo (char m, uint8_t suurus) ( )
Kuna massiivi nimi sisaldab selle esimese elemendi aadressi, pole see midagi muud kui osuti. Massiivis saab liikuda lihtsate aritmeetiliste toimingute abil, näiteks massiivi viienda elemendi väärtuse saamiseks tuleb massiivi aadressile (esimese elemendi aadress) lisada 4 ja rakendada viiteoperaator .
m = *(m + 4);

Ja kohe tekib küsimus, et miks nad kirjutavad igal pool enne kursorit tüüpi? Kõik on lihtne, edastades massiivi esimese elemendi aadressi ja massiivi suuruse, ütleme: Siit (osuti) kaevake 10 auku (massiivi suurus), jõuame kahe tunni pärast ja need, kes pidid auke kaevama kutsutakse traktoriks ja kaevavad auku. Sellesse olukorda sattumise vältimiseks oli vaja meie analoogia kohaselt määrata augu suurus, kursori tüüp on sama, kuna tüüp määrab, mitu baiti muutuja mälus hõivab.

Seega, määrates kursori tüübi, ütleme kompilaatorile, siin on massiivi alguse aadress, üks massiivi element võtab 2 baiti, massiivis on 10 sellist elementi, nii et kui palju mälu peaksime eraldama see massiiv? 20 baiti – kompilaator vastab. Selguse huvides võtame osuti tühjus tüüpi, pole määratletud, kui palju ruumi see võtab- see on lihtsalt aadress, taandame selle erinevat tüüpi osutiteks ja sooritame edasisuunamistoimingu.


Funktsioonile saate suunata ka struktuurile viitava kursori. Kuna struktuuri märgistus on teada, peame edastama ainult selle alguse aadressi ja kompilaator ise jagab selle väljadeks.

Noh, viimane küsimus on, kes selle osuti välja mõtles. Selle probleemi mõistmiseks tuleb pöörduda monteerija poole, näiteks AVR, sealt leiame juhised
st X, r1 ;salvestage r1 sisu SRAM-i aadressil X, kus X on registripaar r26, r27 ld r1,X ; laadige SRAM-i sisu r1-sse aadressil X, kus X on registripaar r26, r27
Selgeks saab, et X sisaldab osuti(aadress) ja tuleb välja, et pole ühtegi kurja meest, kes kõigi lollitamiseks pointeri välja mõtles, MK kerneli tasemel on töö osutitega (aadressidega).

Osuti on muutuja, mis sisaldab objekti aadressi. Osuti ei kanna teavet objekti sisu kohta, vaid sisaldab teavet selle kohta, kus objekt asub.

Osutajad on nagu sildid, mis viitavad asukohtadele mälus. Neil on ka aadress ja nende väärtus on mõne muu muutuja aadress. Osutajana deklareeritud muutuja hõivab RAM-is 4 baiti (kompilaatori 32-bitise versiooni puhul).

Osuti süntaks

tippige *ObjectName;

Osuti tüüp on muutuja tüüp, mille aadressi see sisaldab. C-s osutitega töötamiseks on määratletud kaks toimingut:

  • operatsioon * (tärn) - võimaldab saada objekti väärtust selle aadressi järgi - määrab muutuja väärtuse, mis sisaldub kursoris sisalduvas aadressis;
  • operatsioon & (ampersand) – võimaldab määrata muutuja aadressi.

Näiteks:

Сhar c; // muutuja char *p; // osuti p = // p = aadress c

Osuti deklareerimine, muutuja aadressi saamine

Muutujale viitava osuti deklareerimiseks peate esmalt hankima selle muutuja aadressi. Muutuja mäluaadressi saamiseks peate muutuja nime ees kasutama märki "&". See võimaldab teil teada saada selle mäluelemendi aadressi, kuhu muutuja väärtus on salvestatud. Seda toimingut nimetatakse aadressivõtu toiminguks:

Int var = 5; // muutuja lihtne deklaratsioon esialgse initsialiseerimisega int *ptrVar; // deklareeris kursori, kuid see ei viita veel millelegi ptrVar = // nüüd viitab meie kursor mälus olevale aadressile, kuhu on salvestatud number 5

Kursor kursorile

Kursor salvestab mäluala aadressi. Saate luua kursori kursori, seejärel salvestab see kursori aadressi ja pääseb juurde selle sisule. Osuti kursorile määratletakse järgmiselt:

<тип> **<имя>;

Näide selle kohta, kuidas kursor osutile töötab:

#kaasa #kaasa #define SIZE 10 void main() ( int A; int B; int *p; int **pp; A = 10; B = 111; p = pp = printf("A = %d\n", A); *p = 20 printf("A = %d\n", A *(*pp) = 30 //siin pole sulgusid printf("A = %d\n", A = printf); ("B = %d\n", *p **pp = 333; printf("B = %d", B);

Osutajad ja tüübivalu

Kuna kursor salvestab aadressi, saate selle üle kanda teisele tüübile. See võib olla vajalik, kui peame võtma osa muutujast või kui teame, et muutuja salvestab meile vajaliku tüübi.

Järgmises näites kasutame ära asjaolu, et tüübi int suurus on 4 baiti ja char on 1 bait. Tänu sellele saate pärast esimese baidi aadressi saamist läbida numbri ülejäänud baidid ja kuvada nende sisu.

#kaasa #kaasa #define SIZE 10 void main() ( int A = 10; int *intPtr; char *charPtr; intPtr = printf("%d\n", *intPtr); printf("---------- ----------\n"); charPtr = (char*)intPtr; printf("%d", *charPtr); charPtr++; printf("%d", *charPtr); charPtr++; printf ("%d ", *charPtr) printf("%d ", *charPtr);

NULL osuti - null osuti

Enne lähtestamist salvestab kursor prügi, nagu iga muu muutuja. Kuid samal ajal võib see "prügi" osutuda kehtivaks aadressiks. Näiteks on kursor. Kuidas ma saan teada, kas see on lähtestatud või mitte? Üldiselt mitte mingil juhul. Selle probleemi lahendamiseks võeti kasutusele stdlib teegi NULL makro.

Kui osuti määratlemisel ei ole konkreetse väärtusega initsialiseeritud, on tavaks muuta see võrdseks väärtusega NULL.

Int *ptr = NULL;

Standard garanteerib, et sel juhul on osuti NULL ja võrdne nulliga ning seda saab kasutada tõeväärtusena false. Kuigi olenevalt rakendusest ei pruugi NULL olla võrdne 0-ga. See tähendab, et kursorit saab võrrelda nulliga või NULL-iga, kuid NULL-i ei saa võrrelda täisarvu või ujukoma tüüpi muutujaga.

Isegi kui enamik programmeerijaid mõistab objektide ja neile osutavate viidete erinevust, pole mõnikord täiesti selge, milline viis objektile juurdepääsuks valida. Allpool oleme püüdnud sellele küsimusele vastata.

küsimus

Märkasin, et sageli kasutavad programmeerijad, kelle koodi ma nägin, sagedamini osutajaid objektidele kui need objektid ise, st kasutavad näiteks järgmist konstruktsiooni:

Objekt *myObject = uus objekt;

Objekt minuObject;

Sama meetoditega. Miks selle asemel:

MyObject.testFunc();

me peaksime kirjutama nii:

MyObject->testFunc();

Nagu ma aru saan, annab see kiiruse juurde, sest... me pääseme otse mälule. eks? P.S. Vahetasin Java vastu.

Vastus

Pange tähele muide, et Java-s ei kasutata viiteid otseselt, st. Programmeerija ei pääse koodis olevale objektile sellele kursori kaudu juurde. Kuid tegelikkuses on Javas kõik tüübid, välja arvatud põhitüübid, viitetüübid: neile pääseb juurde viitega, kuigi parameetrit on võimatu otse viitega edastada. Ja pange tähele, uued C++ ja Java või C# on täiesti erinevad asjad.

Et anda veidi aimu, millised osutid on C++-s, on siin kaks sarnast koodifragmenti:

Objekt objekt1 = new Object(); // Uus objekt Objekt objekt2 = new Object(); // Teine uus objekt objekt1 = objekt2 // Mõlemad muutujad viitavad objektile, millele objekt2 viitas // Kui objekt, millele objekt1 viitas, // muutub ka objekt2, kuna tegemist on sama objektiga;

Lähim vaste C++ keeles on:

Objekt * objekt1 = new Object(); // Mälu on eraldatud uue objekti jaoks // Sellele mälule viitab objekt1 Object * objekt2 = new Object(); // Sama teise objektiga kustuta objekt1; // C++-l ei ole prügikoristussüsteemi, nii et kui seda ei tehta, // ei pääse programm enam sellele mälule ligi, // vähemalt kuni programmi taaskäivitamiseni // Seda nimetatakse mälulekke objektiks1 = objekt2; // Nagu Java puhul, osutab objekt1 samale kohale, mis objekt2

See on aga täiesti erinev asi (C++):

Objekt objekt1; // Uus objekt Objekt objekt2; // Teine objekt1 = objekt2 // Objekti 2 täielik kopeerimine objektile 1, // osuti ümberdefineerimise asemel on väga kulukas toiming

Kuid kas me saavutame kiiruse otse mälule juurde pääsedes?

Rangelt võttes ühendab see küsimus kaks erinevat küsimust. Esiteks: millal peaksite kasutama dünaamilist mälujaotust? Teiseks: millal peaksite viiteid kasutama? Loomulikult ei saa siin ilma üldistavate sõnadeta, et alati on vaja valida töö jaoks sobivaim tööriist. Peaaegu alati on parem rakendus kui käsitsi dünaamilise jaotamise kasutamine (dünaamiline jaotamine) ja/või töötlemata näpunäiteid.

Dünaamiline jaotus

Küsimuse sõnastus esitab kaks võimalust objekti loomiseks. Ja peamine erinevus on nende eluiga (salvestuse kestus) programmimälus. Object myObject kasutamine; , tuginete automaatsele eluaegsele tuvastamisele ja objekt hävitatakse niipea, kui see väljub oma ulatusest. Aga Object *myObject = uus objekt; hoiab objekti elus, kuni kustutate selle käsitsi kustutamiskäsuga mälust. Kasutage viimast võimalust ainult siis, kui see on tõesti vajalik. Ja seetõttu Alati võimaluse korral määrata objekti säilivusaeg automaatselt.

Tavaliselt kasutatakse sunnitud eluea määramist järgmistes olukordades:

  • Teil on vaja, et objekt eksisteeriks ka pärast selle ulatust lahkumist- täpselt see objekt, täpselt selles mälupiirkonnas, mitte selle koopia. Kui see pole teie jaoks oluline (enamikul juhtudel see on), tuginege automaatsele eluea määramisele. Siin on aga näide olukorrast, kus teil võib tekkida vajadus pääseda juurde objektile väljaspool selle ulatust, kuid saate seda teha ilma seda selgesõnaliselt salvestamata: Kui kirjutate objekti vektorisse, saate "ühenduse katkestada" objekti endaga - tegelikult on see (ja mitte selle koopia) saadaval, kui vektorist välja kutsuda.
  • Peate kasutama palju mälu, mis võib virnast üle voolata. On tore, kui te ei pea sellise probleemiga tegelema (ja kohtate seda harva), sest see on C++ "pädevusest väljas", kuid kahjuks peate mõnikord ka selle probleemi lahendama.
  • Näiteks te ei tea täpselt kasutatava massiivi suurust. Nagu teate, on C++-s massiividel määratlemisel kindel suurus. See võib põhjustada probleeme näiteks kasutaja sisendi lugemisel. Kursor määratleb ainult selle ala mälus, kuhu massiivi algus kirjutatakse, jämedalt öeldes, ilma selle suurust piiramata.

Kui dünaamilise jaotamise kasutamine on vajalik, peaksite selle kapseldama nutika kursori abil (saate lugeda meie artiklist) või mõnda muud tüüpi, mis toetab idioomi "Ressursi hankimine on lähtestamine" (seda toetavad standardkonteinerid - see on idioom vastavalt milline ressurss: plokkmälu, fail, võrguühendus vms – vastuvõtmisel lähtestatakse see konstruktoris ja seejärel hävitatakse destruktori poolt hoolikalt). Nutikad osutajad on näiteks std::unique_ptr ja std::shared_ptr .

Viidad

Siiski on juhtumeid, kus osutite kasutamine pole õigustatud mitte ainult dünaamilise mälujaotuse seisukohast, vaid peaaegu alati on alternatiivne viis, ilma viiteid kasutamata, mille peaksite valima. Nagu varemgi, ütleme: vali alati alternatiiv, välja arvatud juhul, kui osutite kasutamiseks on eriline vajadus.

Juhtumid, mille puhul võib osutite kasutamist kaaluda kui võimalik, on järgmised:

  • Viitesemantika. Mõnikord võib osutuda vajalikuks juurdepääs objektile (olenemata sellest, kuidas sellele mälu on eraldatud), kuna soovite pääseda juurde selle objekti funktsioonidele, mitte selle koopiale - s.t. kui peate viitega passi juurutama. Kuid enamasti piisab siin lingi, mitte osuti kasutamisest, sest selleks lingid luuakse. Pange tähele, et need on veidi erinevad asjad ülaltoodud punktis 1 kirjeldatust. Aga kui pääsete ligi objekti koopiale, siis pole viidet vaja kasutada (aga arvestage, et objekti kopeerimine on kallis toiming).
  • Polümorfism. Funktsioonide kutsumine polümorfismis (dünaamiline objektiklass) on võimalik viite või osuti abil. Jällegi on eelistatud kasutada viiteid.
  • Valikuline objekt. Sel juhul saate kasutada nullptr-i, et näidata, et objekt on välja jäetud. Kui see on funktsiooni argument, siis on parem seda rakendada vaikeargumentidega või ülekoormusega. Teise võimalusena võite kasutada tüüpi, mis seda käitumist kapseldab, näiteks boost::optional (muudetud C++14 std::valikuline).
  • Koostamise kiiruse parandamine. Võimalik, et peate eraldama kompileerimisüksused (koostamisühikud). Üheks tõhusaks osuti kasutusviisiks on eeldeklareerimine (sest objekti kasutamiseks tuleb see enne defineerida). See võimaldab teil kompileerimisühikuid eraldada, mis võib avaldada positiivset mõju kompileerimisaegade kiirendamisele, vähendades oluliselt sellele protsessile kuluvat aega.
  • Suhtlemine raamatukogugaC või C-sarnane. Siin peate kasutama tooreid viiteid, vabastades neist viimasel hetkel mälu. Toores osuti saad näiteks nutikast osutist get-operatsiooniga. Kui raamatukogu kasutab mälu, mis tuleb hiljem käsitsi vabastada, saate hävitaja raamida nutikas kursoris.