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
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
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
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
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
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
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
Näited
Nüüd mõned näited osutitega töötamise kohta
1. Käime läbi massiivi ja leiame kõik paariselemendid.
#kaasa
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 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 Joon.1 Juurdepääs muutujale kursori kaudu Kokku kasutatakse tähist * osutajate puhul kahel juhul: Samuti on olemas nn null pointerNULL. Null osuti ei viita kuhugi. Seda kasutatakse osutite nullimiseks. Vaata näidet. Nimekiri 5. #kaasa Joon.2 Kursori nullimine Kodu C-keel näidetega C-i funktsioone kasutatakse konkreetsete toimingute tegemiseks üldprogrammis. Programmeerija ise otsustab, milliseid toiminguid funktsioonides kuvada. Eriti mugav on kasutada funktsioone korduvate toimingute jaoks. 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 { 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. 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. 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: 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. 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. 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. 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. 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. 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. 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. Osuti on muutuja, mis salvestab mälukoha aadressi. Osutil, nagu ka muutujal, on tüüp. Osutajate deklareerimise süntaks <тип> *<имя>; Näiteks #kaasa 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. Pärast seda väärtus A samuti muutunud, kuna see osutab samale mälupiirkonnale. Ei midagi keerulist. #kaasa Kuvatakse 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. Nende tegemiseks peate teadma suurust. #kaasa 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. #kaasa Kui osutid on võrdsed, osutavad need samale mälualale. 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 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 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. 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. 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). 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 Nüüd mõned näited osutitega töötamise kohta #kaasa 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 3. Huvitavam näide. Kuna tähetüübi suurus on alati 1 bait, siis saab sellega realiseerida vahetusoperatsiooni – kahe muutuja sisu vahetamist. #kaasa 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 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. 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. 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. Muutuja kursina deklareerimiseks peate selle nime ette eelnema *
, ja muutuja aadressi saamiseks kasutatakse seda &
(üheaadressi operaator). 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. 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. 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 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 tüüp on muutuja tüüp, mille aadressi see sisaldab. C-s osutitega töötamiseks on määratletud kaks toimingut: Näiteks: Сhar c; // muutuja char *p; // osuti p = // p = aadress c 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 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 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 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. 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. 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. 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: 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 . 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:Funktsioonid C-s
Milliseid funktsioone kasutatakse C-s?
Lihtne näide funktsioonist C-s
puts("Funktsioonid C-s");
tagasta EXIT_SUCCESS;
}Kuidas kutsuda C-s ühest funktsioonist teist funktsiooni?
Funktsiooni prototüüp C-s
Mille poolest erineb funktsiooni deklaratsioon C-s funktsiooni definitsioonist C-s?
tagastusavaldus
Osutajad C-keeles
Funktsiooni argumentide edastamine väärtuse järgi
C-funktsiooni osutite edastamine
C/C++ programmis Eclipse
Viidad
Definitsioon
Teema 7. Osutajad C-s.
Kaks peamist osutioperaatorit on &-operaator ja *-de viitamise operaator. Vaatame lihtsat näidet.
Sisu muutmiseks kirjuta
Nüüd veel üks oluline näide
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
Teiseks toetavad osutid aritmeetilisi tehteid.
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.
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.Kursor kursorile
Osutajad ja tüübivalu
NULL osuti - null osuti
Kui osuti määratlemisel ei ole konkreetse väärtusega initsialiseeritud, on tavaks muuta see võrdseks väärtusega NULL.
See tähendab, et antud juhulNäited
1. Käime läbi massiivi ja leiame kõik paariselemendid.Osutajad C-s.
Märkused ja oletused
Osuti on muutuja, mis sisaldab mõne andmeelemendi (muutuja, konstant, funktsioon, struktuur) aadressi.
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.
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);
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.
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 süntaks
tippige *ObjectName; Osuti deklareerimine, muutuja aadressi saamine
Kursor kursorile
Osutajad ja tüübivalu
NULL osuti - null osuti
küsimus
Vastus
Dünaamiline jaotus
Viidad