Tipuri de date și operații în limbajul C. expresii. Variabile în limbajul C. Declararea unei variabile în C

În această lecție vei învăța Alfabetul limbajului C++, și, de asemenea, ce tipuri de date programul îl poate procesa. Poate că acesta nu este cel mai interesant moment, dar aceste cunoștințe sunt necesare În plus, atunci când începi să înveți orice alt limbaj de programare, vei trece printr-o etapă similară de învățare cu mai multă încredere! Un program C++ poate conține următoarele simboluri:

  • litere mari, minuscule latine A, B, C..., x, y, z și liniuță;
  • cifre arabe de la 0 la 9;
  • caractere speciale: ( ) , | , () + - / % * . \‘ : ?< > = ! & # ~ ; ^
  • caractere de spațiu, tabulator și linie nouă.

În testarea programului puteți utiliza comentarii. Dacă textul conține două caractere oblică // și se termină cu un caracter de linie nouă sau este cuprins între caracterele /* și */, atunci compilatorul îl ignoră.

Date în C++

Pentru a rezolva o problemă, orice program prelucrează unele date. Ele pot fi de diferite tipuri: numere întregi și reale, caractere, șiruri de caractere, matrice. În C++, datele sunt de obicei descrise la începutul funcției. LA tipuri de date de bază limbile includ:

Pentru a genera alte tipuri de date, de bază și așa-zise specificatorii. C++ definește patru specificatori de tip de date:

  • scurt - scurt;
  • lung lung;
  • semnat - semnat;
  • nesemnat - nesemnat.

Tipul întreg

Variabila de tip intîn memoria computerului poate ocupa fie 2, fie 4 octeți. Depinde de dimensiunea biților procesorului. În mod implicit, toate tipurile întregi sunt considerate semnate, adică specificatorul semnat poate să nu fie specificat. Specificator nesemnat vă permite să reprezentați numai numere pozitive. Mai jos sunt câteva intervale de valori de tip întreg

Tip Gamă mărimea
int -2147483648…2147483647 4 octeți
nesemnat int 0…4294967295 4 octeți
semnat int -2147483648…2147483647 4 octeți
scurt int -32768…32767 2 octeți
lung int -2147483648…2147483647 4 octeți
nesemnat scurt int 0…65535 2 octeți

Tip real

Un număr în virgulă mobilă este reprezentat sub forma mE +- p, unde m este mantisa (număr întreg sau fracționar cu virgulă zecimală), p este exponentul (întreg). De obicei valori precum pluti ocupă 4 octeți și dubla 8 octeți. Tabel cu intervale de valori reale:

pluti 3.4E-38…3.4E+38 4 octeți
dubla 1.7E-308…1.7E+308 8 octeți
dublu lung 3.4E-4932…3.4E+4932 8 octeți

tip boolean

Variabila de tip bool poate lua doar două valori Adevărat ( Adevărat ) sau fasle ( minciună ). Orice valoare care nu este egală cu zero este interpretată ca Adevărat. Sens fals reprezentat în memorie ca 0.

tip void

Setul de valori de acest tip este gol. Este folosit pentru a defini funcții care nu returnează o valoare, pentru a specifica o listă goală de argumente ale funcției, ca tip de bază pentru pointeri și în operațiunile de turnare a tipului.

Conversia tipului de date

În C++, există două tipuri de conversie a tipurilor de date: explicită și implicită.

  • Conversie implicită se întâmplă automat. Acest lucru se face în timpul comparării, atribuirii sau evaluării expresiilor de diferite tipuri. De exemplu, următorul program va imprima pe consolă o valoare asemănătoare pluti

#include „stdafx.h” #include folosind namespace std; int main() ( int i=5; float f=10,12; cout<>void"); returnează 0; )

#include „stdafx.h”

#include

folosind namespace std;

int main()

int i = 5 ; float f = 10,12;

cout<< i / f ;

sistem ("pause>>void" );

returnează 0;

Cea mai mare prioritate este acordată tipului în care se pierde cea mai mică cantitate de informații. Nu ar trebui să folosiți în exces conversia de tip implicit, deoarece pot apărea diverse situații neprevăzute.

  • Conversie explicită spre deosebire de implicit, este realizat de programator. Există mai multe moduri de a face această conversie:
  1. Conversia la stiluri C: (plutitor) a
  2. Conversia la stiluri C++: pluti()

Turnarea tipului poate fi efectuată și folosind următoarele operații:

static_cast<>() const_cast<>() reinterpret_cast<>() dynamic_cast<> ()

static_cast<> ()

const_cast<> ()

reinterpret_cast<> ()

dynamic_cast<> ()

static_cas- convertește tipurile de date aferente. Acest operator aruncă tipuri în conformitate cu regulile obișnuite, care pot fi necesare atunci când compilatorul nu efectuează conversie automată. Sintaxa va arăta astfel:

Tastați static_cast<Тип>(un obiect);

Folosind static_cast, nu puteți elimina constanța dintr-o variabilă, dar următorul operator o poate face. const_cast- se foloseste numai atunci cand este necesara indepartarea constanței unui obiect. Sintaxa va arăta astfel:

Tipconst_cast< Tip> (un obiect);

reinterpret_cast- folosit pentru a converti diferite tipuri, întreg în pointer și invers. Dacă vedeți un cuvânt nou „index” - nu vă alarmați! Acesta este, de asemenea, un tip de date, dar nu vom lucra cu el în curând. Sintaxa de aici este aceeași cu cea a operatorilor discutați anterior:

Tipreinterpreta_turnați< Tip> (un obiect);

dynamic_cast- folosit pentru conversia de tip dinamic, implementează pointer sau turnare de referință. Sintaxă:

Tipdinamic _turnați< Tip> (un obiect);

Personaje de control

Sunteți deja familiarizat cu unele dintre aceste „personaje de control” (de exemplu, cu \n). Toate încep cu o bară oblică inversă și sunt, de asemenea, înconjurate de ghilimele duble.

Imagine

Cod hexadecimal

Nume

Sunete beeper

Întoarce-te cu un pas

Traducerea paginii (format)

Traducerea liniilor

Retur transport

Tabulare orizontală

Filă verticală

Bazele limbajului

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

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

Acum putem vorbi despre, de exemplu, un octet la adresa 1040 sau un cuvânt la adresa 1024 și să spunem că un octet la adresa 1032 nu este egal cu un octet la adresa 1040.
Cu toate acestea, nu știm ce reprezintă orice octet, orice cuvânt de mașină. Cum să înțelegeți semnificația anumitor 8 biți? Pentru a interpreta în mod unic sensul acestui octet (sau cuvânt, sau alt set de biți), trebuie să cunoaștem tipul de date pe care le reprezintă octetul.
C++ oferă un set de tipuri de date încorporate: caracter, întreg, real - și un set de tipuri compuse și extinse: șiruri de caractere, matrice, numere complexe.
Capitolul 3 oferă o prezentare generală a tipurilor încorporate și extinse și a mecanismelor prin care puteți crea tipuri noi. Acesta este, desigur, în principal mecanismul de clasă introdus în Secțiunea 2.3.

Capitolul 4 acoperă expresii, operațiunile încorporate și prioritățile acestora și conversiile de tip. Capitolul 5 vorbește despre instrucțiunile lingvistice. În cele din urmă, Capitolul 6 prezintă Biblioteca standard C++ și tipurile de containere vector și matrice asociativă.

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

tipuri de bază C++.

3.1. Literale

C++ are un set de tipuri de date încorporate pentru reprezentarea numerelor întregi și reale, a caracterelor, precum și un tip de date „matrice de caractere”, care este folosit pentru a stoca șiruri de caractere. Tipul char este folosit pentru a stoca caractere individuale și numere întregi mici. Ocupă un octet de mașină. Tipurile short, int și long sunt concepute pentru a reprezenta numere întregi. Aceste tipuri diferă doar în intervalul de valori pe care numerele le pot lua, iar dimensiunile specifice ale tipurilor enumerate depind de implementare. De obicei, scurt ocupă jumătate de cuvânt de mașină, int ocupă un cuvânt, lung ocupă unul sau două cuvinte. Pe sistemele pe 32 de biți, int și long au de obicei aceeași dimensiune.
Tipurile float, double și long double sunt destinate numerelor în virgulă mobilă și diferă prin precizia reprezentării (numărul de cifre semnificative) și interval. În mod obișnuit, float (precizie simplă) necesită un cuvânt de mașină, dublu (precizie dublă) necesită două, iar dublu lung (precizie extinsă) necesită trei. char, short, int și long alcătuiesc împreună tipuri întregi, care, la rândul său, poate fi simbolic(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.

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

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

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

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

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

128u 1024UL 1L 8Lu

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

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

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

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

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

Linie nouă \n filă orizontală \t backspace \b filă verticală \v retur car \r feed sheet \f apel \a backslash \\ întrebare \? ghilimele simple \" ghilimele duble \"

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

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

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

Iată exemple de literale șir:

"" (șir gol) "a" "\nCC\toptions\tfile.\n" "un literal \ șir cu mai multe linii semnalează continuarea sa cu o bară oblică inversă"
De fapt, un șir literal este o matrice de constante de caractere, unde, prin convenția limbajelor C și C++, ultimul element este întotdeauna un caracter special cu codul 0 (\0).
Literalul „A” specifică un singur caracter A, iar literalul șir „A” specifică o matrice de două elemente: „A” și \0 (caracterul gol).

Deoarece există un tip wchar_t, există și literale de acest tip, notate, ca și în cazul caracterelor individuale, prin prefixul L:

L „un șir larg literal”
Un șir literal de tip wchar_t este o matrice de caractere de același tip terminată cu nul.

Dacă două sau mai multe literale de șir (cum ar fi char sau wchar_t) apar într-un rând într-un test de program, compilatorul le concatenează într-un șir. De exemplu, următorul text

"doi" "unii"

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

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

Pe un computer, rezultatul va fi un șir semnificativ, dar pe altul poate fi ceva complet diferit. Programele care folosesc caracteristici de implementare ale unui anumit compilator sau sistem de operare nu sunt portabile.

Descurajăm cu tărie utilizarea unor astfel de structuri.

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

Exercițiul 3.2

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

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

3.2. Variabile

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

#include
int main() (
// o primă soluție
cout<< "2 raised to the power of 10: ";
cout<< 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
cout<< endl;
î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:

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

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

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

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

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

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

value, pow, res și cnt sunt variabile care vă permit să stocați, să modificați și să preluați valori. Instrucțiunea buclă for repetă 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. Acesta este modul în care este implementată ultima noastră sarcină - tipăriți un tabel cu puteri de două de la 0 la 15:

#include extern int pow(int,int); int main() ( int val = 2; int exp = 15;
cout<< "Степени 2\n";
pentru (int cnt=0; cnt<= exp; ++cnt)
cout<< cnt << ": "
<< pow(val, cnt) << endl;
î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ă incorporat un obiect Variabil

– 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.
  • valoarea adresei zonei de memorie asociată cu variabila, sau valoarea l (din valoarea locației) - locul în care este stocată valoarea r;

inerente numai obiectului.

În exprimare

Ch = ch - "0";

variabila ch este situată atât în ​​stânga cât și în dreapta simbolului de atribuire.

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

Nu putem scrie următoarele expresii:
// erori de compilare: valorile din stânga nu sunt valori l // eroare: literal nu este o valoare l 0 = 1; // eroare: expresia aritmetică nu este un salariu cu valoare l + salariu * 0,10 = salariu_nou;
Declarația de definire a variabilei îi alocă memorie. Deoarece un obiect are asociată o singură locație de memorie, o astfel de instrucțiune poate apărea o singură dată într-un program. Dacă o variabilă definită într-un fișier sursă trebuie utilizată în altul, apar probleme. De exemplu:
// fisier module0.C // defineste un obiect fileName sir fileName; // ... atribuiți fileName o valoare
// fișier module1.C
// folosește obiectul fileName

// din păcate, nu compilează:

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

// fișierul module1.C // folosește obiectul fileName // fileName este declarat, adică programul primește
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ă Nume variabilă, sau identificator
, poate consta din litere latine, cifre și caracterul de subliniere. Literele mari și mici din nume sunt diferite.

Limbajul C++ nu limitează lungimea identificatorului, dar folosirea numelor prea lungi precum gosh_this_is_an_impossibly_name_to_type este incomod.

Unele cuvinte sunt cuvinte cheie în C++ și nu pot fi folosite ca identificatori; Tabelul 3.1 oferă o listă completă a acestora. Tabelul 3.1. Cuvinte cheie C++ bool asm auto
pauză caz captură char const_cast
clasă const continua Mod implicit dubla
dynamic_cast șterge do altfel enumerare
explicit fals pluti export extern
pentru prieten mergi la int dacă
în linie lung mutabil spatiu de nume nou
operator privat protejat reinterpret_cast public
Inregistreaza-te semnat întoarcere mic de statura static_cast
dimensiunea static struct intrerupator ș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Și În cel mai simplu caz, operatorul de definire a obiectului constă din specificator de tip

numele obiectului

și se termină cu punct și virgulă. De exemplu:

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

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

#include 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 * reducere);
string pet ("riduri"); extern int get_value(); int val = get_value();

nesemnat abs_val = abs(val);
abs() este o funcție standard care returnează valoarea absolută a unui parametru.

get_value() este o funcție definită de utilizator care returnează o valoare întreagă.

Exercițiul 3.3

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

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

Exercițiul 3.4

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

Exercițiul 3.5

Găsiți diferențele în utilizarea numelui și a variabilelor student în primul și al doilea rând din fiecare exemplu: (a) numele șirului 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;// ...
Fiecare pointer este asociat cu un anumit tip de date, iar reprezentarea lor internă nu depinde de tipul intern: atât dimensiunea memoriei ocupată de un obiect de tip pointer, cât și intervalul de valori sunt aceleași. Diferența este modul în care compilatorul tratează obiectul adresat. Pointerii către diferite tipuri pot avea aceeași valoare, dar zona de memorie în care se află tipurile corespunzătoare poate fi diferită:

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

Aici sunt cateva exemple:

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

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

Lung *lp, lp2;

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

Float fp, *fp2;

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

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

Se poate presupune că atât ps, cât și ps2 sunt pointeri, deși pointerul este doar primul dintre ei.
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*
int *pi =

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;<< "Значение ival\n" << "явное значение: " << ival << "\n"
<< "косвенная адресация: " << *pi << "\n"
<< "дважды косвенная адресация: " << **ppi << "\n"

cout

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; //creșteți adresa conținută în pi cu 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 unui tablou folosind un iterator:
Int ia; int *iter = int *iter_end =
}

în timp ce (iter != iter_end) (

face_ceva_cu_valoare (*iter);

++iter;

Exercițiul 3.8

Definițiile variabilelor sunt date:

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

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

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

Exercițiul 3.9

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

Pi = pi = pi + 1024;

aproape sigur va face ca pi să indice o locație aleatoare de memorie. Ce face acest operator de atribuire și când nu va provoca o eroare?
int main() (
Exercițiul 3.10
Acest program conține o eroare legată de utilizarea incorectă a indicatorilor:
întoarce 0;
}

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

int *pi2 = 0;

int ival = foobar(pi2);

Care este eroarea? Cum pot repara?

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

3.4.1. Tip șir încorporat

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

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

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

#include

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

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

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

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

st este dereferențiat și valoarea rezultată este verificată pentru a vedea dacă este adevărată. Orice valoare diferită de zero este considerată adevărată și, prin urmare, bucla se termină 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 de tip încorporat poate fi considerat gol în două cazuri: dacă pointerul către șir are o valoare nulă (caz în care nu avem șir deloc) 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.

#include 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;
întoarce 0;
}

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

#include Î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;
întoarce 0;
}

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ă le aibă clasa șir de caractere:
  • inițializare cu o matrice de caractere (un ș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:

#include

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

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

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

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

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

St2 șir; // linie goală

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

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

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

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

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

St3 st3(st);

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

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

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

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

Pentru a concatena șiruri, utilizați adaosul (+) 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:

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

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

Char *str = s1; // eroare de compilare

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

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

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

Const char *

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

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

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

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

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

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

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

Exercițiul 3.12

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

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

Exercițiul 3.13

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

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

Exercițiul 3.14

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

// ***** Implementare folosind șiruri C ***** #include Variabila poate fi inițializată cu o expresie de orice complexitate, inclusiv apeluri de funcții. De exemplu:
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))
}
cout<< "C-строки: "
<< errors << " ошибок.\n";
++erori;
#include
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;
}
cout<< "класс 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 al clasei șir date în ultima secțiune? Explicați-vă propunerile

3.5. specificatorul const

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; // Dreapta? eroare?
dublu *ptr =

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

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

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

Const double *cptr;

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

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

*buc = 3,14159; // eroare

Adresa unui obiect constant este atribuită doar unui pointer către o constantă. În același timp, unui astfel de 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 cauzat o eroare de compilare. Cu toate acestea, motivul pentru care compilatorul nu trece astfel de definiții este neclar. Să încercăm să ne dăm seama.
Pentru literali, acest lucru este mai mult sau mai puțin clar: nu ar trebui să putem schimba indirect valoarea unui literal folosind pointeri sau referințe. În ceea ce privește obiectele de alte tipuri, compilatorul convertește obiectul original într-un obiect auxiliar. De exemplu, dacă scriem:

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

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

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

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

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

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

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

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

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

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

Să ne uităm la exemple. Daca scriem:

Int *pi = 0;

inițializam indicatorul pi la zero, ceea ce înseamnă că pi nu indică niciun obiect. În același timp, înregistrarea
const int &ri = 0;
inseamna ceva de genul asta:
int temp = 0;

const int &ri = temp;

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

Int ival = 1024, ival2 = 2048; int *pi = &ival, *pi2 = pi = pi2;
variabila ival indicată de pi rămâne neschimbată, iar pi primește valoarea adresei variabilei ival2. Atât pi cât și pi2 indică acum același obiect, ival2.

Dacă lucrăm cu link-uri:

Int &ri = ival, &ri2 = ival2; ri = ri2;
// exemplu de utilizare a legăturilor // Valoarea este returnată în parametrul next_value

bool get_next_value(int &next_value); // operator supraîncărcat Matrix operator+(const Matrix&, const Matrix&);

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

Int &next_value = ival;

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

Exercițiul 3.19

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

daca este gasit)

altfel cout

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

// 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 un obiect de acel tip, cât și tipul parametrilor formali ai funcției:

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

intrare, ieșire și anexare sunt elemente de enumerare. Setul de elemente de enumerare specifică setul de valori permis pentru un obiect de un anumit tip.

O variabilă de tip open_modes (în exemplul nostru) este inițializată cu una dintre aceste valori i se poate atribui și oricare dintre ele. De exemplu:

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

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

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

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

Open_modes om = intrare;

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

// ... om = anexează;

open_file("TailTell", om);

Cout<< 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ă poate fi 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 de index:

Ival = ia[ 2 ];

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

Ia[ 7 ] = ival;

atribuie elementului de la indicele 7 valoarea ival.

O definiție de matrice constă dintr-un specificator de tip, un nume de matrice și o dimensiune.

Dimensiunea specifică numărul de elemente ale matricei (cel puțin 1) și este cuprinsă între paranteze drepte. Mărimea tabloului trebuie să fie cunoscută deja în etapa de compilare și, prin urmare, trebuie să fie o expresie constantă, deși nu este neapărat specificată 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, atunci nu trebuie să specificăm dimensiunea matricei: compilatorul însuși va număra numărul de elemente:

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

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

Dacă există mai multe valori în listă, compilatorul afișează un mesaj de eroare:

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

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

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

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

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

O matrice nu i se poate atribui valoarea unei alte matrice, iar inițializarea unei matrice de către alta nu este permisă. În plus, nu este permisă utilizarea unei matrice de referințe.
{
Iată exemple de utilizare corectă și incorectă a matricelor:
Const int array_size = 3; int ix, jx, kx; // corect: matrice de pointeri de tip int* int *iar = ( &ix, &jx, &kx ); // eroare: tablourile de referință nu sunt permise int &iar = ( ix, jx, kx ); int main()
ia3 = ia;
întoarce 0;
}

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

Const int array_size = 7; int ia1 = ( 0, 1, 2, 3, 4, 5, 6 ); int main() (
int ia3[ array_size ];< 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 rulare. 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ă se utilizeze tablouri multidimensionale, atunci când le declarați trebuie să indicați limita dreaptă a fiecărei dimensiuni între paranteze pătrate separate.

Iată definiția unui tablou bidimensional:

Int ia[ 4 ][ 3 ];

Prima valoare (4) specifică numărul de rânduri, a doua (3) – numărul de coloane.

Obiectul ia este definit ca o matrice de patru șiruri de trei elemente fiecare. Matricele multidimensionale pot fi, de asemenea, inițializate:

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

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 (aceștia sunt încadrați î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; in absenta;

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

După cum am menționat mai devreme, expresia

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

*(ia+1); in absenta;

Observați diferența dintre expresii:

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

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

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

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

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

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

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

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

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

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

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

3.10. Clasa de vectori

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

#include

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

Vector< int >Î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)
cout<< 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. Ca o listă de valori inițiale, este permisă specificarea nu a întregului tablou, ci a unui anumit interval al acesteia:

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;

Cout<< "считаны слова: \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); // ... )

Cout<< "считаны слова: \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 greseala sa 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ă definim 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 î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.

#include

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 sa poate fi schimbată fără ca compilatorul să observe, cum ar fi o variabilă actualizată de ceasul sistemului. Acest specificator îi spune compilatorului că nu trebuie să optimizeze codul pentru a lucra cu acest obiect.

Specificatorul volatil este utilizat în mod similar cu specificatorul const:

Volatil int disp1ay_register; sarcină volatilă *curr_task; volatile int ixa[ max_size ]; Ecran volatil bitmap_buf;
display_register este un obiect volatil de tip int. curr_task este un pointer 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.

#include

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"); Autori musil("robert", "musi1");

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

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

(Vom reveni la clasa 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 un tip de șir încorporat, precum și operația de atribuire a valorilor acestor tipuri. Folosim constructori de clasă și un operator de atribuire supraîncărcat pentru aceasta. Accesul la caracterele String individuale va fi implementat ca o operație de index supraîncărcat. În plus, vom avea nevoie de: funcția size() pentru a obține informații despre lungimea șirului; operația de comparare a obiectelor de tip String și a unui obiect String cu un șir de tip încorporat; precum și operațiunile I/O ale obiectului nostru. În cele din urmă, implementăm capacitatea de a accesa reprezentarea internă a șirului nostru ca tip de șir încorporat.
O definiție de clasă începe cu cuvântul cheie class, urmat de un identificator - numele clasei sau tipul. În general, o clasă este formată din secțiuni precedate de cuvintele public (deschis) și privat (închis). O secțiune publică conține de obicei un set de operații suportate de clasă, numite metode sau funcții membre ale clasei. Aceste funcții membre definesc interfața publică a clasei, cu alte cuvinte, setul de acțiuni care pot fi efectuate asupra obiectelor acelei clase. O secțiune privată include de obicei membri de date care oferă implementare internă. În cazul nostru, membrii interni includ _string - un pointer către un caracter, precum și _size de tip int. _size va stoca informații despre lungimea șirului, iar _string va fi o matrice de caractere alocată dinamic. Iată cum arată o definiție de clasă:

#include clasa String; operator istream&>>(istream&, String&);
ostream& operator<<(ostream&, const String&); class String {
public:
// set de constructori
// pentru inițializare automată
// String strl; // String()
// String str2("literal"); // String(const char*);
// String str3(str2); // String(const String&);
Şir();
String(const char*);
String(const String&);
// distrugător
~String();
// operatori de atribuire
// strl = str2
// str3 = "un șir literal" String& operator=(const String&);
String& operator=(const char*);
// operatori de testare a egalității
// strl == str2;
// str3 == "un șir literal";
operator bool==(const String&);
operator bool==(const char*);
}

// supraîncărcarea operatorului de acces prin index

// strl[ 0 ] = str2[ 0 ];

operator char&(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:

operator Char&(int);

Acest operator are un singur parametru de tip int și returnează o referință la un caracter.
Un operator supraîncărcat poate fi el însuși supraîncărcat dacă listele de parametri ale instanțiilor individuale diferă. Pentru clasa noastră String, vom crea doi operatori diferiți de atribuire și egalitate.

Pentru a apela o funcție de membru, utilizați operatorii de acces membru punct (.) sau săgeata (->). Să avem declarații de obiecte de tip String:
obiect șir ("Danny");
String *ptr = new String("Anna");
matrice de șiruri;
Iată cum arată un apel la size() pe aceste obiecte: vector

dimensiuni (3);
// membru de acces pentru obiecte (.); // obiectele 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 operatorii obișnuiți:
Nume șir ("Yadie"); String name2 ("Yodie"); // operator bool==(const String&)
dacă (nume == nume2)
întoarcere;
altfel
// String& operator=(const String&)

nume = nume2;

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

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

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

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

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

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

obj[ 1024 ];

Apoi, în interiorul size() valoarea acesteia va fi adresa stocată în ptr. În cadrul 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;

#include Operația de preluare a unui index este aproape identică cu implementarea sa pentru matricea Array pe care am creat-o în secțiunea 2.3:
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ă.

#include (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

#include Ostream și operator inline
Mai jos este un exemplu de program folosind clasa String. Acest program preia cuvinte din fluxul de intrare și numără numărul lor total, precum și numărul de cuvinte „the” și „it” și înregistrează vocalele întâlnite.
#include "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&)
cout<< buf << " "; if (wdCnt % 12 == 0)
cout<< 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&)
cout<< "\n\n"
<< "Слов: " << wdCnt << "\n\n"
<< "the/The: " << theCnt << "\n"
<< "it/It: " << itCnt << "\n\n"
<< "согласных: " < << "a: " << aCnt << "\n"
<< "e: " << eCnt << "\n"
<< "i: " << ICnt << "\n"
<< "o: " << oCnt << "\n"
<< "u: " << uCnt << endl;
}

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ă: „Vreau să spun, tati, este acolo?” Cuvinte: 65
cel/Cele: 2
it/It: 1
consoane: 190
a: 22
e: 30
eu: 24
o: 10
u:7

Exercițiul 3.26

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

Exercițiul 3.27

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

Exercițiul 3.28

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

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

Exercițiul 3.29

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

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

Variabilele sunt folosite pentru a stoca diverse date în limbaje de programare. O variabilă este o zonă de memorie care are un nume, altfel numit identificator.

Dând un nume unei variabile, programatorul numește în același timp zona de memorie în care valorile variabilei vor fi scrise pentru stocare.

Este un stil bun să numești variabilele în mod semnificativ. 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ă și nu trebuie să existe spații în numele variabilei. În versiunile moderne ale compilatoarelor, lungimea numelui este practic nelimitată. Numele variabilei nu poate corespunde cuvintelor cheie rezervate. Literele mari și mici din numele variabilelor sunt diferite, variabile AȘi A- variabile diferite.

Cuvinte cheie rezervate auto double int struct break else lung comutare registru tupedef char extern return void case float unsigned implicit pentru uniunea semnată do if sizeof volatile continue enumerarea scurtă
În C, toate variabilele trebuie declarate. Aceasta înseamnă că, în primul rând, la începutul fiecărui program sau funcție trebuie să furnizați o listă cu toate variabilele utilizate și, în al doilea rând, să indicați tipul fiecăreia dintre ele.

Când o variabilă este declarată, compilatorul îi alocă spațiu de memorie în funcție de tipul acesteia. AVR GCC lucrează cu tipuri de date folosind instrumente standard caz(tip de caracter) și int(tip întreg).

Tipuri variabile

Tastați char

caz- este cel mai economic tip. Tipul de caracter poate fi semnat sau nesemnat. Notat în consecință ca „ semnat char„ (tip semnat) și „ nesemnat char" (tip nesemnat). Tipul semnat poate stoca valori în intervalul de la -128 la +127. Unsigned - de la 0 la 255. O variabilă char are 1 octet de memorie (8 biți).

Cuvinte cheie (modificatoare) semnatȘi nesemnat indicați 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

Valoare intreaga int Pot fi Inregistreaza-te(scurt) sau dacă(lung).

Cuvânt cheie (modificator) Inregistreaza-te plasat după cuvinte cheie semnat sau nesemnat. Astfel, se disting următoarele tipuri: signed short int, unsigned short int, signed long int, unsigned long int.

Variabila de tip semnat scurt int(întreg scurt cu semn) poate lua valori de la -32768 la +32767, nesemnat scurt int(întreg scurt fără semn) - de la 0 la 65535. Pentru fiecare dintre ei sunt alocați exact doi octeți de memorie (16 biți).

La declararea unei variabile de tip semnat scurt int Cuvinte cheie semnatȘi Inregistreaza-te poate fi omis, iar un astfel de tip de variabilă poate fi declarat simplu int. De asemenea, este posibil să declarați acest tip cu un singur cuvânt cheie Inregistreaza-te.

Variabil nesemnat scurt int poate fi declarat ca nesemnat int sau scurt nesemnat.

Pentru fiecare dimensiune semnat lung int sau nesemnat lung 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 precum lung lung int, pentru care sunt alocați 8 octeți de memorie (64 de biți). Ele pot fi, de asemenea, 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 declarat pur și simplu prin două cuvinte cheie lung lung.

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

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.

O declarație de variabilă are următorul format:

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

Modificatori- Cuvinte cheie semnat, nesemnat, Inregistreaza-te, dacă.
Specificator de tip- cuvânt cheie caz sau int, care determină tipul variabilei declarate.
Identificator- numele variabilei.

Exemplu: char x;
int a, b, c; nesemnat long long y;, A, În acest fel variabilele vor fi declarate, X, b c nesemnat long long y; y A, În acest fel variabilele vor fi declarate, X. La o variabilă b se vor putea scrie valori de la -128 la 127. În variabile

- de la -32768 la +32767. La o variabilă

- de la 0 la 18446744073709551615. nesemnat long long y; Inițializarea valorii unei variabile la declarare

Când este declarată, o variabilă poate fi inițializată, adică i se poate atribui o valoare inițială. Puteți face acest lucru după cum urmează.

int x = 100; Astfel, în variabilă

Când este anunțat, numărul 100 va fi imediat notat. char Este mai bine să evitați amestecarea variabilelor inițializate într-o singură declarație, adică este mai bine să declarați variabilele inițializate pe linii separate. char constante char O variabilă de orice tip poate fi declarată nemodificabilă. Acest lucru se realizează prin adăugarea cuvântului cheie int sau lung int la specificatorul de tip. Variabile cu tip int sunt date numai pentru citire, ceea ce înseamnă că variabilei nu i se poate atribui o nouă valoare. Dacă după cuvânt lung int.

Dacă nu există un specificator de tip, atunci constantele sunt tratate ca valori semnate și li se atribuie un tip în funcție de valoarea constantei: dacă constanta este mai mică de 32768, atunci i se atribuie tipul, in caz contrar Exemplu: const long int k = 25;

const m = -50;

// implicit const int m=-50

const n = 100000;
// implicit const long int n=100000

Misiune Semnul „=" este folosit pentru atribuire în C. Se evaluează expresia din dreapta semnului de atribuire, iar valoarea rezultată este atribuită variabilei din stânga semnului de atribuire. În acest caz, valoarea anterioară stocată în variabilă este ștearsă și înlocuită cu una nouă. Operatorul „=" nu trebuie înțeles ca egalitate. De exemplu, expresia a = 5; ar trebui citit ca „atribuiți variabila a la 5”. Exemple: x = 5 + 3; // adăugați 2 la valoarea stocată în variabila b, // atribuiți rezultatul rezultat variabilei b (scrieți variabilei b)
În partea dreaptă, valoarea variabilei poate fi folosită de mai multe ori: c = b * b + 3 * b;

Exemplu: x = 3; // variabilei x i se va atribui valoarea 3 y = x + 5; // numărul 5 va fi adăugat la valoarea stocată în variabila x, // rezultatul rezultat va fi scris în variabila y z = x * y; // valorile variabilelor x și y vor fi înmulțite, // rezultatul va fi scris în variabila z z = z - 1; // 1 va fi scăzut din valoarea stocată în variabila z // rezultatul va fi scris în variabila z
Astfel, în variabila z numărul 23 va fi stocat

Pe lângă operatorul simplu de atribuire „=", există mai mulți operatori de atribuire combinați în C: „+=", "-=", "*=
Exemple: x += y; // la fel ca x = x + y; - se adaugă x și y // și se scrie rezultatul la variabila x x -= y; // la fel ca x = x - y; - se scade valoarea y din x // si se scrie rezultatul in variabila x x *= y; // la fel ca x = x * y; - înmulțiți x cu y // și scrieți rezultatul în variabila x x /= y; // la fel ca x = x / y; - împărțiți x la y // și scrieți rezultatul în variabila x x %= y; // la fel ca x = x % y;

// se calculează restul întregului la împărțirea x la y // și se scrie rezultatul în variabila x

Creșteți și descreșteți Dacă trebuie să modificați valoarea unei variabile la 1, atunci utilizați sau creştere.

scăderea Creştere

- operația de creștere a valorii stocate într-o variabilă cu 1. Exemplu: x++;// valoarea variabilei x va fi mărită cu 1
$WinAVR = ($_GET["avr"]); if($WinAVR) include($WinAVR);?> Decrementează

- operația de scădere a valorii stocate într-o variabilă cu 1. Exemplu: x--;
// valoarea variabilei x va fi redusă cu 1

Creșterea și descreșterea sunt operatori de atribuire. Când utilizați decrementarea și creșterea împreună cu operatorul de atribuire "=", utilizați notația postfix (x++) sau prefix (++x). Prefixul este executat mai întâi.
Exemple: y = x++; nesemnat long long y; Să presupunem că în variabilă b valoarea 5 a fost stocată nesemnat long long y; se va scrie valoarea 5, după care valoarea variabilei b va fi majorat cu 1. Astfel, in nesemnat long long y; va fi 5, iar în
- 6. y = --x; nesemnat long long y; Dacă în nesemnat long long y; Dacă valoarea 5 a fost stocată, atunci decrementarea va fi efectuată mai întâi b la 4 și apoi această valoare va fi atribuită variabilei nesemnat long long y;Și b. Prin urmare,

i se va atribui valoarea 4.

Tipurile de date sunt deosebit de importante în C#, deoarece este un limbaj puternic tipizat. Aceasta înseamnă că toate operațiunile sunt supuse unei verificări stricte de tip de către compilator, iar operațiunile ilegale nu sunt compilate. În consecință, verificarea strictă a tipului elimină erorile și crește fiabilitatea programelor. Pentru a impune verificarea tipului, toate variabilele, expresiile și valorile trebuie să fie de un anumit tip. Nu există deloc o variabilă „fără tip” în acest limbaj de programare. Mai mult, tipul unei valori determină operațiunile care pot fi efectuate asupra acesteia. O operațiune care este legală pentru un tip de date poate să nu fie valabilă pentru altul.

Există două categorii generale de tipuri de date încorporate în C#: tipuri de valoriȘi tipuri de referință. Ele diferă în conținutul variabilei. Din punct de vedere conceptual, diferența dintre cele două este că un tip de valoare stochează date direct, în timp ce un tip de referință stochează o referință la o valoare.

Aceste tipuri sunt stocate în diferite locații din memorie: tipurile de valori sunt stocate într-o zonă cunoscută sub numele de stivă, iar tipurile de referință sunt stocate într-o zonă cunoscută sub numele de heap gestionat.

Hai să aruncăm o privire tipuri de valori.

Tipuri întregi

C# definește nouă tipuri de numere întregi: char, byte, sbyte, short, ushort, int, uint, long și ulong. Dar tipul char este folosit în primul rând pentru a reprezenta caractere și, prin urmare, este tratat separat. Restul de opt tipuri de numere întregi sunt pentru calcule numerice. Mai jos sunt gama lor de numere și adâncimea de biți:

Tipuri întregi C#
Tip Tastați CTS Dimensiunea biților Gamă
octet System.Byte 8 0:255
sbyte Sistem.SByte 8 -128:127
Inregistreaza-te System.Int16 16 -32768: 32767
scurt System.UInt16 16 0: 65535
int System.Int32 32 -2147483648: 2147483647
uint System.UInt32 32 0: 4294967295
dacă System.Int64 64 -9223372036854775808: 9223372036854775807
ulong System.UInt64 64 0: 18446744073709551615

După cum arată tabelul de mai sus, C# definește atât variante semnate, cât și nesemnate ale diferitelor tipuri de numere întregi. Tipurile de numere întregi semnate diferă de omologii lor nesemnați în modul în care interpretează cel mai semnificativ bit al întregului. Astfel, dacă un program specifică o valoare întreagă cu semn, compilatorul C# va genera cod care folosește cel mai semnificativ bit al întregului ca indicator de semn. Un număr este considerat pozitiv dacă semnul este 0 și negativ dacă este 1.

Numerele negative sunt aproape întotdeauna reprezentate prin metoda complementului a două, prin care toate cifrele binare ale numărului negativ sunt mai întâi inversate și apoi 1 este adăugat la acel număr.

Probabil cel mai comun tip de număr întreg în programare este tip int. Variabilele de tip int sunt adesea folosite pentru controlul buclei, indexarea matricei și calcule matematice de uz general. Când aveți nevoie de o valoare întreagă cu o gamă mai mare de reprezentări numerice decât tipul int, există o serie de alte tipuri întregi disponibile în acest scop.

Deci, dacă valoarea trebuie stocată fără semn, atunci puteți selecta pentru aceasta tip uint, pentru valori mari semnate - tip lung, iar pentru valori mari nesemnate - tip ulong. Ca exemplu, mai jos este un program care calculează distanța de la Pământ la Soare în centimetri. Pentru a stoca o valoare atât de mare, folosește o variabilă lungă:

Utilizarea sistemului; folosind System.Collections.Generic; folosind System.Linq; folosind System.Text; namespace ConsoleApplication1 ( clasa Program ( static void Main(string args)) ( rezultat lung; const long km = 149800000; // distanță în km. rezultat = km * 1000 * 100; Console.WriteLine (rezultat); Console.ReadLine(); ) ) )

Tuturor variabilelor întregi li se pot atribui valori în notație zecimală sau hexazecimală. În acest din urmă caz, este necesar un prefix 0x:

Long x = 0x12ab;

Dacă există vreo incertitudine dacă o valoare întreagă este de tip int, uint, long sau ulong, atunci Mod implicit int este acceptat. Pentru a specifica în mod explicit ce alt tip întreg ar trebui să aibă o valoare, următoarele caractere pot fi adăugate unui număr:

Uint ui = 1234U; lung l = 1234L; ulong ul = 1234UL;

U și L pot fi scrise și cu litere mici, deși un L mic poate fi ușor confundat vizual cu numărul 1 (unu).

Tipuri în virgulă mobilă

Tipurile cu virgulă mobilă vă permit să reprezentați numere cu o parte fracțională. Există două tipuri de tipuri de date în virgulă mobilă în C#: plutiȘi dubla. Ele reprezintă valori numerice cu precizie simplă și, respectiv, dublă. Astfel, lățimea tipului float este de 32 de biți, ceea ce corespunde aproximativ intervalului de reprezentare a numerelor de la 5E-45 la 3.4E+38. Și lățimea tipului dublu este de 64 de biți, ceea ce corespunde aproximativ intervalului de reprezentare a numerelor de la 5E-324 la 1.7E+308.

Tipul de date float este destinat valorilor mai mici în virgulă mobilă care necesită mai puțină precizie. Tipul de date dublu este mai mare decât float și oferă un grad mai mare de precizie (15 biți).

Dacă în codul sursă este codificată o valoare non-întreg (de exemplu, 12.3), atunci compilatorul presupune de obicei că este intenționată o valoare dublă. Dacă valoarea trebuie specificată ca un float, va trebui să îi adăugați caracterul F (sau f):

Float f = 12,3F;

Tip de date zecimal

De asemenea, este furnizat un tip zecimal pentru a reprezenta numere în virgulă mobilă de înaltă precizie. zecimal, care este destinat utilizării în calcule financiare. Acest tip are o lățime de 128 de biți pentru a reprezenta valori numerice cuprinse între 1E-28 și 7.9E+28. Probabil știți că aritmetica obișnuită în virgulă mobilă este predispusă la erori de rotunjire zecimală. Aceste erori sunt eliminate prin utilizarea tipului zecimal, care permite reprezentarea numerelor cu 28 (și uneori 29) zecimale. Deoarece acest tip de date poate reprezenta valori zecimale fără erori de rotunjire, este util în special pentru calculele financiare:

Utilizarea sistemului; folosind System.Collections.Generic; folosind System.Linq; folosind System.Text; namespace ConsoleApplication1 ( class Program ( static void Main(string args)) ( // *** Calculul costului unei investiții cu *** // *** rata fixă ​​de rentabilitate*** zecimală bani, procent; int i; const byte ani = 15 bani = 1000.0m procente = 0.045m;

Rezultatul acestui program va fi:

Simboluri

În C#, caracterele sunt reprezentate nu în cod de 8 biți, ca în multe alte limbaje de programare, cum ar fi C++, ci în cod de 16 biți, numit Unicode. Setul de caractere Unicode este atât de larg încât acoperă caractere din aproape fiecare limbă naturală din lume. În timp ce multe limbi naturale, inclusiv engleză, franceză și germană, au alfabete relativ mici, unele alte limbi, cum ar fi chineza, folosesc seturi de caractere destul de mari care nu pot fi reprezentate printr-un cod de 8 biți. Pentru a depăși această limitare, C# definește tip char, care reprezintă valori nesemnate de 16 biți, cuprinse între 0 și 65.535. Cu toate acestea, setul de caractere ASCII standard de 8 biți este un subset de Unicode cuprins între 0 și 127. Prin urmare, caracterele ASCII sunt încă valabile în C#.

O diferență importantă între limbajul SI și alte limbi (PL1, FORTRAN etc.) este absența unui principiu implicit, ceea ce duce la necesitatea declarării explicite a tuturor variabilelor utilizate în program împreună cu indicarea tipurilor lor corespunzătoare. .

Declarațiile de variabile au următorul format:

[memory-class-specifier] descriptor de specificator de tip [=inițiator] [,descriptor [= inițiator] ]...

Un descriptor este un identificator pentru o variabilă simplă sau un construct mai complex cu paranteze pătrate, paranteze sau un asterisc (un set de asteriscuri).

Un specificator de tip este unul sau mai multe cuvinte cheie care determină tipul variabilei care este declarată. Limbajul SI are un set standard de tipuri de date, cu ajutorul cărora puteți construi noi tipuri de date (unice).

Inițiator - specifică valoarea inițială sau lista de valori inițiale care sunt atribuite variabilei atunci când este declarată.

Specificatorul clasei de memorie - definit de unul dintre cele patru cuvinte cheie SI: auto, extern, register, static și indică modul în care va fi alocată memoria pentru variabila declarată, pe de o parte, și, pe de altă parte, domeniul de aplicare al acestei variabile, adică din ce părți ale programului poate fi accesat.

1.2.1 Categorii de tipuri de date

Cuvinte cheie pentru definirea tipurilor de date de bază

Tipuri întregi: Tipuri flotante: char float int dublu scurt lung dublu lung semnat nesemnat

O variabilă de orice tip poate fi declarată nemodificabilă. Acest lucru se realizează prin adăugarea cuvântului cheie const la specificatorul de tip. Obiectele cu tipul const reprezintă date numai în citire, de exemplu. acestei variabile nu i se poate atribui o nouă valoare. Rețineți că, dacă nu există un specificator de tip după cuvântul const, atunci este implicat specificatorul de tip int. Dacă cuvântul cheie const vine înainte de declararea tipurilor compozite (matrice, structură, amestec, enumerare), atunci aceasta duce la faptul că fiecare element trebuie să fie, de asemenea, nemodificabil, adică. i se poate atribui o valoare o singură dată.

Const dublu A=2,128E-2;

const B=286; (const int B=286 este implicat)

Exemple de declarare a datelor compozite vor fi discutate mai jos.

Pentru a defini datele de tip întreg, sunt utilizate diverse cuvinte cheie, care determină intervalul de valori și dimensiunea zonei de memorie alocată pentru variabile (Tabelul 6).

Tabelul 6

Rețineți că cuvintele cheie semnate și nesemnate sunt opționale. Ele 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. Dacă cuvântul cheie nesemnat lipsește, variabila întreagă este considerată semnată. Dacă specificatorul de tip constă dintr-un tip de cheie semnat sau nesemnat urmat de un identificator de variabilă, atunci acesta va fi tratat ca o variabilă de tip int. De exemplu:

Unsigned int n;

unsigned int b;

int c; (semnatul int c este implicit);

nesemnat d; (implică int d nesemnat);

semnat f; (este subînțeles semnatul int f).

Rețineți că modificatorul de tip char este folosit pentru a reprezenta un caracter (dintr-o reprezentare matrice de caractere) sau pentru a declara literale șir. Valoarea unui obiect char este codul de 1 octet corespunzător caracterului pe care îl reprezintă. Pentru a reprezenta caracterele alfabetului rus, modificatorul tipului de identificare a datelor este caracter nesemnat, deoarece codurile literelor rusești depășesc valoarea de 127.

nesemnat d; (implică int d nesemnat);

Trebuie făcută următoarea notă: limbajul SI nu definește reprezentarea memoriei și intervalul de valori pentru identificatorii cu modificatorii de tip int și unsigned int. Mărimea memoriei pentru o variabilă cu modificatorul de tip int semnat este determinată de lungimea cuvântului mașină, care are o dimensiune diferită pe diferite mașini. Deci, pe mașinile pe 16 biți dimensiunea cuvântului este de 2 octeți, pe mașinile pe 32 de biți este de 4 octeți, adică. tipul int este echivalent cu tipurile short int sau long int, în funcție de arhitectura PC-ului utilizat. Astfel, același program poate funcționa corect pe un computer și incorect pe altul. Pentru a determina lungimea memoriei ocupate de o variabilă, puteți utiliza operatorul SI language sizeof, care returnează lungimea modificatorului de tip specificat.

1.2.3. Date flotante

Pentru variabilele care reprezintă un număr în virgulă mobilă, se folosesc următorii modificatori de tip: float, double, long double (în unele implementări ale limbajului long double nu există SI).

O valoare cu modificatorul de tip float are 4 octeți. Dintre aceștia, 1 octet este alocat pentru semn, 8 biți pentru excesul de exponent și 23 de biți pentru mantise. Rețineți că cel mai semnificativ bit al mantisei este întotdeauna 1, deci nu este completat, astfel încât intervalul de valori pentru o variabilă în virgulă mobilă este de aproximativ 3.14E-38 până la 3.14E+38.

O valoare dublă ocupă 8 biți în memorie. Formatul său este similar cu formatul float. Biții de memorie sunt distribuiți după cum urmează: 1 bit pentru semn, 11 biți pentru exponent și 52 de biți pentru mantise. Luând în considerare bitul înalt omis al mantisei, intervalul de valori este de la 1,7E-308 la 1,7E+308.

Float f, a, b;

dublu x,y;

1.2.4. Indicatoare

Un pointer este adresa memoriei alocată pentru a găzdui un identificator (identificatorul poate fi numele unei variabile, al unei matrice, al unei structuri sau al unui șir literal). Dacă o variabilă este declarată ca pointer, atunci aceasta conține o adresă de memorie unde poate fi localizată o valoare scalară de orice tip. Când declarați o variabilă de tip pointer, trebuie să specificați tipul de obiect de date a cărui adresă o va conține variabila și numele pointerului precedat de un asterisc (sau grup de asteriscuri). Formatul declarației pointerului:

tip-specificator [ modificator ] * specificator.

Cuvintele cheie const, near, far, huge pot fi folosite ca modificatori la declararea unui pointer. Cuvântul cheie const indică faptul că pointerul nu poate fi modificat în program. Mărimea unei variabile declarate ca pointer depinde de arhitectura computerului și de modelul de memorie utilizat pentru care programul va fi compilat. Indicatorii către diferite tipuri de date nu trebuie să aibă aceeași lungime.

Pentru a modifica dimensiunea indicatorului, puteți utiliza cuvintele cheie aproape, departe, imens.

Unsigned int * a; /* variabila a este un pointer pentru a tasta unsigned int */ double * x; /* variabila x indică tipul de date în virgulă mobilă cu precizie dublă */ char * fuffer ; /* declară un pointer numit fuffer care indică o variabilă de tip char */ double nomer;

void *adrese;

adrese = &nomer;

(dublu *)adresa++;

/* Variabila adresa este declarată ca un pointer către un obiect de orice tip.

Prin urmare, i se poate atribui adresa oricărui obiect (& este operația de calcul a adresei). Cu toate acestea, după cum sa menționat mai sus, nicio operație aritmetică nu poate fi efectuată pe un pointer decât dacă tipul de date către care indică este determinat în mod explicit. Acest lucru se poate face folosind o operație de turnare (double *) pentru a converti adresa într-un pointer pentru a tasta double și apoi incrementând adresa. */ const * dr;

/* Variabila dr este declarată ca un pointer către o expresie constantă, i.e. Valoarea unui pointer se poate modifica în timpul execuției programului, dar valoarea la care indică nu poate. */ unsigned char * const w = &obj.

O variabilă de tip enumerare poate lua valoarea uneia dintre constantele numite ale listei. Constantele listei denumite sunt de tip int. Astfel, memoria corespunzătoare unei variabile de enumerare este memoria necesară pentru a găzdui o valoare int.

Variabilele de tip enum pot fi folosite în expresii de index și ca operanzi în operații aritmetice și relaționale.

În primul format 1, numele și valorile enumerate sunt specificate într-o listă de enumerare. Opțional enumeration-tag-name este un identificator care denumește eticheta de enumerare definită de lista de enumerare. Descriptorul denumește o variabilă de enumerare. Într-o declarație poate fi specificată mai mult de o variabilă de tip enumerare.

O listă de enumerare conține unul sau mai multe constructe de forma:

identificator [= expresie constantă]

Fiecare identificator denumește un element al enumerației. Toți identificatorii din lista de enumerare trebuie să fie unici. Dacă nu există o expresie constantă, primul identificator corespunde valorii 0, următorul identificator valorii 1 etc. Numele unei constante de enumerare este echivalent cu valoarea acesteia.

Un identificator asociat cu o expresie constantă ia valoarea specificată de acea expresie constantă. Expresia constantă trebuie să fie de tip int și poate fi fie pozitivă, fie negativă. Următorul identificator din listă i se atribuie valoarea expresiei constante plus 1 dacă acel identificator nu are propria sa expresie constantă. Utilizarea elementelor de enumerare trebuie să respecte următoarele reguli:

1. Variabila poate conține valori duplicat.

2. Identificatorii dintr-o listă de enumerare trebuie să fie distincți de toți ceilalți identificatori din același domeniu, inclusiv numele de variabile obișnuite și identificatorii din alte liste de enumerare.

3. Denumirile de tipuri de enumerare trebuie să fie distincte de alte denumiri de tipuri de enumerare, structuri și amestecuri din același domeniu.

4. Valoarea poate urma ultimul element al listei de enumerare.

Săptămâna enumerare ( SUB = 0, /* 0 */ VOS = 0, /* 0 */ POND, /* 1 */ VTOR, /* 2 */ SRED, /* 3 */ HETV, /* 4 */ PJAT /* 5 */ ) rab_ned ;

În acest exemplu, este declarată o săptămână etichetă enumerabilă, cu un set corespunzător de valori, iar o variabilă rab_ned este declarată de tipul săptămâni.

Al doilea format folosește numele etichetei enum pentru a se referi la un tip de enumerare definit în altă parte. Numele etichetei de enumerare trebuie să se refere la o etichetă de enumerare deja definită în domeniul curent. Deoarece eticheta de enumerare este declarată în altă parte, lista de enumerare nu este reprezentată în declarație.

În declararea unui pointer către un tip de date de enumerare și declararea typedefs pentru tipurile de enumerare, puteți utiliza numele unei etichete de enumerare înainte ca eticheta de enumerare să fie definită. Cu toate acestea, definiția de enumerare trebuie să preceadă orice utilizare a indicatorului declarației typedef către tip. O declarație fără o listă ulterioară de descriptori descrie o etichetă sau, ca să spunem așa, un model de enumerare.

1.2.6. Matrice

Matricele sunt un grup de elemente de același tip (double, float, int etc.). Din declarația matricei, compilatorul trebuie să obțină informații despre tipul elementelor matricei și numărul acestora. O declarație de matrice are două formate:

descriptor de specificator de tip [const - expresie];

descriptor al specificatorului de tip;

Descriptorul este identificatorul matricei.

Specificatorul de tip specifică tipul elementelor matricei declarate. Elementele de matrice nu pot fi funcții sau elemente void.

Expresia constantă între paranteze pătrate specifică numărul de elemente din matrice. Când se declară o matrice, o expresie constantă poate fi omisă în următoarele cazuri:

Când este declarată, matricea este inițializată,

Matricea este declarată ca parametru formal al funcției,

SI definește doar tablouri unidimensionale, dar deoarece un element al unei matrice poate fi o matrice, și tablourile multidimensionale pot fi definite. Ele sunt formalizate printr-o listă de expresii constante care urmează identificatorul de matrice, fiecare expresie constantă fiind inclusă în propriile paranteze pătrate.

Fiecare expresie constantă între paranteze pătrate specifică numărul de elemente de-a lungul acelei dimensiuni a matricei, astfel încât o declarație de matrice bidimensională conține două expresii constante, o matrice tridimensională conține trei și așa mai departe. Rețineți că în SI, primul element al unui tablou are un indice de 0.

Int a; /* reprezentat ca o matrice a a a a a a */ dublu b; /* vector de 10 elemente de tip double */ int w = ( ( 2, 3, 4 ), ( 3, 4, 8 ), ( 1, 0, 9 ) );

În ultimul exemplu, matricea w este declarată. Listele cuprinse între acolade corespund șirurilor de matrice, dacă nu există acolade, inițializarea nu va fi efectuată corect.

În limba SI, puteți utiliza secțiuni de matrice, ca și în alte limbi de nivel înalt (PL1 etc.), cu toate acestea, sunt impuse o serie de restricții privind utilizarea secțiunilor. Secțiunile sunt formate prin omiterea uneia sau mai multor perechi de paranteze pătrate. Perechile de paranteze pătrate pot fi aruncate numai de la dreapta la stânga și strict secvenţial. Secțiunile de matrice sunt folosite pentru a organiza procesul de calcul în funcții SI dezvoltate de utilizator.

Dacă scrieți s când apelați o funcție, atunci șirul zero al matricei s va fi transmis.

Când accesați o matrice b, puteți scrie, de exemplu, b și va fi transferat un vector de patru elemente, iar accesarea b va da o matrice bidimensională de dimensiunea 3 cu 4. Nu puteți scrie b, ceea ce implică faptul că un vector va fi transferat, deoarece aceasta nu respectă restricția impusă secțiunilor de matrice de utilizare.

Un exemplu de declarație de matrice de caractere.

char str = "declarație matrice de caractere";

Rețineți că mai există un element într-un caracter literal, deoarece ultimul element este secvența de escape „\0”.

1.2.7. Structuri

Structurile sunt un obiect compus care include elemente de orice tip, cu excepția funcțiilor. Spre deosebire de o matrice, care este un obiect omogen, o structură poate fi eterogenă. Tipul structurii este determinat de o intrare de forma:

struct (lista de definiții)

Cel puțin o componentă trebuie specificată în structură. Definiția structurilor este următoarea:

descriptor de tip de date;

unde tipul de date specifică tipul de structură pentru obiectele definite în descriptori. În forma lor cea mai simplă, mânerele sunt identificatori sau matrice.

Struct ( dublu x,y; ) s1, s2, sm;

struct (int year; char moth, day; ) data1, data2;

Variabilele s1, s2 sunt definite ca structuri, fiecare dintre acestea fiind formată din două componente x și y. Variabila sm este definită ca o matrice de nouă structuri. Fiecare dintre cele două variabile data1, data2 este formată din trei componente an, molie, zi. >p>Există o altă modalitate de a asocia un nume cu un tip de structură, se bazează pe utilizarea unei etichete de structură. O etichetă de structură este similară cu o etichetă de tip enumerare. Eticheta de structură este definită după cum urmează:

struct tag(lista descrierilor; );

unde eticheta este un identificator.

Exemplul de mai jos descrie identificatorul studentului ca o etichetă de structură:

Eticheta de structură este utilizată pentru a declara ulterior structuri de acest tip sub forma:

struct id-list tag;

struct studeut st1,st2;

Utilizarea etichetelor de structură este necesară pentru a descrie structuri recursive. În cele ce urmează, se discută utilizarea etichetelor de structură recursive.

Struct node ( int date; struct node * next; ) st1_node;

Nodul de etichetă de structură este într-adevăr recursiv, deoarece este folosit în propria sa descriere, de exemplu. în formalizarea următorului indicator. Structurile nu pot fi direct recursive, de exemplu. o structură de nod nu poate conține o componentă care este o structură de nod, dar orice structură poate avea o componentă care este un pointer către tipul său, așa cum se face în exemplul de mai sus.

Componentele structurii sunt accesate prin specificarea numelui structurii și a următoarelor, separate printr-un punct, numele componentei selectate, de exemplu:

St1.name="Ivanov";

st2.id=st1.id;

st1_node.data=st1.age;

1.2.8. Combinații (amestecuri)

O unire este similară cu o structură, dar numai unul dintre elementele unirii poate fi folosit (sau, cu alte cuvinte, la care se poate răspunde) în orice moment. Tipul de unire poate fi specificat după cum urmează:

Unire ( element 1 descriere; ... element n descriere; );

Caracteristica principală a unei uniuni este că aceeași zonă de memorie este alocată pentru fiecare dintre elementele declarate, de exemplu. se suprapun. Deși această regiune de memorie poate fi accesată folosind oricare dintre elemente, elementul în acest scop trebuie selectat astfel încât rezultatul obținut să nu fie lipsit de sens.

Membrii unui sindicat sunt accesați în același mod ca și structurile. Eticheta de unire poate fi formalizată exact în același mod ca eticheta de structură.

Asociația este utilizată în următoarele scopuri:

Inițializarea unui obiect de memorie în uz dacă la un moment dat doar unul dintre multe obiecte este activ;

Interpretați reprezentarea de bază a unui obiect de un tip ca și cum acel obiect i-ar fi fost atribuit un alt tip.

Când utilizați un obiect infor de tip unire, puteți procesa numai elementul care a primit valoarea, adică. După atribuirea unei valori elementului inform.fio, nu are rost să accesezi alte elemente. Concatenarea ua permite acces separat la octeții ua.al scăzut și ua.al ridicat ai numărului de doi octeți ua.ax .

1.2.9. Câmpuri de biți

Un element al structurii poate fi un câmp de biți care oferă acces la biți individuali de memorie. Câmpurile de biți nu pot fi declarate în afara structurilor. De asemenea, nu puteți organiza matrice de câmpuri de biți și nu puteți aplica operația de determinare a adresei la câmpuri. În general, tipul de structură cu un câmp de biți este specificat după cum urmează:

Struct (identificator nesemnat 1: lungime câmp 1; identificator nesemnat 2: lungime câmp 2; )

lungime - câmpurile sunt specificate printr-o expresie întreagă sau constantă. Această constantă specifică numărul de biți alocați câmpului corespunzător. Un câmp de lungime zero indică alinierea la următoarea limită a cuvântului.

Struct ( nesemnat a1: 1; nesemnat a2: 2; nesemnat a3: 5; nesemnat a4: 2; ) prim;

Structurile câmpurilor de biți pot conține și componente semnate. Astfel de componente sunt plasate automat pe granițele de cuvinte adecvate, iar unii biți de cuvinte pot rămâne neutilizați.

1.2.10. Variabile cu structură mutabilă

Foarte des, unele obiecte de program aparțin aceleiași clase, diferă doar în unele detalii. Luați în considerare, de exemplu, reprezentarea formelor geometrice. Informațiile generale despre forme pot include elemente precum suprafața, perimetrul. Cu toate acestea, informațiile corespunzătoare dimensiunilor geometrice pot diferi în funcție de forma lor.

Luați în considerare un exemplu în care informațiile despre formele geometrice sunt reprezentate pe baza utilizării combinate a structurii și unirii.

Struct figure ( suprafață dublă, perimetru; /* componente comune */ tip int; /* atribut component */ unire /* enumerarea componentelor */ ( rază dublă; /* cerc */ a dublu; /* dreptunghi */ b dublu ; /* triunghi */ ) geom_fig ) fig1, fig2;

În general, fiecare obiect figura va fi format din trei componente: zonă, perimetru, tip. Componenta de tip este numită etichetă de componentă activă deoarece este utilizată pentru a indica ce componentă a uniunii geom_fig este activă în prezent. Această structură se numește structură variabilă deoarece componentele sale se modifică în funcție de valoarea etichetei componentei active (valoarea tipului).

Rețineți că în loc de o componentă de tip int, ar fi recomandabil să utilizați un tip enumerat. De exemplu, așa

Enum figure_chess ( CERCUL, CUTIE, TRIANGUL ) ;

Constantele CIRCLE, BOX, TRIANGLE vor primi valori egale cu 0, 1, 2, respectiv Variabila de tip poate fi declarată ca având un tip enumerat:

enumerare figure_chess type;

În acest caz, compilatorul SI va avertiza programatorul despre atribuiri potențial eronate, cum ar fi

figură.tip = 40;

În general, o variabilă de structură va consta din trei părți: un set de componente comune, o etichetă pentru componenta activă și o parte cu componente în schimbare. Forma generală a unei structuri variabile este următoarea:

Struct (componente comune; etichetă componentă activă; unire (descrierea componentei 1; descrierea componentei 2; ::: descrierea componentei n; ) union-identifier; ) structura-identificator;

Exemplu de definire a unei variabile de structură numită helth_record

Struct ( /* informații generale */ nume de caracter ; /* nume */ vârstă; /* vârstă */ sex de caractere; /* gen */ /* etichetă componentă activă */ /* (starea civilă) */ enumerare starea_meritale ins ; /* parte variabilă */ uniune ( /* singur */ /* fără componentă */ struct ( /* căsătorit */ char nume_soț; int no_copii; ) informație_căsătorie; /* divorțat */ data_divorțată; ) informație_conjugală;

enum stare_conjugală ( SINGUR, /* singur */ MARRIGO, /* căsătorit */ DIVORAT /* divorțat */ ) ;

Puteți accesa componentele structurii folosind link-uri:

Helth_record.neme, helth_record.ins, helth_record.marriage_info.marriage_date .

1.2.11. Definirea obiectelor și a tipurilor

După cum sa menționat mai sus, toate variabilele utilizate în programele SI trebuie să fie declarate. Tipul de variabilă care este declarată depinde de cuvântul cheie folosit ca specificator de tip și dacă specificatorul este un identificator simplu sau o combinație de un identificator și un indicator (asterisc), matrice (paranteze) sau modificator de funcție (paranteze).

Să remarcăm o caracteristică importantă a limbajului SI: atunci când declarăm, mai mult de un modificator poate fi utilizat simultan, ceea ce face posibilă crearea multor descriptori de tip complex diferiți.

Cu toate acestea, trebuie să ne amintim că unele combinații de modificatori sunt inacceptabile:

Elementele matricei nu pot fi funcții.

Funcțiile nu pot returna matrice sau funcții.

La inițializarea descriptorilor complecși, parantezele pătrate și parantezele (în dreapta identificatorului) au prioritate față de asterisc (în stânga identificatorului). Pătratul sau parantezele au aceeași prioritate și sunt extinse de la stânga la dreapta. Specificatorul de tip este luat în considerare la ultimul pas, când descriptorul a fost deja interpretat complet. Puteți folosi parantezele pentru a modifica ordinea interpretării după cum este necesar.

Pentru interpretarea descrierilor complexe, se propune o regulă simplă care sună ca „din interior spre exterior” și constă din patru pași.

1. Începeți cu identificatorul și priviți în dreapta pentru a vedea dacă există pătrat sau paranteze.

2. Dacă sunt, atunci interpretați această parte a descriptorului și apoi priviți spre stânga în căutarea unui asterisc.

3. Dacă în orice stadiu se întâlnește o paranteză de închidere în dreapta, atunci toate aceste reguli trebuie mai întâi aplicate în interiorul parantezei și apoi interpretarea trebuie să continue.

4. Interpretați specificatorul de tip.

Int * (*) comp ) ();

6 5 3 1 2 4

Acest exemplu declară variabila comp (1) ca o matrice de zece (2) pointeri (3) la funcții (4) care returnează pointerii (5) la valori întregi (6).

Char * (* (*) var) ()) ;

7 6 4 2 1 3 5

Variabila var (1) este declarată ca un pointer (2) la o funcție (3) care returnează un pointer (4) la o matrice (5) de 10 elemente, care sunt pointeri (6) la valorile caracterului.

Rețineți că orice tip poate fi declarat folosind cuvântul cheie typedef, inclusiv tipurile pointer, funcție sau matrice. Un nume cu cuvântul cheie typedef pentru tipurile pointer, structură și unire poate fi declarat înainte ca acele tipuri să fie definite, dar în sfera declaratorului.

Typedef double(*MATH)();

/* MATH - nume de tip nou reprezentând un pointer către o funcție care returnează valori duble */ MATH cos;

/* cos pointer către o funcție care returnează valori de tip double */ /* Se poate face o declarație echivalentă */ double (* cos)();

typedef char FIO /* FIO - matrice de patruzeci de caractere */ FIO persoană;

/* Variabila person este o matrice de patruzeci de caractere */ /* Aceasta este echivalentă cu o declarație */ char person;

La declararea variabilelor și a tipurilor, aici au fost folosite numele de tip (MATH FIO). În plus, denumirile de tip pot fi folosite în alte trei cazuri: în lista de parametri formali, în declarațiile de funcții, în operațiunile de turnare a tipului și în dimensiunea operației (operație de turnare a tipului).

Numele de tip pentru tipurile de bază, enumerare, structură și amestec sunt specificatorii de tip pentru acele tipuri. Numele de tip pentru pointerul de matrice și tipurile de funcție sunt specificate folosind descriptori abstracti, după cum urmează:

descriptor-abstract-specificator-tip;

Un handler abstract este un handle non-identificator care constă dintr-unul sau mai mulți indicatori, matrice sau modificatori de funcție. Modificatorul pointerului (*) este dat întotdeauna înaintea identificatorului din descriptor, iar modificatorii de matrice și de funcție () sunt dați după acesta. Astfel, pentru a interpreta corect un descriptor abstract, trebuie să începem interpretarea cu identificatorul implicit.

Descriptorii abstracti pot fi complexi. Parantezele din descriptori abstracti complecși specifică ordinea interpretării într-un mod similar cu interpretarea descriptorilor complecși în declarații.

1.2.12. Inițializarea datelor

Când o variabilă este declarată, i se poate atribui o valoare inițială prin adăugarea unui inițiator la un declarator. Inițiatorul începe cu semnul „=" și are următoarele forme.

Format 1: = initiator;

Format 2: = (lista - inițiatori);

Formatul 1 este folosit la inițializarea variabilelor de tipuri și pointeri de bază, iar formatul 2 este utilizat la inițializarea obiectelor compuse.

O matrice bidimensională de valori întregi este inițializată; elementelor matricei li se atribuie valori din listă. Aceeași inițializare se poate face astfel:

static int b = ( ( 1,2 ), ( 3,4 ) );

Când inițializați o matrice, puteți omite unul sau mai multe dimensiuni

int static b)