Cum se configurează smartphone-uri și PC-uri. Portal informativ

Tipuri de date și variabile. C tipuri de date și operațiuni

În acest tutorial, vei învăța Alfabetul C++ si de asemenea ce tipuri de date poate fi procesat de programul de pe el. Poate că nu este momentul cel mai palpitant, dar aceste cunoștințe sunt necesare!În plus, începând să înveți orice alt limbaj de programare, vei trece cu mai multă încredere printr-o etapă similară de învățare. Un program C++ poate conține următoarele caractere:

  • 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 testul programului, puteți utiliza comentarii... Dacă textul cu două bare oblice // și se termină cu un caracter de nouă linie sau este cuprins între / * și * /, compilatorul îl ignoră.

Date în C++

Pentru a rezolva o problemă în orice program, orice date este procesată. Ele pot fi de diferite tipuri: numere întregi și reale, simboluri, șiruri de caractere, matrice. Este obișnuit să descrii datele în C++ la începutul unei funcții. LA tipuri de date de bază limbajul include:

Pentru formarea altor tipuri de date, de bază și așa-numite 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 dura fie 2, fie 4 octeți. Depinde de bitness-ul procesorului. În mod implicit, toate tipurile de întregi sunt considerate semnate, adică specificatorul semnat poate fi omis. Specificatorul nesemnat permite reprezentarea numai a numerelor pozitive. Mai jos sunt câteva intervale de valori întregi

Tip de Gamă Marimea
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 (întreg sau număr fracționar cu virgulă zecimală), p este ordinea (întreg). De obicei valori precum pluti ocupă 4 octeți și dubla 8 octeți. Tabelul intervalului real:

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 ( Minciuna ). Orice valoare diferită de zero este interpretată ca Adevărat. Sens fals reprezentat în memorie ca 0.

Tip gol

Multe valori de acest tip sunt goale. Este folosit pentru a defini funcții care nu returnează o valoare, pentru a specifica o listă de argumente de funcție goală, ca tip de bază pentru pointeri și în operațiunile de turnare.

Conversia tipului de date

C++ face distincție între 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;

Tipul cu cea mai mică pierdere de informații primește cea mai mare prioritate. Nu ar trebui să abuzați de conversia implicită de tip, deoarece pot apărea tot felul de situații neașteptate.

  • Conversie explicită spre deosebire de făcut implicit de programator. Există mai multe moduri de a face acest lucru:
  1. Conversia la stiluri C: (plutitor) a
  2. Conversia la stiluri C++: pluti ()

De asemenea, conversiile de tip pot fi efectuate folosind următoarele operații:

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

static_cast<> ()

const_cast<> ()

reinterpret_cast<> ()

dynamic_cast<> ()

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

Tipul static_cast<Тип>(un obiect);

Folosind static_cast, nu puteți elimina constness dintr-o variabilă, dar aceasta este în puterea următoarei instrucțiuni. const_cast- se foloseste numai cand este necesara eliminarea constantelor din obiect. Sintaxa va arăta astfel:

Tip deconst_cast< Tip de> (un obiect);

reinterpret_cast- este folosit pentru a converti diferite tipuri, numere întregi într-un pointer și invers. Dacă vedeți noul cuvânt „pointer” - 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 ca pentru operatorii considerați anterior:

Tip dereinterpreta_turnați< Tip de> (un obiect);

dynamic_cast- este folosit pentru conversia de tip dinamic, implementează turnarea de pointeri sau link-uri. Sintaxă:

Tip dedinamic _turnați< Tip de> (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 hexazecimal

Nume

Sunete beeper

Înapoi

Traducerea paginii (format)

Traducerea liniilor

Retur transport

Filă orizontală

Filă verticală

O diferență importantă între limbajul C și alte limbaje (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] specificator de tip [= initiator] [, specifier [= initiator]] ...

Un descriptor este un identificator al unei variabile simple sau al unei construcții mai complexe cu paranteze drepte, 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 C are un set standard de tipuri de date care pot fi folosite pentru a construi noi tipuri de date (unice).

Inițiator - specifică o valoare inițială sau o listă de valori inițiale care (care) este atribuită unei variabile atunci când este declarată.

Specificatorul clasei de memorie este definit de unul dintre cele patru cuvinte cheie ale limbajului C: 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 această variabilă, adică din ce părți ale programului vă puteți referi la ea.

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 Const reprezintă date numai în citire, de ex. 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 se presupune că 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 și nemodificabil, adică. i se poate atribui o valoare o singură dată.

Const dublu A = 2,128E-2; const B = 286; (implică const int B = 286)

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

1.2.2. Tip de date întreg

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 numărului, altfel bitul zero este interpretat ca semnat. Dacă cuvântul cheie nesemnat este absent, variabila întreagă este considerată semnată. În cazul în care specificatorul de tip constă din tipul de cheie semnat sau nesemnat și apoi urmează identificatorul de variabilă, atunci va fi considerată 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; (implicit semnat int f).

Rețineți că modificatorul char este folosit pentru a reprezenta un caracter (dintr-o matrice de reprezentări de caractere) sau pentru a declara literale șir. Valoarea unui obiect de tip char este un cod (dimensiunea de 1 octet) care corespunde caracterului de reprezentat. Pentru a reprezenta caracterele alfabetului rus, modificatorul tipului de identificare a datelor are forma caracter nesemnat, deoarece codurile literelor rusești depășesc valoarea 127.

Trebuie făcută următoarea remarcă: limbajul C nu definește o reprezentare de memorie și un interval de valori pentru identificatorii cu modificatori int și unsigned int. Mărimea memoriei pentru o variabilă cu un modificator int semnat este determinată de lungimea cuvântului mașină, care are dimensiuni diferite pe diferite mașini. Deci, pe mașinile de 16 biți, dimensiunea cuvântului este egală cu 2 octeți, pe mașinile pe 32 de biți, respectiv, cu 4 octeți, i.e. type 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 să nu funcționeze corect pe altul. Pentru a determina lungimea memoriei ocupată de o variabilă, puteți utiliza dimensiunea operațiunii limbajului C, care returnează valoarea lungimii tipului de modificator specificat.

De exemplu:

A = dimensiunea (int); b = sizeof (lung int); c = sizeof (unsigned long); d = sizeof (scurt);

De asemenea, rețineți că constantele octale și hexazecimale pot avea și modificatorul fără semn. Acest lucru se realizează prin specificarea prefixului u sau U după constantă; o constantă fără acest prefix este considerată semnată.

De exemplu:

0xA8C (int semnat); 01786l (semnat lung); 0xF7u (int nesemnat);

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 C long double este absent).

O valoare flotantă durează 4 octeți. Dintre acestea, 1 octet este rezervat 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 umplut; prin urmare, intervalul de valori pentru variabila cu virgulă mobilă este de aproximativ 3,14E-38 până la 3,14E + 38.

Un dublu ocupă 8 biți în memorie. Formatul său este similar cu cel al float. Biții de memorie sunt alocaț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 de ordin î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. Indicatori

Un pointer este adresa memoriei alocată pentru plasarea identificatorului (numele unei variabile, al unei matrice, al unei structuri sau al unui șir literal poate fi folosit ca identificator). În cazul în care o variabilă este declarată ca pointer, atunci aceasta conține o adresă de memorie la care poate fi găsită o valoare scalară de orice tip. La declararea unei variabile de tip pointer, este necesar să se definească tipul obiectului de date, a cărui adresă va conține variabila și numele pointerului, precedat de un asterisc (sau un grup de asteriscuri). Formatul declarației pointerului:

tip-specificator [modificator] * specificator.

Specificatorul de tip specifică tipul obiectului și poate fi de orice tip de bază, tip de structură, amestec (acesta va fi discutat mai jos). Specificând cuvântul cheie void în loc de specificatorul de tip, este posibil într-un mod deosebit să amânăm specificarea tipului la care se referă pointerul. O variabilă declarată ca un pointer la tipul void poate fi folosită pentru a se referi la un obiect de orice tip. Totuși, pentru a putea efectua operații aritmetice și logice pe pointeri sau asupra obiectelor către care acestea indică, este necesar să se determine în mod explicit tipul de obiecte la efectuarea fiecărei operații. Astfel de definiții de tip pot fi efectuate folosind o operație de turnare.

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

Cuvintele cheie aproape, departe, imens pot fi folosite pentru a modifica dimensiunea indicatorului.

Unsigned int * a; / * variabila a este un pointer către tipul int fără semn (numere întregi fără semn) * / double * x; / * variabila x indică tipul de date în virgulă mobilă cu precizie dublă * / char * fuffer; / * este declarat un pointer numit fuffer care indică o variabilă de tip char * / double nomer; void * adresa; adresa = & 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 unei adrese). Cu toate acestea, după cum sa menționat mai sus, nicio operație aritmetică nu poate fi efectuată pe un pointer până când 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 la dublu și apoi pentru a incrementa 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 către care indică nu poate. * / unsigned char * const w = & obj. / * Variabila w este declarată ca un pointer constant către date de tip char unsigned. Aceasta înseamnă că pe parcursul întregului program vom indica aceeași zonă de memorie. Conținutul acestei zone poate fi modificat. * /

1.2.5. Variabile enumerate

O variabilă care poate lua o valoare dintr-o listă de valori se numește variabilă enumerată sau enumerare.

O declarație de enumerare începe cu cuvântul cheie enum și are două formate de prezentare.

Format 1. enum [enum-tag-name] (enumeration-list) descriptor [, descriptor ...];

Format 2. enum enum-tag-name descriptor [, descriptor ..];

O declarație de enumerare specifică tipul unei variabile de enumerare și definește o listă de constante numite enumerare-listă. Valoarea fiecărui nume de listă este un număr întreg.

O variabilă de tip enumerare poate prelua valorile uneia dintre constantele numite din listă. Constantele listei denumite sunt de tip int. Astfel, memoria corespunzătoare variabilei 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 enumerației sunt specificate în lista 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.

Lista-enumerarea conține unul sau mai multe constructe de forma:

identificator [= expresie constantă]

Fiecare identificator denumește un element al enumerației. Toate id-urile din lista de enumerare trebuie să fie unice. În absența unei expresii constante, primul identificator corespunde valorii 0, următorul identificator valorii 1 și așa mai departe. Numele unei constante de enumerare este echivalent cu valoarea acesteia.

Identificatorul asociat cu o expresie constantă ia valoarea specificată de acea expresie constantă. O expresie constantă trebuie să fie de tip int și poate fi fie pozitivă, fie negativă. Următorul identificator din listă i se atribuie o valoare a expresiei constante plus 1 dacă acel identificator nu are o expresie constantă. Utilizarea membrilor enumeratori trebuie să respecte următoarele reguli:

1. O variabilă 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 enumerări.

3. Denumirile tipurilor 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, eticheta enumerată săptămâna este declarată, cu setul corespunzător de valori, iar variabila rab_ned este declarată a fi de tip week.

Al doilea format folosește numele etichetei de enumerare pentru a se referi la un tip de enumerare care este definit în altă parte. Numele etichetei de enumerare trebuie să se refere la o etichetă de enumerare deja definită în domeniul curent. Deoarece eticheta enum este declarată în altă parte, enumerarea nu este prezentă î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ă. Totuși, definiția unei enumerări trebuie să precedă orice acțiune a pointerului utilizat la tipul declarației typedef. O declarație fără o listă ulterioară de descriptori descrie o etichetă sau, dacă pot spune 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 declararea unui tablou, 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 tip-specificator [constant - expresie];

mâner indicator de tip;

Un descriptor este un identificator de matrice.

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

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

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

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

În limbajul C, sunt definite doar matrice unidimensionale, dar, deoarece un element de matrice poate fi o matrice, puteți defini și matrice multidimensionale. Ele sunt formalizate printr-o listă de expresii constante care urmează identificatorul matricei, fiecare expresie constantă fiind inclusă în propriile paranteze pătrate.

Fiecare expresie constantă între paranteze pătrate definește numărul de elemente pentru o anumită dimensiune a matricei, astfel încât o declarație de matrice bidimensională conține două expresii constante, una tridimensională și așa mai departe. Rețineți că în limbajul C, primul element al unui tablou are un index egal cu 0.

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

Ultimul exemplu declară o matrice w. Listele cuprinse între acolade corespund șirurilor de matrice; dacă nu există paranteze, inițializarea nu va fi efectuată corect.

În limbajul C, puteți utiliza felii de matrice, ca și în alte limbaje de nivel înalt (PL1 etc.), cu toate acestea, sunt impuse o serie de restricții privind utilizarea felurilor. 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 utilizate în organizarea procesului de calcul în funcțiile limbajului C, dezvoltat de utilizator.

Dacă, la apelarea unei funcții, scrieți s, atunci șirul zero al matricei s va fi transmis.

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

Un exemplu de declarare a unei matrice de caractere.

char str = "declararea unei 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 conține elemente de orice tip, cu excepția funcțiilor. Spre deosebire de o matrice, care este un obiect omogen, structura poate fi eterogenă. Tipul structurii este determinat de o înregistrare de forma:

struct (lista de definiții)

Structura trebuie să conțină cel puțin o componentă. Definiția structurilor este următoarea:

descriptor de tip de date;

unde data-type indică tipul de structură pentru obiectele definite în descriptori. În forma lor cea mai simplă, descriptorii sunt identificatori sau matrice.

Structura (x dublu, 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 etichetei de structură. O etichetă de structură este similară cu o etichetă enumerată. Eticheta de structură este definită după cum urmează:

struct tag (lista descrierilor;);

unde eticheta este un identificator.

În exemplul de mai jos, ID-ul studentului este descris ca o etichetă de structură:

Struct student (nume char; int id, vârstă; char prp;);

Eticheta de structură este folosită pentru a declara ulterior structuri de acest fel sub forma:

struct tag-list-identificatori;

struct Studeut st1, st2;

Utilizarea etichetelor de structură este necesară pentru a descrie structuri recursive. Utilizarea etichetelor de structură recursive este discutată mai jos.

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

Eticheta structurii nodului este într-adevăr recursivă, deoarece este utilizată în propria sa descriere, adică î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 următoarele, separate printr-un punct, numele componentei selectate, de exemplu:

St1.name = "Ivanov"; st2.id = st1.id; st1_node.data = st1.age;

1.2.8. Asociații (amestecuri)

O unire este similară cu o structură, cu toate acestea, la un moment dat, doar unul dintre elementele de unire poate fi utilizat (sau, cu alte cuvinte, să fie receptiv). Tipul de unire poate fi specificat după cum urmează:

Unirea (descrierea elementului 1; ... descrierea elementului n;);

Caracteristica principală a uniunii este că pentru fiecare dintre elementele declarate este alocată aceeași zonă de memorie, i.e. se suprapun. Deși accesul la această zonă de memorie este posibil folosind oricare dintre elemente, elementul în acest scop trebuie selectat astfel încât rezultatul să nu fie lipsit de sens.

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

Unirea este utilizată în următoarele scopuri:

Inițializarea obiectului de memorie utilizat, dacă la un moment dat este activ doar un obiect din multe;

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

Memoria care corespunde unei variabile de tip uniune este determinată de cantitatea necesară pentru a găzdui cel mai lung membru al uniunii. Când se folosește un element mai scurt, o variabilă de tip uniune poate conține memorie neutilizată. Toate elementele uniunii sunt stocate în aceeași zonă de memorie, începând de la aceeași adresă.

Union (char fio; char adres; int vozrast; int telefon;) inform; unire (int ax; char al;) ua;

Când utilizați obiectul infor de tip unire, puteți procesa doar elementul care a primit valoarea, adică. după atribuirea unei valori elementului inform.fio, nu are sens să se facă referire la alte elemente. Combinarea ua permite acces separat la octeții ua.al inferior și superior ua.al ai numărului de doi octeți ua.ax.

1.2.9. Câmpuri de biți

Membrul de structură 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 unei structuri 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 ca expresie întreagă sau constantă. Această constantă definește 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 Bitfield pot conține, de asemenea, componente de caractere. Astfel de componente sunt plasate automat pe limitele adecvate ale cuvintelor, iar unele părți ale cuvintelor pot rămâne neutilizate.

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, prezentarea formelor geometrice. Informațiile generale despre forme pot include elemente precum suprafața, perimetrul. Cu toate acestea, informațiile relevante despre dimensiunile geometrice pot diferi în funcție de forma lor.

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

Figură structurată (zonă 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 folosită pentru a indica ce componentă a uniunii geom_fig este activă în prezent. O astfel de 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 componenta de tip de tip int, ar fi recomandabil să utilizați un tip enumerat. De exemplu, așa

Enum figură_șah (CERC, CUTIE, TRIANGUL);

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

enumerare figure_chess type;

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

figură.tip = 40;

În general, o variabilă de structură va consta din trei părți: un set de componente comune, o etichetă de componentă activă și o parte cu componente variabile. 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;

Un exemplu de definire a unei variabile de structură numită helth_record

Structură (/ * informații generale * / numele caracterului; / * nume * / vârsta int; / * vârsta * / sexul caracterului; / * genul * / / * eticheta componentei active * / / * (starea civilă) * / enumerarea status_merital ins ; / * parte variabilă * / unire (/ * singur * / / * fără componente * / struct (/ * căsătorit * / char data_căsătorie; char nume_soț; int fără_copii;) informație_căsătorie; / * divorțat * / data_căsătorit_divorțat;) info_marital ;) fișă_sănătate; enumerare stare_conjugală (SINGURĂ, / * necăsătorit * / MARRIGO, / * căsătorit * / DIVORAT / * divorțat * /);

Puteți face referire la componentele structurii folosind link-urile:

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 C trebuie declarate. Tipul variabilei declarate depinde de cuvântul cheie utilizat ca specificator de tip și dacă specificatorul este un identificator simplu sau o combinație a unui identificator cu un modificator de indicator (asterisc), o matrice (paranteze pătrate) sau o funcție (paranteze). ).

Când se declară o variabilă simplă, structură, amestec sau unire sau o enumerare, un descriptor este un simplu identificator. Pentru a declara un pointer, o matrice sau o funcție, identificatorul este modificat în consecință: cu un asterisc în stânga, pătrat sau paranteze în dreapta.

Rețineți o caracteristică importantă a limbajului C, atunci când declarați 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 amintit că unele combinații de modificatori nu sunt permise:

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). Parantezele sau parantezele au aceeași prioritate și se extind de la stânga la dreapta. Specificatorul de tip este luat în considerare în ultimul pas, când specificatorul a fost deja interpretat complet. Puteți folosi paranteze 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 un identificator și priviți în dreapta pentru a vedea dacă există paranteze sau paranteze.

2. Dacă sunt, atunci interpretați această parte a descriptorului și apoi căutați în stânga un asterisc.

3. Dacă în orice etapă în dreapta există o paranteză de închidere, atunci mai întâi este necesar să aplicați toate aceste reguli în interiorul parantezei, apoi să continuați interpretarea.

4. Interpretați specificatorul de tip.

Int * (*) comp) (); 6 5 3 1 2 4

În acest exemplu, variabila comp (1) este declarată 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.

Pe lângă declararea variabilelor de diferite tipuri, este posibil să se declare tipuri. Acest lucru se poate face în două moduri. Prima modalitate este de a furniza un nume de etichetă atunci când declarați o structură, unire sau enumerare și apoi utilizați acel nume în declarațiile de variabile și funcții ca referință la acea etichetă. Al doilea este să utilizați cuvântul cheie typedef pentru a declara tipul.

Când se declară cu cuvântul cheie typedef, identificatorul în locul obiectului care este descris este numele tipului de date care este introdus în considerare, iar apoi acest tip poate fi folosit pentru a declara variabile.

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 pointer, structură, tipuri de uniuni poate fi declarat înainte ca aceste tipuri să fie definite, dar în domeniul de aplicare al declaratorului.

Typedef dublu (* MATH) (); / * MATH - nume de tip nou reprezentând un pointer către o funcție care returnează valori duble * / MATH cos; / * cos este un 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 persoană este o matrice de patruzeci de caractere * / / * Aceasta este echivalentă cu declararea * / 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 parametrilor formali, în declararea funcțiilor, în operațiunile de turnare a tipului și în dimensiunea operației (operația de turnare a tipului).

Numele de tip pentru tipurile de bază, tipurile de enumerare, structurile și amestecurile 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 al specificatorului de tip;

Un abstract de descriptor este un descriptor fără un identificator care constă din unul sau mai mulți modificatori de indicatori, matrice sau funcție. Modificatorul pointerului (*) este întotdeauna dat înaintea identificatorului din descriptor, iar matricea și modificatorii de funcție () sunt întotdeauna specificați după acesta. Astfel, pentru a interpreta corect un descriptor abstract, trebuie să începeți cu un identificator implicit.

Descriptorii abstracti pot fi complicati. Parantezele din descriptori abstracti complecși specifică ordinea interpretării, așa cum au făcut-o atunci când interpretau descriptori complecși în declarații.

1.2.12. Inițializarea datelor

Când declarați o variabilă, îi puteți atribui o valoare inițială prin atașarea unui inițiator la descriptor. 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 folosit la inițializarea obiectelor compuse.

Variabila tol este inițializată cu „N”.

const long megabute = (1024 * 1024);

Variabila nemodificabilă megabute este inițializată cu o expresie constantă după care nu poate fi modificată.

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

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

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

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

static int b type_ specifier identificator [, identificator] ...

Modificatori - cuvinte cheie semnate, nesemnate, scurte, lungi.
Specificatorul de tip este un cuvânt cheie char sau int care definește tipul variabilei care este declarată.
Identificatorul este numele variabilei.

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

Când declarați o variabilă, puteți să o inițializați, adică să îi atribuiți o valoare inițială.

Int x = 100;

Când este declarat, numărul 100 va fi imediat scris în variabila x. Este mai bine să declarați variabilele care urmează să fie inițializate în rânduri separate.

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

În C, tipurile sunt asociate cu numele (identificatorii) valorilor, adică cu variabile. O celulă de memorie este asociată cu o variabilă în limbajul C. Tipul unei variabile specifică dimensiunea celulei, metoda de codificare a conținutului acesteia și transformările permise asupra valorii acestei variabile. Toate variabilele trebuie declarate înainte de a le folosi. Fiecare variabilă trebuie declarată o singură dată.

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

Exemple de descrieri:

char a, b; / * Variabilele a și b sunt de tip

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

* / char sym; / „Sunt descrise variabilele sym de tip char;

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

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

Exemple: char backch = "\ 0";

Să luăm în considerare principalele tipuri din limbajul C.

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

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

Exemple:"x" 2 "?"

În memoria computerului, un caracter ocupă un octet. De fapt, nu este stocat un caracter, ci un număr - un cod de caracter (de la 0 la 255). Toate caracterele valide și codurile lor corespunzătoare sunt indicate în tabele speciale de codificare.

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

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

12.87 -316.12 -3.345e5 12.345e-15

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


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

Trebuie remarcat faptul că ultimul element al matricei este caracterul \ 0. Este un caracter nul și este folosit în C pentru a marca sfârșitul unei linii. Caracterul nul nu este cifra 0; nu este tipărit și este numerotat în tabelul ASCII 0. Prezența unui caracter nul înseamnă că numărul de celule din matrice trebuie să fie. cel puțin unul mai mult decât numărul de caractere care trebuie alocate în memorie.

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

Programul 84

# include principal ()

scanf ("% s", șir);

printf ("% s", șir);

Acest exemplu descrie o matrice de 31 de celule de memorie, dintre care 30 pot găzdui un element de tip char. Este introdus când este apelată funcția scanf ("% s", șir); „&” lipsește când se specifică o matrice de caractere.

Indicatori. Pointer - o reprezentare simbolică a adresei de memorie alocată variabilei.

De exemplu, & name este un pointer către numele variabilei;

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

În limbajul C există și variabile de tip pointer. La fel cum valoarea unei variabile de tip char este un caracter, iar valoarea unei variabile de tip int este un număr întreg, valoarea unei variabile de tip pointer este adresa unei anumite valori.

Dacă dăm indicatorului numele ptr, putem scrie un operator ca acesta:

ptr = / * atribuie adresa numelui variabilei ptr * /

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

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

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

val = * ptr; / * definește valoarea indicată de ptr * / Ultimii doi operatori, luați împreună, sunt echivalenti cu următorii:

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

Exemplu: asistent medical = 22;

ptr = /* indicator catre asistenta */

Rezultatul este atribuirea valorii 22 variabilei val.

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

Exemple de descrierea corectă a indicatorilor: int * pi; char * pc;

Specificația tipului definește tipul variabilei la care se referă indicatorul, iar caracterul * definește variabila în sine ca indicator. Descrierea formei int * pi; spune că pi este un pointer și că * pi este un int.

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

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

Definiția typedef nu introduce niciun tip nou, ci doar adaugă un nou nume pentru tipul deja existent. Variabilele descrise în acest fel au exact aceleași proprietăți ca și variabilele descrise în mod explicit. Redenumirea tipurilor este folosită pentru a introduce nume semnificative sau abreviate pentru a îmbunătăți înțelegerea programului și pentru a îmbunătăți portabilitatea programului (numele de același tip de date pot diferi pe computere diferite).

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

Operatii aritmetice. Acestea includ

Adăugare (+),

Scădere (binară) (-),

Înmulțirea (*),

Divizia (/),

Restul diviziunii (%),

Scăderea (unară) (-).

În limbajul C, există o regulă: dacă dividendul și divizorul sunt de tip int, atunci diviziunea este efectuată în întregime, adică partea fracțională a rezultatului este aruncată.

Ca de obicei, în expresii, operațiile de înmulțire, împărțire și găsire a restului sunt efectuate înainte de adunare și scădere. Utilizați paranteze pentru a schimba ordinea acțiunilor.

Programul 85

#include

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

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

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

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

Rezultatul execuției programului: 11 1 0 1

Tabelul 3.3 Vechimea și ordinea operațiunilor

Bazele limbajului

Codul programului și datele manipulate de program sunt scrise în memoria computerului ca o secvență de biți. Pic- Acesta este cel mai mic element al memoriei computerului, capabil să stocheze fie 0, fie 1. La nivel fizic, acesta corespunde unei tensiuni electrice, care, după cum știți, fie există, fie nu. Privind conținutul memoriei computerului, vedem ceva de genul:
...

Este foarte dificil să înțelegem această secvență, dar uneori trebuie să manipulăm astfel de date nestructurate (de obicei, acest lucru este necesar atunci când programăm drivere 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 regulă, o anumită structură este impusă unei secvențe de biți, grupând biții în octețiși cuvintele... Un octet conține 8 biți și un cuvânt conține 4 octeți sau 32 de biți. Cu toate acestea, definiția unui cuvânt poate fi diferită pe diferite sisteme de operare. Acum începe trecerea la sistemele pe 64 de biți, iar mai recent sistemele cu cuvinte pe 16 biți au fost comune. Deși dimensiunea octeților este aceeași în marea majoritate a sistemelor, ne vom referi în continuare la aceste valori ca fiind dependente de mașină.

Acum putem vorbi, de exemplu, despre octetul cu adresa 1040 sau despre cuvântul cu adresa 1024 și să afirmăm că octetul cu adresa 1032 nu este egal cu octetul cu adresa 1040.
Cu toate acestea, nu știm ce este orice octet, orice cuvânt de mașină. Cum să înțelegeți semnificația anumitor 8 biți? Pentru a interpreta fără ambiguitate semnificația acestui octet (sau cuvânt, sau alt set de biți), trebuie să cunoaștem tipul de date reprezentate de acest octet.
C++ oferă un set de tipuri de date încorporate: caracter, întreg, real - și un set de tipuri compuse și extinse: șiruri de caractere, matrice, numere complexe. În plus, există un set de bază de operații pentru operațiunile cu aceste date: comparație, aritmetică și alte operații. Există, de asemenea, operatori de salt, buclă și condiționali. Aceste elemente ale limbajului C++ alcătuiesc setul de blocuri din care poți construi un sistem de orice complexitate. Primul pas în stăpânirea C++ va fi studiul elementelor de bază enumerate, căruia îi este dedicată Partea a II-a a acestei cărți.
Capitolul 3 oferă o privire de ansamblu asupra tipurilor încorporate și extinse și a mecanismelor prin care pot fi create noi tipuri. Practic acesta este, desigur, mecanismul de clasă introdus în secțiunea 2.3. Capitolul 4 discută expresiile, operațiile încorporate și precedența acestora și conversiile de tip. Capitolul 5 vorbește despre instrucțiunile lingvistice. În cele din urmă, Capitolul 6 prezintă biblioteca standard C++ și tipurile de containere - vector și matrice asociativă.

3. Tipuri de date C++

Acest capitol oferă o privire de ansamblu încorporat, sau elementar, tipuri de date ale limbajului C++. Începe cu o definiție literali, cum ar fi 3.14159 sau pi, și apoi conceptul variabil, sau obiect să fie de unul dintre tipurile de date. Restul acestui capitol este dedicat unei descrieri detaliate a fiecărui tip încorporat. De asemenea, listează 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 astfel de tipuri de date extindere tipuri de bază C++.

3.1. Literale

C++ are un set de tipuri de date încorporate pentru reprezentarea numerelor întregi, numere reale, simboluri și tipul 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ă numai în intervalul de valori pe care numerele le pot lua, iar dimensiunile specifice ale tipurilor enumerate depind de implementare. De obicei, scurt este o jumătate de cuvânt de mașină, int este un cuvânt, lung este 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ă în precizia reprezentării (numărul de cifre semnificative) și interval. În mod obișnuit, float (precizie simplă) ocupă un cuvânt mașină, dublu (precizie dublă) necesită două, iar dublu lung (precizie extinsă) necesită trei.
char, short, int și long împreună formează tipuri de numere întregi care, la rândul său, poate fi simbolic(semnat) și nesemnat(nesemnat). În tipurile cu semn, bitul din stânga este folosit pentru a stoca semnul (0 - plus, 1 - minus), iar biții rămași conțin valoarea. În tipurile fără semn, toți biții sunt utilizați pentru valoare. Caracterul semnat de tip 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 apare un anumit număr în program, de exemplu 1, atunci acest număr este apelat literal, sau constantă literală... O constantă, pentru că nu îi putem schimba valoarea, și un literal, deoarece valoarea ei apare în textul programului. Un literal este o cantitate neadresată: deși în realitate este, desigur, 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 întregi pot fi scrise în notație zecimală, octală și hexazecimală. Iată cum arată numărul 20, reprezentat prin literale zecimale, octale și hexazecimale:

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

Dacă literalul începe cu 0, este tratat ca octal, dacă începe 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 semnate int. Puteți defini în mod explicit un literal întreg atât de lung, atașând litera L la sfârșitul numărului (se folosesc atât L majuscule, cât și l minuscule, dar pentru lizibilitate nu ar trebui să utilizați litere mici: poate fi ușor confundată cu

1). U (sau u) de la sfârșit definește literalul ca int nesemnat, iar cele două litere UL sau LU ca lungi nesemnate. De exemplu:

128u 1024UL 1L 8Lu

Literale care reprezintă numere reale pot fi scrise fie cu punct zecimal, fie cu notație științifică (exponențială). Implicit, acestea sunt de tip dublu. 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 în cazul scrierii cu virgulă 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 de tip 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 \ f apel \ o bară oblică inversă \\ întrebare \? ghilimele simple \ "ghilimele duble \"

secvența generală de evadare este \ 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 (apel) \ 14 (linie nouă) \ 0 (nulă) \ 062 ("2")

Un caracter literal poate fi prefixat cu L (de exemplu, L „a”), ceea ce înseamnă un tip special wchar_t - un tip de caractere 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, caz în care o bară oblică inversă este inserată la sfârșitul liniei. Caracterele speciale pot fi reprezentate prin propriile secvențe de evadare. Iată exemple de literale șir:

"" (linie goală) "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 C și C++, ultimul element este întotdeauna caracterul special cu codul 0 (\ 0).
Literalul „A” specifică un singur caracter A, iar literalul șir „A” este o matrice de două elemente: „A” și \ 0 (caracter gol).
Deoarece există un tip wchar_t, există literale de acest tip, notate, ca și în cazul caracterelor individuale, cu prefixul L:

L „un șir larg literal”

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

"doi" "unii"

va genera 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”

apoi pe un computer rezultatul va fi o linie semnificativă, iar pe altul poate fi ceva complet diferit. Programele care folosesc caracteristicile de implementare ale unui anumit compilator sau sistem de operare nu sunt portabile. Descurajăm cu tărie utilizarea unor astfel de modele.

Exercițiul 3.1

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

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

Exercițiul 3.2

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

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

3.2. Variabile

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

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

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

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

unde X este mărit secvenţial cu 1, iar numărul necesar de literali este înlocuit în locul punctelor suspensive?

Da, ne-am descurcat cu sarcina. Este puțin probabil ca clientul să pătrundă în detalii, fiind mulțumit de rezultat. În viața reală, această abordare funcționează destul de des, în plus, este justificată: problema a fost rezolvată departe de cel mai elegant mod, dar la momentul dorit. Căutarea unei opțiuni mai plăcute, mai inteligente poate fi o pierdere de timp nepractică.

În acest caz, metoda brute-force oferă răspunsul corect, dar cât de neplăcut și plictisitor este să rezolvi o problemă în acest fel! Știm exact ce pași trebuie făcuți, dar pașii în sine sunt simpli și monotoni.

Implicarea unor mecanisme mai complexe pentru aceeași sarcină, de regulă, crește semnificativ timpul de pregătire. În plus, cu cât sunt utilizate mecanisme mai sofisticate, 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 „tehnologiei î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 de decizie în sine poate deveni atractiv.
Să ne întoarcem la exemplul nostru și să încercăm să „îmbunătățim tehnologic” implementarea acestuia. Putem folosi un obiect numit pentru a stoca valoarea gradului în care dorim să ne creștem numărul. De asemenea, în loc de o secvență repetată de literale, folosim operatorul buclă. Cam asa va arata:

#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 este mai mare decât pow
pentru (int cnt = 1; cnt<= pow; ++cnt)
res = res * valoare;
cout<< res << endl;
}

value, pow, res și cnt sunt variabile care vă permit să stocați, să modificați și să preluați valori. Instrucțiunea buclă for repetă linia de evaluare a timpilor pow rezultat.
Fără îndoială, am creat un program mult mai flexibil. Cu toate acestea, aceasta încă nu este 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) (pentru (int res = 1; exp> 0; --exp) res = res * val; return res;)

Acum obținerea oricărui grad din numărul dorit nu va fi dificil. Iată cum este implementată ultima noastră sarcină - să tipărim un tabel cu puteri de doi de la 0 la 15:

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

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

3.2.1. Ce este o variabilă

Variabil, sau un obiect- aceasta este o zonă de memorie numită la care avem acces din program; valorile pot fi puse acolo și apoi recuperate. Fiecare variabilă C++ are un tip specific care caracterizează dimensiunea și locația acelei zone de memorie, gama de valori pe care o poate stoca și setul de operații aplicabile 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ă de memorie. Adresabilitate- asta îi lipsește literalului. Există două cantități asociate unei variabile:

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

În expresie

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 atribuiri 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 valoarea 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ă zonă de memorie, o astfel de declarație poate apărea o singură dată într-un program. Probleme apar dacă o variabilă definită într-un fișier sursă urmează să fie utilizată în altul. De exemplu:

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

C ++ necesită ca un obiect să fie cunoscut înainte de prima accesare. Acest lucru se datorează necesității de a se asigura că obiectul este utilizat corect în funcție de tipul său. În exemplul nostru, module1.C va arunca 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 cu o declarație de variabilă:

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

O declarație de variabilă îi spune compilatorului că un obiect cu numele dat, de tipul dat, este definit undeva în program. Nu este alocată memorie pentru o variabilă atunci când aceasta este declarată. (Cuvântul cheie extern este discutat în Secțiunea 8.2.)
Un program poate conține câte declarații ale aceleiași variabile se 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 ne asigurăm de confortul modificării lor dacă este necesar. (Vom vorbi mai multe despre fișierele antet în secțiunea 8.2.)

3.2.2. Nume variabilă

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

Tabelul 3.1. cuvinte cheie C++

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

Pentru a face textul programului mai ușor de înțeles, vă recomandăm să respectați convențiile general acceptate pentru denumirea obiectelor:

  • un nume de variabilă este de obicei scris cu litere mici, de exemplu index (pentru comparație: Index este un nume de tip, 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, de exemplu, 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 următor cu o literă majusculă (data nașterii). S-a observat că programatorii obișnuiți cu abordarea orientată pe obiecte preferă să scrie cu majuscule cuvintele, în timp ce cei care au scris mult în C folosesc caracterul de subliniere. Care dintre cele două este mai bună este o chestiune de gust.

3.2.3. Definirea obiectului

În cel mai simplu caz, instrucțiunea de definire a obiectului constă în specificator de tipși numele obiectuluiși se termină cu punct și virgulă. De exemplu:

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

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

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

Simpla definiție a unei variabile nu stabilește valoarea sa inițială. Dacă un obiect este definit ca fiind global, specificația C++ garantează că va fi inițializat la zero. Dacă variabila este locală sau alocată dinamic (folosind noul operator), valoarea sa inițială este nedefinită, adică poate conține o valoare aleatorie.
Utilizarea unor astfel de variabile este o greșeală foarte comună și dificil de detectat. Este recomandat să specificați în mod explicit valoarea inițială a unui obiect, cel puțin în cazurile în care nu știți 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 asta în secțiunea 2.3. Vom continua să vorbim despre constructorii impliciti puțin mai târziu, în secțiunile 3.11 și 3.15, unde vom descompune ș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ă între paranteze:

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

Ambele opțiuni sunt echivalente și inițializează ivalul întreg la 1024 și șirul de proiect la „Fantasia 2000”.
Inițializarea explicită poate fi folosită și la definirea variabilelor cu 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ă definirea ei, așa că am putea inițializa variabila salariu cu suma variabilei salariu pe care tocmai am definit-o cu o constantă. Deci definiția este:

// corect, dar fără rost 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, iar dval primește 0.0 int ival = int (); dublu dval = dublu ();

În următoarea definiție:

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

inițializarea cu int () se aplică fiecăruia dintre cele zece elemente ale vectorului. (Am vorbit despre clasa vectorului în Secțiunea 2.8. Consultați Secțiunea 3.10 și Capitolul 6 pentru mai multe despre aceasta.)
O variabilă poate fi inițializată cu o expresie de orice complexitate, inclusiv apeluri de funcții. De exemplu:

#include #include
preț dublu = 109,99, reducere = 0,16;
dublu preț_de vânzare (preț * reducere);
animal de companie șir („ridurile”); 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 personalizată care returnează o valoare întreagă.

Exercițiul 3.3

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

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

Exercițiul 3.4

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

Exercițiul 3.5

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

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

Exercițiul 3.6

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

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

Exercițiul 3.7

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

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

3.3. Indicatori

Pointerii și alocarea memoriei dinamice au fost introduse pe scurt în Secțiunea 2.2. Indicator Este un obiect care conține adresa altui obiect și vă permite să manipulați indirect acel obiect. În mod obișnuit, pointerii sunt utilizați pentru a lucra cu obiecte create dinamic, pentru a construi structuri de date înrudite, cum ar fi liste legate și arbori ierarhici și pentru a trece obiecte mari - matrice și obiecte de clasă - la funcții ca parametri.
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 interpretează obiectul referit. Indicatorii către diferite tipuri pot avea aceeași semnificație, dar zona de memorie în care se află tipurile corespunzătoare poate fi diferită:

  • un pointer către un int care conține valoarea adresei 1000 este direcționat către zona de memorie 1000-1003 (într-un sistem de 32 de biți);
  • un pointer către un dublu care conține valoarea adresei 1000 este direcționat către zona de memorie 1000-1007 (într-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 în fața numelui. În definirea variabilelor cu o listă, înaintea fiecărui pointer trebuie să apară un asterisc (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:

Long * 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 lângă cuvântul cheie tip. Prin urmare, definițiile de mai sus sunt corecte din punct de vedere sintactic și complet echivalente:

// atenție: ps2 nu este un pointer către un șir! ș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 dată 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 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 o adresă zero
pi2 = 0;

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

// eroare: pi nu poate fi int pi = ival

În mod similar, nu puteți atribui un pointer de un tip unei valori 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 tipurilor de date: int *<== double* pi = pd pi = &dval;

Ideea nu este că variabila pi nu poate conține adresele obiectului 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ă momente în care ne interesează valoarea adresei în sine, și nu 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 indicator este să-i atribuim valoarea unui alt pointer sau să-l comparăm cu o anumită valoare de adresă. (Vom vorbi mai multe despre indicatorul de gol în secțiunea 4.14.)
Pentru a face referire la un obiect care are adresa sa, trebuie să utilizați operația de dereferire 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 valorii ival2 variabilei ival * pi = ival2;
// utilizarea indirectă a lui ival ca rvalue și lvalue
* pi = abs (* pi); // ival = abs (ival);
* pi = * pi + 1; // ival = ival + 1;

Când aplicăm operația de preluare a adresei (&) unui obiect de tip int, obținem rezultatul 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 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 int * care conține adresa ival. Pentru a obține obiectul ival în sine, operația de dereferire ppi trebuie aplicată de două ori.

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

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

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

Puteți adăuga o valoare întreagă la indicator, de asemenea, puteți scădea din ea. Adăugarea lui 1 la un pointer crește valoarea pe care o conține cu dimensiunea zonei de memorie alocată unui obiect de tipul corespunzător. Dacă tipul char ocupă 1 octet, int - 4 și double - 8, atunci adăugarea lui 2 la pointerii la char, int și double le va crește valoarea cu 2, 8 și 16. Cum poate fi interpretat? Dacă obiectele de același tip sunt amplasate în memorie unul după altul, atunci creșterea indicatorului cu 1 va face ca acesta să indice următorul obiect. Prin urmare, operațiile aritmetice cu pointeri sunt cele mai des folosite la procesarea tablourilor; în orice alt caz, ele sunt cu greu justificate.
Iată cum arată un exemplu tipic de utilizare a aritmeticii adresei atunci când iterați elementele unei matrice folosind un iterator:

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

Exercițiul 3.8

Sunt date definiții variabile:

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 pointerii este unul dintre cele mai importante aspecte ale C și C++, dar este ușor să greșiți în el. De exemplu codul

Pi = pi = pi + 1024;

aproape sigur va face ca pi să indice o regiune aleatorie a memoriei. Ce face acest operator de atribuire și caz în care nu va avea ca rezultat o eroare?

Exercițiul 3.10

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

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

Care este greșeala? Cum poți să o repari?

Exercițiul 3.11

Erorile din cele două exerciții anterioare se manifestă și duc la consecințe fatale din cauza lipsei de validare a valorilor pointerului în C++ în timp ce programul rulează. De ce credeți că nu a fost pusă în aplicare o astfel de verificare? Puteți oferi câteva îndrumări generale pentru a face indicatoarele mai sigure?

3.4. Tipuri de șiruri

C++ acceptă două tipuri de șiruri de caractere - tipul încorporat moștenit de la C și clasa de șiruri din biblioteca standard C++. Clasa string oferă mult mai multe posibilități și, prin urmare, este mai convenabil de utilizat, dar în practică există adesea situații în care trebuie să utilizați un tip încorporat sau să înțelegeți bine cum funcționează. (Un exemplu ar fi analizarea parametrilor liniei de comandă transmise la main (). Vom acoperi asta în Capitolul 7.)

3.4.1. Tip șir încorporat

După cum sa menționat deja, tipul de șir încorporat a fost transmis la C++ prin moștenirea de la C. Un șir de caractere este stocat în memorie ca o matrice și este accesat folosind un pointer char *. Biblioteca C Standard 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 C Standard face parte din biblioteca C++. Pentru a-l folosi, trebuie să includem un fișier antet:

#include

Indicatorul către char, cu care ne referim la șir, indică șirul 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 din șir într-o matrice și apoi îi atribuie lui st adresa primului element din matrice. Cum se poate lucra cu un șir folosind un astfel de indicator?
În mod obișnuit, aritmetica adresei este folosită pentru a repeta prin caracterele 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 adevăr. Orice valoare diferită de zero este considerată adevărată și, prin urmare, bucla se termină când este atins caracterul cu codul 0. Operatorul de increment ++ adaugă 1 la indicatorul st și, astfel, îl mută la următorul caracter.
Așa ar putea arăta implementarea 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 dereferențiere:

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ă indicatorul către șir are o valoare nulă (atunci nu avem șir deloc) sau indică către o matrice formată dintr-un caracter nul (adică către un șir care nu conține un singur caracter semnificativ).

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

Pentru un programator începător, utilizarea șirurilor încorporate este plină de erori, deoarece nivelul de implementare este prea scăzut și este imposibil să se facă fără aritmetica adresei. Mai jos vom arăta câteva greșeli tipice făcute de începători. Sarcina este simplă: calculați lungimea șirului. Prima versiune este greșită. Corectează-l.

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

În această versiune, indicatorul st nu este dereferențiat. Prin urmare, nu caracterul indicat de st este verificat pentru egalitate, ci indicatorul în sine. Deoarece inițial acest pointer avea o valoare diferită de zero (adresa șirului de caractere), nu va deveni niciodată egal cu zero, iar bucla va rula pe termen nelimitat.
Această eroare a fost eliminată în a doua versiune a programului. Programul se încheie cu succes, dar rezultatul este incorect. Unde gresim de data asta?

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

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

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

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

18: ena sticle 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ă operatorul corect în sfârșit:

St = st - len - 1;

si iata rezultatul corect:

18: Prețul unei sticle de vin

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

St = st - len - 1;

adăugat pentru a corecta o greșeală făcută într-un stadiu incipient al proiectării programului - creșterea directă a indicatorului st. Acest operator nu se încadrează în logica programului, iar codul este acum greu de înțeles. Remedierile de acest fel sunt adesea denumite patch-uri - ceva conceput pentru a astupa o gaură într-un program existent. O soluție mult mai bună ar fi să regândim logica. Una dintre opțiunile î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 lungime, lăsând 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 prea scăzut. Prin urmare, este destul de obișnuit să vă dezvoltați propria clasă sau clase pentru a reprezenta tipul de șir - aproape fiecare companie, departament sau proiect individual a avut propria sa implementare șir. Ce să spun, la fel am procedat și în cele două ediții anterioare ale acestei cărți! Acest lucru a dat naștere la probleme de compatibilitate și portabilitate. Implementarea bibliotecii standard C++ a standardului șirurilor a fost menită să pună capăt acestei invenții a bicicletei.
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 de tip încorporat) sau alt obiect de tip șir. Tipul încorporat nu are a doua opțiune;
  • copierea unei linii pe alta. Pentru un tip încorporat, trebuie să utilizați funcția strcpy ();
  • acces la caracterele individuale ale șirului pentru citire și scriere. În matricea încorporată, acest lucru se realizează folosind operația de preluare a indexului sau a adresei indirecte;
  • compararea a două șiruri de caractere pentru egalitate. Pentru un tip încorporat, este utilizată funcția strcmp ();
  • concatenarea a două șiruri, obținând rezultatul fie ca 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 pe o linie nouă, trebuie să utilizați secvențial funcțiile strcpy () și strcat ();
  • calculând lungimea șirului. Puteți afla lungimea unui șir de tip încorporat folosind funcția strlen ();
  • capacitatea de a afla dacă un șir este gol. În acest scop, șirurile încorporate trebuie să verifice două condiții: char str = 0; // ... dacă (! str ||! * str) întoarce;

Clasa de șiruri C++ Standard Library implementează toate aceste operațiuni (ș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 printr-un obiect șir și un șir de caractere inițializat:

#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ă de definiție a șirului specifică un șir gol:

Str 2; // linie goală

Cum știm dacă o linie este goală? Desigur, puteți compara lungimea sa cu 0:

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

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

Dacă (st.gol ()) // 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 linii sunt aceleași? Să folosim operatorul de comparație (==):

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

Cum pot copia o linie pe alta? Cu operația obișnuită de atribuire:

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

Pentru a concatena șiruri, utilizați operațiile de adăugare (+) sau de adăugare și atribuire (+ =). Să fie date două linii:

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

Putem obține a treia linie, constând din concatenarea primelor două, astfel:

Șirul s3 = s1 + s2;

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

S1 + = s2;

Operația de adăugare poate concatena obiecte din clasa șiruri 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 clasa șirurilor:

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

Expresii ca acestea funcționează deoarece compilatorul știe cum să convertească automat obiectele de tip încorporat în obiecte din clasa șir. De asemenea, este posibilă o simplă atribuire a unui șir inline unui obiect șir:

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

Conversia inversă nu funcționează, totuși. Încercarea de a face următoarea inițializare a unui șir de tip încorporat va genera o eroare de compilare:

Char * str = s1; // eroare de compilare

Pentru a efectua această conversie, trebuie să apelați în mod explicit o funcție membru cu numele oarecum ciudat 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 fi într-un tip ș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 posibilitatea modificării directe a conținutului unui obiect prin acest pointer de 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 de tip șir, precum și un tip încorporat, pot fi accesate folosind operația de preluare a indexului. De exemplu, iată un fragment 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 de înlocuire ():

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

replace () este unul dintre algoritmii generici pe care i-am văzut în Secțiunea 2.8 și va fi tratat în detaliu în Capitolul 12. Această funcție rulează de la begin () la end (), care returnează pointerii la începutul și sfârșitul unui șir și înlocuiește elemente egale cu al treilea parametru al său, cu al patrulea.

Exercițiul 3.12

Căutaț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 = * pc;
(h) pc = st; (l) * pc = ival;

Exercițiul 3.13

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

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

Exercițiul 3.14

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

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

Ce fac aceste programe?
Se pare că a doua implementare este de două ori mai rapidă decât prima. Te asteptai la acest rezultat? Cum explici?

Exercițiul 3.15

Ați putea îmbunătăți sau completa setul de operații cu clase de șiruri prezentate în ultima secțiune? Explicați sugestiile dvs

3.5. Specificator const

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

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

Există două probleme cu 512 literal. 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 tău are 10.000 de linii, iar literalul 512 apare în 4% dintre ele. Să presupunem că în 80% din cazuri numărul 512 ar trebui 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?
Ambele probleme sunt rezolvate în același timp: trebuie să creați un obiect cu valoarea 512. Dându-i un nume semnificativ, de exemplu bufSize, vom face programul mult mai ușor de înțeles: este clar care este variabila buclă. fiind comparat cu.

Index< bufSize

În acest caz, redimensionarea bufSize nu necesită examinarea a 400 de linii de cod pentru a modifica 320 dintre ele. Cât de mai puțină eroare este posibilă cu prețul adăugării unui singur obiect! Acum valoarea este 512 localizat.

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

Rămâne o mică problemă: variabila bufSize aici este o valoare l care poate fi schimbată accidental în program, ceea ce duce la o eroare greu de detectat. Una dintre cele mai frecvente greșeli este folosirea atribuirii (=) în loc de comparare (==):

// schimbă aleatoriu valoarea bufSize dacă (bufSize = 1) // ...

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

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

transformăm variabila într-o constantă cu o valoare de 512, a cărei valoare nu poate fi modificată: astfel de încercări sunt dejucate 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: o încercare de a atribui o valoare unei constante dacă (bufSize = 0) ...

Deoarece unei constante nu i se poate atribui o valoare, ea trebuie inițializată acolo unde este definită. Definirea unei constante fără a o inițializa, aduce ș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; // schimbați obiectul 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.
Ei bine, suntem lipsiți de posibilitatea de a folosi pointeri către constante? Nu. Pentru aceasta, există pointeri declarați cu specificatorul const:

Const double * cptr;

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

Const dublu * pc = 0; const double minWage = 9,60; // corect: nu se poate modifica minWage cu computerul
pc = dublu dval = 3,14; // corect: nu se poate modifica minWage cu computerul
// deși dval nu este o constantă
pc = // corect dval = 3,14159; //dreapta
* buc = 3,14159; // eroare

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

Pc =

Un pointer constant nu permite ca obiectul pe care îl adresează să fie schimbat folosind adresarea indirectă. Deși dval din exemplul de mai sus nu este o constantă, compilatorul nu va permite computerului să modifice dval. (Din nou, deoarece nu este capabil să determine ce adresă a obiectului poate conține pointerul într-un moment arbitrar al execuției programului.)
În programele reale, pointerii către constante sunt cel mai adesea folosiți ca parametri formali ai funcțiilor. Folosirea acestora asigură că obiectul transmis funcției 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 către constante în capitolul 7 când vorbim despre funcții.)
Există, de asemenea, indicii constante. (Rețineți diferența dintre un indicator constant și un indicator constant!). Un pointer constant poate adresa atât o constantă, cât și o variabilă. De exemplu:

Int errNumb = 0; int * const currErr =

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

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

Încercarea de a atribui o valoare unui pointer constant va genera o eroare de compilare:

CurErr = // eroare

Un indicator constant către o constantă este unirea 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. Există unele greșite printre 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, specificați operatorii de atribuire corecti. Explica.

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

3.6. Tip de referință

Un tip de referință, uneori numit alias, este folosit pentru a da unui obiect un nume alternativ. O referință vă permite să manipulați un obiect indirect, la fel ca folosind un pointer. Totuși, această manipulare indirectă nu necesită sintaxa specială necesară pentru pointeri. Referințele sunt de obicei folosite ca parametri formali ai funcțiilor. În această secțiune, vom analiza utilizarea independentă a obiectelor de tip referință.
Un tip de referință este notat prin specificarea operatorului de preluare a adresei (&) în fața numelui variabilei. Legătura trebuie inițializată. De exemplu:

Int ival = 1024; // corect: refVal este o referință la ival int & refVal = ival; // eroare: linkul trebuie inițializat 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ă ce definiți o legătură, nu o veți mai putea modifica pentru a funcționa cu un alt obiect (de aceea legătura trebuie inițializată în locul definiției sale). Î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. La fel int ii = refVal; atribuie ii valorii curente a lui ival, int * pi = inițializează pi cu adresa ival.

// două obiecte de tip int sunt definite int ival = 1024, ival2 = 2048; // o legătură ș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ă referințe sunt definite int & rval3 = ival3, & rval4 = ival2;

O referință constantă poate fi inițializată cu un obiect de alt tip (dacă, desigur, este posibil să se convertească un tip în altul), precum și o valoare neadresată, cum ar fi o constantă literală. De exemplu:

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

Dacă nu am fi furnizat specificatorul const, toate cele trei definiții de referință ar fi cauzat o eroare de compilare. Cu toate acestea, motivul pentru care compilatorul nu omite 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 alt tip, compilatorul convertește obiectul original într-unul auxiliar. De exemplu, dacă scriem:

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

atunci compilatorul îl va converti cam așa:

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

Dacă am putea atribui o nouă valoare lui ri, am schimba de fapt nu dval, ci temp. Valoarea dval ar rămâne aceeași, ceea ce nu este deloc evident pentru programator. Prin urmare, compilatorul interzice astfel de acțiuni și 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ă. Vrem să definim o referință la adresa unui obiect constant, dar prima noastră opțiune aruncă o eroare de compilare:

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

De asemenea, o încercare de a rezolva problema prin adăugarea specificatorului 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 int. Și avem nevoie de un pointer non-constant către un obiect constant, astfel încât următoarea intrare va 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 definiției sale. În al doilea rând, orice modificare a legăturii nu o transformă, ci obiectul la care se referă. Să ne uităm la câteva exemple. Daca scriem:

Int * pi = 0;

inițializam 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 și acum indică 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 Operator matrice + (const Matrix &, const Matrix &);

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

Int & next_value = ival;

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

Exercițiul 3.19

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

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

Exercițiul 3.20

Există atribuții eronate printre următoarele (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. tip bool

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

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

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

// eroare short bool found = false;

Obiectele de tip bool sunt implicit convertite la tipul int. Adevărat devine 1, iar fals devine 0. De exemplu:

Bool găsit = fals; int număr_ocurență = 0; în timp ce (/ * mormăi * /)
{
găsit = caută (/ * ceva * /); // 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 (șir constant &); bool găsit = fals; if (found = find ("rosebud")) // corect: found == true // returnează un pointer la element
extern int * find (valoare int); if (găsit = găsi (1024)) // corect: găsit == adevărat

3.8. Enumerări

Nu este neobișnuit să definiți o variabilă care preia valori dintr-un set. Să presupunem că un fișier este deschis în oricare dintre cele trei moduri: pentru citire, pentru scriere, pentru adăugare.
Desigur, trei constante pot fi definite pentru a desemna aceste moduri:

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

și folosiți aceste constante:

Bool open_file (șir nume_fișier, int open_mode); // ...
fișier_deschis („Phoenix_and_the_Crane”, anexă);

Această soluție este acceptabilă, dar nu în totalitate, deoarece nu putem garanta că argumentul transmis funcției open_file () este doar 1, 2 sau 3.
Utilizarea unui tip enumerat 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 1, 2 și 3, fiecare dintre valorile specificate având un nume mnemonic. Putem folosi numele acestui nou tip pentru a defini atât obiectul acelui tip, cât și tipul parametrilor formali ai funcției:

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

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

Open_file ("Phoenix și Macara", anexă);

O încercare de a atribui unei variabile de acest tip o altă valoare decât unul dintre elementele de enumerare (sau de a-l 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, primim totuși o eroare:

// eroare: 1 nu este membru al enumerației open_modes open_file ("Iona", 1);

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

Open_modes om = intrare; // ... om = anexează; fişier_deschis ("TailTell", om);

Cu toate acestea, este imposibil să obțineți numele unor astfel de elemente. Dacă scriem o instrucțiune de ieșire:

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

mai primim:

Această problemă este rezolvată dacă definiți o matrice de șiruri în care elementul cu indexul egal cu valoarea elementului de enumerare va conține numele său. Cu o astfel de matrice, putem scrie:

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

De asemenea, nu puteți repeta peste toate valorile de enumerare:

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

Cuvântul cheie enum este folosit pentru a defini o enumerare, iar numele elementelor sunt specificate între acolade, separate prin virgulă. În mod implicit, primul este 0, următorul este 1 și așa mai departe. Această regulă poate fi modificată folosind operatorul de atribuire. Mai mult, fiecare element următor fără o valoare specificată explicit va fi cu 1 mai mult decât elementul care îl precede în listă. În exemplul nostru, am specificat în mod explicit valoarea 1 pentru intrare, în timp ce output și append ar fi 2 și 3. Iată un alt exemplu:

// forma == 0, sfera == 1, cilindru == 2, poligon == 3 enum Forme (cota, spere, cilindru, poligon);

Valorile întregi care corespund diferiților membri ai 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 enumerat poate fi definit, utilizat în expresii și transmis unei funcții ca argument. Un astfel de obiect este inițializat numai cu valoarea unuia dintre elementele de enumerare și numai o astfel de 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 = point3d; // corect: pt2d == 3 // eroare: pt3w este inițializat pentru a tasta 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. Tip matrice

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

Int ival;

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

Int ia [10];

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

Ival = ia [2];

atribuie variabilei ival valoarea elementului tabloului ia cu indice 2. La fel

Ia [7] = ival;

setează elementul de la indicele 7 la 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 din matrice (cel puțin 1) și este inclusă între paranteze drepte. Mărimea tabloului trebuie să fie cunoscută deja în momentul compilării și, prin urmare, trebuie să fie o expresie constantă, deși nu este neapărat specificată de un literal. Iată exemple de definiții corecte și incorecte ale matricelor:

Extern int get_size (); // constantele buf_size și max_files
const int buf_size = 512, max_files = 20;
int personal_size = 27; // corect: caracter constant input_buffer [buf_size]; // corect: expresie constantă: 20 - 3 caractere * fileTable [max_files-3]; // eroare: salarii duble nu constante [staff_size]; // eroare: expresie non-constant int test_scores [get_size ()];

Obiectele buf_size și max_files sunt constante, astfel încât definițiile matricelor input_buffer și fileTable sunt corecte. Dar staff_size este o variabilă (deși este inițializată cu o constantă de 27), ceea ce înseamnă că salariile nu sunt permise. (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, prin urmare 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 corect de indici nu este 1 - 10, ci 0 - 9. Iată un exemplu de iterare peste toate elementele matricei:

Int main () (const int array_size = 10; int ia [array_size]; 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 [dimensiune_matrice] = (0, 1, 2);

Dacă specificăm în mod explicit o listă de valori, atunci putem sări peste specificarea dimensiunii matricei: compilatorul va calcula singur 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. Când dimensiunea și numărul de valori coincid, totul este evident. Dacă lista de valori este mai scurtă decât dimensiunea specificată, elementele rămase ale matricei 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 [dimensiune_matrice] = (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 tabloului ca1 este 3, a matricei ca2 - 4 (caracterul nul de terminare este luat în considerare în literalele șir). Următoarea definiție va genera 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 cu alta este invalidă. De asemenea, 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: o matrice de pointeri de tip int * int * iar = (& ix, & jx, & kx); // eroare: matricele de referințe nu sunt permise int & iar = (ix, jx, kx); int main ()
{
int ia3 (dimensiune_matrice]; // corect
// eroare: matricele încorporate nu pot fi copiate
ia3 = ia;
returnează 0;
}

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

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

Orice expresie care dă un rezultat întreg poate fi folosită ca index de matrice. De exemplu:

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

Să subliniem că limbajul C++ nu oferă control asupra indicilor de matrice - nici în timpul compilării, nici în timpul executării. Programatorul însuși trebuie să se asigure că indexul nu depășește limitele matricei. Erorile de index sunt destul de frecvente. Din păcate, nu este atât de greu să dai peste exemple de programe care se compilează și chiar funcționează, 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 de matrice conține erori? Explica.

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

Exercițiul 3.23

Următorul fragment de cod ar trebui să inițializeze fiecare element al matricei cu o valoare de index. Găsiți greșelile pe care le-ați făcut:

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

3.9.1. Matrice multidimensionale

În C ++ este posibil să se utilizeze tablouri multidimensionale, atunci când declarați care trebuie să specificați chenarul drept al 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 linii a câte trei elemente fiecare. Matricele multidimensionale pot fi și ele inițializate:

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

Acoladele interne care despart lista de valori în linii sunt opționale și sunt de obicei folosite pentru lizibilitate. Inițializarea de mai jos este exact aceeași cu exemplul anterior, deși mai puțin clară:

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

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

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

Dacă omiteți bretelele interioare, rezultatul este complet diferit. Toate cele trei elemente ale primei linii și primul element al celei de-a doua vor primi valoarea specificată, iar restul vor fi inițializate implicit la 0.

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

Când vă referiți la elementele unui tablou multidimensional, trebuie să utilizați indici pentru fiecare dimensiune (acestea sunt încadrate între paranteze drepte). Iată cum arată inițializarea unei matrice bidimensionale folosind bucle imbricate:

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

Proiecta

Ia [1, 2]

este valabil din punctul de vedere al sintaxei C++, dar nu înseamnă deloc la ce s-ar aștepta un programator fără experiență. Aceasta nu este o declarație a unui tablou bidimensional de 1 pe 2. Agregatul dintre paranteze pătrate este o listă de expresii separate prin virgulă care va returna ultima valoare 2 (vezi operatorul virgulă în Secțiunea 4.2). Prin urmare, declarația ia este echivalentă cu ia. Aceasta este o altă oportunitate de eroare.

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ă să-i indice pur și simplu numele în program?

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

În mod similar, vă puteți referi la valoarea primului element al matricei în două moduri:

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

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

După cum am menționat mai devreme, expresia

oferă, de asemenea, adresa celui de-al doilea element al matricei. În consecință, sensul său ne este dat prin 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 (a se vedea secțiunea 4.13 pentru prioritățile operațiunii). Prin urmare, prima expresie dereferențează mai întâi variabila ia și primește primul element al matricei, apoi îi adaugă 1. A doua expresie furnizează valoarea celui de-al doilea element.

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

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

Pointerul pbegin este inițializat cu adresa primului element din matrice. 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 pbegin este 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 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 versatilă, totuși, poate funcționa numai cu matrice de tip int. Există o modalitate de a elimina și această limitare: transformați această funcție într-un șablon (șabloanele au fost prezentate pe scurt în secțiunea 2.5):

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

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

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

Noi am scris generalizat funcţie. Biblioteca standard oferă un set de algoritmi generici (am menționat 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. De exemplu, iată cum arată apelurile la algoritmul de sortare generalizată:

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

(Ne vom opri asupra algoritmilor generici în detaliu în Capitolul 12; Anexa oferă 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 aborda vectorul standard de tip container, 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ă multe mai multe opțiuni, așa că utilizarea sa este de preferat. Cu toate acestea, există situații în care nu puteți face fără matrice încorporate. 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 al clasei vectoriale este utilizat în același mod ca o matrice de tip încorporat. Se determină un vector cu o dimensiune dată:

Vector< int >ivec (10);

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

Int ia [10];

Pentru a accesa elementele individuale ale vectorului, se utilizează operația de preluare a indexului:

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

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

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

Elementele vectoriale sunt inițializate cu valori implicite. Pentru tipurile numerice și pointerii, această valoare este 0. Dacă obiectele de clasă acționează ca elemente, inițiatorul pentru acestea este setat de constructorul implicit (vezi Secțiunea 2.3). Cu toate acestea, inițiatorul poate fi specificat și în mod explicit folosind formularul:

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

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

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

Pentru un obiect al vectorului de clasă, aceeași acțiune nu este posibilă. Cu toate acestea, un astfel de obiect poate fi inițializat folosind o matrice de tip încorporat:

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

Doi pointeri sunt trecuți către constructorul vectorial ivec - un pointer către începutul tabloului ia și către elementul care urmează ultimului. Ca o listă de valori inițiale, este permis să specificați nu întreaga matrice, ci o parte din intervalul său:

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

O altă diferență între un vector și o matrice de tip încorporat este capacitatea de a inițializa un obiect de tip vector la altul și de a utiliza operația de atribuire pentru a copia obiecte. De exemplu:

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

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

Vector< string >text;

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

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

Deși putem folosi operația de preluare a indexului pentru a itera elementele vectorului:

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

mai tipic în cadrul acestui idiom ar fi folosirea iteratoarelor:

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

Un iterator este o clasă de bibliotecă standard care este de fapt un pointer către un element de matrice.
Expresie

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

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

Vector ivec;

Ar fi o greșeală să scriu:

Nu avem încă un singur element vector ivec; numărul de elemente se află cu ajutorul funcției dimensiune ().

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

Vector ia (10);

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

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

Mă refeream la inițializarea vectorului ivec cu valorile elementelor ia, în loc de care a apărut vectorul ivec de dimensiunea 14.
Urmând limbajul STL, puteți nu numai să adăugați, ci și să eliminați elemente ale unui vector. (Vom acoperi toate acestea în detaliu și cu exemple în Capitolul 6.)

Exercițiul 3.24

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

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

Exercițiul 3.25

Implementați următoarea funcție:
bool este_egal (const int * ia, int ia_size,
const vector & ivec);
Funcția is_equal () compară două containere element cu element. În cazul containerelor de diferite dimensiuni, „coada” unuia 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 diferă - fals. Utilizați un iterator pentru a itera elementele. Scrieți o funcție principală () care apelează is_equal ().

3.11. Clasa complexa

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

#include

Un număr complex are două părți - reală și imaginară. Partea imaginară este rădăcina pătrată a unui număr negativ. Este obișnuit să scrieți un număr complex în formă

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

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

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

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

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

3.12. Directiva Typedef

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

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

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

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

Această directivă începe cu cuvântul cheie typedef, urmat de un specificator de tip și se termină cu un identificator care devine un sinonim pentru tipul specificat.
Pentru ce sunt utilizate numele definite cu directiva typedef? Folosind nume mnemonice pentru tipurile de date, vă puteți face programul mai ușor de citit. În plus, se obișnuiește să se folosească astfel de nume pentru tipurile compozite complexe, care altfel sunt greu de înțeles (vezi exemplul din secțiunea 3.14), să se declare 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 dă un răspuns greșit. Eroarea este cauzată de o înțelegere greșită a directivei typedef ca o simplă substituție de macro-text. Definitia este data:

Typedef char * cstring;

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

Extern const cstring cstr;

Răspunsul, care pare evident:

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 constant către char:

Char * const cstr;

3.13. Specificator volatil

Un obiect este declarat ca volatil (volatil, mutabil asincron) dacă valoarea lui poate fi modificată fără a fi observată de compilator, de exemplu, o variabilă care este actualizată de valoarea ceasului sistemului. Acest specificator îi spune compilatorului să nu optimizeze codul pentru a lucra cu obiectul dat.
Specificatorul volatil este folosit ca și specificatorul const:

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

display_register este un obiect int volatil. curr_task este un pointer către un obiect Task volatil. ixa este o matrice instabilă de numere întregi și fiecare element al unei astfel de matrice este considerat instabil. bitmap_buf este un obiect Screen volatil, fiecare dintre membrii săi de date este, de asemenea, considerat volatil.
Singurul scop al utilizării specificatorului volatil este de a spune compilatorului că nu poate determina cine și cum poate schimba valoarea unui obiect dat. Prin urmare, compilatorul nu ar trebui să efectueze optimizarea codului care utilizează acest obiect.

3.14. Clasa de perechi

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

#include

De exemplu, instrucțiunea

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

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

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

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

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

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

Clasa EntrySlot; extern EntrySlot * 1ook_up (șir); 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 revizui clasa pereche în discuția noastră despre tipurile de containere din Capitolul 6 și despre algoritmii generici din Capitolul 12.)

3.15. Tipuri de clasă

Mecanismul de clasă vă permite să creați noi tipuri de date; introduce tipurile șir, vector, complex și pereche discutate mai sus. În capitolul 2, am introdus conceptele și mecanismele care suportă abordările orientate pe obiecte și pe obiecte prin implementarea clasei Array. Aici, pe baza unei abordări de obiect, vom crea o clasă String simplă, a cărei implementare vă va ajuta să înțelegeți, în special, supraîncărcarea operației - am vorbit despre aceasta în secțiunea 2.3. (Clasele sunt discutate în detaliu în capitolele 13, 14 și 15). Am oferit o scurtă descriere a clasei pentru a oferi exemple mai interesante. Cititorul nou în C ++ poate sări peste această secțiune și să aștepte o descriere mai sistematică a claselor în capitolele următoare.)
Clasa noastră String trebuie să accepte inițializarea cu un obiect 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 individuale ale șirului va fi implementat ca o operație de preluare a indexului 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 de intrare/ieșire ale obiectului nostru. În cele din urmă, vom implementa 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 și private. O secțiune publică conține de obicei un set de operații susținute de o clasă, numite metode sau funcții membre ale clasei. Aceste funcții membre definesc interfața publică a unei clase, cu alte cuvinte, un set de acțiuni care pot fi efectuate asupra obiectelor unei clase date. O secțiune privată include de obicei membri de date care asigură implementare internă. În cazul nostru, membrii interni sunt _string - un pointer către char, precum și _size de tip int. _size va deține informații despre lungimea șirului, iar _string va fi o matrice de caractere alocată dinamic. Iată cum arată definiția clasei:

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

Clasa String are trei constructori. După cum sa menționat în Secțiunea 2.3, mecanismul de supraîncărcare vă permite să definiți mai multe implementări de funcții cu același nume, dacă 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:

Un astfel de constructor este chemat pentru str1.
Cei doi constructori rămași au fiecare câte un parametru. Prin urmare

String str2 („șir de caractere”);

Constructorul este numit

String (const char *);

String str3 (str2);

Constructor

String (const String &);

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

String str4 (1024);

Acest lucru va cauza o eroare de compilare, deoarece nu există un singur constructor cu un parametru 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 și op este unul dintre operatorii predefiniti: +, =, == și așa mai departe. (Consultați Capitolul 15 pentru definiția exactă a sintaxei.) Iată declarația operatorului de preluare a indexului supraîncărcat:

Char & operator (int);

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

obiect șir ("Danny");
String * ptr = new String ("Anna");
matrice de șiruri;
Iată cum arată apelul la funcția size () pentru aceste obiecte:
vector dimensiuni (3);

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

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

String namel („Yadie”); Nume șir2 ("Yodie"); // operator bool == (const String &)
dacă (nume == nume2)
întoarcere;
altfel
// String & operator = (const String &)
namel = nume2;

O declarație de funcție membru trebuie să fie în interiorul unei definiții de clasă, iar o definiție de funcție poate sta atât în ​​interiorul unei definiții de clasă, cât și în afara acesteia. (Atât dimensiunea () 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ă, de exemplu, String.C, iar definiția clasei în sine este plasată în fișierul antet (String.h în exemplul nostru), care ar trebui inclus în sursa:

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

Amintiți-vă că strcmp () este o funcție standard de bibliotecă C. Compară două șiruri de caractere de tip încorporat, returnând 0 dacă șirurile sunt egale și diferite de zero dacă nu sunt egale. Operatorul condiționat (? :) testează valoarea înainte de semnul întrebării. Dacă este adevărată, este returnată valoarea expresiei din stânga două puncte; în caz contrar, expresia din dreapta. În exemplul nostru, valoarea expresiei este falsă dacă strcmp () a returnat o valoare diferită de zero și adevărată dacă a returnat zero. (Operatorul condiționat este discutat în Secțiunea 4.7.)
Operatorul de comparație 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 inline. Compilatorul înlocuiește textul funcției în loc să o apeleze, astfel încât nu se petrece timp pentru un astfel de apel. (Funcțiile în linie sunt discutate în secțiunea 7.6.) O funcție membru definită într-o clasă este implicită în linie. Dacă este definit în afara clasei, pentru a o declara inline, trebuie să utilizați cuvântul cheie inline:

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

Definiția funcției inline trebuie să fie în fișierul antet care conține definiția clasei. Prin redefinirea operatorului == ca încorporat, 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 între un obiect String și un șir încorporat:

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

Numele constructorului este același cu numele clasei. Nu se consideră că returnează o valoare, deci nu trebuie să setați valoarea returnată nici în definiția sa, nici în corpul acesteia. 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) (dacă (! str) (_size = 0; _string = 0;) else (_size = str1en (str); strcpy (_string, str);) // constructor de copiere
inline String :: String (const String & rhs)
{
dimensiune = rhs._size;
dacă (! rhs._string)
_șir = 0;
altfel (
_string = caracter nou [_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ă de membru servește acestui scop - un destructor, care este apelat automat pe un obiect în momentul în care acel obiect încetează să existe. (Vezi capitolul 7 pentru durata de viață a unui obiect.) Numele destructorului 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:

Inline String:: ~ String () (ștergeți _șirul;)

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

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

Apoi, în interiorul mărimii (), această valoare va fi adresa stocată în ptr. În cadrul operației de preluare a indexului, acesta conține adresa obj. Dereferențând acest lucru (folosind * this), obținem obiectul în sine. (Acest indicator este detaliat în Secțiunea 13.4.)

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

La implementarea operațiunilor de atribuire, se comite destul de des o greșeală: ei uită să verifice dacă obiectul copiat nu este același obiect în care este copiat. Vom efectua această verificare folosind același indicator:

Inline String & String :: operator = (const String & rhs) (// în expresia // namel = * pointer_to_string // acesta este name1, // rhs - * pointer_to_string. Dacă (acest! = & Rhs) (

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

Inline String & String :: operator = (const String & rhs) (dacă (acest! = & Rhs) (ștergeți _string; _size = rhs._size; dacă (! Rhs._string)
_șir = 0;
altfel (
_string = caracter nou [_size + 1];
strcpy (_string, rhs._string);
}
}
returnează * aceasta;
}

Operația de preluare a unui index este aproape aceeași cu implementarea lui pentru Array, pe care am creat-o în secțiunea 2.3:

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

Operatorii de intrare și de ieșire sunt implementați ca funcții separate, nu ca membri de clasă. (Vom discuta de ce în secțiunea 15.2. Secțiunile 20.4 și 20.5 vorbesc despre supraîncărcarea instrucțiunilor de intrare și de ieșire ale bibliotecii iostream.) Instrucțiunea noastră de intrare nu poate citi mai mult de 4095 de caractere. setw () este un manipulator predefinit, citește un anumit număr de caractere minus 1 din fluxul de intrare, asigurându-ne astfel că nu ne depășim tamponul intern în Buf. (Capitolul 20 acoperă manipulatorul setw () în detaliu.) Pentru a utiliza manipulatoare, trebuie să includeți fișierul antet corespunzător:

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

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

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

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

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

Să testăm programul oferindu-i un paragraf dintr-o poveste pentru copii scrisă de unul dintre autorii acestei cărți (vom vedea această poveste din nou în capitolul 6). Iată rezultatul programului:

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

Exercițiul 3.26

Există multe repetări în implementările noastre de constructori și sarcini. Încercați să mutați codul duplicat într-o funcție separată de membru privat, așa cum ați făcut în secțiunea 2.3. Asigurați-vă că noua opțiune este funcțională.

Exercițiul 3.27

Modificați programul de testare pentru a număra ș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: // ... Operator String + (const String & rhs) const; // ...);

Top articole similare