Variabile și tipuri de date

Utilitatea programului "Hello World", prezentat în capitolul anterior este discutabilă. Am avut de scris mai multe linii de cod, de compilat, iar apoi de executat programul rezultat, doar pentru a obține afișarea unei propoziții simple în scris pe ecran. Cu siguranță ar fi fost mult mai rapid să introducem propoziția noi înșine.

Cu toate acestea, programarea nu se limitează numai la imprimarea de texte simple pe ecran. În scopul de a merge un pic mai departe cât și pentru a deveni capabili să scriem programe pentru sarcini utile, care într-adevăr ne salvează, avem nevoie să introducem conceptul de variablă.

Să ne imaginăm că: vă rog să vă amintiți numărul 5, apoi să vă cer să memorați și numărul 2, în același timp. Tocmai ați salvat două valori diferite în memorie (5 și 2). Acum, dacă vă rog să adăugați 1 la primul număr spus, ar trebui să fie reținute în memorie numerele 6 (obținut din 5 + 1) și 2. Apoi, am putea, de exemplu, să scădem aceste valori și să obținem rezultatul 4.

Întregul proces descris mai sus este un exemplu relativ la ceea ce poate face un calculator cu două variabile. Același proces poate fi exprimat în C ++ cu următorul set de instrucțiuni:

1
2
3
4
a = 5;
b = 2;
a = a + 1;
rezultat = a - b;

Desigur, acesta este un exemplu foarte simplu, deoarece am folosit doar două valori întregi mici, dar să ținem cont că un calculator poate stoca simultan milioane de numere precum acestea și poate efectua operațiuni matematice sofisticate cu ele.

Putem defini acum o variabilă ca o zonă din memorie în care stocăm o valoare.

Fiecare variabilă are nevoie de un nume care o identifică și o distinge de celelalte. De exemplu, în codul anterior numele de variabile au fost a, b și rezultat, dar am fi putut numi variabilele cu orice nume doream, atâta timp cât acestea ar fi fost identificatori valizi in limbajul C ++.

Identificatori

Un identificator valid este o secvență de una sau mai multe litere, cifre sau caractere de subliniere (_). Spații, semne de punctuație și simboluri nu pot face parte dintr-un identificator. În plus, identificatorii încep întotdeauna cu o literă. Ei pot începe, de asemenea, cu un caracter de subliniere (_), dar astfel de identificatori sunt considerați rezervați pentru cuvinte cheie (specifice compilatorului sau pentru date de identificare externe). În nici un caz, nu poate începe cu o cifră.

C ++ folosește o serie de cuvinte cheie pentru a identifica operațiile și descrierile de date; prin urmare, identificatorii creați de un programator nu pot folosi aceste cuvinte cheie. Cuvintele cheie rezervate standard care nu pot fi utilizate de către programator pentru identificatorii creați sunt:

alignas, alignof, and, and_eq, asm, auto, bitand, bitor, bool, break, case, catch, char, char16_t, char32_t, class, compl, const, constexpr, const_cast, continue, decltype, default, delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend, goto, if, inline, int, long, mutable, namespace, new, noexcept, not, not_eq, nullptr, operator, or, or_eq, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static, static_assert, static_cast, struct, switch, template, this, thread_local, throw, true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while, xor, xor_eq

Anumite compilatoare pot avea, de asemenea, cuvinte cheie rezervate specifice suplimentare.

Foarte important: limbajul C ++ este un limbaj de tip "sensitive case". Asta înseamnă că un identificator scris cu litere mari nu este echivalent cu un altul cu același nume, dar scris cu litere mici. Astfel, de exemplu, variabila REZULTAT nu este aceeași cu variabila rezultat sau cu variabila Rezultat. Acestia sunt trei identificatori diferiți identificand trei variabile diferite.

Tipuri de date fundamentale

Valorile variabilelor sunt stocate undeva într-o locație neprecizată în memoria calculatorului (adresele sunt numere în baza 16). Programul nostru nu are nevoie să știe care este locația exactă unde este stocată o variabilă; o poate referi pur și simplu prin numele acesteia. Dar programul trebuie să știe care este tipul de date stocate în variabilă. Nu e același lucru stocarea unui număr întreg ca și stocarea unei litere sau a unui număr mare în virgulă mobilă; chiar dacă acestea sunt toate reprezentate folosind cifre unu și zero, ele nu sunt interpretate în același mod și, în multe cazuri, ele nu ocupă aceeași cantitate de memorie.

Tipurile de date fundamentale sunt tipuri de bază recunoscute direct de limbaj și reprezintă unitățile de stocare de bază sprijinite nativ de cele mai multe sisteme. Acestea pot fi clasificate în principal astfel:
  • Tipuri caracter: Acestea pot reprezenta un singur caracter, cum ar fi 'A' sau '$'. Tipul cel mai de bază este char, care este un caracter (reprezentat pe un octet). Alte tipuri sunt de asemenea prevăzute de caractere mai largi.
  • Tipuri numerice întregi: Ele pot stoca o valoare număr întreg, cum ar fi 7 sau 1024. Există într-o mare varietate de dimensiuni și pot fi cu semn (signed)sau fără semn(unsigned), în funcție de capacitatea de a suporta sau nu valori negative.
  • Tipuri pentru numere zecimale: Pot reprezenta valori reale, cum ar fi 3.14 sau 0.01, cu diferite grade de precizie; în funcție de aceasta se decide care dintre cele trei va fi folosit.
  • Tip boolean: Tipul boolean, cunoscut în C ++ ca bool, poate reprezenta date care iau numai una din cele două stări: true sau false.

Aici este lista completă a tipurilor fundamentale în C ++:
GrupNumele tipurilor*Observații privind dimensiunea / precizia
Tipuri caractercharExact un byte în dimensiune. Cel puțin 8 biți.
char16_tNu mai mic decât char. Cel puțin 16 biți.
char32_tNu mai mic decât char16_t.Cel puțin 32 biți.
wchar_tPoate reprezenta cea mai mare mulțime de caractere suportată.
Tipuri întregi cu semn(signed)signed charAcceași dimensiune ca și char. Cel puțin 8 biți.
signed short intNu mai mic decât char. Cel puțin 16 biți.
signed intNu mai mic decât short. Cel puțin 16 biți.
signed long intNu mai mic decât int. Cel puțin 32 biți.
signed long long intNu mai mic decât long. Cel puțin 64 biți.
Tipuri întregi fără semn (unsigned)unsigned char(aceeași dimensiune ca și corespondentul cu semn)
unsigned short int
unsigned int
unsigned long int
unsigned long long int
Tipuri pentru numere zecimalefloat
doublePrecizie nu mai mică decât float
long doublePrecizie nu mai mică decât double
Tip booleanbool
Tip vidvoidfără spațiu
Pointer nuldecltype(nullptr)

* The names of certain integer types can be abbreviated without their signed and int components - only the part not in italics is required to identify the type, the part in italics is optional. I.e., signed short int can be abbreviated as signed short, short int, or simply short; they all identify the same fundamental type.

În fiecare dintre grupele de mai sus, diferența dintre tipuri este doar dimensiunea lor (de exemplu, cât de mult spațiu ocupă în memorie): primul tip din fiecare grup este cel mai mic, iar ultimul este cel mai mare. Fiecare tip este cel puțin la fel de mare ca și cel precedent din cadrul aceluiași grup, excepție făcând tipurile dintr-un grup care au aceleași proprietăți.

Observăm în panoul de mai sus că, în afară de char (care are o dimensiune de exact un octet), nici unul dintre tipurile fundamentale nu are o dimensiune standard specificată (cel mult o dimensiune minimă). De aceea, nu este necesar ca tipul să aibă exact dimensiunea minimă (și chiar nu are, în multe cazuri). Aceasta nu înseamnă că tipurile sunt de dimensiune nedeterminată, ci doar că nu există o dimensiune standard pentru toate compilatoarele și mașinile; fiecare compilator implementează acea dimensiune care se potrivește arhitecturii pe care va rula programul. Această specificație, mai mult decât generică, privind dimensiunea tipurilor de date îi dă limbajului C++ o mare flexibilitate, astfel încât poate fi adaptat să lucreze optim pe tot felul de platforme, atât în prezent, cât și în viitor.

Dimensiunile de tip de mai sus sunt exprimate în biți; cu cât are mai mulți biți, cu atât mai multe valori distincte pot fi reprezentate cu ajutorul tipului respectiv, dar, în același timp, consumă și mai mult spațiu de memorie:

DimensiuneValori unic reprezentabileNotes
8-bit256= 28
16-bit65 536= 216
32-bit4 294 967 296= 232 (~4 billion)
64-bit18 446 744 073 709 551 616= 264 (~18 billion billion)

Pentru tipurile întregi, a avea mai multe valori reprezentabile înseamnă un interval de valori mai mari; de exemplu, un tip întreg fără semn pe 16 biți ar putea reprezenta 65536 de valori distincte în intervalul de la 0 la 65535, în timp ce tipul corespunzător cu semn ar putea reprezenta, în cele mai multe cazuri, valori între -32768 și 32767. Să observăm că intervalul de valori pozitive este aproximativ jumătate pentru tipul cu semn față de tipurile fără semn datorită faptului că unul dintre cei 16 biți este folosit pentru semn; aceasta este o diferență aproape neglijabilă în domeniu și aproape justifică folosirea unui tip fără semn bazându-ne pe intervalul de valori pozitive pe care îl poate reprezenta.

Pentru numerele zecimale, dimensiunea afectează precizia, dat fiind că baza și exponentul poate avea mai mulți sau mai puțini biți. For floating-point types, the size affects their precision, by having more or less bits for their significant and exponent.

Dacă dimensiunea și precizia unui tip nu reprezintă o problemă, atunci If the size or precision of the type is not a concern, then char, int și double sunt cele pe care le putem alege pentru a reprezenta respectiv: caractere, întregi și numere zecimale. Celelate tipuri din grupele respective se folosesc numai în cayuri foarte speciale.

Proprietățile tipurilor de date fundamentale, într-o anumită implementare pentru un sistem și un compilator pot fi determinare folosind clasele numeric_limits (vezi antetul standard <limits>). Dacă, din unele motive, avem nevoie de dimensiunile specifice tipurilor, biblioteca definește anumite alias-uri de dimensiune fixă în antetul <cstdint>.

Tipurile descrise mai sus (caractere, întregi, numere zecimale și boolean) sunt cunoscute ca tipuri aritmetice. Dar mai există încă două tipuri fundamentale: void, care marchează absența tipului, și tipul nullptr, care este un tip speciald e pointer. Ambele tipuri vor fi discutate într-un capitol următor despre pointeri.

C++ suportă o mare varietate de tipuri construite pe baza tipurilor de date fundamentale discutate mai sus; aceste alte tipuri sunt cunoscute ca tipuri de date compuse și reprezintă unul dintre atu-urile limbajului C++. Le vom vedea, de asemenea, în capitolele următoare.

Declarații de variabile

C++ este un limbaj de tip ”strongly-typed” și necesită ca fiecare variabilă să fie declarată cu tipul de dată corespunzător, înainte de prima sa utilizare. Declarația informează compilatorul cu privire la dimensiunea pe care să o rezerve în memorie pentru acea variabilă și îi spune cum să interpreteze valorile sale. Sintaxa pentru declararea unei noi variabile în C++ este extrme de simplă: doar scriem tipul de dată urmat de numele variabilei (adică de identificatorul său). De exemplu:

1
2
int a;
float numar;

Acestea sunt două declarații valide de variabile. Prima declară o variabilă de tip int al cărei identificator este a. A doua declară o variabilă de tip float al cărei identificator este numar. Odată declarate, variabilele a și numar pot fi folosite în interiorul domeniului lor din program.
Dacă avem mai multe variabile de același tip, ele pot fi toate declarate într-o singură instrucțiune separaându-le identificatorii cu ajutorul virgulei. De exemplu:

1
int a, b, c;

Aceasta declară trei variabile (a, b și c), toate fiind de tip int și având exact același sens ca și:

1
2
3
int a;
int b;
int c;

Pentru a înțelege cum se comportă într-un program, să ne uităm puțin la întregul cod C++ al exemplului cu exercițiul de memorie mintal propus la începutul acestui capitol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// lucrul cu variabile

#include <iostream>
using namespace std;

int main ()
{
  // declararea variabilelor:
  int a, b;
  int rezultat;

  // calcul:
  a = 5;
  b = 2;
  a = a + 1;
  rezultat = a - b;

  // afisarea rezultatului:
  cout << rezultat;

  // terminarea programului:
  return 0;
}
4

Nu vă temeți dacă altceva în afară de declarațiile de variabile mi s-a părut puțin ciudat. În capitolele următoare vom explica mai în detaliu.

Inițializări de variabile

Când am declarat variabilele din exemplul de mai sus, ele aveau valori nedeterminate până în momentul în care li s-au atribuit valori pentru prima oară. Dar se poate să atribuim o anumită valoare unei variabile chiar în momentul declarării. Aceasta se numește inițializarea variabilei.

În C++, există trei modalități de inițializare a variabilelor. Toate sunt echivalente și au rămas datorită evoluției în timp a limbajului:

Prima dintre ele, cunoscută ca inițializare în stilul c (deoarece a fost moștenită din limbajul C), constă în adăugarea unui simbol egal urmat de valoarea care se atribuie la inițializare:

tip identificator = valoare_initiala;
De exemplu, pentru a declara o variabilă de tip int, numită x, și pentru a o inițializa cu valoarea zero chiar în momentul declarării, putem scrie:

1
int x = 0;

A doua metodă, cunoscută ca inițializare prin constructor (introdusă de limbajul C++), cuprinde valoarea inițială între paranteze (()):

tip identificator (valoare_initiala);
De exemplu:

1
int x (0);

În fine, a treima metodă, cunoscută care inițializare uniformă, asemănătoare celei de mai sus, dar care folosește acolade ({}) în loc de paranteze (aceasta a fost introdusă de revizuirea standardului C++, în 2011):

tip identificator {valaore_initiala};
De exemplu:

1
int x {0};

Toate cele trei modalități de inițializare a variabilelor sunt valide și echivalente în C++.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// initializari de variabile

#include <iostream>
using namespace std;

int main ()
{
  int a=5;               // valoare initiala: 5
  int b(3);              // valoare initiala: 3
  int c{2};              // valoare initiala: 2
  int rezultat;            // valoare initiala nedeterminata

  a = a + b;
  rezultat = a - c;
  cout << rezultat;

  return 0;
}
6

Deducție de tip: auto și decltype

Când o nouă variabilă este inițializată, compilatorul identifică automat tipul de dată al variabilei, conform cu inițializarea. De aceea, este suficient să folosim auto ca specificator de tip pentru acea variabilă:

1
2
int foo = 0;
auto bar = foo;  // la fel ca: int bar = foo; 

Aici, bar este declarată ca având un tip auto; de aceea, tipul său bar este tipul valorii folosite pentru inițializare: în acest caz se folosește tipul lui foo, care este int.

Variabilele care nu sunt inițializate pot face uz, de asemenea, de deducția tipului cu specificatorul decltype:

1
2
int foo = 0;
decltype(foo) bar;  // la fel ca: int bar; 

Aici, bar este declarată ca având același tip cu foo.

auto și decltype sunt caracteristici puternice ale limbajului recent adăugate. Dar deducția de tip merită să fie folosită numai când nu se poate obține prin alte mijloace sau când folosirea ei îmbunătățește claritatea programului. Niciunul dintre exemplele de mai sus nu se încadrează în aceste situații. Din contră, ele chiar înrăutățesc claritatea, căci atunci când sitim sursa ar trebui să căutăm după tipul lui foo pentru a realiza care este de fapt tipul lui bar.

Introducere în tipul string

Tipurile de date fundamentale reprezintă tipurile de bază gestionate de mașina pe care ar putea rula programul. Dar unul din punctele forte ale limbajului C++ este multitudinea de tipuri de date compuse, construite chiar cu ajutorul tipurilor de date fundamentale.

Un exemplu de tip compus este clasa string. Variabilele de acest tip pot memora o secvență de caractere, precum cuvinte sau propoziții. O proprietate foarte utilă!

O primă diferență față de tipurile de date fundamentale este aceea că pentru a declara și folosi un obiect (variabilă de tip obiect) de acest tip, în program trebuie să includem antetul (fișierul)în care este definit tipul respectiv în biblioteca standard (antetul <string>):

// primul meu string
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string sirul_meu;
  sirul_meu = "Acesta este un string";
  cout << sirul_meu;
  return 0;
}
Acesta este un string

După cum am văzut în exemplul anterior, string-urile pot fi inițializate cu orice notație validă, exact cum variabilele de tip numeric pot fi inițializate cu orice notație numerică. Ca și la tipurile fundamentale, toate modalitățile de inițializare rămân valide:

1
2
3
string sirul_meu = "Acesta este un string";
string sirul_meu ("Acesta este un string");
string sirul_meu {"Acesta este un string"};

String-urile suportă toate operațiile de bază pe care le avem și la tipurile fundamentale, precum declarații fără inițializare și schimbarea valorii în timpul execuției programului:

// primul meu string
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string sirul_meu;
  sirul_meu = "Acesta este continutul initial al sirului";
  cout << sirul_meu << endl;
  sirul_meu = "Acesta este un al continut al sirului";
  cout << sirul_meu << endl;
  return 0;
}
Acesta este continutul initial al sirului
Acesta este un al continut al sirului

Observație: inserarea manipulatorului endl termină (end ) linia (afișarea caracterului newline și curățarea stream-ului).

Clasa string este un tip compus. După cum puteți vedea în exemplul de mai sus, tipurile compuse sunt folosite la fel ca și tipurile de date fundamentale: se folosește aceeași sintaxă pentru declarații și inițializări.

Pentru mai multe detalii privind string-urile standard C++ vedeți referința la clasa string.
Index
Index