Modificatori de tip de date în C. Tipuri de date și declararea acestora

Pe lângă împărțirea datelor în variabile și constante, există o clasificare a datelor după tip. Descrierea variabilelor constă în primul rând în declararea tipului acestora. Tipul de date caracterizează intervalul valorilor sale și forma de reprezentare în memoria computerului. Fiecare tip este caracterizat de un set de operații efectuate asupra datelor. În mod tradițional, limbajele de programare de uz general au tipuri standard, cum ar fi întreg, real, caracter și logic 3 . Să observăm imediat că nu există niciun tip logic în C. O expresie (în cazul particular, o variabilă) este considerată adevărată dacă este diferită de zero, în caz contrar este considerată falsă.

Existența a două tipuri numerice (întreg și real) este asociată cu două forme posibile de reprezentare a numerelor în memoria computerului.

Date întreg tip stocate sub formă de prezentare punct fix. Se caracterizează prin acuratețe absolută în reprezentarea numerelor și efectuarea de operații asupra acestora, precum și o gamă limitată de valori numerice. Tipul întreg este folosit pentru datele care, în principiu, nu pot avea o parte fracțională (număr de persoane, mașini etc., numere și contoare).

Tastați real corespunde formei de reprezentare a numerelor virgulă flotantă, care se caracterizează printr-o reprezentare aproximativă a unui număr cu un număr dat de cifre semnificative (semne mantise) și o gamă largă de ordinul numărului, ceea ce face posibilă reprezentarea atât a numerelor foarte mari, cât și a celor foarte mici în valoare absolută. Datorită reprezentării aproximative a datelor de tip real, acestea este incorect a compara pentru egalitate.

În implementările moderne ale limbajelor de programare universale, există de obicei mai multe tipuri întregi și mai multe tipuri reale, fiecare dintre acestea fiind caracterizat de propria sa dimensiune de memorie alocată pentru o valoare și, în consecință, gama sa de valori numerice și pentru tipurile reale - și precizie (numărul de cifre ale mantisei).

Date tip de caracter ia valori pe întregul set de caractere permis pentru un anumit computer. Un octet este alocat pentru a stoca valoarea unui caracter; caracterele sunt codificate în conformitate cu un tabel de codificare standard (de obicei ASCII).

Există 4 tipuri de bază în C:

char- tipul caracterului;

int- un tip întreg,

plutesc- tip real de o singură precizie,

dubla- tip real de dublă precizie.

Pentru a defini tipurile derivate, utilizați calificative:scurt(scurt) - folosit cu tip int,lung(lung) - folosit cu tipuri intŞi dubla;semnat(cu semn), nesemnată(nesemnat) - se aplică oricărui tip întreg. În lipsa cuvântului nesemnat, valoarea este considerată semnată, adică. adică implicit este semnat. Datorită admisibilității unei combinații arbitrare de calificative și nume de tipuri de bază, un tip poate avea mai multe denumiri. Informațiile despre tipurile C standard sunt prezentate în Tabelele 1 și 2. Descriptorii de sinonime sunt listați în celulele primei coloane, separați prin virgule.

Tabelul 1. Tipuri de date standard C întregi

Tip de date

Gama de valori

char, semnat char

unsigned int, nesemnat

int, semnat int, scurt int, scurt

2147483648...2147483647

Interesant este că în C, tipul char poate fi folosit ca caracter sau ca tip întreg, în funcție de context.

Tabelul 2. Tipuri de date reale standard C

Comentariu. Pentru a scrie programe pentru prima parte a manualului, vom avea nevoie în principal doua tipuri:plutescŞiint.

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, de exemplu, despre 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 încorporat, sau elementar, tipuri de date ale limbajului C++. Începe cu o definiție literali Acest capitol oferă o privire de ansamblu , cum ar fi 3.14159 sau pi, și apoi este introdus conceptul variabilă 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

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 iconic (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. Tipul de caracter semnat pe 8 biți poate reprezenta valori de la -128 la 127, iar caracterul nesemnat poate reprezenta valori de la 0 la 255. Acest capitol oferă o privire de ansamblu Când un anumit număr, de exemplu 1, este întâlnit într-un program, acest număr este apelat literal

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
Î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:

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<< "2 raised to the power of 10: ";
// o primă soluție<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
// o primă soluție<< endl;
cout
}

întoarce 0;
Problema a fost rezolvată, deși a trebuit să verificăm în mod repetat dacă literalul 2 a fost de fapt repetat de 10 ori. Nu am greșit în scrierea acestei lungi secvențe de doi, iar programul a produs rezultatul corect - 1024.

Dar acum ni s-a cerut să ridicăm 2 la puterea a 17-a, iar apoi la puterea a 23-a Este extrem de incomod să modifici de fiecare dată textul programului! Ș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:<< "2 в степени X\t"; cout << 2 * ... * 2;

Cout

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:

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem:
int main()
{
// obiecte de tip int
valoare int = 2;
int pow = 10;
// o primă soluție<< 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;
// o primă soluție<< 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ă timpii pow ale liniei de rezultat.
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. Iată cum este implementată ultima noastră sarcină - tipăriți un tabel cu puteri de două de la 0 la 15:

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: extern int pow(int,int); int main() ( int val = 2; int exp = 15;
cout<< "Степени 2\n";
pentru (int cnt=0; cnt<= exp; ++cnt)
// o primă soluție<< cnt << ": "
<< pow(val, cnt) << endl;
întoarce 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ă Acest capitol oferă o privire de ansamblu Variabilă 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

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 o declarație 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ă unei variabile atunci când 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. Astfel, 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ă Numele variabilei, 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ă char caz captură clasă
const const_cast continua implicit dubla
şterge do dynamic_cast altfel enumerare
explicit export plutesc extern fals
du-te la dacă în linie int lung
mutabil spatiu de nume nou operator privat
protejat public registru reinterpret_cast reveni
scurt semnat dimensiunea static static_cast
struct comutator șablon acest arunca
typedef adevărat încerca tipizat nume de tip
uniune nulă unire folosind virtual gol

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 cel mai simplu caz, 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();< int >În următoarea definiție:

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

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: 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
preț dublu = 109,99, reducere = 0,16;

dublu pret_de vanzare(pret * discount);
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

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

elevi;

vector

elevi;< int >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

_; (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).

Iată câteva 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:
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ă, notată 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*
Dacă aplicăm aceeași operație 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 de 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 unei matrice 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?
#include
Exercițiul 3.10
Acest program conține o eroare legată de utilizarea incorectă a indicatorilor:
cout
}

Int foobar(int *pi) ( *pi = 1024; return *pi; )

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:

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

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 utilizată 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ă când este atins un caracter cu codul 0. Operatorul de increment ++ adaugă 1 la indicatorul st și, astfel, îl deplasează 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.

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: Sarcina este simplă: calculați lungimea șirului. Prima versiune este incorectă. Repare-o.
const char *st = "Prețul unei sticle de vin\n"; int main() (
int len ​​= 0;<< len << ": " << st;
cout
}

în timp ce (st++) ++len;
cout

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: Î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.
{
const char *st = "Prețul unei sticle de vin\n"; int main() (
Î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?<< len << ": " << st << endl;
cout
}

const char *st = "Prețul unei sticle de vin\n"; int main()
while (*st++) ++len;

cout<< len << ": " << st;

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

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;

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

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ă îl 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();

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:

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

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

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: string st("Prețul unei sticle de vin\n");

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

Dar acum ni s-a cerut să ridicăm 2 la puterea a 17-a, iar apoi la puterea a 23-a Este extrem de incomod să modifici de fiecare dată textul programului! Ș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:<< "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";

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

St2 șir; // șir gol

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; // Corect

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(); // Corect

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) string 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 Fiecare dintre cele zece elemente ale vectorului este inițializat cu int().
int main()
{
int erori = 0;
const char *pc = "un șir literal foarte lung";< 1000000; ++ix)
{
pentru (int ix = 0; ix
int len ​​​​= strlen(buc);
char *pc2 = new char[ len + 1 ];
strcpy(pc2, pc);
dacă (strcmp(pc2, pc))
}
// o primă soluție<< "C-строки: "
<< errors << " ошибок.\n";
++erori;
șterge pc2;
int main()
{
int erori = 0;
string str("un șir literal foarte lung");< 1000000; ++ix)
{
pentru (int ix = 0; ix
int len ​​​​= str.size();
șir str2 = str;
}
// o primă soluție<< "класс string: "
<< errors << " ошибок.\n;
}

dacă (str != str2)
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:< 512; ++index) ... ;

Pentru (index int = 0; 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 care este exact variabila buclă. comparativ cu.< bufSize

Index Î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< bufSize; ++index)
// ...

Int bufSize = 512; // dimensiunea tamponului de intrare // ... pentru (index int = 0; 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

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; // Nu? 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; //Corect

*buc = 3,14159; // eroare

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

Pc =
Î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ță 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 generat 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; // Corect
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

(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;
// o primă soluție<< "ok, мы нашли слово\n";
// ... // scurtătură: dacă (găsit == adevărat)<< "нет, наше слово не встретилось.\n";

dacă (găsit)

altfel cout

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 denota aceste moduri:
Intrare const int = 1; const int output = 2; const int append = 3;

și folosiți aceste constante:
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 obiectul acelui 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 permis de valori 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;

Dar acum ni s-a cerut să ridicăm 2 la puterea a 17-a, iar apoi la puterea a 23-a Este extrem de incomod să modifici de fiecare dată textul programului! Ș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:<< input << " " << om << endl;

// ... om = anexează;

open_file("TailTell", om);

Dar acum ni s-a cerut să ridicăm 2 la puterea a 17-a, iar apoi la puterea a 23-a Este extrem de incomod să modifici de fiecare dată textul programului! Ș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:<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

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

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:

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 tablou 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

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. Să zicem

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()
ia3 = ia;
cout
}

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 ];< array_size; ++ix)
pentru (int ix = 0; ix
}

ia2[ ix ] = ia1[ ix ];

întoarce 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<= array_size; ++ix)
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

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 în 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 ) );

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 unui tablou bidimensional 1 pe 2. Un agregat între paranteze drepte 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; ia;

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); ia;

Observați diferența dintre expresii:

*ia+1 și *(ia+1); Operația de dereferință are o mai mare prioritate

decât operația de adăugare (prioritățile operatorului sunt discutate în secțiunea 4.13).

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: Prin urmare, prima expresie dereferențează variabila ia și primește primul element al matricei, apoi adaugă 1 la acesta.<< *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:

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: void ia_print(int *pbegin, int *pend) (
în timp ce (pbegin != pend) (
// o primă soluție<< *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):

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: ș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:

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: 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:

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

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 >În următoarea definiție:

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);< e1em_size; ++ix)
int ia[ e1em_size ];
}

pentru (int ix = 0; ix

ia[ ix ] = ivec[ ix ]; // ...< ivec.size(); ++ix)
// o primă soluție<< ivec[ ix ] << " ";
}

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

Vector< int >Void print_vector(vector

ivec) ( dacă (ivec.empty()) return; pentru (int ix=0; 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:

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 >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:

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;

Vector< string >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:

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;

Dar acum ni s-a cerut să ridicăm 2 la puterea a 17-a, iar apoi la puterea a 23-a Este extrem de incomod să modifici de fiecare dată textul programului! Ș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:<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

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

Dar acum ni s-a cerut să ridicăm 2 la puterea a 17-a, iar apoi la puterea a 23-a Este extrem de incomod să modifici de fiecare dată textul programului! Ș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:<< "считаны слова: \n"; for (vectorDeși putem folosi operația de index pentru a itera elementele unui vector:<< *it << " "; cout << endl;

Este mai obișnuit în cadrul acestui idiom să folosiți iteratoare:
::iterator it = text.begin(); it != text.end(); ++it) cout

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

Expresie

Vector 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:

ivec;

Vector 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:< int >ia(10);< size; ++ix) ivec.push_back(ia[ ix ]);

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

ivec(dimensiune); pentru (int ix = 0; ix
Aceasta a însemnat inițializarea vectorului ivec cu valorile elementelor ia, ceea ce a rezultat în schimb î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.< vector< int >(Vom analiza toate acestea în detaliu și cu exemple în capitolul 6.)
Exercițiul 3.24< int >Există erori în următoarele definiții?
int ia[ 7 ] = ( 0, 1, 1, 2, 3, 5, 8 );< int >(a)vector
>ivec;< string >(b) vector
ivec = (0, 1, 1, 2, 3, 5, 8);< string >(c)vector

ivec(ia, ia+7);

(d)vector
svec = ivec;
(e)vector svec(10, string("null"));
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.

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

Ca de obicei, pentru a-l folosi trebuie să includeți fișierul antet:

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

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:< double >Complex< double >conjugate[ 2 ] = ( complex< double >(2, -3) };

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:< double >(2, 3), complex< double >*ptr = complex

&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< bool >test0(c1ass_size); const int c1ass_size = 34; test_scores test0(c1ass_size); //vector< in_attendance >prezența; vector

prezență(c1ass_size); // int *tabel[ 10 ]; Masa cu halbe [10];
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 compozite 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 lui poate fi schimbată fără să observe compilatorul, 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.

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

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.< string, string >Aceste valori pot fi aceleași sau diferite. Pentru a utiliza această clasă trebuie să includeți fișierul antet:

De exemplu, instrucțiuni
Pereche

autor(„James”, „Joyce”);
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"); Autorii 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 perechi când vorbim 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 de ș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 clasa Array ca exemplu. 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 char, 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ă:

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: 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

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:

Char& operator(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);
// acces membru pentru obiecte (.); // obiectele au 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)
reveni;
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ă. :
șterge pc2;
// conținutul fișierului sursă: String.C // activând definirea clasei String
#include "String.h" // include definiția funcției strcmp().
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;
}

Reamintim că strcmp() este o funcție de bibliotecă standard C Compară două șiruri de caractere încorporate, returnând 0 dacă șirurile sunt egale și diferite de zero dacă nu sunt egale. Operatorul condiționat (?:) testează valoarea înainte de semnul întrebării. Dacă este adevărată, valoarea expresiei din stânga două puncte este returnată, în caz contrar, valoarea din dreapta este returnată. Î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.

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: // 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);

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:
_șir = 0;
altceva(
_string = new char[ _size + 1 ];
Inline String& String::operator=(const String &rhs) ( dacă (acest != &rhs) ( șterge _string; _size = rhs._size; dacă (! rhs._string)
}
}
strcpy(_string, rhs._string);
}

returnează *aceasta;

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: Operația de preluare a unui index este aproape identică cu implementarea lui pentru matricea Array pe care am creat-o în secțiunea 2.3:
inline char&
{
String::operator (int elem)< _size);
assert(elem >= 0 && elem
}

Operatorii de intrare și de ieșire sunt implementați ca funcții separate, mai degrabă decât membri de clasă.

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: (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 de 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:

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 citibil la 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf s = mBuf // String::operator=(const char*);<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

Operatorul de ieșire are nevoie de acces la reprezentarea internă a șirului.<<(ostream& os, const String &s) { return os << s.c_str(); }

Din moment ce operator

Să ne imaginăm că rezolvăm problema ridicării lui 2 la puterea lui 10. Scriem: Ostream și operator inline
Mai jos este un exemplu de program care utilizează 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 "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");<<(ostream&, const String&)
// o primă soluție<< buf << " "; if (wdCnt % 12 == 0)
// o primă soluție<< endl; // String::operator==(const String&) and
// operator>>(ostream&, String&)
în timp ce (cin >> buf) (
++wdCnt;
// operator
// String::operator==(const char*);
dacă (buf == the | | buf == "The")
++theCnt;< buf.sizeO; ++ix)
{
altfel
if (buf == it || buf == "It")
{
++itCnt;
// invocă String::s-ize()
pentru (int ix =0; ix
// invocă String::operator(int)
comutați (buf[ix])
case "a": caz "A": ++aCnt; pauză;
}
}
case "e": caz "E": ++eCnt; pauză;<<(ostream&, const String&)
// o primă soluție<< "\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;
}

case "i": caz "I": ++iCnt; pauză;

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ă: „Adică, tati, este acolo?” Cuvinte: 65
cel/cel: 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; // ... );

Î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 ghilimele).

  • 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 de mașină. De exemplu, în compilatoarele pentru procesoare pe 16 biți, o variabilă 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 dublu 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 se potrivește cu cuvintele 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 scurtă nesemnată poate fi declarată ca int nesemnată sau scurtă nesemnată.

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ă Dimensiune
nesemnat char 0 … 255 0x00...0xFF 8 biți
semnat char
sau doar
char
-128 … 127 -0x80…0x7F 8 biți
nesemnat scurt int
sau doar
nesemnat int sau scurt nesemnat
0 … 65535 0x0000…0xFFFF 16 biți
semnat scurt int sau semnat int
sau doar
scurt sau int
-32768 … 32767 0x8000…0x7FFF 16 biți
nesemnat lung int
sau doar
nesemnat lung
0 … 4294967295 0x00000000 … 0xFFFFFFFF 32 de biți
semnat lung
sau doar
lung
-2147483648 … 2147483647 0x80000000 … 0x7FFFFFFF 32 de biți
nesemnat lung lung 0 … 18446744073709551615 0x0000000000000000 … 0xFFFFFFFFFFFFFFFFFF pe 64 de biți
semnat lung lung
sau doar
lung lung
-9223372036854775808 … 9223372036854775807 0x8000000000000000 … 0x7FFFFFFFFFFFFFFFF pe 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] tip_specificator 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ă, variabila x va conține imediat numărul 100. Este mai bine să declarați variabilele inițializate pe linii separate.

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ă:

Iată-l:

Car a = *"A";

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

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, trebuia să pui semnul * în fața semnului = ș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. Iar 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 dificil să recitesc 200 de articole în mod constant pentru a corecta ceva. Iar 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 atenți la 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. Multumesc.

Salut 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. Înțeleg 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 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.