MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Tipizare statică și conversii de tipuri

C++ este un limbaj de programare cu tipizare statică. Asta înseamnă că, dacă am definit pentru o variabilă un anumit tip de date, ulterior nu vom putea modifica acel tip. În consecință, variabila poate primi doar valori de tipul pe care îl reprezintă. Totuși, deseori apare necesitatea de a atribui unei variabile valori de alte tipuri. În astfel de cazuri se aplică conversii de tipuri.

O serie de conversii pot fi efectuate de compilator în mod implicit, adică automat. De exemplu:

#include <iostream>
 
int main()
{
    unsigned int age{25};
    std::cout << "age = " << age << std::endl;
}

Aici, variabila age este de tip unsigned int și, convențional, stochează vârsta. Această variabilă este inițializată cu valoarea 25, iar toți literalii întregi fără sufixe reprezintă implicit tipul int (signed int). Dar compilatorul știe cum să convertească valoarea 25 în unsigned int, astfel că în acest caz nu vor exista probleme.

Dar să analizăm un alt exemplu:

#include <iostream>
 
int main()
{
    unsigned int age{-25};
    std::cout << "age = " << age << std::endl;
}

Aici, variabilei age i se atribuie valoarea -25 – un număr negativ, în timp ce tipul variabilei unsigned int presupune utilizarea numai a numerelor pozitive. În acest caz, ne vom confrunta cu o eroare de compilare. De exemplu, ieșirea compilatorului g++:

error: narrowing conversion of '-25' from 'int' to 'unsigned int' [-Wnarrowing]

Exemple de conversii implicite

Să analizăm cum se efectuează unele conversii de bază:

  • Unei variabile de tip bool i se atribuie o valoare de alt tip. În acest caz, variabila primește false dacă valoarea este 0. În toate celelalte cazuri, variabila primește true.
bool a = 1;     // true
bool b = 0;     // false
bool c = 'g'; // true
bool d = 3.4;   // true
  • Unei variabile numerice sau de tip caracter i se atribuie o valoare de tip bool. În acest caz, variabila va primi 1 dacă valoarea este true, sau va primi 0 dacă valoarea atribuită este false.
int c = true;       // 1  
double d = false;   // 0
  • Unei variabile întregi i se atribuie un număr fracționar. În acest caz, partea fracționară de după virgulă este ignorată.
int a = 3.4;        // 3  
int b = 3.6;        // 3
  • Unei variabile de tip număr cu virgulă mobilă i se atribuie un număr întreg. În acest caz, dacă numărul întreg conține mai mulți biți decât poate stoca tipul variabilei, o parte din informație se pierde.
float a = 35005;                // 35005  
double b = 3500500000033;       // 3.5005e+012
  • Unei variabile de tip fără semn (unsigned) i se atribuie o valoare în afara intervalului său. În acest caz, rezultatul va fi restul împărțirii modulo. De exemplu, tipul unsigned char poate stoca valori între 0 și 255. Dacă i se atribuie o valoare în afara acestui interval, compilatorul va atribui restul împărțirii modulo 256 (deoarece unsigned char poate stoca 256 de valori). Astfel, dacă i se atribuie valoarea -5, variabila de tip unsigned char va primi valoarea 251.
unsigned char a = -5;           // 251
unsigned short b = -3500;       // 62036
unsigned int c = -50000000;     // 4244967296
  • Unei variabile de tip cu semn (signed) i se atribuie o valoare în afara intervalului său. În acest caz, rezultatul nu este determinat. Programul poate funcționa corect sau poate produce rezultate incorecte.

Conversii în operațiile aritmetice

În operațiile aritmetice este necesar ca ambii operanzi să fie de același tip. Dacă operanzii au tipuri diferite, compilatorul va alege automat operandul cu tipul cu interval mai mic de valori și va încerca să-l convertească la tipul celuilalt operand, care are un interval mai mare de valori. Din punctul de vedere al conversiilor în operații, tipurile pot fi aranjate după prioritate (de la cel mai înalt la cel mai scăzut) astfel:

  • long double
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int

Așadar, dacă într-o operație participă un număr de tip float și unul de tip long double, compilatorul va converti automat operandul de tip float în tip long double (care, conform listei de mai sus, are o prioritate mai mare).

Operanzii de tip char, signed char, unsigned char, short și unsigned short sunt întotdeauna convertiți în timpul operațiilor cel puțin în tipul int.

De exemplu, un programator a câștigat 100,2$ într-o zi de lucru de 8 ore; să calculăm cât a câștigat pe oră:

#include <iostream>
 
int main()
{
    double sum {100.2};
    int hours {8};
    double revenuePerHour {sum / hours};
    std::cout << "Revenue per hour = " << revenuePerHour << std::endl;
}

Aici, variabila hours, care este de tip int și stochează numărul de ore, va fi convertită în tipul cu „prioritate mai mare” – double.

Pe de o parte, acest lucru poate părea destul de convenabil. Pe de altă parte, astfel de conversii pot duce la rezultate nedorite. De exemplu:

#include <iostream>
 
int main()
{
    int n {5};
    unsigned int x {8};
    std::cout << "result = " << n - x << std::endl;   // result = 4294967293
}

Aici, în operația n - x, numărul n va fi convertit la tipul cu prioritate mai mare – unsigned int. Formal, această operație returnează 5 - 8 = -3. Însă, în cazul nostru, ambii operanzi și, implicit, rezultatul sunt de tip unsigned int, astfel că, în cele din urmă, rezultatul va fi 4294967293.

Conversii sigure și periculoase

Conversiile în care nu se pierde informație sunt considerate sigure. De regulă, acestea sunt conversii de la un tip cu o capacitate mai mică (mai puțini biți) la un tip cu o capacitate mai mare. În special, următoarele lanțuri de conversii sunt considerate sigure:

  • bool -> char -> short -> int -> double -> long double
  • bool -> char -> short -> int -> long -> long long
  • unsigned char -> unsigned short -> unsigned int -> unsigned long
  • float -> double -> long double

Exemple de conversii sigure:

short a = 'g';      // conversie din char în short  
int b = 10;  
double c = b;       // conversie din int în double  
float d = 3.4;  
double e = d;       // conversie din float în double  
double f = 35;      // conversie din int în double

Dar există și conversii periculoase. În cazul unor astfel de conversii, există riscul de a pierde precizia datelor. De regulă, acestea sunt conversii de la un tip cu o capacitate mai mare la un tip cu o capacitate mai mică.

unsigned int a = -25;           // 4294967271
unsigned short b = -3500;       // 62036

În acest caz, variabilelor a și b li se atribuie valori care depășesc intervalul de valori permise pentru tipurile respective.

În astfel de exemple, multe depind de compilator. În unele cazuri, compilatoarele emit un avertisment în timpul compilării, totuși programul poate fi compilat cu succes. În alte cazuri, compilatorul nu emite niciun avertisment. De fapt, în aceasta constă pericolul: programul se compilează cu succes, dar există riscul de pierderea preciziei datelor.

Valoarea unei variabile nu este altceva decât un set de biți în memorie, care este interpretat în funcție de tipul său.

Iar pentru tipuri diferite, același set de biți poate fi interpretat în mod diferit. De aceea, este important să luăm în considerare domeniul de valori al unui anumit tip atunci când atribuim o valoare unei variabile.

Dacă este vorba de inițializarea variabilelor, pentru a evita conversiile periculoase care pot duce la pierderea preciziei, se recomandă utilizarea inițializării cu acolade:

unsigned int a {-25};           // ! Eroare  
unsigned short b {-3500};       // ! Eroare  

În acest caz, compilatorul va genera o eroare, iar programul nu va fi compilat.

Conversii explicite de tipuri

Pentru efectuarea conversiilor explicite de tipuri (explicit type conversion) se folosește operatorul static_cast:

static_cast<type>(value)

Acest operator convertește valoarea din parantezele rotunde – value – la tipul specificat între parantezele unghiulare – type. Cuvântul static din denumirea operatorului reflectă faptul că conversia este verificată static, adică în timpul compilării.

Utilizarea operatorului static_cast indică compilatorului că suntem conștienți că în acel loc trebuie aplicată conversia, astfel că chiar și în cazul inițializării cu acolade, compilatorul nu va genera eroare. De exemplu, un programator a câștigat 100,2$ într-o zi de lucru de 8 ore; să calculăm câștigul pe oră, dar ca valoare de tip unsigned int:

#include <iostream>
 
int main()
{
    double sum {100.2};
    unsigned int hours {8};
    unsigned int revenuePerHour { static_cast<unsigned int>(sum/hours) };  // revenuePerHour = 12
    std::cout << "Revenue per hour = " << revenuePerHour<< std::endl;
}

Aici, expresia static_cast<unsigned int>(sum/hours) evaluează rezultatul expresiei sum/hours (care va fi de tip double), iar apoi îl convertește în tipul unsigned int.

Trebuie menționat că, pe vremuri, în epoca dinozaurilor, în C++ se folosea un mod de conversie moștenit din limbajul C:

(tip) valoare

Adică, înaintea valorii care trebuie convertită, între paranteze rotunde era indicat tipul în care trebuie făcută conversia. De exemplu, să folosim această operație în codul prezentat anterior:

#include <iostream>
 
int main()
{
    double sum {100.2};
    unsigned int hours {8};
    unsigned int revenuePerHour { (unsigned int)sum/hours};     // revenuePerHour = 12
    std::cout << "Revenue per hour = " << revenuePerHour<< std::endl;
}

Rezultatul va fi același. Totuși, în C++ modern, această operație a fost practic înlocuită de operatorul static_cast.