Operácie so súbormi c. Statické pole: deklarovanie, vypĺňanie, používanie

  • 21.07.2019

Doteraz sme pracovali s jednoduchými dátovými typmi – boolean (boolean), celé číslo (word, byte, longint), real (real), znak (char). Pomocou týchto štyroch základných typov je možné naprogramovať akýkoľvek algoritmus. Na spracovanie informácií o rôznorodom reálnom svete sú však potrebné údaje so zložitejšou štruktúrou. Takéto zložité konštrukcie, založené na najjednoduchších skalárnych typoch, sa nazývajú štruktúry. Štruktúra je nejaký zložený dátový typ vytvorený zo základných skalárnych. Ak štruktúra nemení svoju štruktúru počas celého vykonávania programu, v ktorom je opísaná, potom sa takáto štruktúra nazýva statická.

Array - homogénna zbierka prvkov

Najbežnejšou štruktúrou implementovanou takmer vo všetkých programovacích jazykoch je pole.

Polia sa skladajú z obmedzeného počtu komponentov, pričom všetky komponenty v poli sú rovnakého typu, nazývaného základný typ. Štruktúra poľa je vždy jednotná. Pole môže pozostávať z prvkov typu integer, real alebo char alebo z iných prvkov rovnakého typu. Z toho by sa však nemalo usudzovať, že komponenty poľa môžu mať iba skalárny typ.

Ďalšou vlastnosťou poľa je, že ku ktorejkoľvek z jeho komponentov možno pristupovať akýmkoľvek spôsobom. Čo to znamená? Program môže okamžite získať prvok, ktorý potrebuje podľa jeho poradového čísla (indexu).

Index poľa

Zavolá sa číslo prvku poľa index... Index je hodnota ordinálneho typu, definovaná ako typ indexu dané pole. Veľmi často ide o celočíselný typ (celé číslo, slovo alebo bajt), ale môže to byť boolovská hodnota aj znak.

Popis poľa v jazyku Pascal. V jazyku Pascal sa typ poľa špecifikuje pomocou špeciálneho slova pole(angličtina - pole) a jeho deklarácia v programe vyzerá takto:

Typ< имя _ типа >= pole [I] z T;

kde I je typ indexu poľa, T je typ jeho prvkov.

Okamžite môžete popísať premenné typu pole, t.j. v časti deklarácie premennej:

Var a, b: pole [I] z T;

Typ indexu je zvyčajne charakterizovaný určitým rozsahom hodnôt akéhokoľvek poradového typu: I 1 .. I n. Napríklad indexy sa môžu pohybovať od 1..20 alebo "a" .. "n".

V tomto prípade je dĺžka poľa Pascal charakterizovaná výrazom:

ord (I n) - ord (I 1) +1.

Napríklad tu je deklarácia dvoch typov: vektor ako pole Pascal s 10 celými číslami a stroka ako pole s 256 znakmi:

Typ
Vektor = pole celého čísla;
Stroka = pole znakov;

Pomocou indexu poľa môžete odkazovať na jednotlivé prvky ľubovoľného poľa ako na regulárnu premennú: môžete získať hodnotu tohto prvku, samostatne mu priradiť hodnotu a použiť ju vo výrazoch.

Popíšme si premenné typu vector a stroka:

Výpočet indexu poľa Pascal

Index poľa Pascal nie je potrebné explicitne špecifikovať. Ako index poľa môžete použiť premennú alebo výraz zodpovedajúci typu indexu. Inými slovami, indexy sa dajú vypočítať.

Tento mechanizmus je mimoriadne výkonný softvér. Vygeneruje však bežnú chybu: výsledok výpočtov môže byť mimo rozsahu platných hodnôt indexu, to znamená, že dôjde k pokusu o prístup k prvku, ktorý neexistuje. Táto bežná chyba sa nazýva „pole mimo hraníc“.

Príklad programu s chybou poľa Pascal

Program primer _ chyba;
Typ
vektor = pole slov;
var
n: celé číslo;
a: vektor;
začať
n = 45;
a: = 25;
koniec.

Hoci tento program plne vyhovuje syntaxi jazyka a prekladateľ ho „preskočí“, v štádiu vykonávania sa vyskytne chyba, keď je mimo hraníc poľa Pascal. Pre n = 45, výraz n * 2 = 90, sa počítač pokúsi získať prístup k prvku poľa a, ale takýto prvok neexistuje, pretože je opísané pole s 80 rozmermi.

Predpokladajme, že dobrý program by mal vydať varovnú správu v prípade pokusu o prístup k neexistujúcim prvkom poľa. Nebude zbytočné kontrolovať možné prekročenie pravých a ľavých hraníc poľa, pretože je možné, že v dôsledku výpočtu hodnoty výrazu sa získa číslo, ktoré sa nachádza naľavo od hranice. poľa Pascal.

Z toho všetkého treba vyvodiť záver: programátor musí byť veľmi opatrný pri práci s indexmi poľa.

Základné operácie s poľami Pascal

Ako viete, definícia dátového typu znamená obmedzenie rozsahu prípustných hodnôt, vnútornú reprezentáciu v počítači, ako aj množinu prípustných operácií s dátami tohto typu. Údajový typ sme definovali ako pole Pascal. Aké operácie sú definované pre tento typ údajov? Jedinou akciou, ktorú možno vykonať na celých poliach a iba ak sú polia rovnakého typu, je priradenie. Ak program popisuje dve premenné rovnakého typu, napr.

Var
a, b: pole reálnych;

potom môžete meniť a priradiť hodnotu premennej b(a: = b). Navyše každý prvok poľa a bude priradená zodpovedajúca hodnota z poľa b. Všetky ostatné akcie na poliach Pascal sa vykonávajú prvok po prvku (to je dôležité!) .

Vstup poľa Pascal

Aby bolo možné zadať hodnoty prvkov poľa, je potrebné postupne meniť hodnotu indexu, počnúc od prvej po poslednú, a zadať zodpovedajúci prvok. Na realizáciu týchto akcií je vhodné použiť slučku s daným počtom opakovaní, t.j. jednoduchý aritmetický cyklus, kde parametrom cyklu bude premenná - index poľa Pascal. Hodnoty prvkov je možné zadať z klávesnice alebo určiť pomocou operátora priradenia.

Príklad útržku vstupného programu poľa Pascal

Var
A: pole celých čísel;
Začať
Pre i: = 1 až 10 do
Readln (a [i]); (i-tý prvok sa zadáva z klávesnice)

Uvažujme teraz o prípade, keď sa pole Pascal vyplní automaticky náhodnými číslami, na to použijeme funkciu náhodný (N).

Príklad fragmentu programu na vyplnenie poľa Pascal náhodnými číslami

Var
I: byte; (premenná I je zadaná ako index poľa)
Začať
Pre i: = 1 až 10 do
A [i]: = náhodný (10); (i-tý prvok poľa má priradené „náhodné“ celé číslo v rozsahu od 0 do 10)

Výstup poľa Pascal

Výstup poľa v Pascale sa tiež vykonáva prvok po prvku v slučke, kde parametrom je index poľa, pričom postupne preberá všetky hodnoty od prvej po poslednú.

Príklad fragmentu programu pre výstup poľa Pascal

Var
A: pole celých čísel;
I: byte; (premenná I je zadaná ako index poľa)
Začať
Pre i: = 1 až 10 do
Napíšte (a [i], ""); (pole sa vypíše do reťazca, po každom prvku sa vytlačí medzera)

Výstup je možné vykonať v stĺpci s uvedením príslušného indexu. No v tomto prípade treba počítať s tým, že pri veľkom rozmere poľa sa nemusia všetky prvky zmestiť na obrazovku a dôjde k rolovaniu, t.j. pri vyplnení všetkých riadkov obrazovky sa vytlačí ďalší prvok a horný sa presunie mimo obrazovku.

Príklad programu na výstup poľa Pascal do stĺpca

Var
A: pole celých čísel;
I: byte; (premenná I je zadaná ako index poľa)
Začať
Pre i: = 1 až 10 do
Writeln ("a [", i, "] =", a [i]); (výstup prvkov poľa v stĺpci)

Na obrazovke uvidíme napríklad tieto hodnoty:

a = 2
a = 4
a = 1 atď.

Príklad riešenia problému pomocou polí Pascal

Úloha: dané dva n-rozmerné vektory. Nájdite súčet týchto vektorov.

Riešenie problému:

  • Vstupnými údajmi v tejto úlohe budú dve jednorozmerné polia. Veľkosť týchto polí môže byť ľubovoľná, ale určitá. Tie. vieme popísať vedome veľké pole a v programe vieme určiť, koľko prvkov sa skutočne použije. Prvky týchto polí môžu byť celé čísla. Potom bude popis vyzerať takto:

    var a, b: pole celých čísel;

  • Výstupom budú prvky výsledného poľa, nazvime ho c. Typ výsledného poľa musí byť tiež celé číslo.
  • Okrem troch polí potrebujeme premennú – parameter cyklu a index poľa, nazvime ho i, a tiež premennú n na určenie počtu prvkov v každom poli.

Pokrok pri riešení problému:

  • určiť počet prvkov (rozmer) polí, zadať hodnotu n;
  • predstavme pole a;
  • zadajme pole b;
  • v slučke, opakujúc hodnoty indexu i od 1 do n, postupne vypočítame hodnoty prvkov poľa c podľa vzorca:

    c [i] = a [i] + b [i];

  • zobraziť výsledné pole.

Text programu:

Príklad programu vektorového súčtu

Program summa;
Var
a, b, c: pole celých čísel;
I, n: byte;
Začať
Napíšte ("zadajte rozmer polí:");
Readln (n);
Pre i: = 1 až n do
Readln (a [i]); (vstupné pole a)
Pre i: = 1 až n do
Readln (b [i]); (vstupné pole b)
Pre i: = 1 až n do
C [i]: = a [i] + b [i]; (výpočet súčtu polí)
Pre i: = 1 až n do
napísať (c [i], ""); (výstupné pole s)
koniec.

Čo sú polia v C?

Ako deklarujem polia v C?

Ako inicializovať polia v C?

Polia v C pre figuríny.

Polia v C

Pole v C je kolekcia prvkov rovnakého typu, ku ktorým možno pristupovať pomocou indexu. Prvky polí v C sú umiestnené jeden po druhom v pamäti počítača.

Jednoduchý príklad vytvorenia a vyplnenia poľa v C:

// @author Subbotin BP.h> void main (void) (int nArr; nArr = 1; nArr = 2; nArr = 3; printf ("\ n \ tArray \ n \ n"); printf ("nArr \ t = \ t% d \ n ", nArr); printf (" nArr \ t = \ t% d \ n ", nArr); printf (" nArr \ t = \ t% d \ n ", nArr); návrat 0 ;)

Dostaneme:

V príklade deklarujeme pole obsahujúce prvky typu int:

tu je názov poľa nArr, počet prvkov poľa sú tri, typ prvkov poľa je int.

Pole je zbierka prvkov. Na každý prvok poľa možno odkazovať jeho číslom. Číslo sa zvyčajne nazýva index. Prvky poľa sú číslované od nuly. Priraďme hodnotu prvému prvku poľa a prvý prvok má index nula:

Priraďme hodnotu druhému prvku poľa a druhý prvok má index jedna:

Priraďme hodnotu tretiemu prvku poľa a tretí prvok je na indexe dva:

Pri zobrazení prvkov poľa dostávame ich hodnoty. Páči sa ti to:

printf ("nArr \ t = \ t% d \ n", nArr);

Ak chcete získať prvok poľa, musíte zadať názov poľa a index prvku:

toto je prvý prvok poľa, pretože prvý prvok má index nula.

Priraďme hodnotu tretieho prvku poľa premennej int:

index tretieho prvku poľa sa rovná dvom, pretože indexy sa počítajú od nuly.

Teraz všeobecné pravidlo pre deklarovanie polí v C: pri deklarovaní poľa musíte zadať jeho názov, typ prvkov, počet prvkov. Počet prvkov je prirodzené číslo, t.j. celé pozitívne. Nula nemôže byť počet prvkov. Nemôžete zadať premenlivý počet prvkov poľa. Tu sú príklady deklarácií poľa v C:

int nArr; // Je deklarované pole na uloženie sto celých čísel;
float farArr; // Je deklarované pole na uloženie 5 pohyblivých čísel;
char cArr; // Je deklarované pole na uloženie dvoch znakov;

Bolo by chybou deklarovať pole s premenlivým počtom prvkov:

Int varElem;
int nArr; // Chyba! Počet prvkov nemožno nastaviť na premennú;

Počet prvkov však môžete nastaviť ako konštantu: buď okamžité kladné celé číslo 1, 2, 3 ... alebo konštantu:

Const int dĺžka poľa = 3;
int nArr;

Pri deklarovaní poľa v C ho môžete okamžite inicializovať:

int nMasiv = (1, 2, 3);

Počet prvkov poľa v hranatých zátvorkách môžete vynechať, ak sú inicializované všetky prvky poľa:

int nMasiv = (1, 2, 3);

počet prvkov sa v tomto prípade určí automaticky.

Keď deklarujete, môžete definovať iba časť prvkov poľa:

int nMasiv = (1, 2);

v tomto príklade sú prvé dva prvky poľa inicializované a tretí je nedefinovaný.

Príklad poľa znakov:

char cArr = ("S", "B", "P");

Pri deklarovaní poľa nemôžete zadať počet prvkov v premennej. Pri prístupe k prvkom poľa však môžete použiť premenné:

Int ind = 0;
char cr = cArr;

Používa sa pri práci so slučkami. Príklad:

// @author Subbotin B.P..h> void main (void) (const int arrayLength = 3; int nArr; for (int inn = 0; inn< 3; inn++) { nArr = inn + 1; } printf("\n\tArray\n\n"); for(int inn = 0; inn < 3; inn++) { printf("nArr[%d]\t=\t%d\n", inn, nArr); } return 0; }

V príklade v prvej slučke vyplníme pole prvkami int a v druhej slučke tieto prvky zobrazíme na obrazovke.

Posledná aktualizácia: 17.09.2017

Pole predstavuje množinu údajov rovnakého typu. Formálna definícia poľa vyzerá takto:

Názov_poľa typu premennej [dĺžka_pola]

Za typom premennej nasleduje názov poľa, za ktorým nasleduje jeho veľkosť v hranatých zátvorkách. Napríklad definujme pole 4 čísel:

Int čísla;

Toto pole má štyri čísla, ale všetky tieto čísla nie sú definované. Môžeme však vykonať inicializáciu a priradiť niektoré počiatočné hodnoty týmto číslam pomocou zložených zátvoriek:

Int čísla = (1,2,3,4);

Hodnoty v zložených zátvorkách sa tiež nazývajú inicializátory. Ak je inicializátorov menej ako prvkov v poli, potom sa inicializátory použijú pre prvé prvky. Ak je v poli viac inicializátorov ako prvkov, počas kompilácie sa vyskytne chyba:

Int čísla = (1, 2, 3, 4, 5, 6);

Tu má pole veľkosť 4, ale odovzdáva sa mu 6 hodnôt.

Ak veľkosť poľa nie je explicitne špecifikovaná, potom je odvodená z počtu inicializátorov:

Int čísla = (1, 2, 3, 4, 5, 6);

V tomto prípade je v poli 6 prvkov.

Inicializácia polí znakov má svoje zvláštnosti. Do poľa znakov môžeme odovzdať množinu inicializátorov aj reťazec:

Char s1 = ("h", "e", "l", "l", "o"); char s2 = "svet";

Navyše v druhom prípade pole s2 nebude mať 5 prvkov, ale 6, pretože pri inicializácii reťazcom sa do poľa znakov automaticky pridá nulový znak "\ 0".

V tomto prípade nie je dovolené priradiť ďalšie pole k jednému poľu:

Int nums1 = (1,2,3,4,5); int nums2 = nums1; // chyba nums2 = nums1; // chyba

Po definovaní poľa sa môžeme odvolávať na jeho jednotlivé prvky podľa indexu. Indexy začínajú od nuly, takže na odkazovanie na prvý prvok je potrebné použiť index 0. Odkazovaním na prvok po indexe môžeme získať jeho hodnotu alebo ju zmeniť:

#include int main () (int čísla = (1,2,3,4); int prvé_číslo = čísla; std :: cout<< first_number << std::endl; // 1 numbers = 34; // изменяем элемент std::cout << numbers << std::endl; // 34 return 0; }

Počet prvkov poľa možno určiť aj pomocou konštanty:

Const int n = 4; int čísla [n] = (1,2,3,4);

Iterácia cez polia

Pomocou slučiek môžete prejsť celým poľom a odkazovať na jeho prvky prostredníctvom indexov:

#include int main () (int čísla = (1,2,3,4); int size = sizeof (čísla) / sizeof (čísla); for (int i = 0; i< size; i++) std::cout << numbers[i] << std::endl; return 0; }

Ak chcete prejsť cez pole, musíte najprv nájsť dĺžku poľa. Na zistenie dĺžky sa používa operátor sizeof. V skutočnosti sa dĺžka poľa rovná celkovej dĺžke jeho prvkov. Všetky prvky predstavujú rovnaký typ a zaberajú rovnakú veľkosť v pamäti. Preto pomocou výrazu sizeof (čísla) zistíme dĺžku celého poľa v bajtoch a pomocou výrazu sizeof (čísla) zistíme dĺžku jedného prvku v bajtoch. Vydelením týchto dvoch hodnôt získate počet prvkov v poli. A potom pomocou cyklu for iterujte cez všetky prvky, kým sa počítadlo i nestane rovným dĺžke poľa. V dôsledku toho sa na konzole zobrazia všetky prvky poľa:

Existuje však aj iná forma cyklu for, ktorá je špeciálne navrhnutá na prácu s kolekciami vrátane polí. Tento formulár má nasledujúcu formálnu definíciu:

Pre (premenná typu: kolekcia) (pokyny;)

Použime tento formulár na iteráciu poľa:

#include int main () (int čísla = (1,2,3,4); for (int number: čísla) std :: cout<< number << std::endl; return 0; }

Pri iterácii cez pole sa každý iterovaný prvok umiestni do premennej číslo, ktorej hodnota sa vytlačí na konzolu v slučke.

Ak nepoznáme typ objektov v poli, na určenie typu môžeme použiť automatický špecifikátor:

Pre (automatické číslo: čísla) std :: cout<< number << std::endl;

Viacrozmerné polia

Okrem jednorozmerných polí má C++ polia viacrozmerné. Prvky takýchto polí sú samotné polia, v ktorých prvky môžu byť tiež poliami. Napríklad definujme dvojrozmerné pole čísel:

Int čísla;

Takéto pole pozostáva z troch prvkov, pričom každý prvok predstavuje pole dvoch prvkov. Inicializujeme podobné pole:

Int čísla = ((1, 2), (4, 5), (7, 8));

Vnorené zložené zátvorky vymedzujú položky pre každé podpole. Takéto pole môže byť reprezentované aj ako tabuľka:

1 2
4 5
7 8

Počas inicializácie môžete tiež vynechať zložené zátvorky:

Int čísla = (1, 2, 4, 5, 7, 8);

Je tiež možné inicializovať nie všetky prvky, ale iba niektoré:

Int čísla = ((1, 2), (), (7));

A na odkazovanie na prvky vnoreného poľa sú potrebné dva indexy:

Int čísla = ((1, 2), (3, 4), (5, 6)); std :: cout<< numbers << std::endl; // 3 numbers = 12; // изменение элемента std::cout << numbers << std::endl; // 12

Prejdeme cez dvojrozmerné pole:

#include int main () (konšt. int riadkov = 3, stĺpcov = 2; int čísla = ((1, 2), (3, 4), (5, 6)); for (int i = 0; i< rows; i++) { for(int j=0; j < columns; j++) { std::cout << numbers[i] [j] << "\t"; } std::cout << std::endl; } return 0; }

Na iteráciu prvkov viacrozmerného poľa môžete použiť aj inú formu cyklu for:

#include int main () (const int rows = 3, columns = 2; int numbers = ((1, 2), (3, 4), (5, 6)); for (auto & subnumbers: numbers) (for (int číslo : podčísla) (std :: cout<< number << "\t"; } std::cout << std::endl; } return 0; }

Odkazy sa používajú na iteráciu polí, ktoré sú zahrnuté v poli. To znamená, že vo vonkajšej slučke for (auto & subnumbers: numbers) & subnumbers predstavuje odkaz na podpole v poli. Vo vnútornej slučke for (int číslo: podčísla) z každého podpola v podčíslach dostaneme jeho jednotlivé prvky do premennej číslo a vypíšeme jej hodnotu do konzoly.

  • Návod

V tomto príspevku sa pokúsim konečne rozobrať také jemné koncepty v C a C++, ako sú ukazovatele, odkazy a polia. Najmä odpoviem na otázku, či polia C sú ukazovatele alebo nie.

Notácia a predpoklady

  • Budem predpokladať, že čitateľ chápe, že napríklad v C ++ sú odkazy, ale v C nie, takže vám nebudem neustále pripomínať, o ktorom jazyku (C / C ++ alebo konkrétne C ++) hovorím teraz to čitateľ pochopí z kontextu;
  • Taktiež predpokladám, že čitateľ už na základnej úrovni pozná C a C++ a pozná napríklad syntax pre deklarovanie odkazu. V tomto príspevku sa budem zaoberať starostlivým rozborom maličkostí;
  • Typy budem označovať tak, ako by vyzerala deklarácia premennej TYPE zodpovedajúceho typu. Napríklad typ "pole dĺžky 2 int" s "označím ako int TYPE;
  • Budem predpokladať, že máme do činenia najmä s bežnými dátovými typmi, ako sú int TYPE, int * TYPE atď., pre ktoré operácie =, &, * a iné nie sú prepísané a označujú bežné veci;
  • "Objekt" bude vždy znamenať "čokoľvek, čo nie je odkaz", nie "inštancia triedy";
  • Pokiaľ nie je uvedené inak, C89 a C++ 98 sú implikované.

Ukazovatele a odkazy

Ukazovatele... Nepoviem vám, čo sú ukazovatele. :) Predpokladajme, že to viete. Dovoľte mi pripomenúť vám nasledujúce veci (predpokladá sa, že všetky príklady kódu sú súčasťou nejakej funkcie, napríklad hlavnej):

Int x; int * y = // Pomocou operácie "&" môžete prevziať adresu z ľubovoľnej premennej. Táto operácia vráti ukazovateľ int z = * y; // Ukazovateľ je možné dereferencovať pomocou operácie dereferencovania "*". Táto operácia vráti objekt, na ktorý ukazuje ukazovateľ

Dovoľte mi tiež pripomenúť nasledovné: char je vždy presne jeden bajt a vo všetkých štandardoch C a C++ sizeof (char) == 1 (normy však nezaručujú, že bajt obsahuje presne 8 bitov :)). Ďalej, ak pridáme číslo k ukazovateľu na nejaký typ T, potom sa skutočná číselná hodnota tohto ukazovateľa zvýši o toto číslo vynásobené veľkosťou (T). To znamená, že ak p je typu T * TYPE, potom p + 3 je ekvivalentné s (T *) ((char *) p + 3 * veľkosť (T)). Podobné úvahy platia aj pre odčítanie.

Odkazy... Teraz o odkazoch. Odkazy sú rovnaké ako ukazovatele, ale s inou syntaxou a niektorými ďalšími dôležitými rozdielmi, o ktorých sa bude diskutovať neskôr. Nasledujúci kód sa nelíši od predchádzajúceho, okrem toho, že obsahuje odkazy namiesto ukazovateľov:
int x; int & y = x; int z = y;

Ak je naľavo od znaku priradenia odkaz, potom nie je možné pochopiť, či chceme priradiť odkaz samotnému alebo objektu, na ktorý odkazuje. Preto takéto priradenie vždy priraďuje k objektu, nie k referencii. To však neplatí pre inicializáciu odkazu: samozrejme, inicializuje sa samotný odkaz. Preto po inicializácii odkazu neexistuje spôsob, ako ho zmeniť, to znamená, že odkaz je vždy konštantný (ale nie jeho objekt).

Lvalue... Výrazy, ktoré je možné priradiť, sa v C, C++ a mnohých ďalších jazykoch nazývajú lvalues ​​(skratka pre „left value“, teda naľavo od znamienka rovnosti). Ostatné výrazy sa nazývajú rvalues. Názvy premenných sú samozrejme lvalues, ale nie sú jediné. Výrazy a, some_struct.some_field, * ptr, * (ptr + 3) sú tiež lvalues.

Prekvapivým faktom je, že referencie a hodnoty sú v istom zmysle to isté. Poďme špekulovať. Čo je to lvalue? Je to niečo, čo sa dá priradiť. To znamená, že je to určité pevné miesto v pamäti, kam si môžete niečo vložiť. Teda adresu. Teda ukazovateľ alebo odkaz (ako už vieme, ukazovatele a odkazy sú dva syntakticky odlišné spôsoby vyjadrenia pojmu adresa v C ++). Navyše je to skôr odkaz ako ukazovateľ, pretože odkaz môže byť umiestnený naľavo od znamienka rovnosti, čo bude znamenať priradenie k objektu, na ktorý odkaz ukazuje. Takže lvalue je referencia.

Dobre, ale (takmer akákoľvek) premenná môže byť aj naľavo od znamienka rovnosti. Takže (taká) premenná je referencia? Takmer. Výraz reprezentujúci premennú je odkaz.

Inými slovami, povedzme, že sme deklarovali int x. Teraz je x premenná typu int TYPE a nič iné. Je to int a to je všetko. Ale ak teraz napíšem x + 2 alebo x = 3, tak v týchto výrazoch je podvýraz x typu int & TYPE. Pretože inak by sa toto x nelíšilo od povedzme 10 a nedalo by sa mu (ako desiatke) priradiť nič.

Tento princíp ("výraz, ktorý je premennou je odkaz") je mojím vynálezom. To znamená, že tento princíp som nevidel v žiadnej učebnici, norme atď. Napriek tomu veľa zjednodušuje a je vhodné ho považovať za správny. Ak by som implementoval kompilátor, považoval by som premenné vo výrazoch za referencie a je dosť možné, že to majú robiť skutočné kompilátory.

Princíp „akákoľvek hodnota je referencia“ je tiež mojím vynálezom. Ale princíp „akýkoľvek odkaz je lvalue“ je úplne legálny, všeobecne akceptovaný princíp (samozrejme, odkaz musí byť odkazom na meniteľný objekt a tento objekt musí byť priraditeľný).

Teraz, berúc do úvahy naše konvencie, sformulujeme prísne pravidlá pre prácu s referenciami: ak je povedzme deklarované int x, potom výraz x má teraz typ int & TYPE. Ak je teraz tento výraz (alebo akýkoľvek iný výraz referenčného typu) naľavo od znamienka rovnosti, potom sa používa presne ako odkaz, takmer vo všetkých ostatných prípadoch (napríklad v situácii x + 2) je x automaticky prevedená na typ int TYPE (ďalšou operáciou , vedľa ktorej sa odkaz nekonvertuje na svoj objekt je &, ako uvidíme neskôr). Odkaz môže byť len naľavo od znamienka rovnosti. Iba odkaz môže inicializovať (nekonštantný) odkaz.

Operácie * a &... Naše konvencie vám umožňujú nový pohľad na operácie * a &. Teraz je jasné: operáciu * možno použiť iba na ukazovateľ (toto bolo vždy konkrétne známe) a vráti odkaz na rovnaký typ. & sa vždy použije na odkaz a vráti ukazovateľ rovnakého typu. Preto * a & konvertujú ukazovatele a odkazy na seba. To znamená, že v skutočnosti nerobia vôbec nič a iba nahrádzajú podstatu jednej syntaxe podstatou inej! Takže & v skutočnosti nie je úplne správne volať operáciu prijatia adresy: dá sa použiť iba na už existujúcu adresu, len mení syntaktické stelesnenie tejto adresy.

Všimnite si, že ukazovatele a odkazy sú deklarované ako int * x a int & x. Opäť sa teda potvrdzuje princíp „deklarácia vyzýva na použitie“: deklarácia ukazovateľa pripomína, ako ho premeniť na odkaz, ale deklarácia odkazu – naopak.

Všimnite si tiež, že & * EXPR (kde EXPR je ľubovoľný výraz, nie nevyhnutne jeden identifikátor) ​​je ekvivalentné s EXPR vždy, keď to dáva zmysel (teda vždy, keď je EXPR ukazovateľ), a * & EXPR je tiež ekvivalentom EXPR vždy, keď má význam (t. j. keď EXPR je odkaz).

Polia

Existuje teda taký typ údajov - pole. Polia sú definované napríklad takto:
int x;
Výraz v hranatých zátvorkách musí byť konštanta v čase kompilácie v C89 a C++ 98. V tomto prípade musí byť v hranatých zátvorkách číslo, prázdne hranaté zátvorky nie sú povolené.

Rovnako ako všetky lokálne premenné (nezabudnite, predpokladáme, že všetky príklady kódu sú vo funkciách) sú v zásobníku, polia sú tiež v zásobníku. To znamená, že daný kód viedol k alokácii priamo na zásobníku obrovského bloku pamäte 5 * sizeof (int), v ktorom sa nachádza celé naše pole. Nemusíte si myslieť, že tento kód deklaroval nejaký ukazovateľ, ktorý ukazuje na pamäť umiestnenú niekde ďaleko, na halde. Nie, deklarovali sme pole, skutočné. Tu na stohu.

Čomu sa rovná veľkosť (x)? Samozrejme, bude sa rovnať veľkosti nášho poľa, t.j. 5 * sizeof (int). Ak píšeme
struct foo (int a; int b;);
potom bude opäť celý priestor pre pole alokovaný priamo vo vnútri štruktúry a sizeof z tejto štruktúry to potvrdí.

Z poľa môžete prevziať adresu (& x) a bude to skutočný ukazovateľ na miesto, kde sa toto pole nachádza. Typ výrazu & x, ako ľahko pochopíte, bude int (* TYPE). Na začiatku poľa je umiestnený jeho nulový prvok, takže adresa samotného poľa a adresa jeho nulového prvku sú číselne rovnaké. To znamená, že & x a & (x) sú číselne rovnaké (tu som famózne napísal výraz & (x), v skutočnosti to nie je také jednoduché, k tomu sa vrátime neskôr). Ale tieto výrazy majú rôzne typy - int (* TYPE) a int * TYPE, takže ich nemôžete porovnávať pomocou ==. Môžete však použiť trik void *: nasledujúci výraz bude pravdivý: (void *) & x == (void *) & (x).

Dobre, predpokladajme, že som vás presvedčil, že pole je pole a nie niečo iné. Odkiaľ teda pochádza všetok tento zmätok medzi ukazovateľmi a poliami? Faktom je, že názov poľa sa prevedie na ukazovateľ na jeho nulový prvok počas takmer akejkoľvek operácie.

Takže sme deklarovali int x. Ak teraz napíšeme x + 0, potom to prevedie naše x (ktoré bolo typu int TYPE, alebo presnejšie, int (& TYPE)) na & (x), teda ukazovateľ na nulový prvok poľa x. . Naše x je teraz int * TYPE.

Konverzia názvu poľa na void * alebo použitie == naň tiež vedie k predbežnej konverzii tohto názvu na ukazovateľ na prvý prvok, preto:
& x == x // chyba kompilácie, rôzne typy: int (* TYPE) a int * TYPE (neplatné *) & x == (neplatné *) x // pravda x == x + 0 // pravda x == & (x) // pravda

Prevádzka... Zápis a [b] je vždy ekvivalentný * (a + b) (nezabudnite, že neuvažujeme o prevažujúcich operáciách a iných operáciách). Označenie x teda znamená nasledovné:

  • x je ekvivalentné * (x + 2)
  • x + 2 sa týka tých operácií, v ktorých sa názov poľa skonvertuje na ukazovateľ na jeho prvý prvok, takže sa to stane
  • Ďalej, podľa mojich vysvetlení vyššie, x + 2 je ekvivalentné (int *) ((char *) x + 2 * sizeof (int)), to znamená, že x + 2 znamená "posunúť ukazovateľ x o dva int" a "
  • Nakoniec sa z výsledku vyberie operácia dereferencie a získame objekt, ktorý sa nachádza na tomto posunutom ukazovateli

Typy použitých výrazov sú nasledovné:
x // int (& TYPE), po konverzii typu: int * TYPE x + 2 // int * TYPE * (x + 2) // int & TYPE x // int & TYPE

Všimnite si tiež, že naľavo od hranatých zátvoriek nie je potrebné mať pole, môže tam byť ľubovoľný ukazovateľ. Môžete napríklad napísať (x + 2), čo je ekvivalent x. Všimnite si tiež, že * a a a sú vždy ekvivalentné, a to ako v prípade, keď a je pole, tak aj v prípade, že a je ukazovateľ.

Teraz, ako som sľúbil, som späť na & (x). Teraz je jasné, že v tomto výraze sa x najskôr skonvertuje na ukazovateľ, potom sa na tento ukazovateľ aplikuje v súlade s vyššie uvedeným algoritmom a výsledkom je hodnota typu int & TYPE a nakoniec pomocou & sa skonvertovaný na typ int * TYPE. Preto vysvetliť pomocou tohto zložitého výrazu (v rámci ktorého sa už vykonáva prevod poľa na ukazovateľ) trochu jednoduchší koncept prevodu poľa na ukazovateľ - to bol malý podvod.

A teraz otázka na vyplnenie: čo je & x + 1? No, & x je ukazovateľ na celé pole, + 1 vedie ku kroku cez celé pole. To znamená, že & x + 1 je (int (*)) ((char *) & x + sizeof (int)), t.j. (int (*)) ((char *) & x + 5 * sizeof (int )) (tu int (*) je int (* TYP)). Takže & x + 1 je číselne x + 5, nie x + 1, ako by si niekto mohol myslieť. Áno, v dôsledku toho ukazujeme na pamäť, ktorá je mimo poľa (hneď za posledným prvkom), ale koho to zaujíma? Koniec koncov, v C všetky rovnaké, pole mimo hraníc nie je kontrolované. Všimnite si tiež, že * (& x + 1) == x + 5 je pravda. Dá sa to zapísať aj takto: (& x) == x + 5. Bude to tiež pravda * ((& x)) == x, alebo, čo je to isté, (& x) == x (pokiaľ, samozrejme, nezachytíme chybu segmentácie pri pokuse o prístup z našej pamäte: )).

Pole nemôže byť odovzdané ako argument funkcii... Ak do hlavičky funkcie napíšete int x alebo int x, potom to bude ekvivalentné int * x a funkcii bude vždy odovzdaný ukazovateľ (veľkosť z odovzdávanej premennej bude rovnaká ako ukazovateľ). V tomto prípade bude veľkosť poľa zadaná v hlavičke ignorovaná. Môžete jednoducho zadať int x v hlavičke a odovzdať tam pole dĺžky 3.

V C ++ však existuje spôsob, ako odovzdať odkaz na pole funkcii:
void f (int (& x)) (// sizeof (x) je 5 * sizeof (int)) int main (void) (int x; f (x); // OK f (x + 0); // Nepovolené int y; f (y); // Nepovolené, nesprávna veľkosť)
Pri tomto prenose stále odovzdávate iba odkaz, nie pole, to znamená, že pole sa neskopíruje. Stále však máte niekoľko rozdielov oproti bežnému prechádzaniu ukazovateľa. Odkaz na pole je odovzdaný. Namiesto toho nie je možné odovzdať ukazovateľ. Je potrebné odovzdať presne pole zadanej veľkosti. Vo vnútri funkcie sa odkaz na pole bude správať presne ako odkaz na pole, napríklad bude mať sizeof ako pole.

A čo je najzaujímavejšie, tento prevod sa dá použiť takto:
// Vypočíta dĺžku poľa šablóny size_t len ​​​​(t (& a) [n]) (návrat n;)
Podobne je funkcia std :: end implementovaná v C ++ 11 pre polia.

"Ukazovateľ na pole"... Presne povedané, "ukazovateľ na pole" je len ukazovateľ na pole a nič iné. Inými slovami:
int (* a); // Toto je ukazovateľ na pole. Najskutočnejšie. Je typu int (* TYPE) int b; int * c = b; // Toto nie je ukazovateľ na pole. Je to len ukazovateľ. Ukazovateľ na prvý prvok nejakého poľa int * d = new int; // A toto nie je ukazovateľ na pole. Toto je ukazovateľ
Niekedy sa však slovné spojenie „ukazovateľ na pole“ neformálne chápe ako ukazovateľ na pamäťovú oblasť, v ktorej sa pole nachádza, aj keď typ tohto ukazovateľa nie je vhodný. Podľa tohto neformálneho chápania sú c a d (a b + 0) ukazovatele na polia.

Viacrozmerné polia... Ak je deklarované int x, potom x nie je pole s dĺžkou 5 nejakých ukazovateľov smerujúcich niekam ďaleko. Nie, x je teraz jeden monolitický blok 5 x 7 alokovaný v zásobníku. sizeof (x) je 5 * 7 * sizeof (int). Prvky sú v pamäti umiestnené takto: x, x, x, x, x, x, x, x atď. Keď napíšeme x, veci vyzerajú takto:
x // int (& TYPE), po konverzii: int (* TYPE) x // int (& TYPE), po konverzii: int * TYPE x // int & TYPE
To isté platí pre ** x. Všimnite si, že vo výrazoch, povedzme, x + 3 a ** x + 3, sa v skutočnosti pamäť získa iba raz (napriek prítomnosti dvoch hviezdičiek), a to v čase konverzie konečného odkazu typu int & TYPE len na int TYP. To znamená, že ak by sme sa pozreli na kód zostavy, ktorý je vygenerovaný z výrazu ** x + 3, videli by sme v ňom, že operácia načítania údajov z pamäte sa tam vykonáva iba raz. ** x + 3 možno zapísať iným spôsobom ako * (int *) x + 3.

Teraz sa pozrime na túto situáciu:
int ** y = new int *; for (int i = 0; i! = 5; ++ i) (y [i] = nové int;)

čo je teraz? y je ukazovateľ na pole (v neformálnom zmysle!) ukazovateľov na polia (opäť v neformálnom zmysle). Nikde sa tu nevyskytuje jediný blok veľkosti 5 x 7, je tu 5 blokov veľkosti 7 * sizeof (int), ktoré môžu byť od seba ďaleko. čo je y?
y // int ** & TYPE y // int * & TYPE y // int & TYPE
Teraz, keď napíšeme y + 3, načítanie z pamäte nastane dvakrát: načítanie z poľa y a potom načítanie z poľa y, ktoré môže byť ďaleko od poľa y. Dôvodom je, že nedochádza k žiadnej konverzii názvu poľa na ukazovateľ na jeho prvý prvok, na rozdiel od príkladu s viacrozmerným poľom x. Preto ** y + 3 tu nie je ekvivalentné * (int *) y + 3.

Dovoľte mi to vysvetliť ešte raz. x je ekvivalentné * (* (x + 2) + 3). A y je ekvivalentné * (* (y + 2) + 3). Ale v prvom prípade je našou úlohou nájsť „tretí prvok v druhom rade“ v jedinom bloku 5 x 7 (prvky sú samozrejme číslované od nuly, takže tento tretí prvok bude v určitom zmysle štvrtý: )). Kompilátor vypočíta, že požadovaný prvok sa v skutočnosti nachádza na 2 * 7 + 3 mieste v tomto bloku a extrahuje ho. To znamená, že x je tu ekvivalentné ((int *) x), alebo, čo je rovnaké, * ((int *) x + 2 * 7 + 3). V druhom prípade najprv získa 2. prvok v poli y a potom 3. prvok vo výslednom poli.

V prvom prípade, keď urobíme x + 2, posunieme sa naraz o 2 * sizeof (int), teda o 2 * 7 * sizeof (int). V druhom prípade je y + 2 posun 2 * sizeof (int *).

V prvom prípade (void *) x a (void *) * x (a (void *) & x!) sú rovnakým ukazovateľom, v druhom nie.