Alte tipuri de date

Alias-uri de tip (typedef / using)

Un alias de tip este un alt nume cu care poate fi identificat un tip de dată. În C++ orice tip de dată valid poate avea alias astfel încât să poată fi referit cu un alt identificator.

În C++, există două metode pentru crearea unui alias: prima, moștenită de la limbajul C, folosește cuvântul cheie typedef:

typedef tip_existent noul_nume ;

unde tip_existent este orice tip, fundamental sau derivat, and noul_nume este un identificator nou pentru tipul dat.
De exemplu:

1
2
3
4
typedef char C;
typedef unsigned int WORD;
typedef char * pChar;
typedef char field [50];

Se definesc patru alias-uri de tip: C, WORD, pChar și field pentru char, unsigned int, char* și respectiv char[50]. O dată definite, ele pot fi folosite în orice declarație ca orice tip de dată valid:

1
2
3
4
C un_caracter, alt_caracter, *ptc1;
WORD un_cuvant;
pChar ptc2;
field nume;

O sintaxă mai recentă pentru definirea unui alias de tip a fost introdusă de limbajul C++:

1
using noul_nume = tip_existent ;

De exemplu, aceleași alias-uri ca mai sus s-ar putea defini astfel:

1
2
3
4
using C = char;
using WORD = unsigned int;
using pChar = char *;
using field = char [50];

Alias-urile definite cu typedef și cele definite cu using sunt semantic echivalente. Singura diferență o reprezintă faptul că typedef are anumite limite, când vine vorba de șabloane, limite pe care using nu le are. De aceea, using este mai generic, deși typedef are o istorie mai lungă și este probabil mult mai întâlnit în codul actual.

Să observăm că nici typedef nici using nu creează un tip de dată nou. Ele doar creeează sinonime pentru tipurile existente. Aceasta înseamnă că tipul myword de mai sus, declarat cu tipul WORD, poate fi la fel de bine considerat ca unsigned int; chiar nu contează, căci ambele se referă la același tip.

Alias-urile de tip se pot folosi pentru a reduce lungimea unui tip sau confuzia de nume de tipuri, dar sunt utile mai ales ca instrumente pentru a separa programele de tipurile de date pe care le folosesc. De exemplu, folosirea unui alias pentru int pentru a ne referi la un anumit tip de parametru în loc de folosirea directă a lui int ar permite înlocuirea ușoară cu long (sau alte tipuri de date) într-o versiune ulterioară, fără a fi necesară schimbarea fiecărei instanțe care a folosit tipul respectiv.

Uniuni

Uniunile permit ca o anumită zonă de memorie să fie accesată pentru diverse tipuri de date. Declararea și utilizarea sunt similare cu cele de la structuri, dar funcționalitatea este complet diferită:


union nume_tip {
  tip_membru1 id_membru1;
  tip_membru2 id_membru2;
  tip_memebru3 id_membru3;
  .
  .
} nume_obiecte;


Aceasta creează un nou tip uniune, identificat prin nume_tip, în care toate datele membru ocupă același spațiu fizic de memorie. Dimensiunea acestui tip este egală cu dimensiunea celui mai larg membru. De exemplu:

1
2
3
4
5
union tipuri_t {
  char c;
  int i;
  float f;
} niste_tipuri;

declară un obiect (niste_tipuri) cu trei membri:

1
2
3
niste_tipuri.c
niste_tipuri.i
niste_tipuri.

Fiecare dintre acești membri are un tip de dată diferit. Dar cum toate se referă la aceeași locație de memorie, schimbarea unui membru va afecta valoarea tuturor. Nu este posibilă stocarea de valori diferite în aceeeași manieră ca și cum ar fi variabile independente.

Una din utilizările unei uniuni constă în posibilitatea de accesare a unei valori , fie că este independentă, fie cel mai mic dintre elementele unei structuri sau al unui tablou. De exemplu:

1
2
3
4
5
6
7
8
union mix_t {
  int l;
  struct {
    short hi;
    short lo;
    } s;
  char c[4];
} mix;

Dacă presupunem că în sistemul unde rulează acest program tipul de dată int ocupă 4 bytes, iar tipul short ocupă 2 bytes, uniunea definită mai suspermite accesarea aceluiași grup de 4 bytes: mix.l, mix.s și mix.c, pe care îl putem folosi cum dorim pentru a accesa acești bytes: ca și cum ar fi o singură valoare de tip int sau ca și cum ar fi două valori de tip short sau ca un tablou de elemente de tip char. Exemplul amestecă tipuri, tablouri și structuri într-o uniune pentru a evidenția diverse metro de accesare a informațiilor. Pentru un sistem de tip little-endian, această uniune s-ar putea reprezenta astfel:


Aliniamentul exact și ordinea membrilor unei uniuni depinde de sistem, cu posibilitatea apariției unor probleme de portabilitate.

Uniuni anonime

Când uniunile sunt membri ai unei clase (sau structuri) pot fi declarate fără nume. În acest caz, ele devin uniuni anonime și membrii lor din cadrul obiectelor sunt accesibili direct prin id-urile de membru. De exemplu, să vedem diferența dintre următoarele două declarații de structuri:

structură cu uniune obișnuităstructură cu uniune anonimă
struct carte1_t {
char titlu[50];
char autor[50];
union {
float dolari;
int yen;
} pret;
} carte1;
struct carte2_t {
char titlu[50];
char autor[50];
union {
float dolari;
int yen;
};
} carte2;

Singura diferență între cele două constă în faptul că prima conține o uniune denumită (pret), în timp ce pentru a doua uniunea nu are un nume. Aceasta afectează modalitatea de accesare a membrilor dolari și yen ai unui obiect de acest tip. Pentru un obiect de primul tip (cu o uniune obișnuită), se procedează astfel:

1
2
carte1.pret.dolari
carte1.pret.

în timp ce pentru un obiect de al doilea tip (cu o uniune anonimă), se face referirea astfel:

1
2
carte2.dolari
carte2.

Să evidențiem, din nou, faptul că fiind o uniune (nu o structură), membrii dolari și yen partajează aceeași locație de memorie, deci nu se pot reține simultan două valori diferite. Valoarea lui pret se exprimă în dolari sau yen, dar nu simultan în ambele.

Tipuri enumerare (enum)

Tipurile enumerare sunt tipuri definite ca un set de identificatori personalizați, denumiți enumeratori, set care conține valorile posibile. Obiecte de tip enumerare pot lua orice valoare dintre enumeratori.

Sintaxa este:


enum nume_tip {
  valoare1,
  valoare2,
  valoare3,
  .
  .
} denumiri_obiecte;


Aceasta creează tipul de dată nume_tip, care poate avea pe oricare dintre valoare1, valoare2, valoare3, ... ca valoare. Obiectele (variabilele) de acest tip pot fi instanțiate direct prin denumiri_obiecte.

De exemplu, s-ar putea defini un nou tip de variabilă culori_t pentru a memora culori, folosind următoarea sintaxă:

1
enum culori_t {negru, albastru, verde, cyan, rosu, mov, galben, alb};

Să observăm că definiția nu include niciun alt tip, nici fundamental, nici derivat. Altfel spus, se creează de la zero un tip de dată absolut nou, fără a necesita vreun alt tip existent. Valorile pe care le pot lua variabilele de acest tip culori_t sunt doar cele cuprinse între acoladele definiției. De exemplu, după definirea tipului enumerare culori_t, devin valide următoarele expresii:

1
2
3
4
culori_t culoare;
 
culoare = albastru;
if (culoare == verde) culoare = rosu;

Valorile unui tip enumerare definit cu enum se convertesc implicit la numere întregi de tip int și invers. De fapt, elementelor unei enum li se atribuie întotdeauna o valoare numerică întreagă cu care vor fi echivalente și pentru care devin un alias. Dacă nu se precizează explicit, valoarea întreagă atribuită primului element al enumerării este 0, valoarea pentru al doilea este 1, pentru al treilea 2, și așa mai departe... De aceea, pentru tipul de dată culois_t definit mai sus, negru este echivalent cu 0, albastru cu 1, verde cu 2, și așa mai departe...

Se poate priciza o anumită valoare pentru fiecare dintre elementele unui tip enumerare. Dacă pentru unul dintre elemente nu se precizează valoarea, atunci se consideră ca fiind valoarea anterioară incrementată cu 1. De exemplu:

1
2
3
enum luni_t { ianuarie=1, februarie, martie, aprilie,
                mai, iunie, iulie, august,
                septembrie, octombrie, noiembrie, decembrie} y2k;

În acest caz, variabila y2k de tip enumerare luni_t poate lua oricare dintre cele 12 valori posibile, de la ianuarie până la decembrie și care sunt echivalente cu valorile de la 1 la 12 (nu de la 0 la 11, pentru că lui ianuarie i s-a atribuit valoarea 1).

Deoarece tipurile de data enumerare definite cu enum sunt convertibile implicit la int, iar fiecare dintre valorile enumeratorilor este de fapt de tip int, nu se poate face distincție între 1 și ianuarie - ele reprezintă exact aceeași valoare, de același tip. Motivația este de ordin istoric și se moștenește de la limbajul C.

Tipuri enumerare cu enum class

Dar, în C++, este posibilă crearea unui tip enum real, care nici nu este convertibil implicit la int și nici nu are valorile enumeratorilor de tip int, ci de exact tipul definit în el însuși, păstrându-se siguranța informației. Asemenea definiții se implementează cu enum class (sau enum struct) în loc de enum:

1
enum class Culori {negru, albastru, verde, cyan, rosu, mov, galben, alb};

Fiecare dintre valorile enumeratorilor unui tip enum class trebuie inclusă în tipul de dată (acest lucru era posibil și cu tipurile enum, dar era opțional). De exemplu:

1
2
3
4
Culori culoare;
 
culoare = Culori::albastru;
if (culoare == Culori::verde) culoare = Culori::rosu;

Tipurile enumerare definite cu enum class au un control mai mare asupra tipurilor de date incluse; pot fi orice tipuri de date întregi, precum char, short sau unsigned int, ceea ce este esențial pentru determinarea dimensiunii tipului. Se preciează prin includerea simbolului două puncte si a tipului de dată, urmat de tipul enumerare. De exemplu:

1
enum class CuloareOchi : char {albastru, verde, maro};

Aici, CuloareOchi este un tip diferit, având dimensiunea egală cu a lui char (1 byte).
Index
Index