Ogni linguaggio di programmazione definisce dei formati per la rappresentazione dei dati. C++ è un linguaggio caratterizzato da tipizzazione statica: ciò significa che ogni ad ogni variabile deve essere associato un tipo.
Poichè durante l'esecuzione di un programma tutto si riduce ad una sequenza di bit, deve esistere un meccanismo che ci consenta di interpretare correttamente il significato di queste sequenze, e di definire un insieme di operazioni su di esse.
La definizione dei tipi risolve questo problema e ci consente di operare su differenti tipi di dato.
Tipi primitivi o fondamentali
I tipi primitivi o fondamentali sono quelli che consentono di rappresentare informazioni "semplici", come ad esempio un singolo valore numerico, testo, valori booleani etc.
Ogni definizione di tipo contempla le seguenti proprietà:
- la dimensione, espressa in numero di bit necessari per la rappresentazione di una variabile;
- l'intervallo di valori rappresentabili;
- la distribuzione dei valori rappresentabili;
- nel caso dei tipi numerici, il segno.
Ad alcuni tipi, in particolare quelli numerici, possono essere applicati dei modificatori che ne alterano la dimensione ed il segno rispetto alle caratteristiche predefinite del tipo. A queste modifiche, come vedremo, corrisponde una variazione del'intervallo dei valori rappresentabili.
Numeri interi
Il tipo int è usato per rappresentare numeri naturali o numeri interi relativi. È possibile alterare la dimensione e/o il segno del tipo int
mediante l'uso degli appositi modificatori short, long, long long, signed e unsigned.
La dimensione minima del tipo intero (con o senza modificatori) è definita dallo standard C++. La dimensione effettiva dipende dal data model in uso. Quest'ultimo è semplicemente la convezione usata per attribuire ai tipi una dimensione fissa dipendentemente dall'architettura del processore (x86 o x64) e dal sistema operativo.
I data model vengono comunemente identificati da una sigla composta dai simboli I (intero), L (long), P (pointer), e LL (long long), seguita dal numero di bit usati per la rappresentazione (32 o 64). Ad esempio, ILP32 identifica il data model in cui interi, long e puntatori hanno dimensione 32. La tabella seguente mostra la dimensione del tipo int
per i data model più comuni.
Tipo | C++ standard | ILP32 | LLP64 | ILP64 |
---|---|---|---|---|
(signed | unsigned) short int | min. 16 bit | 16 bit | 16 bit | 16 bit |
(signed | unsigned) int | min. 16 bit | 16 bit | 32 bit | 32 bit |
(signed | unsigned) long int | min. 32 bit | 32 bit | 32 bit | 64 bit |
(signed | unsigned) long long int | min. 64 bit | 64 bit | 64 bit | 64 bit |
Caratteri
Il tipo char è usato per la rappresentazione di singoli caratteri o stringhe di testo. Per il tipo char
è possibile usare i modificatori di segno, ma non quelli per la dimensione, che è tradizionalmente pari a 8 bit, sufficiente per la codifica ASCII.
Per codifiche differenti, ad esempio UTF, esistono i tipi wchar_t (wide char), char16_t e char32_t. La tabella seguente riporta le caratteristiche principali:
Tipo | Dimensione | Note |
---|---|---|
(signed | unsigned) char | 8 bit | Quando usato senza modificatori di segno, è il compilatore che sceglie la rappresentazione più efficiente per la piattaforma target |
wchar_t | tipicamente 32 bit | Unica eccezione è Windows, dove questo tipo è a 16 bit |
char16_t | 16 bit | Il formato è unsigned. Introdotto dallo standard c++11 specificamente per la codifica UTF-16 |
char32_t | 32 bit | Il formato è unsigned. Introdotto dallo standard c++11 specificamente per la codifica UTF-32 |
Numeri in virgola mobile
Per i numeri decimali con segno, esistono i tipi float, double e long double:
Tipo | Dimensione | Note |
---|---|---|
float | 32 bit | Singola precisione nel formato definito dallo standard IEEE-754 |
double | tipicamente 64 bit | Doppia precisione nel formato definito dallo standard IEEE-754. Unicamente ei sistemi Windows a 32 bit (ILP32), la dimensione è 32 bit. |
long double | tipicamente 80 bit | Precisione estesaTipicamente nei sistemi Windows la dimesione è 64 bit, ciò rende indifferente l'uso di double e long double. |
Gli aggettivi "singola" e "doppia" sono usati impropriamente in riferimento alla precisione, ma sono in realtà da riferire alla dimensione in bit che influenza non solo l'intervallo di valori rappresentabili, ma anche la loro distribuzione. Esistono delle sequenze di bit riservate alla rappresentazione di valori speciali, come +/- INFINITY, NaN (Not a Number), e lo zero negativo, che possono essere il risultato di operazioni artimetiche quali 1/0 o 0/0.
Valori booleani
Il tipo bool è usato per rappresentare due valori: vero (true
) e falso (false
). La dimensione è tipicamente pari a 8 bit.
Void
Il tipo void è usato per indicare un set di valori vuoto o indefinito. È un tipo incompleto, nel senso che non può essere allocato, non si possono usare reference o array di elementi void. In realtà è definibile solo come puntatore o come tipo di ritorno per funzioni che non restituiscono un risultato.
Dimensioni e limiti dei tipi fondamentali
Sebbene la dimensione in bit dei tipi fondamentali sia specificata dallo standard, esistono delle eccezioni o interpretazioni particolari. Nel caso di incertezze possiamo ricorrere a due strumenti che il linguaggio ci mette a disposizione:
- l'operatore sizeof(tipo);
std::numeric_limits
.
Il seguente listato produce una tabella con la dimensione in byte ed i valori massimi e minimi di molti tipi fondamentali. In questa sede tralasciamo volutamente la spiegazione dettagliata riga per riga e ci concetriamo soltanto sul risultato prodotto.
#include <iostream>
#include <limits>
#include <iomanip>
using namespace std;
int main()
{
int w = 20;
cout << setw(w) << "TIPO"
<< setw(w) << "BYTES"
<< setw(w) << "MIN"
<< setw(w) << "MAX" << endl;
cout << setw(w) << "int"
<< setw(w) << sizeof(int)
<< setw(w) << numeric_limits<int>::min()
<< setw(w) << numeric_limits<int>::max() << endl;
cout << setw(w) << "char"
<< setw(w) << sizeof(char)
<< setw(w) << (int) numeric_limits<char>::min()
<< setw(w) << (int) numeric_limits<char>::max() << endl;
cout << setw(w) << "float"
<< setw(w) << sizeof(float)
<< setw(w) << numeric_limits<float>::min()
<< setw(w) << numeric_limits<float>::max() << endl;
cout << setw(w) << "double"
<< setw(w) << sizeof(double)
<< setw(w) << numeric_limits<double>::min()
<< setw(w) << numeric_limits<double>::max() << endl;
cout << setw(w) << "bool"
<< setw(w) << sizeof(bool)
<< setw(w) << numeric_limits<bool>::min()
<< setw(w) << numeric_limits<bool>::max() << endl;
/* Attenzione: queste righe generano errori di compilazione!
cout << setw(w) << "void"
<< setw(w) << sizeof(void)
<< setw(w) << numeric_limits<void>::min()
<< setw(w) << numeric_limits<void>::max() << endl;
*/
return 0;
}
I tipi composti
I tipi composti sono quelli costituiti per aggregazione a partire da tipi fondamentali. Questi tipi sono tipicamente definiti mediante l'ausilio dei costrutti struct o class e template, che impareremo a conoscere meglio nel seguito.
Lo standard C++ incorpora non solo le caratteristiche sintattiche del linguaggio, ma anche la definizione di alcune librerie, la cui implementazione è poi demandata alle singole piattaforme.
Una di queste è la String library, che include la definizione del tipo string.
Stringhe
Il tipo string consente di manipolare seguenze di caratteri in modo più agevole rispetto alla gestione di un array di char. Ciò è possibile grazie alla definizione di alcuni metodi che ci consentono di stabilire la dimensione della stringa, concatenare stringhe differenti, cercare e/o sostituire sequenze di caratteri.
Poichè il tipo string
è un tipo composto, esso si basa sulla definizione di tipi primitivi, ed in particole i tipi di carattere. Esistono numerose varianti del tipo string
, a seconda del tipo base sottostante:
std:string
(char
)std:wstring
(wchar_t
)std:u16string
(char16_t
)std:u32string
(char32_t
)
Quello più usato di frequente è std:string
. Per l'uso della string library è necessario includere il relativo header file con la seguente istruzione nei propri sorgenti.
#include <string>