Cum se declară o variabilă dublă complexă. Tipuri de date și declararea acestora

Programatorul spune:

Buna ziua! Am citit articolul tau. Eram foarte trist și amuzant în același timp. Această expresie a ta este deosebit de ucigătoare: „Deoarece o variabilă de tip char este adesea folosită ca matrice, numărul de valori posibile este determinat.” 😆 😆 😆
Nu râd de tine. Crearea unui site web este cu adevărat o ispravă. Vreau doar să vă susțin cu sfaturi și să subliniez câteva greșeli.

1. Valoarea unei variabile de tip char este atribuită după cum urmează:

Aici:

Car a = *"A";

Pointerul către matrice este de-adresat și, ca rezultat, valoarea primului element al matricei este returnată, adică. 'A'

2. Resetarea are loc după cum urmează:

Car a = NULL;
char b = ();

//Și așa este șters linia din corpul programului

"" - acest simbol se numește terminator zero. Este plasat la capătul liniei. Tu însuți, fără să știi, ai umplut matricea s1 din articolul tău cu acest simbol. Dar a fost posibil să se atribuie acest simbol numai elementului zero al matricei.

3. Simțiți-vă liber să utilizați terminologia.
Semnul = este o operație de atribuire.
Semnul * este o operație de de-adresare.
Mă refer la acest fragment din articol: „Totul s-a dovedit a fi atât de simplu, înainte de semnul = trebuia să pui semnul * și trebuia să declari numărul elementului (zero corespunde primului)”

Nu mă înțelege greșit, articolul nu poate exista în forma sa actuală. Nu fi leneș, rescrie-l.
Ai o mare responsabilitate! Sunt serios. Paginile site-ului dvs. sunt incluse în prima pagină a rezultatelor Yandex. Mulți oameni au început deja să-ți repete greșelile.

Noroc! O poți face!

:
Știu asta de mult timp, este doar dificil să recitesc 200 de articole în mod constant pentru a corecta ceva. Și unele tipuri nepoliticoase scriu în așa fel încât chiar și știind ce este mai bine de corectat, nu sunt deloc dispuși să o corecteze.

Voi fi bucuros să corectez alte erori. corectați eventualele inexactități dacă apar. Apreciez ajutorul tau. mulțumesc. Știu asta de mult timp, este doar greu să recitesc 200 de articole în mod constant pentru a corecta ceva. Și unele tipuri nepoliticoase scriu în așa fel încât chiar și știind ce este mai bine de corectat, nu sunt deloc dispuși să o corecteze.
Cu caracterul tău b = (); Acest lucru nu este deloc zero. Ar trebui măcar să verifice.
dacă vorbim despre caracterul nul „” ; Știam bine, când am umplut rândul cu ea, că scopul este să arăt o curățenie reală, și nu ceva vizibil pentru ochi, pentru că rândul include gunoi, care uneori îți stau în cale. Ar trebui să fiți mai atent cu termenii, „simbol de terminare nul” sau doar „simbol nul”, nu un terminator))) Și simbolul de terminare sună bine.

Voi moderniza articolul, dar nu voi trece la stilul altcuiva. Dacă cred că este mai ușor pentru un începător să înțeleagă în acest fel decât așa cum își dorește, atunci o voi lăsa așa. Nu mă înțelege greșit nici pe mine. Cuvântul „semn” este mult mai ușor de înțeles și de amintit pentru un începător slab decât definiția și numele fiecărui semn. Nu este deloc greșeală în asta, este un semn - un semn. Mai puțin accent pe unul dă mai mult accent pe celălalt.

Voi fi bucuros să corectez alte erori. corectați eventualele inexactități dacă apar. Apreciez ajutorul tau. Mulțumesc.

Buna din nou!
vreau sa clarific. Termenul „zero-terminator” (terminator din engleză limitator) a fost folosit de profesorul meu de la universitate. Se pare că asta e școala veche!
Cât despre resetarea rândurilor.
char b = (); Aceasta este cu adevărat o resetare. Întreaga matrice este umplută cu zerouri. Dacă nu mă crezi, verifică-l!
Dacă luăm în considerare o linie în sensul său natural, de zi cu zi, atunci o linie „goală” va fi o linie în care nu există un singur caracter. Prin urmare, în 99,9% din cazuri este suficient să puneți un caracter nul la început. În mod obișnuit, un șir este procesat până la primul caracter nul, iar caracterele care vin după el nu mai sunt importante. Am înțeles că ai vrut să resetați linia. Tocmai am decis să ofer o opțiune clasică testată în timp.

:
Când „De obicei, procesarea șirurilor ajunge până la primul caracter nul și ce caractere vin după acesta nu mai este important” - da, șirul este anulat
Dacă luăm în considerare „reducerea la zero reală a tuturor celulelor unui rând (despre care am scris)” - nu, nu zero, și chiar și primul caracter nu este zero. Am bifat varianta asta. MinGW(CodeBlock) - întreaga matrice oferă caracterul „a”
Nu cred că acesta este un motiv de dezbatere.

Tipuri de date. Un program în limbaje procedurale, care include C, este o descriere a operațiunilor pe cantități de diferite tipuri. Un tip definește setul de valori pe care o valoare le poate lua și setul de operațiuni la care poate participa.

În limbajul C, tipurile sunt asociate cu nume (identificatori) de cantități, adică cu variabile. O variabilă în limbajul C este asociată cu o locație de memorie. Tipul unei variabile specifică dimensiunea celulei, modul în care este codificat conținutul acesteia și transformările acceptabile ale valorii acestei variabile. Toate variabilele trebuie declarate înainte de a fi utilizate. Fiecare variabilă trebuie declarată o singură dată.

Descrierea constă dintr-un specificator de tip urmat de o listă de variabile. Variabilele din listă sunt separate prin virgulă. Un punct și virgulă este plasat la sfârșitul descrierii.

Exemple de descrieri:

char a,b; /* Variabilele a și b au tip

char */ intх; /* Variabila x este de tip int

*/ char sym; /" Sunt descrise variabile sym de tip char;

*/ int count.num; /* num și număr de tip int */

Variabilelor li se pot atribui valori inițiale în cadrul declarațiilor lor. Dacă un nume de variabilă este urmat de un semn egal și o constantă, atunci acea constantă servește ca inițializator.

Exemple: char backch = "\0";

Să ne uităm la principalele tipuri din limbajul C.

int - întreg („întreg”). Valorile de acest tip sunt numere întregi dintr-un interval limitat (de obicei de la - 32768 la 32767). Intervalul este determinat de dimensiunea celulei pentru tip și depinde de computerul specific. În plus, există cuvinte funcționale care pot fi folosite cu tipul int: short int („întreg scurt” - „întreg scurt”), unsigned int („întreg fără semnă” - „întreg fără semn”), long int (întreg lung) ” ), care reduc sau, dimpotrivă, extind domeniul de reprezentare a numerelor.

char- caracter („personaj”). Valoarea validă pentru acest tip este un caracter (a nu se confunda cu text!). Simbolul este scris cu apostrofe.

Exemple:"x"2"?"

În memoria computerului, un caracter ocupă un octet. De fapt, nu este un simbol care este stocat, ci un număr - codul simbolului (de la 0 la 255). Tabelele speciale de codificare indică toate caracterele valide și codurile lor corespunzătoare.

În limbajul C este permisă utilizarea tipului char ca unul numeric, adică efectuarea de operații cu codul caracterului, folosind specificatorul de tip întreg între paranteze - (int).

float - real (virgula flotantă). Valorile de acest tip sunt numere, dar, spre deosebire de char și int, nu neapărat numere întregi.

12.87 -316.12 -3.345e5 12.345e-15

dublu - numere reale de precizie dublă. Acest tip este similar cu tipul float, dar are o gamă semnificativ mai mare de valori (de exemplu, pentru sistemul de programare Borland-C de la 1.7E-308 la 1.7E+308 în loc de intervalul de la 3.4E-38 la 3.4E+38 pentru tipul float). Cu toate acestea, o creștere a intervalului și a acurateței reprezentării numerelor duce la o scădere a vitezei de execuție a programului și la utilizarea risipitoare a memoriei RAM a computerului.


Rețineți că nu există niciun tip de șir în această listă. Nu există un tip special în C care să poată fi folosit pentru a descrie șiruri. În schimb, șirurile de caractere sunt reprezentate ca o matrice de elemente char. Aceasta înseamnă că caracterele din șir vor fi localizate în celulele de memorie adiacente.

Trebuie remarcat faptul că ultimul element al tabloului este caracterul \0. Acesta este caracterul „null”, iar în limbajul C este folosit pentru a marca sfârșitul unui șir. Caracterul nul nu este numărul 0; nu este tipărit și are în tabelul de coduri ASCII numărul 0. Prezența unui caracter nul înseamnă că numărul de celule din matrice trebuie să fie. cu cel puțin unul mai mult decât numărul de caractere care trebuie stocate în memorie.

Să dăm un exemplu de utilizare a șirurilor.

Programul 84

#include principal()

scanf("%s",șir);

printf("%s",șir);

Acest exemplu descrie o matrice de 31 de locații de memorie, dintre care 30 pot conține un singur element char. Se introduce la apelarea funcţiei scanf("%s",string); „&” lipsește când se specifică o matrice de caractere.

Indicatoare. Un pointer este o reprezentare simbolică a adresei unei locații de memorie alocată pentru o variabilă.

De exemplu, &name este un pointer către variabila nume;

Iată și operațiunea de obținere a unei adrese. Adresa reală este un număr, iar reprezentarea simbolică a adresei &nume este o constantă pointer.

Limbajul C are, de asemenea, variabile de tip pointer. La fel cum valoarea unei variabile char este un caracter, iar valoarea unei variabile int este un număr întreg, valoarea unei variabile pointer este adresa unei anumite valori.

Dacă dăm indicatorului numele ptr, putem scrie o declarație ca aceasta:

ptr = /* atribuie numele adresei variabilei ptr */

Spunem în acest caz că prt este un nume „indicator către”. Diferența dintre cele două notații: ptr și &name este că prt este o variabilă, în timp ce &name este o constantă. Dacă este necesar, puteți face ca variabila ptr să indice un alt obiect:

ptr= /* ptr indică bah, nu nume */

Acum valoarea variabilei prt este adresa variabilei bah. Să presupunem că știm că variabila ptr conține o referință la variabila bah. Apoi, pentru a accesa valoarea acestei variabile, puteți folosi operația de „adresare indirectă” *:

val = *ptr; /* definește valoarea indicată de ptr */ Ultimele două instrucțiuni luate împreună sunt echivalente cu următoarele:

Deci, când în spatele semnului & urmat de numele variabilei, rezultatul operației este adresa variabilei specificate; &nurse dă adresa variabilei asistentă; când un * este urmat de un pointer către o variabilă, rezultatul operației este valoarea plasată în locația de memorie la adresa specificată.

Exemplu: asistent medical = 22;

ptr = /* indicator catre asistenta */

Rezultatul este atribuirea valorii 22 variabilei val.

Nu este suficient să spunem că o variabilă este un pointer. În plus, este necesar să se indice la ce tip de variabilă se referă acest indicator. Motivul este că diferite tipuri de variabile ocupă un număr diferit de locații de memorie, în timp ce unele operațiuni legate de pointer necesită cunoașterea cantității de memorie alocată.

Exemple descrierea corectă a pointerilor: int *pi; char *buc;

Specificația de tip specifică tipul variabilei la care se referă pointerul, iar caracterul * identifică variabila în sine ca indicator. Descrierea tipului int *pi; spune că pi este un pointer și că *pi este o valoare int.

Limbajul C oferă posibilitatea de a defini numele tipurilor de date. Puteți atribui un nume oricărui tip de date folosind un typedef și puteți utiliza acest nume mai târziu când descrieți obiecte.

Format: typedef<старый тип> <новый тип> Exemplu: typedef long LARGE; /* definește tipul mare, care este echivalent cu tipul lung */

Un typedef nu introduce niciun tip nou, ci doar adaugă un nou nume unui tip existent. Variabilele declarate în acest fel au exact aceleași proprietăți ca și variabilele declarate explicit. Redenumirea tipurilor este folosită pentru a introduce nume de tip semnificative sau abreviate, ceea ce îmbunătățește înțelegerea programelor și pentru a îmbunătăți portabilitatea programelor (numele pentru același tip de date pot diferi pe computere diferite).

Operațiuni. Limbajul C se distinge printr-o mare varietate de operații (mai mult de 40). Aici le vom lua în considerare doar pe cele principale, tabelul. 3.3.

Operatii aritmetice. Acestea includ

Adăugați(+),

Scădere (binară) (-),

Înmulțirea (*),

Divizia (/),

Restul întregii diviziuni (%),

Scăderea (unară) (-) .

Limbajul C are o regulă: dacă dividendul și divizorul sunt de tip int, atunci împărțirea este efectuată în întregime, adică partea fracțională a rezultatului este aruncată.

Ca de obicei, în expresii, operațiile de înmulțire, împărțire și rest sunt efectuate înainte de adunare și scădere. Pentru a schimba ordinea acțiunilor, utilizați paranteze.

Programul 85

#include

5 = -3 + 4 * 5 - 6; printf("%d\n",s);

s = -3 + 4%5 - 6; printf("%d\n",s);

s = -3 * 4% - 6/5; printf("%d\n",s);

s= (7 + 6)%5/2; printf("%d\n",s);

Rezultatul execuției programului: 11 1 0 1

Tabelul 3.3 Precedenta si ordinea operatiilor

În limbajul C, există o distincție între conceptele de „tip de date” și „modificator de tip”. Tipul de date este întreg, iar modificatorul este semnat sau nesemnat. Un întreg cu semn va avea atât valori pozitive, cât și negative, în timp ce un întreg fără semn va avea doar valori pozitive. Există cinci tipuri de bază în limbajul C.

  • char – caracter.
  • O variabilă de tip char are o dimensiune de 1 octet, valorile sale sunt diferite caractere din tabelul de coduri, de exemplu: 'f', ':', 'j' (când sunt scrise în program, sunt incluse într-un singur citate).

  • int – întreg.
  • Mărimea unei variabile de tip int nu este definită în standardul limbajului C. În majoritatea sistemelor de programare, dimensiunea unei variabile int corespunde mărimii unui întreg cuvânt mașină. De exemplu, în compilatoarele pentru procesoare pe 16 biți, o variabilă de tip int are o dimensiune de 2 octeți. În acest caz, valorile semnate ale acestei variabile se pot situa în intervalul de la -32768 la 32767.

  • plutire – real.
  • Cuvântul cheie float vă permite să definiți variabile de tip real. Valorile lor au o parte fracțională separată de un punct, de exemplu: -5,6, 31,28 etc. Numerele reale pot fi scrise și sub formă de virgulă mobilă, de exemplu: -1.09e+4. Numărul dinaintea simbolului „e” se numește mantisă, iar după „e” se numește exponent. O variabilă de tip float ocupă 32 de biți în memorie. Poate lua valori în intervalul de la 3.4e-38 la 3.4e+38.

  • dublu – dublă precizie reală;
  • Cuvântul cheie dublu vă permite să definiți o variabilă reală cu precizie dublă. Ocupă de două ori mai mult spațiu de memorie decât o variabilă float. O variabilă de tip double poate lua valori în intervalul 1.7e-308 până la 1.7e+308.

  • nul – fără valoare.
  • Cuvântul cheie void este folosit pentru a neutraliza valoarea unui obiect, de exemplu, pentru a declara o funcție care nu returnează nicio valoare.

Tipuri de variabile:

Programele operează cu diverse date, care pot fi simple sau structurate. Datele simple sunt numere întregi și reale, simboluri și pointeri (adresele obiectelor din memorie). Numerele întregi nu au o parte fracțională, dar numerele reale au. Datele structurate sunt matrice și structuri; acestea vor fi discutate mai jos.

O variabilă este o celulă din memoria computerului care are un nume și stochează o anumită valoare. Valoarea unei variabile se poate modifica în timpul execuției programului. Când o nouă valoare este scrisă într-o celulă, cea veche este ștearsă.

Este un stil bun să numești variabilele în mod semnificativ. Numele variabilei poate conține de la unu la 32 de caractere. Este permisă utilizarea literelor mici și mari, a cifrelor și a liniuței de subliniere, care este considerată o literă în C. Primul caracter trebuie să fie o literă. Numele variabilei nu poate corespunde cuvintelor rezervate.

Tastați char

char este cel mai economic tip. Tipul de caracter poate fi semnat sau nesemnat. Notat ca „car semnat” (tip semnat) și „car nesemnat” (tip nesemnat). Tipul semnat poate stoca valori în intervalul -128 până la +127. Unsigned – de la 0 la 255. 1 octet de memorie (8 biți) este alocat pentru o variabilă char.

Cuvintele cheie cu semn și fără semn indică modul în care este interpretat bitul zero al variabilei declarate, adică dacă este specificat cuvântul cheie fără semn, atunci bitul zero este interpretat ca parte a unui număr, în caz contrar bitul zero este interpretat ca semnat.

Tastați int

Valoarea întreagă int poate fi scurtă sau lungă. Cuvântul cheie scurt este plasat după cuvintele cheie semnate sau nesemnate. Deci există tipuri: signed short int, unsigned short int, signed long int, unsigned long int.

O variabilă de tip signed short int (signed short integer) poate lua valori de la -32768 la +32767, unsigned short int (unsigned short integer) - de la 0 la 65535. Fiecare dintre ele are exact doi octeți de memorie (16 biți).

Când se declară o variabilă de tip signed short int, cuvintele cheie signed și short pot fi omise, iar un astfel de tip de variabilă poate fi declarat pur și simplu int. De asemenea, este posibil să declarați acest tip cu un singur cuvânt cheie, scurt.

O variabilă int unsigned short poate fi declarată unsigned int sau unsigned short.

Pentru fiecare valoare semnată long int sau unsigned long int, sunt alocați 4 octeți de memorie (32 de biți). Valorile variabilelor de acest tip pot fi în intervalele de la -2147483648 la 2147483647 și, respectiv, de la 0 la 4294967295.

Există și variabile de tip long long int, pentru care sunt alocați 8 octeți de memorie (64 de biți). Ele pot fi semnate sau nesemnate. Pentru un tip semnat, intervalul de valori este de la -9223372036854775808 la 9223372036854775807, pentru un tip nesemnat - de la 0 la 18446744073709551615. Un tip semnat poate fi, de asemenea, declarat lung de două cuvinte cheie.

Tip Gamă Gama hexagonală mărimea
nesemnat char 0 … 255 0x00...0xFF 8 biți
semnat char
sau pur și simplu
char
-128 … 127 -0x80…0x7F 8 biți
unsigned short int
sau pur și simplu
nesemnat int sau scurt nesemnat
0 … 65535 0x0000…0xFFFF 16 biți
semnat scurt int sau semnat int
sau pur și simplu
mic de statura sau int
-32768 … 32767 0x8000…0x7FFF 16 biți
nesemnat lung int
sau pur și simplu
nesemnat lung
0 … 4294967295 0x00000000 … 0xFFFFFFFF 32 de biți
semnat lung
sau pur și simplu
lung
-2147483648 … 2147483647 0x80000000 … 0x7FFFFFFF 32 de biți
nesemnat lung lung 0 … 18446744073709551615 0x0000000000000000 … 0xFFFFFFFFFFFFFFFFFF 64 de biți
semnat lung lung
sau pur și simplu
lung lung
-9223372036854775808 … 9223372036854775807 0x8000000000000000 … 0x7FFFFFFFFFFFFFFFFF 64 de biți

Declararea variabilelor

Variabilele sunt declarate într-o declarație. O instrucțiune de declarație constă dintr-o specificație de tip și o listă de nume de variabile separate prin virgulă. Trebuie să existe un punct și virgulă la sfârșit.

[modificatoare] type_specifier identificator [, identificator] ...

Modificatori – cuvinte cheie semnate, nesemnate, scurte, lungi.
Un specificator de tip este un cuvânt cheie char sau int care specifică tipul variabilei care este declarată.
Identificatorul este numele variabilei.

Car x; int a, b, c; nesemnat long long y;

Când este declarată, o variabilă poate fi inițializată, adică i se poate atribui o valoare inițială.

Int x = 100;

Când este declarată, variabila x va conține imediat numărul 100. Este mai bine să declarați variabilele inițializate pe linii separate.

Un tip de date definește un set de valori, un set de operații care pot fi aplicate acelor valori și modul în care sunt stocate valorile și sunt efectuate operațiunile.

Procesul de verificare și plasare a restricțiilor asupra tipurilor de date utilizate se numește verificare de tip sau tastarea datelor programului. Se disting următoarele tipuri de tastare:

  • Tastarea statică - controlul tipului se efectuează în timpul compilării.
  • Tastare dinamică - verificarea tipului se face în timpul rulării.

Limbajul C acceptă tastarea statică, iar tipurile tuturor datelor utilizate într-un program trebuie specificate înainte de a fi compilat.

Există simple, compuse și alte tipuri de date.

Date simple

Datele simple pot fi împărțite în

  • numere întregi,
  • real,
  • simbolic
  • joc de inteligență.

Date compuse (complexe).

  • Un tablou este o colecție indexată de elemente de același tip.
  • Tipul șirului este o matrice care stochează un șir de caractere.
  • Structura este un set de diferite elemente (câmpuri de înregistrare), stocate ca un întreg și care oferă acces la câmpurile individuale ale structurii.

Alte tipuri de date

  • Pointer - stochează o adresă în memoria computerului care indică anumite informații, de obicei un pointer către o variabilă.

Un program scris în limbaj C operează cu date de diferite tipuri. Toate datele au un nume și un tip. Datele din program sunt accesate prin numele lor (identificatori).

Un identificator este o secvență care nu conține mai mult de 32 de caractere, care poate include orice litere din alfabetul latin a - z, A - Z, numere 0 - 9 și litera de subliniere (_). Primul caracter al identificatorului nu trebuie să fie un număr.

Deși este permis un nume de până la 32 de caractere, doar primele 8 caractere sunt semnificative. În afară de nume, toate datele au un tip. Specificarea tipului este necesară pentru a se ști cât spațiu în RAM va ocupa obiectul dat.

Compilatorul C respectă potrivirea strictă a literelor mari și mici în numele identificatorilor și jetoanelor.

Date întregi

Datele întregi pot fi reprezentate sub formă semnată sau nesemnată.

Numerele întregi fără semn sunt reprezentate ca o secvență de biți în intervalul de la 0 la 2 n -1, unde n este numărul de biți ocupați.

Numerele întregi semnate sunt reprezentate în intervalul -2 n-1 …+2 n-1 -1. În acest caz, cel mai semnificativ bit al datelor este atribuit semnului numărului (0 corespunde unui număr pozitiv, 1 unui număr negativ).

Tipuri și dimensiuni de bază ale datelor întregi:

Date reale

Tip real este destinat să reprezinte numere reale. Numerele reale sunt reprezentate în grila de biți a mașinii într-o formă standardizată.

Forma normalizată a numărului presupune prezența unei cifre semnificative (nu 0) înainte de a separa părțile întregi și fracționale. Această reprezentare este înmulțită cu baza sistemului numeric la gradul corespunzător. De exemplu, numărul 12345.678 în formă normalizată poate fi reprezentat ca

12345,678 = 1,2345678 10 4

Numărul 0,009876 în formă normalizată poate fi reprezentat ca

0,009876 = 9,876·10 -3

În sistemul numeric binar, cifra semnificativă dinaintea separatorului zecimal poate fi doar egală cu 1. Dacă numărul nu poate fi reprezentat în formă standardizată (de exemplu, numărul 0), cifra semnificativă înaintea separatorului zecimal este 0.

Cifrele semnificative ale unui număr, aflate în formă normalizată după separatorul părților întregi și fracționale, sunt numite mantisa numărului.

În cazul general, un număr real din grila de biți a unui computer poate fi reprezentat ca 4 câmpuri.

  • semn— un bit care determină semnul unui număr real (0 pentru numere pozitive, 1 pentru numere negative).
  • grad— determină puterea lui 2 cu care doriți să înmulțiți un număr în formă normalizată. Deoarece puterea lui 2 pentru un număr în formă normalizată poate fi fie pozitivă, fie negativă, puterea zero a lui 2 în reprezentarea unui număr real corespunde unei valori de deplasare, care este definită ca

    unde n este numărul de cifre alocate pentru a reprezenta puterea unui număr.

  • întreg- un bit care este întotdeauna egal cu 1 pentru numerele normalizate, astfel încât în ​​unele reprezentări de tip acest bit este omis și se presupune că este egal cu 1.
  • mantisa— cifre semnificative ale reprezentării unui număr, care se află după separatorul părților întregi și fracționale într-o formă standardizată.

Există trei tipuri principale de reprezentare a numerelor reale în limbajul C:

După cum se poate vedea din tabel, tipurile float și double nu au un bit întreg. În acest caz, intervalul de reprezentare a unui număr real este format din două intervale situate simetric față de zero. De exemplu, intervalul de reprezentare a numerelor flotante poate fi reprezentat ca:

Exemplu: Reprezentați numărul -178.125 într-o grilă de 32 de biți (tip flotant).

Pentru a reprezenta un număr în sistemul de numere binar, transformăm părțile întregi și fracționale separat:

178 10 = 10110010 2 .

0,125 10 = 0,001 2 .

178,125 10 = 10110010,001 2 =1,0110010001 2 111

Pentru a converti în formă normalizată, mutați 7 biți la stânga).

Pentru a determina puterea unui număr, utilizați o schimbare:

0111111+00000111 = 10000110 .

Astfel, numărul -178.125 va fi reprezentat în grila de biți ca

Tip de caracter

Tipul de caracter stochează codul unui caracter și este folosit pentru a afișa caractere în diferite codificări. Datele de caractere sunt specificate în coduri și sunt în esență o valoare întreagă. Pentru a stoca coduri de caractere în limbajul C, este utilizat tipul de caractere.

tip boolean

Tipul boolean este folosit în operații logice, este folosit în testele de condiție algoritmică și în bucle și are două semnificații:

  • adevarat adevarat
  • fals — — fals

Programul trebuie să declare toate datele utilizate, indicând numele și tipul acestora. O descriere a datelor trebuie să preceadă utilizarea lor în program.

Exemplu de declarare a obiectului

int n; // Variabila n de tip întreg
dublu a; // Variabila a de tip real de precizie dublă

Bazele limbajului

Codul programului și datele pe care programul le manipulează sunt înregistrate în memoria computerului ca o secvență de biți. Pic este cel mai mic element al memoriei computerului capabil să stocheze fie 0, fie 1. La nivel fizic, aceasta corespunde tensiunii electrice, care, după cum știm, fie este prezentă, fie nu. Privind conținutul memoriei computerului, vom vedea ceva de genul:
...

Este foarte dificil să înțelegem o astfel de secvență, dar uneori trebuie să manipulăm astfel de date nestructurate (de obicei, necesitatea acestui lucru apare la programarea driverelor de dispozitiv hardware). C++ oferă un set de operații pentru lucrul cu date pe biți. (Vom vorbi despre asta în capitolul 4.)
De obicei, o anumită structură este impusă unei secvențe de biți, grupând biții în octețiȘi cuvinte. Un octet conține 8 biți, iar un cuvânt conține 4 octeți sau 32 de biți. Cu toate acestea, definiția cuvântului poate fi diferită pe diferite sisteme de operare. Tranziția la sistemele pe 64 de biți începe acum, iar mai recent sistemele cu cuvinte pe 16 biți au fost răspândite. Deși marea majoritate a sistemelor au aceeași dimensiune de octeți, totuși vom numi aceste cantități specifice mașinii.

Acum putem vorbi despre, de exemplu, un octet la adresa 1040 sau un cuvânt la adresa 1024 și să spunem că un octet la adresa 1032 nu este egal cu un octet la adresa 1040.
Cu toate acestea, nu știm ce reprezintă orice octet, orice cuvânt de mașină. Cum să înțelegeți semnificația anumitor 8 biți? Pentru a interpreta în mod unic sensul acestui octet (sau cuvânt, sau alt set de biți), trebuie să cunoaștem tipul de date pe care le reprezintă octetul.
C++ oferă un set de tipuri de date încorporate: caracter, întreg, real - și un set de tipuri compuse și extinse: șiruri de caractere, matrice, numere complexe. În plus, pentru acțiunile cu aceste date există un set de bază de operații: comparație, aritmetică și alte operații. Există, de asemenea, operatori de tranziție, bucle și operatori condiționali. Aceste elemente ale limbajului C++ alcătuiesc setul de cărămizi din care poți construi un sistem de orice complexitate. Primul pas în stăpânirea C++ va fi studiul elementelor de bază enumerate, căruia îi este dedicată Partea a II-a a acestei cărți.
Capitolul 3 oferă o prezentare generală a tipurilor încorporate și extinse și a mecanismelor prin care puteți crea tipuri noi. Acesta este, desigur, în principal mecanismul de clasă introdus în Secțiunea 2.3. Capitolul 4 acoperă expresii, operațiunile încorporate și prioritățile acestora și conversiile de tip. Capitolul 5 vorbește despre instrucțiunile lingvistice. În cele din urmă, Capitolul 6 prezintă Biblioteca standard C++ și tipurile de containere vector și matrice asociativă.

3. Tipuri de date C++

Acest capitol oferă o privire de ansamblu incorporat, sau elementar, tipuri de date ale limbajului C++. Începe cu o definiție literali, cum ar fi 3.14159 sau pi, și apoi este introdus conceptul variabil, sau obiect, care trebuie să aparțină unuia dintre tipurile de date. Restul capitolului este dedicat unei descrieri detaliate a fiecărui tip încorporat. În plus, sunt furnizate tipurile de date derivate pentru șiruri și matrice furnizate de Biblioteca standard C++. Deși aceste tipuri nu sunt elementare, ele sunt foarte importante pentru scrierea unor programe reale C++ și dorim să le prezentăm cititorului cât mai curând posibil. Vom numi aceste tipuri de date expansiune tipuri de bază C++.

3.1. Literale

C++ are un set de tipuri de date încorporate pentru reprezentarea numerelor întregi și reale, a caracterelor, precum și un tip de date „matrice de caractere”, care este folosit pentru a stoca șiruri de caractere. Tipul char este folosit pentru a stoca caractere individuale și numere întregi mici. Ocupă un octet de mașină. Tipurile short, int și long sunt concepute pentru a reprezenta numere întregi. Aceste tipuri diferă doar în intervalul de valori pe care numerele le pot lua, iar dimensiunile specifice ale tipurilor enumerate depind de implementare. De obicei, scurt ocupă jumătate de cuvânt de mașină, int ocupă un cuvânt, lung ocupă unul sau două cuvinte. Pe sistemele pe 32 de biți, int și long au de obicei aceeași dimensiune.

Tipurile float, double și long double sunt pentru numere în virgulă mobilă și diferă în precizia reprezentării (numărul de cifre semnificative) și interval. În mod obișnuit, float (precizie simplă) necesită un cuvânt de mașină, dublu (precizie dublă) necesită două, iar dublu lung (precizie extinsă) necesită trei.
char, short, int și long alcătuiesc împreună tipuri întregi, care, la rândul său, poate fi simbolic(semnat) și nesemnat(nesemnat). În tipurile cu semn, bitul din stânga stochează semnul (0 este plus, 1 este minus), iar biții rămași conțin valoarea. În tipurile fără semn, toți biții sunt utilizați pentru valoare. Caracterul cu semn pe 8 biți poate reprezenta valori de la -128 la 127, iar caracterul nesemnat poate reprezenta valori de la 0 la 255.

Când un anumit număr, de exemplu 1, este întâlnit într-un program, acest număr este apelat literal, sau constantă literală. O constantă pentru că nu-i putem modifica valoarea și un literal pentru că valoarea sa apare în textul programului. Un literal este o valoare neadresabilă: deși este, desigur, de fapt stocat în memoria mașinii, nu există nicio modalitate de a-i cunoaște adresa. Fiecare literal are un tip specific. Deci, 0 este de tip int, 3.14159 este de tip double.

Literale de tip întreg pot fi scrise în zecimal, octal și hexazecimal. Iată cum arată numărul 20 reprezentat prin literale zecimale, octale și hexazecimale:

20 // zecimală
024 // octal
0x14 // hexazecimal

Dacă un literal începe cu 0, este tratat ca octal, dacă cu 0x sau 0X, atunci ca hexazecimal. Notația obișnuită este tratată ca un număr zecimal.
În mod implicit, toate literalele întregi sunt de tipul semnate int. Puteți defini în mod explicit un literal întreg ca fiind de tip lung, adăugând litera L la sfârșitul numărului (se folosesc atât L majuscule, cât și l minuscule, dar pentru ușurință de lizibilitate nu ar trebui să utilizați literele mici: poate fi ușor confundată cu

1). Litera U (sau u) de la sfârșit identifică literalul ca un int nesemnat și două litere - UL sau LU - ca un tip lung nesemnat. De exemplu:

128u 1024UL 1L 8Lu

Literale care reprezintă numere reale pot fi scrise fie cu virgulă zecimală, fie în notație științifică (științifică). Implicit sunt de tip double. Pentru a indica în mod explicit tipul de float, trebuie să utilizați sufixul F sau f și pentru dublu lung - L sau l, dar numai dacă este scris cu un punct zecimal. De exemplu:

3.14159F 0/1f 12.345L 0.0 3el 1.0E-3E 2. 1.0L

Cuvintele adevărat și fals sunt literale bool.
Constantele de caractere literale reprezentabile sunt scrise ca caractere între ghilimele simple. De exemplu:

"a" "2" "," " " (spațiu)

Caracterele speciale (tab, întoarcere car) sunt scrise ca secvențe de evacuare. Sunt definite următoarele astfel de secvențe (încep cu un caracter backslash):

Linie nouă \n filă orizontală \t backspace \b filă verticală \v retur car \r alimentare hârtie \f apel \o bară oblică inversă \\ întrebare \? ghilimele simple \" ghilimele duble \"

Secvența generală de evadare este de forma \ooo, unde ooo este una până la trei cifre octale. Acest număr este codul caracterului. Folosind codul ASCII, putem scrie următoarele literale:

\7 (clopot) \14 (linie nouă) \0 (null) \062 ("2")

Un caracter literal poate fi prefixat cu L (de exemplu, L „a”), ceea ce înseamnă că tipul special wchar_t este un tip de caracter de doi octeți care este folosit pentru a stoca caractere din alfabetele naționale dacă nu pot fi reprezentate printr-un tip de caracter obișnuit. , cum ar fi literele chineze sau japoneze.
Un șir literal este un șir de caractere cuprins între ghilimele duble. Un astfel de literal poate cuprinde mai multe linii; în acest caz, o bară oblică inversă este plasată la sfârșitul liniei. Caracterele speciale pot fi reprezentate prin propriile secvențe de evadare. Iată exemple de literale șir:

"" (șir gol) "a" "\nCC\toptions\tfile.\n" "un literal \ șir cu mai multe linii semnalează continuarea sa cu o bară oblică inversă"

De fapt, un șir literal este o matrice de constante de caractere, unde, prin convenția limbajelor C și C++, ultimul element este întotdeauna un caracter special cu codul 0 (\0).
Literalul „A” specifică un singur caracter A, iar literalul șir „A” specifică o matrice de două elemente: „A” și \0 (caracterul gol).
Deoarece există un tip wchar_t, există și literale de acest tip, notate, ca și în cazul caracterelor individuale, prin prefixul L:

L „un șir larg literal”

Un șir literal de tip wchar_t este o matrice de caractere de același tip terminată cu nul.
Dacă două sau mai multe literale de șir (cum ar fi char sau wchar_t) apar într-un rând într-un test de program, compilatorul le concatenează într-un șir. De exemplu, următorul text

"doi" "unii"

va produce o matrice de opt caractere - două și un caracter nul final. Rezultatul concatenării șirurilor de diferite tipuri este nedefinit. Daca scrii:

// aceasta nu este o idee bună "două" L"unii"

Pe un computer, rezultatul va fi un șir semnificativ, dar pe altul poate fi ceva complet diferit. Programele care folosesc caracteristici de implementare ale unui anumit compilator sau sistem de operare nu sunt portabile. Descurajăm cu tărie utilizarea unor astfel de structuri.

Exercițiul 3.1

Explicați diferența dintre definițiile următoarelor literale:

(a) "a", L"a", "a", L"a" (b) 10, 10u, 10L, 10uL, 012, 0*C (c) 3.14, 3.14f, 3.14L

Exercițiul 3.2

Ce greșeli sunt făcute în exemplele de mai jos?

(a) „Cine merge cu F\144rgus?\014” (b) 3.14e1L (c) „două” L „unele” (d) 1024f (e) 3.14UL (f) „comentare pe mai multe rânduri”

3.2. Variabile

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem:

#include
int main() (
// o primă soluție
cout<< "2 raised to the power of 10: ";
cout<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
cout<< endl;
returnează 0;
}

Problema a fost rezolvată, deși a trebuit să verificăm în mod repetat dacă literalul 2 s-a repetat efectiv de 10 ori. Nu am greșit scriind această secvență lungă de doi, iar programul a produs rezultatul corect - 1024.
Dar acum ni s-a cerut să ridicăm 2 la puterea 17, iar apoi la puterea 23. Este extrem de incomod să modificăm textul programului de fiecare dată! Și, și mai rău, este foarte ușor să faci o greșeală scriind două în plus sau omițând-o... Dar dacă trebuie să tipăriți un tabel de puteri a doi de la 0 la 15? Repetați două rânduri care au forma generală de 16 ori:

Cout<< "2 в степени X\t"; cout << 2 * ... * 2;

unde X este crescut succesiv cu 1, iar numărul necesar de literale este înlocuit cu zecimală?

Da, ne-am descurcat sarcinii. Este puțin probabil ca clientul să pătrundă în detalii, fiind mulțumit de rezultatul obținut. În viața reală, această abordare funcționează adesea; în plus, este justificată: problema este rezolvată într-un mod deloc elegant, dar în intervalul de timp dorit. Căutarea unei opțiuni mai frumoase și mai competente se poate dovedi a fi o pierdere de timp nepractică.

În acest caz, „metoda forței brute” oferă răspunsul corect, dar cât de neplăcut și plictisitor este să rezolvi problema în acest fel! Știm exact ce pași trebuie făcuți, dar acești pași în sine sunt simpli și monotoni.

Implicarea unor mecanisme mai complexe pentru aceeași sarcină, de regulă, crește semnificativ timpul etapei pregătitoare. În plus, cu cât sunt utilizate mecanisme mai complexe, cu atât este mai mare probabilitatea de erori. Dar chiar și în ciuda greșelilor inevitabile și a mișcărilor greșite, utilizarea „tehnologiilor înalte” poate aduce beneficii în viteza de dezvoltare, ca să nu mai vorbim de faptul că aceste tehnologii ne extind semnificativ capacitățile. Și – ce este interesant! – procesul decizional în sine poate deveni atractiv.
Să revenim la exemplul nostru și să încercăm să „îmbunătățim tehnologic” implementarea acestuia. Putem folosi un obiect numit pentru a stoca valoarea puterii la care dorim să ne ridicăm numărul. În plus, în loc de o secvență repetată de literale, poate fi folosit un operator de buclă. Iată cum va arăta:

#include
int main()
{
// obiecte de tip int
valoare int = 2;
int pow = 10;
cout<< value << " в степени "
<< pow << ": \t";
int res = 1;
// operator de buclă:
// repeta calculul res
// până când cnt devine mai mare decât pow
pentru (int cnt=1; cnt<= pow; ++cnt)
res = res * valoare;
cout<< res << endl;
}

value, pow, res și cnt sunt variabile care vă permit să stocați, să modificați și să preluați valori. Instrucțiunea buclă for repetă linia rezultată pow ori.
Nu există nicio îndoială că am creat un program mult mai flexibil. Cu toate acestea, aceasta nu este încă o funcție. Pentru a obține o funcție reală care poate fi utilizată în orice program pentru a calcula puterea unui număr, trebuie să selectați partea generală a calculelor și să setați valori specifice cu parametri.

Int pow(int val, int exp) ( for (int res = 1; exp > 0; --exp) res = res * val; return res; )

Acum nu va fi dificil să obțineți orice putere a numărului dorit. Acesta este modul în care este implementată ultima noastră sarcină - tipăriți un tabel cu puteri de două de la 0 la 15:

#include extern int pow(int,int); int main() ( int val = 2; int exp = 15;
cout<< "Степени 2\n";
pentru (int cnt=0; cnt<= exp; ++cnt)
cout<< cnt << ": "
<< pow(val, cnt) << endl;
returnează 0;
}

Desigur, funcția noastră pow() nu este încă suficient de generală și suficient de robustă. Nu poate funcționa cu numere reale, ridică incorect numerele la o putere negativă - returnează întotdeauna 1. Rezultatul ridicării unui număr mare la o putere mai mare poate să nu se încadreze într-o variabilă int, iar apoi o valoare incorectă aleatorie va fi returnată. Vedeți cât de dificil se dovedește a fi să scrieți funcții concepute pentru utilizare pe scară largă? Mult mai dificilă decât implementarea unui algoritm specific care vizează rezolvarea unei anumite probleme.

3.2.1. Ce este o variabilă

Variabil, sau un obiect– aceasta este o zonă numită de memorie la care avem acces din program; Puteți pune valori acolo și apoi le puteți recupera. Fiecare variabilă C++ are un tip specific, care caracterizează dimensiunea și locația acelei locații de memorie, gama de valori pe care o poate stoca și setul de operații care pot fi aplicate acelei variabile. Iată un exemplu de definire a cinci obiecte de diferite tipuri:

Int student_count; salariu dublu; bool on_loan; strins adresa_strada; delimitator de caractere;

O variabilă, ca un literal, are un tip specific și își stochează valoarea într-o anumită zonă a memoriei. Adresabilitate– asta îi lipsește literalului. Există două cantități asociate cu o variabilă:

  • valoarea reală, sau valoarea r (din valoarea citită - valoare pentru citire), care este stocată în această zonă de memorie și este inerentă atât variabilei, cât și literalului;
  • valoarea adresei zonei de memorie asociată cu variabila, sau valoarea l (din valoarea locației) - locul în care este stocată valoarea r; inerente numai obiectului.

În exprimare

Ch = ch - "0";

variabila ch este situată atât în ​​stânga cât și în dreapta simbolului de atribuire. În dreapta este valoarea de citit (ch și caracterul literal „0”): datele asociate cu variabila sunt citite din zona de memorie corespunzătoare. În stânga este valoarea locației: rezultatul scăderii este plasat în zona de memorie asociată cu variabila ch. În general, operandul din stânga al unei operații de atribuire trebuie să fie o valoare l. Nu putem scrie următoarele expresii:

// erori de compilare: valorile din stânga nu sunt valori l // eroare: literal nu este o valoare l 0 = 1; // eroare: expresia aritmetică nu este un salariu cu valoare l + salariu * 0,10 = salariu_nou;

Declarația de definire a variabilei îi alocă memorie. Deoarece un obiect are asociată o singură locație de memorie, o astfel de instrucțiune poate apărea o singură dată într-un program. Dacă o variabilă definită într-un fișier sursă trebuie utilizată în altul, apar probleme. De exemplu:

// fisier module0.C // defineste un obiect fileName sir fileName; // ... atribuiți fileName o valoare
// fișier module1.C
// folosește obiectul fileName
// din păcate, nu compilează:
// fileName nu este definit în module1.C
ifstream input_file(fișierNume);

C++ necesită ca un obiect să fie cunoscut înainte de a fi accesat prima dată. Acest lucru este necesar pentru a vă asigura că obiectul este utilizat corect în funcție de tipul său. În exemplul nostru, module1.C va provoca o eroare de compilare deoarece variabila fileName nu este definită în ea. Pentru a evita această eroare, trebuie să spunem compilatorului despre variabila fileName deja definită. Acest lucru se face folosind declarația de variabilă:

// fișierul module1.C // folosește obiectul fileName // fileName este declarat, adică programul primește
// informații despre acest obiect fără definiția sa secundară
extern string fileName; ifstream input_file(nume fișier)

O declarație de variabilă îi spune compilatorului că un obiect cu un nume dat, de un tip dat, este definit undeva în program. Memoria nu este alocată pentru o variabilă atunci când aceasta este declarată. (Cuvântul cheie extern este tratat în Secțiunea 8.2.)
Un program poate conține câte declarații ale aceleiași variabile dorește, dar poate fi definit o singură dată. Este convenabil să plasați astfel de declarații în fișierele antet, incluzându-le în acele module care necesită acest lucru. În acest fel, putem stoca informații despre obiecte într-un singur loc și le putem modifica cu ușurință dacă este necesar. (Vom vorbi mai multe despre fișierele antet în Secțiunea 8.2.)

3.2.2. Nume variabilă

Nume variabilă, sau identificator, poate consta din litere latine, cifre și caracterul de subliniere. Literele mari și mici din nume sunt diferite. Limbajul C++ nu limitează lungimea identificatorului, dar folosirea numelor prea lungi precum gosh_this_is_an_impossibly_name_to_type este incomod.
Unele cuvinte sunt cuvinte cheie în C++ și nu pot fi folosite ca identificatori; Tabelul 3.1 oferă o listă completă a acestora.

Tabelul 3.1. Cuvinte cheie C++

asm auto bool pauză caz
captură char clasă const const_cast
continua Mod implicit șterge do dubla
dynamic_cast altfel enumerare explicit export
extern fals pluti pentru prieten
mergi la dacă în linie int lung
mutabil spatiu de nume nou operator privat
protejat public Inregistreaza-te reinterpret_cast întoarcere
mic de statura semnat dimensiunea static static_cast
struct intrerupator șablon acest arunca
typedef Adevărat încerca tipizat nume de tip
uniune nulă unire folosind virtual vid

Pentru a face programul mai ușor de înțeles, vă recomandăm să urmați convențiile de denumire a obiectelor general acceptate:

  • numele variabilei este scris de obicei cu litere mici, de exemplu index (pentru comparație: Index este numele tipului, iar INDEX este o constantă definită folosind directiva #define preprocesor);
  • identificatorul trebuie să aibă o anumită semnificație, explicând scopul obiectului din program, de exemplu: data_nașterii sau salariul;

Dacă un astfel de nume constă din mai multe cuvinte, cum ar fi data_nașterii, atunci se obișnuiește fie să se separe cuvintele cu o liniuță de subliniere (data_nașterii) fie să se scrie fiecare cuvânt ulterior cu o literă majusculă (data nașterii). S-a observat că programatorii obișnuiți cu abordarea orientată pe obiecte preferă să evidențieze cuvintele cu majuscule, în timp ce cei_care_au_scris_mult_în_C folosesc caracterul de subliniere. Care dintre cele două metode este mai bună este o chestiune de gust.

3.2.3. Definirea obiectului

În cazul său cel mai simplu, operatorul de definire a obiectului constă din specificator de tipȘi numele obiectuluiși se termină cu punct și virgulă. De exemplu:

Salariu dublu salariu dublu; int luna; int zi; int an; distanță lungă nesemnată;

Puteți defini mai multe obiecte de același tip într-o singură instrucțiune. În acest caz, numele lor sunt enumerate separate prin virgule:

Salariu dublu, salariu; int luna, zi, an; distanță lungă nesemnată;

Pur și simplu definirea unei variabile nu îi dă o valoare inițială. Dacă un obiect este definit ca fiind global, specificația C++ garantează că va fi inițializat la zero. Dacă variabila este locală sau alocată dinamic (folosind noul operator), valoarea sa inițială este nedefinită, adică poate conține o valoare aleatorie.
Utilizarea unor astfel de variabile este o greșeală foarte frecventă, care este și greu de detectat. Se recomandă să specificați în mod explicit valoarea inițială a unui obiect, cel puțin în cazurile în care nu se știe dacă obiectul se poate inițializa singur. Mecanismul de clasă introduce conceptul de constructor implicit, care este folosit pentru a atribui valori implicite. (Am vorbit deja despre acest lucru în Secțiunea 2.3. Vom vorbi despre constructorii impliciti puțin mai târziu, în Secțiunile 3.11 și 3.15, unde ne vom uita la șirurile și clasele complexe din biblioteca standard.)

Int main() ( // obiect local neinițializat int ival;
// obiectul de tip șir este inițializat
// constructor implicit
proiect string;
// ...
}

Valoarea inițială poate fi specificată direct în instrucțiunea de definire a variabilei. În C++, sunt permise două forme de inițializare a variabilei - explicit, folosind operatorul de atribuire:

Int ival = 1024; string project = „Fantasia 2000”;

și implicit, cu valoarea inițială specificată în paranteze:

Int ival(1024); string project("Fantasia 2000");

Ambele opțiuni sunt echivalente și setează valorile inițiale pentru variabila întreagă ival la 1024 și pentru șirul de proiect la „Fantasia 2000”.
Inițializarea explicită poate fi folosită și la definirea variabilelor dintr-o listă:

Salariu dublu = 9999,99, salariu = salariu + 0,01; int luna = 08; ziua = 07, anul = 1955;

Variabila devine vizibilă (și valabilă în program) imediat după ce este definită, așa că am putea inițializa variabila salariu cu suma variabilei salariale nou definite cu o constantă. Deci definiția este:

// corect, dar fără sens int bizarre = bizar;

este valabil din punct de vedere sintactic, deși lipsit de sens.
Tipurile de date încorporate au o sintaxă specială pentru specificarea unei valori nule:

// ival primește valoarea 0 și dval primește 0.0 int ival = int(); dublu dval = dublu();

În următoarea definiție:

// int() se aplică fiecăruia dintre cele 10 elemente ale vectorului< int >ivec(10);

Fiecare dintre cele zece elemente ale vectorului este inițializat cu int(). (Am vorbit deja despre clasa vectorului în Secțiunea 2.8. Pentru mai multe despre aceasta, vezi Secțiunea 3.10 și Capitolul 6.)
Variabila poate fi inițializată cu o expresie de orice complexitate, inclusiv apeluri de funcții. De exemplu:

#include #include
preț dublu = 109,99, reducere = 0,16;
dublu pret_de_vanzare(pret * reducere);
string pet ("riduri"); extern int get_value(); int val = get_value();
nesemnat abs_val = abs(val);

abs() este o funcție standard care returnează valoarea absolută a unui parametru.
get_value() este o funcție definită de utilizator care returnează o valoare întreagă.

Exercițiul 3.3

Care dintre următoarele definiții de variabile conțin erori de sintaxă?

(a) int car = 1024, auto = 2048; (b) int ival = ival; (c) int ival(int()); (d) salariu dublu = salariu = 9999,99; (e) cin >> int input_value;

Exercițiul 3.4

Explicați diferența dintre valoarea l și valoarea r. Dă exemple.

Exercițiul 3.5

Găsiți diferențele în utilizarea numelui și a variabilelor student în primul și al doilea rând din fiecare exemplu:

(a) nume șir extern; nume șir ("exercițiul 3.5a"); (b) vector extern elevi; vector elevi;

Exercițiul 3.6

Ce nume de obiecte sunt nevalide în C++? Schimbați-le astfel încât să fie corecte din punct de vedere sintactic:

(a) int dublu = 3,14159; (b) vector< int >_; (c) spațiu de nume șir; (d) string catch-22; (e) char 1_or_2 = "1"; (f) float Float = 3,14f;

Exercițiul 3.7

Care este diferența dintre următoarele definiții ale variabilelor globale și locale?

String global_class; int global_int; int main() (
int local_int;
string local_class; // ...
}

3.3. Indicatoare

Pointerii și alocarea memoriei dinamice au fost introduse pe scurt în Secțiunea 2.2. Indicator este un obiect care conține adresa altui obiect și permite manipularea indirectă a acestui obiect. În mod obișnuit, pointerii sunt folosiți pentru a manipula obiecte create dinamic, pentru a construi structuri de date înrudite, cum ar fi liste legate și arbori ierarhici și pentru a transmite obiecte mari - matrice și obiecte de clasă - ca parametri funcțiilor.
Fiecare pointer este asociat cu un anumit tip de date, iar reprezentarea lor internă nu depinde de tipul intern: atât dimensiunea memoriei ocupată de un obiect de tip pointer, cât și intervalul de valori sunt aceleași. Diferența este modul în care compilatorul tratează obiectul adresat. Pointerii către diferite tipuri pot avea aceeași valoare, dar zona de memorie în care se află tipurile corespunzătoare poate fi diferită:

  • un pointer către int care conține valoarea adresei 1000 este direcționat către zona de memorie 1000-1003 (pe un sistem pe 32 de biți);
  • un pointer către dublu care conține valoarea adresei 1000 este direcționat către zona de memorie 1000-1007 (pe un sistem pe 32 de biți).

Aici sunt cateva exemple:

Int *ip1, *ip2; complex *cp; șir *pstring; vector *pvec; dublu *dp;

Indexul este indicat printr-un asterisc înaintea numelui. În definirea variabilelor cu o listă, un asterisc trebuie să preceadă fiecare indicator (vezi mai sus: ip1 și ip2). În exemplul de mai jos, lp este un pointer către un obiect lung, iar lp2 este un obiect lung:

Lung *lp, lp2;

În următorul caz, fp este interpretat ca un obiect flotant, iar fp2 este un pointer către acesta:

Float fp, *fp2;

Operatorul de dereferință (*) poate fi separat prin spații de nume și chiar adiacent cu cuvântul cheie tip. Prin urmare, definițiile de mai sus sunt corecte din punct de vedere sintactic și complet echivalente:

//atentie: ps2 nu este un pointer catre un sir! șir* ps, ps2;

Se poate presupune că atât ps, cât și ps2 sunt pointeri, deși un pointer este doar primul dintre ele.
Dacă valoarea indicatorului este 0, atunci nu conține nicio adresă de obiect.
Să fie specificată o variabilă de tip int:

Int ival = 1024;

Următoarele sunt exemple de definire și utilizare a indicatorilor către int pi și pi2:

//pi este inițializat la adresa zero int *pi = 0;
// pi2 este inițializat cu adresa ival
int *pi2 =
// corect: pi si pi2 contin adresa ival
pi = pi2;
// pi2 conține adresa zero
pi2 = 0;

Nu i se poate atribui unui pointer o valoare care nu este o adresă:

// eroare: pi nu poate accepta valoarea int pi = ival

În același mod, nu puteți atribui o valoare unui pointer de un tip care este adresa unui obiect de alt tip. Dacă sunt definite următoarele variabile:

Dval dublu; dublu *ps =

atunci ambele expresii de atribuire de mai jos vor provoca o eroare de compilare:

// erori de compilare // atribuire nevalidă a tipului de date: int*<== double* pi = pd pi = &dval;

Nu este că variabila pi nu poate conține adresele unui obiect dval - adresele obiectelor de diferite tipuri au aceeași lungime. Astfel de operațiuni de amestecare a adreselor sunt interzise în mod deliberat deoarece interpretarea obiectelor de către compilator depinde de tipul pointerului către acestea.
Desigur, există cazuri când suntem interesați de valoarea adresei în sine, și nu de obiectul către care indică (să spunem că vrem să comparăm această adresă cu alta). Pentru a rezolva astfel de situații, a fost introdus un pointer special de gol, care poate indica orice tip de date, iar următoarele expresii vor fi corecte:

// corect: void* poate conține // adrese de orice tip void *pv = pi; pv = pd;

Tipul obiectului indicat de void* este necunoscut și nu putem manipula acest obiect. Tot ce putem face cu un astfel de pointer este să îi atribuim valoarea unui alt pointer sau să o comparăm cu o anumită valoare a adresei. (Vom vorbi mai multe despre indicatorul de gol în Secțiunea 4.14.)
Pentru a accesa un obiect având în vedere adresa sa, trebuie să utilizați operația de dereferențiere, sau adresare indirectă, notate cu un asterisc (*). Având următoarele definiții de variabilă:

Int ival = 1024;, ival2 = 2048; int *pi =

// atribuirea indirectă a variabilei ival la valoarea ival2 *pi = ival2;
// utilizarea indirectă a variabilei ival ca rvalue și lvalue
*pi = abs(*pi); // ival = abs(ival);
*pi = *pi + 1; // ival = ival + 1;

Când aplicăm operatorul de adresă (&) unui obiect de tip int, obținem un rezultat de tip int*
int *pi =
Dacă aceeași operație este aplicată unui obiect de tip int* (pointer către int), obținem un pointer către un pointer către int, adică. int**. int** este adresa unui obiect care conține adresa unui obiect de tip int. Prin dereferențierea ppi, obținem un obiect de tip int* care conține adresa lui ival. Pentru a obține obiectul ival în sine, operația de dereferință pe ppi trebuie aplicată de două ori.

Int **ppi = π int *pi2 = *ppi;
cout<< "Значение ival\n" << "явное значение: " << ival << "\n"
<< "косвенная адресация: " << *pi << "\n"
<< "дважды косвенная адресация: " << **ppi << "\n"

Indicatorii pot fi folosiți în expresii aritmetice. Observați următorul exemplu, în care două expresii fac lucruri complet diferite:

Int i, j, k; int *pi = // i = i + 2
*pi = *pi + 2; //mărește adresa conținută în pi cu 2
pi = pi + 2;

Puteți adăuga o valoare întreagă la un indicator sau puteți scădea din aceasta. Adăugarea lui 1 la un pointer mărește valoarea pe care o conține cu dimensiunea memoriei alocate unui obiect de tipul corespunzător. Dacă char este de 1 octet, int este de 4 octeți și double este 8, atunci adăugarea a 2 la pointerii la char, int și double le va crește valoarea cu 2, 8 și, respectiv, 16. Cum poate fi interpretat? Dacă obiectele de același tip sunt localizate unul după altul în memorie, atunci creșterea indicatorului cu 1 va face ca acesta să indice următorul obiect. Prin urmare, aritmetica pointerului este folosită cel mai adesea la procesarea tablourilor; în orice alte cazuri, acestea sunt greu justificate.
Iată cum arată un exemplu tipic de utilizare a aritmeticii adresei atunci când iterați elementele unui tablou folosind un iterator:

Int ia; int *iter = int *iter_end =
în timp ce (iter != iter_end) (
face_ceva_cu_valoare (*iter);
++iter;
}

Exercițiul 3.8

Definițiile variabilelor sunt date:

Int ival = 1024, ival2 = 2048; int *pi1 = &ival, *pi2 = &ival2, **pi3 = 0;

Ce se întâmplă când efectuați următoarele operațiuni de atribuire? Există greșeli în aceste exemple?

(a) ival = *pi3; (e) pi1 = *pi3; (b) *pi2 = *pi3; (f) ival = *pi1; (c) ival = pi2; (g) pi1 = ival; (d) pi2 = *pi1; (h)pi3 =

Exercițiul 3.9

Lucrul cu pointeri este unul dintre cele mai importante aspecte ale C și C++, dar este ușor să faci greșeli. De exemplu, cod

Pi = pi = pi + 1024;

aproape sigur va face ca pi să indice o locație aleatoare de memorie. Ce face acest operator de atribuire și când nu va provoca o eroare?

Exercițiul 3.10

Acest program conține o eroare legată de utilizarea incorectă a indicatorilor:

Int foobar(int *pi) ( *pi = 1024; return *pi; )
int main() (
int *pi2 = 0;
int ival = foobar(pi2);
returnează 0;
}

Care este eroarea? Cum o pot repara?

Exercițiul 3.11

Erorile din cele două exerciții anterioare se manifestă și duc la consecințe fatale din cauza lipsei C++ de a verifica corectitudinea valorilor pointerului în timpul execuției programului. De ce credeți că nu a fost pusă în aplicare o astfel de verificare? Puteți oferi câteva instrucțiuni generale pentru a face lucrul cu pointeri mai sigur?

3.4. Tipuri de șiruri

C++ acceptă două tipuri de șiruri - un tip încorporat moștenit de la C și clasa de șiruri din biblioteca standard C++. Clasa șir oferă mult mai multe capacități și, prin urmare, este mai convenabil de utilizat, cu toate acestea, în practică există adesea situații în care este necesar să se folosească un tip încorporat sau să se înțeleagă bine cum funcționează. (Un exemplu ar fi analizarea parametrilor liniei de comandă transmise funcției main(). Vom acoperi acest lucru în Capitolul 7.)

3.4.1. Tip șir încorporat

După cum sa menționat deja, tipul de șir încorporat provine din C++, moștenit de la C. Șirul de caractere este stocat în memorie ca o matrice și accesat folosind un pointer char*. Biblioteca standard C oferă un set de funcții pentru manipularea șirurilor. De exemplu:

// returnează lungimea șirului int strlen(const char*);
// compară două șiruri
int strcmp(const char*, const char*);
// copiează o linie pe alta
char* strcpy(char*, const char*);

Biblioteca standard C face parte din biblioteca C++. Pentru a-l folosi trebuie să includem fișierul antet:

#include

Indicatorul către char, pe care îl folosim pentru a accesa un șir, indică matricea de caractere corespunzătoare șirului. Chiar și atunci când scriem un șir literal ca

Const char *st = "Prețul unei sticle de vin\n";

compilatorul pune toate caracterele șirului într-o matrice și apoi atribuie st adresa primului element al matricei. Cum poți lucra cu un șir folosind un astfel de indicator?
În mod obișnuit, aritmetica adresei este folosită pentru a repeta peste caractere dintr-un șir. Deoarece șirul se termină întotdeauna cu un caracter nul, puteți crește indicatorul cu 1 până când următorul caracter devine nul. De exemplu:

În timp ce (*st++) ( ... )

st este dereferențiat și valoarea rezultată este verificată pentru a vedea dacă este adevărată. Orice valoare diferită de zero este considerată adevărată și, prin urmare, bucla se termină atunci când este atins un caracter cu codul 0. Operația de creștere ++ adaugă 1 la indicatorul st și, astfel, îl mută la următorul caracter.
Așa ar putea arăta o implementare a unei funcții care returnează lungimea unui șir. Rețineți că, deoarece un pointer poate conține o valoare nulă (nu indică la nimic), ar trebui verificat înainte de operația de dereferire:

Int string_length(const char *st) ( int cnt = 0; if (st) while (*st++) ++cnt; return cnt; )

Un șir încorporat poate fi considerat gol în două cazuri: dacă indicatorul către șir are o valoare nulă (caz în care nu avem deloc șir) sau dacă indică către o matrice formată dintr-un singur caracter nul (adică , la un șir care nu conține caractere semnificative).

// pc1 nu abordează nicio matrice de caractere char *pc1 = 0; // pc2 abordează caracterul nul const char *pc2 = "";

Pentru un programator începător, utilizarea șirurilor de caractere încorporate este plină de erori din cauza nivelului prea scăzut de implementare și a incapacității de a face fără aritmetica adresei. Mai jos vom arăta câteva greșeli comune făcute de începători. Sarcina este simplă: calculați lungimea șirului. Prima versiune este incorectă. Repare-o.

#include const char *st = "Prețul unei sticle de vin\n"; int main() (
int len ​​= 0;
în timp ce (st++) ++len; cout<< len << ": " << st;
returnează 0;
}

În această versiune, indicatorul st nu este dereferențiat. Prin urmare, nu caracterul indicat de st este verificat pentru egalitatea 0, ci indicatorul însuși. Deoarece acest pointer a avut inițial o valoare diferită de zero (adresa șirului de caractere), nu va deveni niciodată zero, iar bucla va rula la nesfârșit.
În a doua versiune a programului această eroare a fost eliminată. Programul se termină cu succes, dar rezultatul este incorect. Unde greșim de data asta?

#include const char *st = "Prețul unei sticle de vin\n"; int main()
{
int len ​​= 0;
while (*st++) ++len; cout<< len << ": " << st << endl;
returnează 0;
}

Eroarea este că după terminarea buclei, indicatorul st nu se adresează caracterului literal original, ci caracterului situat în memorie după nulul final al acelui literal. Orice poate fi în acest loc, iar rezultatul programului va fi o secvență aleatorie de caractere.
Puteți încerca să remediați această eroare:

St = st – len; cout<< len << ": " << st;

Acum programul nostru produce ceva semnificativ, dar nu complet. Răspunsul arată astfel:

18: pret sticla de vin

Am uitat să luăm în considerare faptul că caracterul nul final nu a fost inclus în lungimea calculată. st trebuie să fie compensat cu lungimea șirului plus 1. Iată, în sfârșit, operatorul corect:

St = st – len - 1;

si iata rezultatul corect:

18: Prețul unei sticle de vin

Cu toate acestea, nu putem spune că programul nostru arată elegant. Operator

St = st – len - 1;

adăugat pentru a corecta o greșeală făcută într-un stadiu incipient al proiectării programului - creșterea directă a indicatorului st. Această afirmație nu se încadrează în logica programului, iar codul este acum greu de înțeles. Aceste tipuri de corecții sunt adesea numite patch-uri - ceva conceput pentru a astupa o gaură într-un program existent. O soluție mult mai bună ar fi reconsiderarea logicii. O opțiune în cazul nostru ar fi definirea unui al doilea pointer inițializat cu valoarea st:

Const char *p = st;

Acum p poate fi folosit într-o buclă de calcul a lungimii, lăsând valoarea lui st neschimbată:

În timp ce (*p++)

3.4.2. Clasa de șiruri

După cum tocmai am văzut, utilizarea tipului de șir încorporat este predispusă la erori și incomod, deoarece este implementată la un nivel atât de scăzut. Prin urmare, a fost destul de obișnuit să vă dezvoltați propria clasă sau clase pentru a reprezenta un tip de șir - aproape fiecare companie, departament sau proiect individual avea propria sa implementare a unui șir. Ce să spun, în cele două ediții anterioare ale acestei cărți am făcut același lucru! Acest lucru a dat naștere la probleme de compatibilitate și portabilitate a programelor. Implementarea clasei standard de șiruri de către biblioteca standard C++ a fost menită să pună capăt acestei reinventări a bicicletelor.
Să încercăm să specificăm setul minim de operații pe care ar trebui să le aibă clasa șir de caractere:

  • inițializare cu o matrice de caractere (un tip șir încorporat) sau alt obiect de tip șir. Un tip încorporat nu are a doua capacitate;
  • copierea unei linii pe alta. Pentru un tip încorporat trebuie să utilizați funcția strcpy();
  • acces la caracterele individuale ale unui șir pentru citire și scriere. Într-o matrice încorporată, acest lucru se face folosind o operație de index sau o adresare indirectă;
  • comparând două șiruri de caractere pentru egalitate. Pentru un tip încorporat, utilizați funcția strcmp();
  • concatenarea a două șiruri, producând rezultatul fie ca un al treilea șir, fie în locul unuia dintre cele originale. Pentru un tip încorporat, se folosește funcția strcat(), dar pentru a obține rezultatul într-o linie nouă, trebuie să utilizați secvențial funcțiile strcpy() și strcat();
  • calcularea lungimii unui șir. Puteți afla lungimea unui șir de tip încorporat folosind funcția strlen();
  • capacitatea de a afla dacă un șir este gol. Pentru șirurile încorporate, în acest scop trebuie verificate două condiții: char str = 0; //... dacă (! str || ! *str) întoarce;

Clasa de șiruri a Bibliotecii Standard C++ implementează toate aceste operații (și multe altele, așa cum vom vedea în Capitolul 6). În această secțiune vom învăța cum să folosim operațiunile de bază ale acestei clase.
Pentru a utiliza obiecte din clasa șir, trebuie să includeți fișierul antet corespunzător:

#include

Iată un exemplu de șir din secțiunea anterioară, reprezentat de un obiect de tip șir și inițializat într-un șir de caractere:

#include string st("Prețul unei sticle de vin\n");

Lungimea șirului este returnată de funcția membru size() (lungimea nu include caracterul nul final).

Cout<< "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";

A doua formă a definiției unui șir specifică un șir gol:

St2 șir; // linie goală

Cum știm dacă un șir este gol? Desigur, puteți compara lungimea sa cu 0:

Dacă (! st.size()) // corect: gol

Cu toate acestea, există și o metodă specială empty() care returnează true pentru un șir gol și false pentru unul nevid:

Dacă (st.empty()) // corect: gol

A treia formă a constructorului inițializează un obiect de tip șir cu un alt obiect de același tip:

St3 st3(st);

Șirul st3 este inițializat cu șirul st. Cum ne putem asigura că aceste șiruri se potrivesc? Să folosim operatorul de comparație (==):

Dacă (st == st3) // inițializarea a funcționat

Cum să copiem o linie pe alta? Folosind operatorul normal de atribuire:

St2 = st3; // copiați st3 în st2

Pentru a concatena șiruri, utilizați adăugarea (+) sau adăugarea cu operația de alocare (+=). Să fie date două linii:

String s1 ("bună ziua, "); șir s2(„lume\n”);

Putem obține un al treilea șir format dintr-o concatenare a primelor două, astfel:

Șirul s3 = s1 + s2;

Dacă vrem să adăugăm s2 la sfârșitul lui s1, ar trebui să scriem:

S1 += s2;

Operația de adăugare poate concatena obiecte din clasa șirurilor de caractere nu numai între ele, ci și cu șiruri de caractere de tip încorporat. Puteți rescrie exemplul de mai sus, astfel încât caracterele speciale și semnele de punctuație să fie reprezentate printr-un tip încorporat, iar cuvintele semnificative să fie reprezentate de obiecte din șirul clasei:

Const char *pc = ", "; șir s1 ("bună ziua"); șir s2(„lume”);
șir s3 = s1 + pc + s2 + "\n";

Astfel de expresii funcționează deoarece compilatorul știe cum să convertească automat obiectele de tip încorporat în obiecte din clasa șir. De asemenea, este posibil să atribuiți pur și simplu un șir încorporat unui obiect șir:

Șirul s1; const char *pc = "o matrice de caractere"; s1 = pc; // Dreapta

Conversia inversă, însă, nu funcționează. Încercarea de a efectua următoarea inițializare a șirurilor de tip încorporat va cauza o eroare de compilare:

Char *str = s1; // eroare de compilare

Pentru a realiza această conversie, trebuie să apelați în mod explicit funcția membru oarecum ciudat numită c_str():

Char *str = s1.c_str(); // aproape corect

Funcția c_str() returnează un pointer către o matrice de caractere care conține șirul obiectului șir așa cum ar apărea în tipul șir încorporat.
Exemplul de mai sus de inițializare a unui pointer char *str încă nu este complet corect. c_str() returnează un pointer către o matrice constantă pentru a preveni modificarea directă a conținutului obiectului de către acest pointer, care are tip

Const char *

(Vom acoperi cuvântul cheie const în secțiunea următoare.) Opțiunea de inițializare corectă arată astfel:

Const char *str = s1.c_str(); // Dreapta

Caracterele individuale ale unui obiect șir, cum ar fi un tip încorporat, pot fi accesate folosind operația index. De exemplu, iată o bucată de cod care înlocuiește toate punctele cu caractere de subliniere:

String str("fa.disney.com"); int size = str.size(); pentru (int ix = 0; ix< size; ++ix) if (str[ ix ] == ".")
str[ ix ] = "_";

Asta este tot ce am vrut să spunem despre clasa șiruri chiar acum. De fapt, această clasă are multe mai multe proprietăți și capacități interesante. Să presupunem că exemplul anterior este implementat și prin apelarea unei singure funcții replace():

Înlocuiește(str.begin(), str.end(), ".", "_");

replace() este unul dintre algoritmii generali pe care i-am introdus în Secțiunea 2.8 și care va fi discutat în detaliu în Capitolul 12. Această funcție rulează în intervalul de la begin() la end(), care returnează pointerii la începutul și sfârșitul lui șirul și înlocuiește elementele , egale cu al treilea parametru, cu al patrulea.

Exercițiul 3.12

Găsiți erori în declarațiile de mai jos:

(a) char ch = „Drumul lung și întortocheat”; (b) int ival = (c) char *pc = (d) șir st(&ch); (e) pc = 0; (i) pc = "0";
(f) st = pc; (j) st =
(g) ch = pc; (k) ch = *buc;
(h) pc = st; (l) *pc = ival;

Exercițiul 3.13

Explicați diferența de comportament a următoarelor instrucțiuni de buclă:

While (st++) ++cnt;
în timp ce (*st++)
++cnt;

Exercițiul 3.14

Sunt date două programe echivalente semantic. Primul folosește tipul șir încorporat, al doilea folosește clasa șir:

// ***** Implementare folosind șiruri C ***** #include #include
int main()
{
int erori = 0;
const char *pc = "un șir literal foarte lung"; pentru (int ix = 0; ix< 1000000; ++ix)
{
int len ​​​​= strlen(buc);
char *pc2 = new char[ len + 1 ];
strcpy(pc2, pc);
dacă (strcmp(pc2, pc))
++erori; șterge pc2;
}
cout<< "C-строки: "
<< errors << " ошибок.\n";
) // ***** Implementare folosind clasa șir de caractere ***** #include
#include
int main()
{
int erori = 0;
string str("un șir literal foarte lung"); pentru (int ix = 0; ix< 1000000; ++ix)
{
int len ​​​​= str.size();
șir str2 = str;
dacă (str != str2)
}
cout<< "класс string: "
<< errors << " ошибок.\n;
}

Ce fac aceste programe?
Se pare că a doua implementare rulează de două ori mai repede decât prima. Te asteptai la un asemenea rezultat? Cum explici?

Exercițiul 3.15

Ai putea să îmbunătățești sau să adaugi ceva la setul de operații ale clasei șir date în ultima secțiune? Explicați-vă propunerile

3.5. const specificator

Să luăm următorul exemplu de cod:

Pentru (index int = 0; index< 512; ++index) ... ;

Există două probleme cu utilizarea literalului 512. Prima este ușurința de a percepe textul programului. De ce ar trebui ca limita superioară a variabilei buclei să fie exact 512? Ce se ascunde în spatele acestei valori? Ea pare la întâmplare...
A doua problemă se referă la ușurința modificării și întreținerii codului. Să presupunem că programul are 10.000 de linii și literalul 512 apare în 4% dintre ele. Să presupunem că în 80% din cazuri numărul 512 trebuie schimbat în 1024. Vă puteți imagina complexitatea unei astfel de lucrări și numărul de greșeli care pot fi făcute prin corectarea valorii greșite?
Rezolvăm ambele probleme în același timp: trebuie să creăm un obiect cu valoarea 512. Dându-i un nume semnificativ, cum ar fi bufSize, facem programul mult mai ușor de înțeles: este clar cu ce anume este comparată variabila buclă. la.

Index< bufSize

În acest caz, schimbarea dimensiunii bufSize nu necesită parcurgerea a 400 de linii de cod pentru a modifica 320 dintre ele. Cât de mult se reduce probabilitatea de erori prin adăugarea unui singur obiect! Acum valoarea este 512 localizat.

Int bufSize = 512; // dimensiunea tamponului de intrare // ... pentru (index int = 0; index< bufSize; ++index)
// ...

Rămâne o mică problemă: variabila bufSize aici este o valoare l care poate fi schimbată accidental în program, ceea ce duce la o eroare greu de detectat. Iată o greșeală comună: folosirea operatorului de atribuire (=) în loc de operatorul de comparație (==):

// modificare aleatorie a valorii bufSize dacă (bufSize = 1) // ...

Executarea acestui cod va face ca valoarea bufSize să fie 1, ceea ce poate duce la un comportament complet imprevizibil al programului. Erorile de acest fel sunt de obicei foarte greu de detectat deoarece pur și simplu nu sunt vizibile.
Utilizarea specificatorului const rezolvă această problemă. Prin declararea obiectului ca

Const int bufSize = 512; // dimensiunea tamponului de intrare

transformăm variabila într-o constantă cu valoarea 512, a cărei valoare nu poate fi modificată: astfel de încercări sunt suprimate de compilator: utilizarea incorectă a operatorului de atribuire în loc de comparație, ca în exemplul de mai sus, va provoca o eroare de compilare.

// eroare: încercați să atribuiți o valoare unei constante dacă (bufSize = 0) ...

Deoarece unei constante nu i se poate atribui o valoare, ea trebuie inițializată în locul în care este definită. Definirea unei constante fără inițializare provoacă și o eroare de compilare:

Const dublu pi; // eroare: constantă neinițializată

Const dublu minWage = 9,60; // Dreapta? eroare?
dublu *ptr =

Ar trebui compilatorul să permită o astfel de atribuire? Deoarece minWage este o constantă, nu i se poate atribui o valoare. Pe de altă parte, nimic nu ne împiedică să scriem:

*ptr += 1,40; // schimbarea obiectului minWage!

De regulă, compilatorul nu este capabil să protejeze împotriva utilizării pointerilor și nu va putea semnala o eroare dacă acestea sunt utilizate în acest mod. Acest lucru necesită o analiză prea profundă a logicii programului. Prin urmare, compilatorul interzice pur și simplu alocarea de adrese constante către pointerii obișnuiți.
Deci, suntem lipsiți de capacitatea de a folosi pointeri către constante? Nu. În acest scop, există pointeri declarați cu specificatorul const:

Const double *cptr;

unde cptr este un pointer către un obiect de tip const double. Subtilitatea este că indicatorul în sine nu este o constantă, ceea ce înseamnă că îi putem schimba valoarea. De exemplu:

Const dublu *buc = 0; const double minWage = 9,60; // corect: nu putem schimba minWage folosind computerul
pc = dublu dval = 3,14; // corect: nu putem schimba minWage folosind computerul
// deși dval nu este o constantă
pc = // corect dval = 3,14159; //Dreapta
*buc = 3,14159; // eroare

Adresa unui obiect constant este atribuită doar unui pointer către o constantă. În același timp, unui astfel de pointer i se poate atribui și adresa unei variabile obișnuite:

Pc =

Un pointer constant nu permite ca obiectul pe care îl adresează să fie modificat folosind adresare indirectă. Deși dval din exemplul de mai sus nu este o constantă, compilatorul nu va permite ca dval să fie schimbat prin computer. (Din nou, deoarece nu poate determina adresa obiectului care poate conține un pointer în orice moment în timpul execuției programului.)
În programele reale, pointerii către constante sunt cel mai adesea folosiți ca parametri formali ai funcțiilor. Utilizarea lor asigură că obiectul transmis unei funcții ca argument real nu va fi modificat de acea funcție. De exemplu:

// În programele reale, pointerii către constante sunt cel mai des // utilizați ca parametri formali ai funcțiilor int strcmp(const char *str1, const char *str2);

(Vom vorbi mai multe despre indicatorii constante în capitolul 7, când vorbim despre funcții.)
Există, de asemenea, indicii constante. (Rețineți diferența dintre un pointer const și un pointer către o constantă!). Un pointer const poate adresa fie o constantă, fie o variabilă. De exemplu:

Int errNumb = 0; int *const currErr =

Aici curErr este un pointer const către un obiect non-const. Aceasta înseamnă că nu îi putem atribui adresa altui obiect, deși obiectul în sine poate fi modificat. Iată cum ar putea fi folosit indicatorul curErr:

Fă ceva(); dacă (*curErr) (
errorHandler();
*curErr = 0; // corect: resetați valoarea errNumb
}

Încercarea de a atribui o valoare unui pointer const va cauza o eroare de compilare:

CurErr = // eroare

Un indicator constant către o constantă este o unire a celor două cazuri luate în considerare.

Const dublu pi = 3,14159; const double *const pi_ptr = π

Nici valoarea obiectului indicat de pi_ptr, nici valoarea pointerului în sine nu pot fi modificate în program.

Exercițiul 3.16

Explicați semnificația următoarelor cinci definiții. Greșește vreunul dintre ei?

(a) int i; (d) int *const cpi; (b) const int ic; (e) const int *const cpic; (c) const int *pic;

Exercițiul 3.17

Care dintre următoarele definiții sunt corecte? De ce?

(a) int i = -1; (b) const int ic = i; (c) const int *pic = (d) int *const cpi = (e) const int *const cpic =

Exercițiul 3.18

Folosind definițiile din exercițiul anterior, identificați operatorii de atribuire corecti. Explica.

(a) i = ic; (d) pic = cpic; (b) pic = (i) cpic = (c) cpi = pic; (f) ic = *cpic;

3.6. Tip de referință

Un tip de referință, uneori numit alias, este folosit pentru a da unui obiect un nume suplimentar. O referință vă permite să manipulați indirect un obiect, la fel cum puteți face cu un indicator. Totuși, această manipulare indirectă nu necesită sintaxa specială necesară pentru pointeri. De obicei, referințele sunt folosite ca parametri formali ai funcțiilor. În această secțiune, ne vom uita la utilizarea obiectelor de tip referință pe cont propriu.
Un tip de referință este indicat prin specificarea operatorului de adresă (&) înaintea numelui variabilei. Link-ul trebuie inițializat. De exemplu:

Int ival = 1024; // corect: refVal - referire la ival int &refVal = ival; // eroare: referința trebuie inițializată la int

Int ival = 1024; // eroare: refVal este de tip int, nu int* int &refVal = int *pi = // corect: ptrVal este o referință la un pointer int *&ptrVal2 = pi;

Odată definită o referință, nu o puteți modifica pentru a funcționa cu un alt obiect (de aceea referința trebuie inițializată în punctul în care este definită). În exemplul următor, operatorul de atribuire nu modifică valoarea refVal; noua valoare este atribuită variabilei ival - cea pe care o adresează refVal.

Int min_val = 0; // ival primește valoarea min_val, // în loc de refVal schimbă valoarea în min_val refVal = min_val;

Val ref += 2; adaugă 2 la ival, variabila referită de refVal. În mod similar int ii = refVal; atribuie ii valoarea curentă a lui ival, int *pi = inițializează pi cu adresa lui ival.

// două obiecte de tip int sunt definite int ival = 1024, ival2 = 2048; // o referință și un obiect definit int &rval = ival, rval2 = ival2; // sunt definite un obiect, un pointer și o referință
int inal3 = 1024, *pi = ival3, &ri = ival3; // două legături sunt definite int &rval3 = ival3, &rval4 = ival2;

O referință const poate fi inițializată printr-un obiect de alt tip (presupunând, desigur, că este posibilă convertirea unui tip în altul), precum și printr-o valoare fără adresă, cum ar fi o constantă literală. De exemplu:

dval dublu = 3,14159; // adevărat numai pentru referințe constante
const int &ir = 1024;
const int &ir2 = dval;
const dublu &dr = dval + 1,0;

Dacă nu am fi specificat specificatorul const, toate cele trei definiții de referință ar fi cauzat o eroare de compilare. Cu toate acestea, motivul pentru care compilatorul nu trece astfel de definiții este neclar. Să încercăm să ne dăm seama.
Pentru literali, acest lucru este mai mult sau mai puțin clar: nu ar trebui să putem schimba indirect valoarea unui literal folosind pointeri sau referințe. În ceea ce privește obiectele de alte tipuri, compilatorul convertește obiectul original într-un obiect auxiliar. De exemplu, dacă scriem:

dval dublu = 1024; const int &ri = dval;

apoi compilatorul îl convertește în ceva de genul acesta:

Int temp = dval; const int &ri = temp;

Dacă am putea atribui o nouă valoare referinței ri, am schimba de fapt nu dval, ci temp. Valoarea dval ar rămâne aceeași, ceea ce este complet neevident pentru programator. Prin urmare, compilatorul interzice astfel de acțiuni, iar singura modalitate de a inițializa o referință cu un obiect de alt tip este să o declarați ca const.
Iată un alt exemplu de link care este greu de înțeles prima dată. Dorim să definim o referință la adresa unui obiect constant, dar prima noastră opțiune provoacă o eroare de compilare:

Const int ival = 1024; // eroare: este necesară o referință constantă
int *&pi_ref =

De asemenea, o încercare de a corecta problema prin adăugarea unui specificator const eșuează:

Const int ival = 1024; // încă o eroare const int *&pi_ref =

Care este motivul? Dacă citim cu atenție definiția, vom vedea că pi_ref este o referință la un pointer constant către un obiect de tip int. Și avem nevoie de un pointer non-const către un obiect constant, astfel încât următoarea intrare ar fi corectă:

Const int ival = 1024; // Dreapta
int *const &piref =

Există două diferențe principale între o legătură și un pointer. În primul rând, legătura trebuie inițializată în locul în care este definită. În al doilea rând, orice modificare a unei legături nu transformă legătura, ci obiectul la care se referă. Să ne uităm la exemple. Daca scriem:

Int *pi = 0;

inițializam indicatorul pi la zero, ceea ce înseamnă că pi nu indică niciun obiect. În același timp, înregistrarea

const int &ri = 0;
inseamna ceva de genul asta:
int temp = 0;
const int &ri = temp;

În ceea ce privește operațiunea de atribuire, în exemplul următor:

Int ival = 1024, ival2 = 2048; int *pi = &ival, *pi2 = pi = pi2;

variabila ival indicată de pi rămâne neschimbată, iar pi primește valoarea adresei variabilei ival2. Atât pi cât și pi2 indică acum același obiect, ival2.
Dacă lucrăm cu link-uri:

Int &ri = ival, &ri2 = ival2; ri = ri2;

// exemplu de utilizare a legăturilor // Valoarea este returnată în parametrul next_value
bool get_next_value(int &next_value); // operator supraîncărcat Matrix operator+(const Matrix&, const Matrix&);

Int ival; în timp ce (get_next_value(ival))...

Int &next_value = ival;

(Folosirea referințelor ca parametri formali ai funcțiilor este discutată mai detaliat în Capitolul 7.)

Exercițiul 3.19

Există erori în aceste definiții? Explica. Cum le-ai repara?

(a) int ival = 1,01; (b) int &rval1 = 1,01; (c) int &rval2 = ival; (d) int &rval3 = (e) int *pi = (f) int &rval4 = pi; (g) int &rval5 = pi*; (h) int &*prval1 = pi; (i) const int &ival2 = 1; (j) const int &*prval2 =

Exercițiul 3.20

Este vreunul dintre următorii operatori de atribuire eronați (folosind definițiile din exercițiul anterior)?

(a) rval1 = 3,14159; (b) prval1 = prval2; (c) prval2 = rval1; (d) *prval2 = ival2;

Exercițiul 3.21

Găsiți erori în instrucțiunile date:

(a) int ival = 0; const int *pi = 0; const int &ri = 0; (b)pi =
ri =
pi =

3.7. Tastați bool

Un obiect de tip bool poate lua una din două valori: adevărat și fals. De exemplu:

// inițializarea șirului de caractere search_word = get_word(); // inițializarea variabilei găsite
bool găsit = fals; string următorul_cuvânt; în timp ce (cin >> următorul_cuvânt)
dacă (next_word == search_word)
găsit = adevărat;
// ... // scurtătură: dacă (găsit == adevărat)
daca este gasit)
cout<< "ok, мы нашли слово\n";
altfel cout<< "нет, наше слово не встретилось.\n";

Deși bool este unul dintre tipurile întregi, nu poate fi declarat ca semnat, nesemnat, scurt sau lung, așa că definiția de mai sus este eronată:

// eroare short bool found = false;

Obiectele de tip bool sunt implicit convertite în tipul int. Adevărat devine 1 și fals devine 0. De exemplu:

Bool găsit = fals; int număr_ocurență = 0; în timp ce (/* mormăi */)
{
găsit = caută(/* ceva */); // valoarea găsită este convertită la 0 sau 1
număr_ocurență += găsit; )

În același mod, valorile tipurilor întregi și ale indicatorilor pot fi convertite în valori de tip bool. În acest caz, 0 este interpretat ca fals și orice altceva este adevărat:

// returnează numărul de apariții extern int find(const șir&); bool găsit = fals; if (found = find("rosebud")) // corect: found == true // returnează un pointer la element
extern int* find(int value); if (found = find(1024)) // corect: găsit == adevărat

3.8. Transferuri

Adesea trebuie să definiți o variabilă care ia valori dintr-un anumit set. Să presupunem că un fișier este deschis în oricare dintre cele trei moduri: pentru citire, pentru scriere, pentru atașare.
Desigur, trei constante pot fi definite pentru a desemna aceste moduri:

Intrare const int = 1; const int output = 2; const int append = 3;

și folosiți aceste constante:

Bool open_file(string nume_fișier, int open_mode); // ...
open_file("Phoenix_and_the_Crane", append);

Această soluție este posibilă, dar nu în totalitate acceptabilă, deoarece nu putem garanta că argumentul transmis funcției open_file() este doar 1, 2 sau 3.
Utilizarea unui tip de enumerare rezolvă această problemă. Când scriem:

Enum open_modes( input = 1, output, append );

definim un nou tip open_modes. Valorile valide pentru un obiect de acest tip sunt limitate la setul de 1, 2 și 3, fiecare dintre valorile specificate având un nume mnemonic. Putem folosi numele acestui nou tip pentru a defini atât un obiect de acel tip, cât și tipul parametrilor formali ai funcției:

Void open_file(string nume_fișier, open_modes om);

intrare, ieșire și anexare sunt elemente de enumerare. Setul de elemente de enumerare specifică setul de valori permis pentru un obiect de un anumit tip. O variabilă de tip open_modes (în exemplul nostru) este inițializată cu una dintre aceste valori; i se poate atribui și oricare dintre ele. De exemplu:

Open_file(„Phoenix și macaraua”, anexează);

O încercare de a atribui o altă valoare decât unul dintre elementele de enumerare unei variabile de acest tip (sau de a o transmite ca parametru unei funcții) va provoca o eroare de compilare. Chiar dacă încercăm să transmitem o valoare întreagă corespunzătoare unuia dintre elementele de enumerare, vom primi o eroare:

// eroare: 1 nu este un element al enumerarii open_modes open_file("Iona", 1);

Există o modalitate de a defini o variabilă de tip open_modes, de a-i atribui valoarea unuia dintre elementele de enumerare și de a o transmite ca parametru funcției:

Open_modes om = intrare; // ... om = anexează; open_file("TailTell", om);

Cu toate acestea, nu este posibil să se obțină denumirile unor astfel de elemente. Dacă scriem instrucțiunea de ieșire:

Cout<< input << " " << om << endl;

atunci mai primim:

Această problemă este rezolvată prin definirea unui tablou de șiruri în care elementul cu un index egal cu valoarea elementului de enumerare va conține numele său. Având în vedere o astfel de matrice, putem scrie:

Cout<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

În plus, nu puteți repeta peste toate valorile unei enumerații:

// nu este acceptat pentru (open_modes iter = input; iter != append; ++inter) // ...

Pentru a defini o enumerare, utilizați cuvântul cheie enum, iar numele elementelor sunt specificate între acolade, separate prin virgulă. În mod implicit, primul este 0, următorul este 1 și așa mai departe. Puteți modifica această regulă folosind operatorul de atribuire. În acest caz, fiecare element ulterior fără o valoare specificată explicit va fi cu 1 mai mult decât elementul care vine înaintea lui în listă. În exemplul nostru, am specificat în mod explicit valoarea 1 pentru intrare, iar ieșirea și adăugarea vor fi egale cu 2 și 3. Iată un alt exemplu:

// forma == 0, sfera == 1, cilindru == 2, poligon == 3 enum Forms( share, spere, cylinder, polygon );

Valorile întregi corespunzătoare elementelor diferite ale aceleiași enumerații nu trebuie să fie diferite. De exemplu:

// point2d == 2, point2w == 3, point3d == 3, point3w == 4 enum Points ( point2d=2, point2w, point3d=3, point3w=4 );

Un obiect al cărui tip este o enumerare poate fi definit, utilizat în expresii și transmis unei funcții ca argument. Un astfel de obiect este inițializat doar cu valoarea unuia dintre elementele de enumerare și numai acea valoare îi este atribuită, fie în mod explicit, fie ca valoare a altui obiect de același tip. Nici măcar valorile întregi corespunzătoare elementelor de enumerare valide nu îi pot fi atribuite:

Void mumble() ( Puncte pt3d = punct3d; // corect: pt2d == 3 // eroare: pt3w este inițializat cu tipul int Puncte pt3w = 3; // eroare: poligonul nu este inclus în enumerarea de puncte pt3w = poligon; / / corect: ambele obiecte de tip Puncte pt3w = pt3d; )

Cu toate acestea, în expresiile aritmetice, o enumerare poate fi convertită automat în tipul int. De exemplu:

Const int array_size = 1024; // corect: pt2w este convertit în int
int chunk_size = array_size * pt2w;

3.9. Tastați „matrice”

Am atins deja matricele în secțiunea 2.1. O matrice este un set de elemente de același tip, accesate de un index - numărul de serie al elementului din matrice. De exemplu:

Int ival;

definește ival ca o variabilă int și instrucțiunea

Int ia[ 10 ];

specifică o matrice de zece obiecte de tip int. La fiecare dintre aceste obiecte, sau elemente de matrice, poate fi accesat folosind operația index:

Ival = ia[ 2 ];

atribuie variabilei ival valoarea elementului de matrice ia cu indicele 2. La fel

Ia[ 7 ] = ival;

atribuie elementului de la indicele 7 valoarea ival.

O definiție de matrice constă dintr-un specificator de tip, un nume de matrice și o dimensiune. Dimensiunea specifică numărul de elemente ale matricei (cel puțin 1) și este cuprinsă între paranteze drepte. Mărimea tabloului trebuie cunoscută în etapa de compilare și, prin urmare, trebuie să fie o expresie constantă, deși nu este specificată neapărat ca un literal. Iată exemple de definiții corecte și incorecte ale matricei:

Extern int get_size(); // constantele buf_size și max_files
const int buf_size = 512, max_files = 20;
int personal_size = 27; // corect: constant char input_buffer[ buf_size ]; // corect: expresie constantă: 20 - 3 caractere *fileTable[ max_files-3 ]; // eroare: nu este un salariu dublu constant[ personal_size ]; // eroare: nu este o expresie constantă int test_scores[ get_size() ];

Obiectele buf_size și max_files sunt constante, astfel încât definițiile matricei input_buffer și fileTable sunt corecte. Dar staff_size este o variabilă (deși inițializată cu constanta 27), ceea ce înseamnă că salariile sunt inacceptabile. (Compilatorul nu poate găsi valoarea variabilei staff_size când este definită matricea de salarii.)
Expresia max_files-3 poate fi evaluată în timpul compilării, astfel încât definiția matricei fileTable este corectă din punct de vedere sintactic.
Numerotarea elementelor începe de la 0, deci pentru o matrice de 10 elemente intervalul de index corect nu este 1 – 10, ci 0 – 9. Iată un exemplu de iterare prin toate elementele matricei:

Int main() (const int dimensiune_matrice = 10; int ia[ dimensiune_matrice]; for (int ix = 0; ix< array_size; ++ ix)
ia[ ix ] = ix;
}

Atunci când definiți o matrice, o puteți inițializa în mod explicit, listând valorile elementelor sale în acolade, separate prin virgule:

Const int array_size = 3; int ia[ array_size ] = ( 0, 1, 2 );

Dacă specificăm în mod explicit o listă de valori, nu trebuie să specificăm dimensiunea matricei: compilatorul însuși va număra numărul de elemente:

// matrice de dimensiune 3 int ia = ( 0, 1, 2 );

Când atât dimensiunea, cât și lista de valori sunt specificate în mod explicit, sunt posibile trei opțiuni. Dacă dimensiunea și numărul de valori coincid, totul este evident. Dacă lista de valori este mai scurtă decât dimensiunea specificată, elementele de matrice rămase sunt inițializate la zero. Dacă există mai multe valori în listă, compilatorul afișează un mesaj de eroare:

// ia ==> ( 0, 1, 2, 0, 0 ) const int array_size = 5; int ia[ array_size ] = ( 0, 1, 2 );

O matrice de caractere poate fi inițializată nu numai cu o listă de valori de caractere între acolade, ci și cu un șir literal. Cu toate acestea, există unele diferențe între aceste metode. Sa spunem

Const char cal = ("C", "+", "+" ); const char cal2 = "C++";

Dimensiunea matricei ca1 este 3, matricea ca2 este 4 (în literalele șir, se ia în considerare caracterul nul final). Următoarea definiție va provoca o eroare de compilare:

// eroare: șirul „Daniel” este format din 7 elemente const char ch3[ 6 ] = „Daniel”;

O matrice nu i se poate atribui valoarea unei alte matrice, iar inițializarea unei matrice de către alta nu este permisă. În plus, nu este permisă utilizarea unei matrice de referințe. Iată exemple de utilizare corectă și incorectă a matricelor:

Const int array_size = 3; int ix, jx, kx; // corect: matrice de pointeri de tip int* int *iar = ( &ix, &jx, &kx ); // eroare: tablourile de referință nu sunt permise int &iar = ( ix, jx, kx ); int main()
{
int ia3( array_size ]; // corect
// eroare: matricele încorporate nu pot fi copiate
ia3 = ia;
returnează 0;
}

Pentru a copia o matrice în alta, trebuie să faceți acest lucru pentru fiecare element separat:

Const int array_size = 7; int ia1 = ( 0, 1, 2, 3, 4, 5, 6 ); int main() (
int ia3[ array_size ]; pentru (int ix = 0; ix< array_size; ++ix)
ia2[ ix ] = ia1[ ix ]; returnează 0;
}

Un index de matrice poate fi orice expresie care produce un rezultat de tip întreg. De exemplu:

Int someVal, get_index(); ia2[ get_index() ] = someVal;

Subliniem că limbajul C++ nu asigură controlul indicilor de matrice, nici în etapa de compilare, nici în etapa de execuție. Programatorul însuși trebuie să se asigure că indexul nu depășește limitele matricei. Erorile la lucrul cu un index sunt destul de frecvente. Din păcate, nu este foarte dificil să găsești exemple de programe care să compileze și chiar să funcționeze, dar care conțin totuși erori fatale care mai devreme sau mai târziu duc la blocarea.

Exercițiul 3.22

Care dintre următoarele definiții ale matricei sunt incorecte? Explica.

(a) int ia[ buf_size ]; (d) int ia[ 2 * 7 - 14 ] (b) int ia[ get_size() ]; (e) char st[ 11 ] = „fundamental”; (c) int ia[ 4 * 7 - 14 ];

Exercițiul 3.23

Următorul fragment de cod trebuie să inițializeze fiecare element al matricei cu o valoare de index. Găsiți greșelile făcute:

Int main() (const int dimensiune_matrice = 10; int ia[ dimensiune_matrice]; for (int ix = 1; ix<= array_size; ++ix)
ia[ ia ] = ix; // ...
}

3.9.1. Matrice multidimensionale

În C++, este posibil să folosiți tablouri multidimensionale; atunci când le declarați, trebuie să indicați limita dreaptă a fiecărei dimensiuni între paranteze pătrate separate. Iată definiția unui tablou bidimensional:

Int ia[ 4 ][ 3 ];

Prima valoare (4) specifică numărul de rânduri, a doua (3) – numărul de coloane. Obiectul ia este definit ca o matrice de patru șiruri de trei elemente fiecare. Matricele multidimensionale pot fi, de asemenea, inițializate:

Int ia[ 4 ][ 3 ] = ( ( 0, 1, 2 ), ( 3, 4, 5 ), ( 6, 7, 8 ), ( 9, 10, 11 ) );

Acoladele interioare, care despart lista de valori în linii, sunt opționale și sunt în general folosite pentru a face codul mai ușor de citit. Inițializarea de mai jos este exact aceeași cu exemplul anterior, deși este mai puțin clară:

Int ia = (0,1,2,3,4,5,6,7,8,9,10,11);

Următoarea definiție inițializează doar primele elemente ale fiecărei linii. Elementele rămase vor fi zero:

Int ia[ 4 ][ 3 ] = ( (0), (3), (6), (9) );

Dacă omiteți bretelele interioare, rezultatul va fi complet diferit. Toate cele trei elemente ale primului rând și primul element al celui de-al doilea vor primi valoarea specificată, iar restul vor fi implicit inițializate la 0.

Int ia[ 4 ][ 3 ] = ( 0, 3, 6, 9 );

Când accesați elemente ale unui tablou multidimensional, trebuie să utilizați indecși pentru fiecare dimensiune (acestea sunt încadrate între paranteze drepte). Iată cum arată inițializarea unei matrice bidimensionale folosind bucle imbricate:

Int main() ( const int rowSize = 4; const int colSize = 3; int ia[ rowSize ][ colSize ]; for (int = 0; i< rowSize; ++i)
pentru (int j = 0; j< colSize; ++j)
ia[ i ][ j ] = i + j j;
}

Proiecta

Ia[ 1, 2 ]

este valabil din punctul de vedere al sintaxei C++, dar nu înseamnă deloc ceea ce se așteaptă un programator fără experiență. Aceasta nu este o declarație a unei matrice bidimensionale 1 pe 2. Un agregat între paranteze pătrate este o listă de expresii separate prin virgulă, care va avea ca rezultat valoarea finală 2 (vezi operatorul virgulă în Secțiunea 4.2). Prin urmare, declarația ia este echivalentă cu ia. Aceasta este o altă oportunitate de a greși.

3.9.2. Relația dintre tablouri și pointeri

Dacă avem o definiție de matrice:

Int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 );

atunci ce înseamnă pur și simplu indicarea numelui său în program?

Utilizarea unui identificator de matrice într-un program este echivalentă cu specificarea adresei primului său element:

În mod similar, puteți accesa valoarea primului element al unui tablou în două moduri:

// ambele expresii returnează primul element *ia; in absenta;

Pentru a lua adresa celui de-al doilea element al tabloului, trebuie să scriem:

După cum am menționat mai devreme, expresia

oferă, de asemenea, adresa celui de-al doilea element al matricei. În consecință, sensul său ne este dat în următoarele două moduri:

*(ia+1); in absenta;

Observați diferența dintre expresii:

*ia+1 și *(ia+1);

Operația de dereferință are o mai mare o prioritate decât operația de adăugare (prioritățile operatorului sunt discutate în secțiunea 4.13). Prin urmare, prima expresie dereferențează mai întâi variabila ia și primește primul element al matricei, apoi îi adaugă 1. A doua expresie furnizează valoarea celui de-al doilea element.

Puteți parcurge o matrice folosind un index, așa cum am făcut în secțiunea anterioară, sau folosind pointeri. De exemplu:

#include int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); int *pbegin = ia; int *pend = ia + 9; while (pbegin != pend) ( cout<< *pbegin <<; ++pbegin; } }

Pointerul pbegin este inițializat la adresa primului element al matricei. Fiecare trecere prin buclă crește acest indicator cu 1, ceea ce înseamnă că este mutat la următorul element. De unde știi unde să stai? În exemplul nostru, am definit un al doilea indicator pend și l-am inițializat cu adresa care urmează ultimul element al matricei ia. De îndată ce valoarea lui pbegin devine egală cu pend, știm că matricea s-a încheiat. Să rescriem acest program, astfel încât începutul și sfârșitul matricei să fie transmise ca parametri unei anumite funcții generalizate care poate imprima o matrice de orice dimensiune:

#include void ia_print(int *pbegin, int *pend) (
în timp ce (pbegin != pend) (
cout<< *pbegin << " ";
++pbegin;
}
) int main()
{
int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 );
ia_print(ia, ia + 9);
}

Funcția noastră a devenit mai universală, totuși, poate funcționa numai cu matrice de tip int. Există o modalitate de a elimina această limitare: convertiți această funcție într-un șablon (șabloanele au fost introduse pe scurt în secțiunea 2.5):

#include șablon void print(elemType *pbegin, elemType *pend) ( în timp ce (pbegin != pend) ( cout<< *pbegin << " "; ++pbegin; } }

Acum putem apela funcția noastră print() pentru a imprima matrice de orice tip:

Int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); double da = ( 3.14, 6.28, 12.56, 25.12 ); string sa = ( "purcel", " eeyore", "pooh"); print(ia, ia+9);
print(da, da+4);
print(sa, sa+3);
}

Noi am scris generalizat funcţie. Biblioteca standard oferă un set de algoritmi generici (am menționat deja acest lucru în Secțiunea 3.4) implementați într-un mod similar. Parametrii unor astfel de funcții sunt pointeri către începutul și sfârșitul matricei cu care efectuează anumite acțiuni. Iată, de exemplu, cum arată apelurile la un algoritm de sortare generalizat:

#include int main() ( int ia = ( 107, 28, 3, 47, 104, 76 ); string sa = ( „purcel”, „eeyore”, „pooh” ); sort(ia, ia+6);
sort(sa, sa+3);
};

(Vom intra în mai multe detalii despre algoritmii generalizați în Capitolul 12; Anexa va oferi exemple de utilizare a acestora.)
Biblioteca standard C++ conține un set de clase care încapsulează utilizarea containerelor și a indicatorilor. (Acest lucru a fost discutat în Secțiunea 2.8.) În secțiunea următoare, vom arunca o privire asupra vectorului de tip container standard, care este o implementare orientată pe obiect a unui tablou.

3.10. Clasa de vectori

Utilizarea clasei vectoriale (vezi Secțiunea 2.8) este o alternativă la utilizarea tablourilor încorporate. Această clasă oferă mult mai multe funcționalități, așa că utilizarea ei este de preferat. Cu toate acestea, există situații în care nu puteți face fără matrice de tip încorporat. Una dintre aceste situații este procesarea parametrilor liniei de comandă trecuți programului, despre care vom discuta în secțiunea 7.8. Clasa vectorială, ca și clasa șir, face parte din biblioteca standard C++.
Pentru a utiliza vectorul trebuie să includeți fișierul antet:

#include

Există două abordări complet diferite pentru utilizarea unui vector, să le numim idiomul matrice și idiomul STL. În primul caz, un obiect vectorial este utilizat exact în același mod ca o matrice de tip încorporat. Se determină un vector cu o dimensiune dată:

Vector< int >ivec(10);

care este similar cu definirea unei matrice de tip încorporat:

Int ia[ 10 ];

Pentru a accesa elementele individuale ale unui vector, se utilizează operația index:

Void simp1e_examp1e() (const int e1em_size = 10; vector< int >ivec(e1em_size); int ia[ e1em_size ]; pentru (int ix = 0; ix< e1em_size; ++ix)
ia[ ix ] = ivec[ ix ]; // ...
}

Putem afla dimensiunea unui vector folosind funcția size() și putem verifica dacă vectorul este gol folosind funcția empty(). De exemplu:

Void print_vector(vector ivec) ( dacă (ivec.empty()) return; pentru (int ix=0; ix< ivec.size(); ++ix)
cout<< ivec[ ix ] << " ";
}

Elementele vectorului sunt inițializate cu valori implicite. Pentru tipurile numerice și pointerii, această valoare este 0. Dacă elementele sunt obiecte de clasă, atunci inițiatorul pentru ele este specificat de constructorul implicit (vezi secțiunea 2.3). Cu toate acestea, inițiatorul poate fi specificat și în mod explicit folosind formularul:

Vector< int >ivec(10, -1);

Toate cele zece elemente ale vectorului vor fi egale cu -1.
O matrice de tip încorporat poate fi inițializată explicit cu o listă:

Int ia[6] = (-2, -1, O, 1, 2, 1024);

O acțiune similară nu este posibilă pentru un obiect din clasa vectorului. Cu toate acestea, un astfel de obiect poate fi inițializat folosind un tip de matrice încorporat:

// 6 elemente sunt copiate în vectorul ivec< int >ivec(ia, ia+6);

Constructorului vectorului ivec îi trec doi pointeri - un pointer la începutul tabloului ia și la elementul care urmează ultimului. Este permis să specificați nu întregul tablou, ci un anumit interval al acestuia, ca o listă de valori inițiale:

// Se copiază 3 elemente: ia, ia, ia vector< int >ivec(&ia[2], &ia[5]);

O altă diferență între un vector și o matrice încorporată este capacitatea de a inițializa un obiect vectorial cu altul și de a folosi operatorul de atribuire pentru a copia obiecte. De exemplu:

Vector< string >svec; void init_and_assign() ( // un vector este inițializat de un alt vector< string >nume_utilizator(svec); // ... // un vector este copiat pe altul
svec = nume_utilizator;
}

Când vorbim despre idiomul STL, ne referim la o abordare complet diferită a utilizării unui vector. În loc să specificăm imediat dimensiunea dorită, definim un vector gol:

Vector< string >text;

Apoi îi adăugăm elemente folosind diverse funcții. De exemplu, funcția push_back() inserează un element la sfârșitul unui vector. Iată o bucată de cod care citește o secvență de linii de la intrarea standard și le adaugă la un vector:

Cuvânt șir; în timp ce (cin >> cuvânt) ( text.push_back(cuvânt); // ... )

Deși putem folosi operația de index pentru a itera elementele unui vector:

Cout<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

O utilizare mai tipică a iteratorilor în cadrul acestui idiom ar fi:

Cout<< "считаны слова: \n"; for (vector::iterator it = text.begin(); it != text.end(); ++it) cout<< *it << " "; cout << endl;

Un iterator este o clasă de bibliotecă standard care este în esență un pointer către un element de matrice.
Expresie

dereferențiază iteratorul și oferă elementul vectorial însuși. Instrucțiuni

Mută ​​indicatorul la următorul element. Nu este nevoie să amestecăm aceste două abordări. Dacă urmați limbajul STL când definiți un vector gol:

Vector ivec;

Ar fi o greșeală să scriu:

Nu avem încă un singur element al vectorului ivec; Numărul de elemente este determinat cu ajutorul funcției size().

Se poate face și greșeala inversă. Dacă am definit un vector de o anumită dimensiune, de exemplu:

Vector ia(10);

Apoi inserarea elementelor crește dimensiunea acesteia, adăugând elemente noi celor existente. Deși acest lucru pare evident, un programator începător ar putea scrie:

Const int dimensiune = 7; int ia[ mărime ] = ( 0, 1, 1, 2, 3, 5, 8 ); vector< int >ivec(dimensiune); pentru (int ix = 0; ix< size; ++ix) ivec.push_back(ia[ ix ]);

Aceasta a însemnat inițializarea vectorului ivec cu valorile elementelor ia, ceea ce a rezultat într-un vector ivec de dimensiunea 14.
Urmând limbajul STL, puteți nu numai să adăugați, ci și să eliminați elemente dintr-un vector. (Vom analiza toate acestea în detaliu și cu exemple în capitolul 6.)

Exercițiul 3.24

Există erori în următoarele definiții?
int ia[ 7 ] = ( 0, 1, 1, 2, 3, 5, 8 );

(a)vector< vector< int >>ivec;
(b) vector< int >ivec = (0, 1, 1, 2, 3, 5, 8);
(c)vector< int >ivec(ia, ia+7);
(d)vector< string >svec = ivec;
(e)vector< string >svec(10, string("null"));

Exercițiul 3.25

Implementați următoarea funcție:
bool is_equal(const int*ia, int ia_size,
const vector &ivec);
Funcția is_equal() compară două containere element cu element. În cazul containerelor de diferite dimensiuni, „coada” celui mai lung nu este luată în considerare. Este clar că dacă toate elementele comparate sunt egale, funcția returnează adevărat, dacă cel puțin unul este diferit, fals. Utilizați un iterator pentru a itera elemente. Scrieți o funcție main() care apelează is_equal().

3.11. complex de clasă

Clasa de complexe de numere complexe este o altă clasă din biblioteca standard. Ca de obicei, pentru a-l folosi trebuie să includeți fișierul antet:

#include

Un număr complex este format din două părți - real și imaginar. Partea imaginară este rădăcina pătrată a unui număr negativ. Un număr complex este de obicei scris sub formă

unde 2 este partea reală, iar 3i este partea imaginară. Iată exemple de definiții ale obiectelor de tip complex:

// număr pur imaginar: complex 0 + 7-i< double >purei(0, 7); // partea imaginară este 0: 3 + complex Oi< float >rea1_num(3); // atât părțile reale cât și cele imaginare sunt egale cu complexul 0: 0 + 0-i< long double >zero; // inițializarea unui număr complex cu un alt complex< double >purei2(purei);

Deoarece complex, ca și vector, este un șablon, îl putem instanția cu tipurile float, double și long double, ca în exemplele date. De asemenea, puteți defini o matrice de elemente de tip complex:

Complex< double >conjugate[ 2 ] = ( complex< double >(2, 3), complex< double >(2, -3) };

Complex< double >*ptr = complex< double >&ref = *ptr;

3.12. directivă typedef

Directiva typedef vă permite să specificați un sinonim pentru un tip de date încorporat sau personalizat. De exemplu:

Typedef salarii duble; vector typedef vec_int; typedef vec_int test_scores; typedef bool in_attendance; typedef int *Pint;

Numele definite folosind directiva typedef pot fi folosite exact în același mod ca specificatorii de tip:

// dublu orar, săptămânal; salariu orar, saptamanal; //vector vecl(10);
vec_int vecl(10); //vector test0(c1ass_size); const int c1ass_size = 34; test_scores test0(c1ass_size); //vector< bool >prezența; vector< in_attendance >prezență(c1ass_size); // int *tabel[ 10 ]; Masa cu halbe [10];

Această directivă începe cu cuvântul cheie typedef, urmat de un specificator de tip și se termină cu un identificator, care devine un sinonim pentru tipul specificat.
Pentru ce sunt definite numele folosind directiva typedef? Folosind nume mnemonice pentru tipurile de date, puteți face programul mai ușor de înțeles. În plus, este obișnuit să se folosească astfel de nume pentru tipurile compuse complexe care sunt altfel greu de înțeles (vezi exemplul din Secțiunea 3.14), pentru a declara pointeri către funcții și funcții membre ale unei clase (vezi Secțiunea 13.6).
Mai jos este un exemplu de întrebare la care aproape toată lumea răspunde incorect. Eroarea este cauzată de o înțelegere greșită a directivei typedef ca o simplă substituție de macro-text. Definitie data:

Typedef char *cstring;

Care este tipul variabilei cstr din următoarea declarație:

Extern const cstring cstr;

Răspunsul care pare evident este:

Const char *cstr

Cu toate acestea, acest lucru nu este adevărat. Specificatorul const se referă la cstr, deci răspunsul corect este un pointer const către char:

Char *const cstr;

3.13. specificator volatil

Un obiect este declarat volatil dacă valoarea sa poate fi schimbată fără ca compilatorul să observe, cum ar fi o variabilă actualizată de ceasul sistemului. Acest specificator îi spune compilatorului că nu trebuie să optimizeze codul pentru a lucra cu acest obiect.
Specificatorul volatil este utilizat în mod similar cu specificatorul const:

Volatil int disp1ay_register; sarcină volatilă *curr_task; volatile int ixa[ max_size ]; Ecran volatil bitmap_buf;

display_register este un obiect volatil de tip int. curr_task – indicator către un obiect volatil din clasa Task. ixa este o matrice instabilă de numere întregi și fiecare element al unei astfel de matrice este considerat instabil. bitmap_buf este un obiect volatil al clasei Screen, fiecare dintre membrii săi de date fiind, de asemenea, considerat volatil.
Singurul scop al utilizării specificatorului volatil este de a spune compilatorului că nu poate determina cine poate schimba valoarea unui anumit obiect și cum. Prin urmare, compilatorul nu ar trebui să efectueze optimizări pe codul care utilizează acest obiect.

3.14. pereche de clasă

Clasa de perechi a bibliotecii standard C++ ne permite să definim o pereche de valori cu un singur obiect dacă există vreo conexiune semantică între ele. Aceste valori pot fi aceleași sau diferite. Pentru a utiliza această clasă trebuie să includeți fișierul antet:

#include

De exemplu, instrucțiuni

Pereche< string, string >autor(„James”, „Joyce”);

creează un obiect autor de tip pereche, format din două valori șir.
Părțile individuale ale unei perechi pot fi obținute folosind primul și al doilea membru:

String firstBook; dacă (Joyce.first == „James” &&
Joyce.second == „Joyce”)
prima carte = „Stephen Hero”;

Dacă trebuie să definiți mai multe obiecte de același tip din această clasă, este convenabil să utilizați directiva typedef:

Typedef pereche< string, string >Autorii; Autori proust("marcel", "proust"); Autorii joyce ("James", "Joyce"); Autori musil("robert", "musi1");

Iată un alt exemplu de utilizare a unei perechi. Prima valoare conține numele unui obiect, a doua este un pointer către elementul tabelului corespunzător acestui obiect.

Clasa EntrySlot; extern EntrySlot* 1ook_up(string); pereche typedef< string, EntrySlot* >SymbolEntry; SymbolEntry current_entry ("autor", 1ook_up ("autor"));
// ... dacă (EntrySlot *it = 1ook_up("editor")) (
current_entry.first = „editor”;
current_entry.second = it;
}

(Vom reveni la clasa pereche când vom vorbi despre tipurile de containere în Capitolul 6 și despre algoritmi generici în Capitolul 12.)

3.15. Tipuri de clasă

Mecanismul de clasă vă permite să creați noi tipuri de date; cu ajutorul acestuia, au fost introduse tipurile șir, vector, complex și pereche discutate mai sus. În Capitolul 2, am introdus conceptele și mecanismele care suportă abordările orientate pe obiecte și pe obiecte, folosind exemplul de implementare a clasei Array. Aici, pe baza abordării obiectului, vom crea o clasă String simplă, a cărei implementare ne va ajuta să înțelegem, în special, supraîncărcarea operatorului - am vorbit despre aceasta în secțiunea 2.3. (Clasele sunt tratate în detaliu în capitolele 13, 14 și 15.) Am oferit o scurtă descriere a clasei pentru a oferi exemple mai interesante. Un cititor nou în C++ ar putea dori să sări peste această secțiune și să aștepte o descriere mai sistematică a claselor în capitolele ulterioare.)
Clasa noastră String trebuie să accepte inițializarea de către un obiect din clasa String, un literal șir și tipul șir încorporat, precum și operația de atribuire a valorilor acestor tipuri. Folosim constructori de clasă și un operator de atribuire supraîncărcat pentru aceasta. Accesul la caracterele String individuale va fi implementat ca o operație de index supraîncărcat. În plus, vom avea nevoie de: funcția size() pentru a obține informații despre lungimea șirului; operația de comparare a obiectelor de tip String și a unui obiect String cu un șir de tip încorporat; precum și operațiunile I/O ale obiectului nostru. În cele din urmă, implementăm capacitatea de a accesa reprezentarea internă a șirului nostru ca tip de șir încorporat.
O definiție de clasă începe cu cuvântul cheie class, urmat de un identificator - numele clasei sau tipul. În general, o clasă este formată din secțiuni precedate de cuvintele public (deschis) și privat (închis). O secțiune publică conține de obicei un set de operații suportate de clasă, numite metode sau funcții membre ale clasei. Aceste funcții membre definesc interfața publică a clasei, cu alte cuvinte, setul de acțiuni care pot fi efectuate asupra obiectelor acelei clase. O secțiune privată include de obicei membri de date care oferă implementare internă. În cazul nostru, membrii interni includ _string - un pointer către un caracter, precum și _size de tip int. _size va stoca informații despre lungimea șirului, iar _string va fi o matrice de caractere alocată dinamic. Iată cum arată o definiție de clasă:

#include clasa String; operator istream&>>(istream&, String&);
ostream& operator<<(ostream&, const String&); class String {
public:
// set de constructori
// pentru inițializare automată
// String strl; // String()
// String str2("literal"); // String(const char*);
// String str3(str2); // String(const String&); Şir();
String(const char*);
String(const String&); // distrugător
~String(); // operatori de atribuire
// strl = str2
// str3 = "un șir literal" String& operator=(const String&);
String& operator=(const char*); // operatori de testare a egalității
// strl == str2;
// str3 == "un șir literal"; operator bool==(const String&);
operator bool==(const char*); // supraîncărcarea operatorului de acces prin index
// strl[ 0 ] = str2[ 0 ]; char& operator(int); // acces la membrii clasei
int size() ( returnează _size; )
char* c_str() ( return _string; ) privat:
int_size;
char *_string;
}

Clasa String are trei constructori. După cum sa discutat în Secțiunea 2.3, mecanismul de supraîncărcare vă permite să definiți mai multe implementări ale funcțiilor cu același nume, atâta timp cât toate diferă în numărul și/sau tipurile parametrilor lor. Primul constructor

Este constructorul implicit, deoarece nu necesită o valoare inițială explicită. Când scriem:

Pentru str1, un astfel de constructor este numit.
Cei doi constructori rămași au fiecare câte un parametru. Da, pentru

String str2(„șir de caractere”);

Constructorul este numit

String(const char*);

String str3(str2);

Constructor

String(const String&);

Tipul de constructor apelat este determinat de tipul argumentului actual. Ultimul dintre constructori, String(const String&), se numește constructor de copiere deoarece inițializează un obiect cu o copie a altui obiect.
Daca scrii:

String str4(1024);

Acest lucru va provoca o eroare de compilare, deoarece nu există un constructor cu un parametru de tip int.
O declarație de operator supraîncărcată are următorul format:

Operator tip_return op(lista_parametri);

Unde operator este un cuvânt cheie, iar op este unul dintre operatorii predefiniti: +, =, == și așa mai departe. (Consultați Capitolul 15 pentru o definiție precisă a sintaxei.) Iată declarația operatorului de index supraîncărcat:

operator Char&(int);

Acest operator are un singur parametru de tip int și returnează o referință la un caracter. Un operator supraîncărcat poate fi el însuși supraîncărcat dacă listele de parametri ale instanțiilor individuale diferă. Pentru clasa noastră String, vom crea doi operatori diferiți de atribuire și egalitate.
Pentru a apela o funcție de membru, utilizați operatorii de acces membru punct (.) sau săgeata (->). Să avem declarații de obiecte de tip String:

obiect șir ("Danny");
String *ptr = new String("Anna");
matrice de șiruri;
Iată cum arată un apel la size() pe aceste obiecte:
vector dimensiuni (3);

// membru de acces pentru obiecte (.); // obiectele are o dimensiune de 5 dimensiuni[ 0 ] = object.size(); // acces membru pentru pointeri (->)
// ptr are dimensiunea 4
sizes[ 1 ] = ptr->size(); // acces membru (.)
// tabloul are dimensiunea 0
sizes[ 2 ] = array.size();

Returnează 5, 4 și, respectiv, 0.
Operatorii supraîncărcați sunt aplicați unui obiect în același mod ca și cei obișnuiți:

Nume șir ("Yadie"); String name2 ("Yodie"); // operator bool==(const String&)
dacă (nume == nume2)
întoarcere;
altfel
// String& operator=(const String&)
nume = nume2;

O declarație de funcție membru trebuie să fie în interiorul unei definiții de clasă, iar o definiție a funcției poate fi în interiorul sau în afara unei definiții de clasă. (Atât funcțiile size() cât și c_str() sunt definite în interiorul unei clase.) Dacă o funcție este definită în afara unei clase, atunci trebuie să specificăm, printre altele, cărei clase îi aparține. În acest caz, definiția funcției este plasată în fișierul sursă, să spunem String.C, iar definiția clasei în sine este plasată în fișierul antet (String.h în exemplul nostru), care trebuie inclus în sursă. :

// conținutul fișierului sursă: String.C // care permite definirea clasei String
#include "String.h" // include definiția funcției strcmp().
#include
bool // returnează tipul
String:: // clasa căreia îi aparține funcția
operator== // numele funcției: operator de egalitate
(const String &rhs) // listă de parametri
{
if (_size != rhs._size)
returnează fals;
returnează strcmp(_strinq, rhs._string) ?
fals adevarat;
}

Amintiți-vă că strcmp() este o funcție de bibliotecă standard C. Compară două șiruri de caractere încorporate, returnând 0 dacă șirurile sunt egale și diferit de zero dacă nu sunt egale. Operatorul condiționat (?:) testează valoarea înainte de semnul întrebării. Dacă este adevărată, este returnată valoarea expresiei din stânga două puncte; în caz contrar, este returnată valoarea din dreapta. În exemplul nostru, valoarea expresiei este falsă dacă strcmp() a returnat o valoare diferită de zero și adevărată dacă a returnat o valoare zero. (Operatorul condiționat este discutat în secțiunea 4.7.)
Operația de comparare este folosită destul de des, funcția care o implementează s-a dovedit a fi mică, așa că este util să declarați această funcție încorporată (inline). Compilatorul înlocuiește textul funcției în loc să o apeleze, astfel încât să nu se piardă timp la un astfel de apel. (Funcțiile încorporate sunt discutate în Secțiunea 7.6.) O funcție membru definită într-o clasă este încorporată implicit. Dacă este definit în afara clasei, pentru a o declara încorporată, trebuie să utilizați cuvântul cheie inline:

Inline bool String::operator==(const String &rhs) ( // același lucru)

Definiția unei funcții încorporate trebuie să fie în fișierul antet care conține definiția clasei. Prin redefinirea operatorului == ca operator inline, trebuie să mutăm textul funcției în sine din fișierul String.C în fișierul String.h.
Următoarea este implementarea operației de comparare a unui obiect String cu un tip șir încorporat:

Inline bool String::operator==(const char *s) ( return strcmp(_string, s) ? false: true; )

Numele constructorului este același cu numele clasei. Se consideră că nu returnează o valoare, deci nu este nevoie să specificați o valoare returnată nici în definiția sa, nici în corpul său. Pot exista mai mulți constructori. Ca orice altă funcție, acestea pot fi declarate inline.

#include // constructor implicit inline String::String()
{
_dimensiune = 0;
_șir = 0;
) inline String::String(const char *str) ( if (! str) ( _size = 0; _string = 0; ) else ( _size = str1en(str); strcpy(_string, str); ) // copiere constructor
inline String::String(const String &rhs)
{
dimensiune = rhs._size;
dacă (! rhs._string)
_șir = 0;
altceva(
_string = new char[ _size + 1 ];
} }

Deoarece am alocat dinamic memorie folosind noul operator, trebuie să o eliberăm apelând delete atunci când nu mai avem nevoie de obiectul String. O altă funcție specială membru servește acestui scop - destructorul, care este apelat automat pe un obiect în momentul în care acest obiect încetează să existe. (Vezi capitolul 7 despre durata de viață a obiectului.) Numele unui destructor este format din caracterul tilde (~) și numele clasei. Iată definiția destructorului de clasă String. Aici apelăm operația de ștergere pentru a elibera memoria alocată în constructor:

Șir inline: :~String() ( șterge _șirul; )

Ambii operatori de atribuire supraîncărcați folosesc cuvântul cheie special this.
Când scriem:

String namel("orville"), name2("wilbur");
nume = "Orville Wright";
acesta este un pointer care se adresează obiectului name1 din corpul funcției operației de atribuire.
aceasta indică întotdeauna obiectul de clasă prin care este apelată funcția. Dacă
ptr->size();
obj[ 1024 ];

Apoi, în interiorul size() valoarea acesteia va fi adresa stocată în ptr. În interiorul operației de index, aceasta conține adresa obj. Dereferențând acest lucru (folosind *this), obținem obiectul în sine. (Acest indicator este descris în detaliu în Secțiunea 13.4.)

Inline String& String::operator=(const char *s) ( if (! s) ( _size = 0; delete _string; _string = 0; ) else ( _size = str1en(s); delete _string; _string = new char[ _size + 1 ]; strcpy(_string, s); ) return *this; )

La implementarea unei operațiuni de atribuire, o greșeală care se face adesea este că ei uită să verifice dacă obiectul copiat este același în care este făcută copia. Vom efectua această verificare folosind același indicator:

Inline String& String::operator=(const String &rhs) ( // în expresia // namel = *pointer_to_string // aceasta reprezintă name1, // rhs - *pointer_to_string. if (acest != &rhs) (

Iată textul complet al operației de atribuire a unui obiect de același tip unui obiect String:

Inline String& String::operator=(const String &rhs) ( dacă (acest != &rhs) ( șterge _string; _size = rhs._size; dacă (! rhs._string)
_șir = 0;
altceva(
_string = new char[ _size + 1 ];
strcpy(_string, rhs._string);
}
}
returnează *aceasta;
}

Operația de preluare a unui index este aproape identică cu implementarea sa pentru matricea Array pe care am creat-o în secțiunea 2.3:

#include inline char&
String::operator (int elem)
{
assert(elem >= 0 && elem< _size);
return _string[ elem ];
}

Operatorii de intrare și de ieșire sunt implementați ca funcții separate, mai degrabă decât membri de clasă. (Vom vorbi despre motivele acestui lucru în Secțiunea 15.2. Secțiunile 20.4 și 20.5 vorbesc despre supraîncărcarea operatorilor de intrare și ieșire ai bibliotecii iostream.) Operatorul nostru de intrare poate citi maximum 4095 de caractere. setw() este un manipulator predefinit, citește un număr dat de caractere minus 1 din fluxul de intrare, asigurându-ne astfel că nu ne depășim tamponul intern inBuf. (Capitolul 20 discută manipulatorul setw() în detaliu.) Pentru a utiliza manipulatoare, trebuie să includeți fișierul antet corespunzător:

#include operator istream& inline>>(istream &io, String &s) ( // limită artificială: 4096 caractere const int 1imit_string_size = 4096; char inBuf[ limit_string_size ]; // setw() este inclus în biblioteca iostream // limitează dimensiunea blocul care poate fi citit la 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf; s = mBuf; // String::operator=(const char*); return io; )

Operatorul de ieșire are nevoie de acces la reprezentarea internă a șirului. Din moment ce operator<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

Ostream și operator inline<<(ostream& os, const String &s) { return os << s.c_str(); }

Mai jos este un exemplu de program folosind clasa String. Acest program preia cuvinte din fluxul de intrare și numără numărul lor total, precum și numărul de cuvinte „the” și „it” și înregistrează vocalele întâlnite.

#include #include "String.h" int main() ( int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0; / / Cuvintele „The” și „It”
// vom verifica folosind operator==(const char*)
String dar, the("the"), it("it"); // operator>>(ostream&, String&)
în timp ce (cin >> buf) (
++wdCnt; // operator<<(ostream&, const String&)
cout<< buf << " "; if (wdCnt % 12 == 0)
cout<< endl; // String::operator==(const String&) and
// String::operator==(const char*);
dacă (buf == the | | buf == "The")
++theCnt;
altfel
if (buf == it || buf == "It")
++itCnt; // invocă String::s-ize()
pentru (int ix =0; ix< buf.sizeO; ++ix)
{
// invocă String::operator(int)
comutați (buf[ix])
{
case "a": caz "A": ++aCnt; pauză;
case "e": caz "E": ++eCnt; pauză;
case "i": caz "I": ++iCnt; pauză;
case "o": case "0": ++oCnt; pauză;
case "u": case "U": ++uCnt; pauză;
implicit: ++notVowe1; pauză;
}
}
) // operator<<(ostream&, const String&)
cout<< "\n\n"
<< "Слов: " << wdCnt << "\n\n"
<< "the/The: " << theCnt << "\n"
<< "it/It: " << itCnt << "\n\n"
<< "согласных: " < << "a: " << aCnt << "\n"
<< "e: " << eCnt << "\n"
<< "i: " << ICnt << "\n"
<< "o: " << oCnt << "\n"
<< "u: " << uCnt << endl;
}

Să testăm programul: îi vom oferi un paragraf dintr-o poveste pentru copii scrisă de unul dintre autorii acestei cărți (ne vom întâlni cu această poveste în capitolul 6). Iată rezultatul programului:

Alice Emma are părul lung și roșcat. Tatăl ei spune că atunci când vântul îi bate prin păr, pare aproape viu, ca o pasăre de foc în zbor. O pasăre frumoasă de foc, îi spune el, magică, dar neîmblânzită. „Tati, taci, nu există așa ceva”, îi spune ea, dorind în același timp să-i spună mai multe. Sfioasă, ea întreabă: „Vreau să spun, tati, este acolo?” Cuvinte: 65
cel/Cele: 2
it/It: 1
consoane: 190
a: 22
e: 30
eu: 24
o: 10
u:7

Exercițiul 3.26

Implementările noastre de constructori și operatori de atribuire conțin multă repetiție. Luați în considerare mutarea codului duplicat într-o funcție separată de membru privat, așa cum sa făcut în Secțiunea 2.3. Asigurați-vă că noua opțiune funcționează.

Exercițiul 3.27

Modificați programul de testare astfel încât să numere și consoanele b, d, f, s, t.

Exercițiul 3.28

Scrieți o funcție membru care numără numărul de apariții ale unui caracter într-un șir folosind următoarea declarație:

Class String ( public: // ... int count(char ch) const; // ... );

Exercițiul 3.29

Implementați operatorul de concatenare a șirurilor (+) astfel încât să concateneze două șiruri și să returneze rezultatul într-un nou obiect String. Iată declarația funcției:

Clasa String ( public: // ... String operator+(const String &rhs) const; // ... );