Tipo di dato
Da Wikipedia, l'enciclopedia libera.
In informatica un tipo di dato (o semplicemente tipo) è un nome che indica l'insieme di valori che una variabile, o il risultato di un'espressione, possono assumere, e le operazioni che su tali valori si possono effettuare. Dire per esempio che la variabile X è di tipo "numero intero" significa affermare che X può assumere come valori solo numeri interi (appartenenti ad un certo intervallo) e che su tali valori sono ammesse solo certe operazioni (ad esempio le operazioni aritmetiche elementari).
Ogni linguaggio di programmazione consente di usare, in modo più o meno esplicito, un certo numero di tipi di dati predefiniti di uso generale. Inoltre, ogni linguaggio di programmazione fornisce di solito un certo insieme di strumenti per definire nuovi tipi sulla base delle necessità specifiche di un programma.
Può anche accadere, durante la scrittura di un programma, che sia necessario, o utile, "tradurre" una variabile di un certo tipo in una variabile di un altro tipo (l'operazione è detta type casting): in alcuni casi è sufficiente usare appositi costrutti sintattici che alcuni linguaggi mettono a disposizione per questo scopo; in altri casi è necessario scrivere un'apposita funzione che associ i valori di un tipo a quelli dell'altro.
Indice |
[modifica] Linguaggi non tipizzati
I linguaggi di programmazione più semplici, come i linguaggi macchina della maggior parte degli elaboratori o il lambda calcolo puro sono detti non tipizzati in quanto non prevedono tipi o, secondo un altro punto di vista, consentono l'utilizzo dell'unico tipo contenente tutti i valori possibili.
Ad esempio i linguaggi macchina manipolano configurazioni di bit, per le quali l'onere dell'interpretazione (ossia stabilire quali operazioni sono sensate su un valore e quali no) spetta completamente al programmatore: la stessa configurazione di bit nella memoria di un computer potrebbe indicare valori concettualmente diversi per tipo, come ad esempio il numero intero 0, il puntatore nullo o l'istruzione vuota NOP (no operation). Nel lambda calcolo si manipolano funzioni mediante le stesse funzioni e uno stesso termine può rappresentare il valore intero 0, il booleano false ("falso") o la funzione che dati due valori scarta il primo e restituisce il secondo.
[modifica] Linguaggi tipizzati
Nei linguaggi tipizzati è necessario associare alle variabili, alle espressioni e più in generale ai termini dei programmi delle annotazioni o dichiarazioni di tipo. Queste annotazioni di tipo, a seconda del linguaggio, devono essere specificate esplicitamente dal programmatore, oppure possono essere generate in modo automatico dall’interprete o dal compilatore.
Si noti che assegnare un tipo di dato ad una variabile permette di risolvere le ambiguità viste per i linguaggi non tipizzati: se si cercasse di utilizzare un valore intero laddove è richiesto un puntatore, si otterrebbe normalmente un errore di tipo o violazione di tipo.
[modifica] Controllo sui tipi: linguaggi fortemente e debolmente tipizzati
Il "controllo sui tipi" (type checking) è il procedimento che permette di verificare se i vincoli imposti dai tipi sono soddisfatti. Tale verifica può avvenire sia durante la compilazione, e si parla in tal caso di "controllo statico" (in inglese "static check), sia durante l'esecuzione del programma, e si parla in tal caso di "controllo dinamico" (in inglese "dynamic check"). Alcuni linguaggi operano alcuni controlli di tipo in fase di compilazione e altri durante l'esecuzione.
Se un linguaggio impone regole rigide sui tipi, impedendo qualsiasi uso dei dati incoerente col tipo specificato in fase di dichiarazione, si dice che esso è "fortemente tipizzato", in caso contrario che è "debolmente tipizzato".
[modifica] Tipizzazione statica e dinamica
Si parla di "tipizzazione statica" quando a una variabile viene associato rigidamente un tipo che rimane lo stesso per tutto il programma, di "tipizzazione dinamica" quando una variabile può cambiare tipo durante l'esecuzione del programma.
C, C++ e Java sono, per fare qualche esempio, linguaggi con tipizzazione statica, mentre Lisp, Visual Basic e Python sono linguaggi con tipizzazione dinamica.
Per vedere come funziona il controllo sui tipi, si può considerare il seguente esempio in pseudocodice:
var x; // (1) x := 5; // (2) x := "ciao"; // (3)
In questo esempio: (1) dichiara la variabile x, (2) associa a x il valore di tipo intero 5, (3) associa a x il valore di tipo stringa "ciao" (qui si suppone che "intero" e "stringa" siano due tipi). Nella maggior parte dei linguaggi con tipizzazione statica un codice di questo tipo sarebbe illegale, poiché (2) e (3) associano alla variabile x valori appartenenti a tipi diversi; al contrario un linguaggio con tipizzazione totalmente dinamica troverebbe questo codice perfettamente legale. In quest'ultimo caso, ovviamente, la dichiarazione iniziale in (1) avrebbe dovuto specificare, con una qualche sintassi, il tipo da associare a x. Un esempio in Java potrebbe essere come segue:
int x; // (1) x = 5; // (2) x = "ciao"; // (3) -> rifiutata dal compilatore
Come si può intuire, un linguaggio con tipizzazione dinamica consente di catturare "errori di tipo" (cioè errori dovuti a un uso scorretto dei valori che una variabile può assumere) solo durante l'esecuzione del programma.
Si consideri ad esempio il seguente pseudocodice:
var x = 5; // (1) var y = "ciao"; // (2) var z = x + y; // (3)
In questo esempio: (1) associa a x il valore 5, (2) associa a x il valore "ciao" e (3) cerca di sommare x e y. In un linguaggio con tipizzazione dinamica, durante l'esecuzione del frammento di pseudocodice indicato, la variabile x risulterebbe (in quel momento) di tipo intero con valore 5, mentre la variabile y risulterebbe di tipo stringa con valore "ciao" (qui si suppone che "intero" e "stringa" siano due tipi). Se la definizione del linguaggio non ammette l'operazione di addizione fra un intero e una stringa, durante l'esecuzione del programma verrà segnalato un errore.
[modifica] Tipi di dati
I tipi di dati possono essere classificati secondo la struttura in tipi atomici o primitivi e tipi derivati. I tipi primitivi sono i tipi semplici che non possono essere decomposti, come ad esempio numeri interi o booleani; ogni linguaggio tipizzato ne prevede un certo insieme. I tipi derivati si ottengono dai tipi atomici mediante opportuni operatori forniti dal linguaggio: essi includono i tipi strutturati (record) o gli array, ma anche i puntatori di un tipo fissato (in linguaggi come il C), i tipi funzione (specialmente nei linguaggi funzionali), le classi dei linguaggi object-oriented e così via.
Un'altra classificazione suddivide i tipi di dati in predefiniti e definiti dall'utente. Si potrebbe pensare che i tipi predefiniti coincidano con i tipi atomici, mentre i tipi definiti dall'utente siano essenzialmente quelli derivati: in realtà le due classificazioni non sono perfettamente sovrapponibili – ad esempio le enumerazioni in linguaggi come il C sono tipi atomici definiti dall'utente, mentre sempre in C le stringhe sono tipi derivati (dal tipo carattere) ma predefinite dal linguaggio. D'altra parte è quasi sempre vero che i tipi definiti dal programmatore sono necessariamente tipi derivati.
Di seguito elenchiamo e descriviamo alcuni dei tipi di dati più comuni nei linguaggi di programmazione.
[modifica] Booleani
Il tipo booleano prevede due soli valori: true ("vero") e false ("falso"). Questi valori vengono utilizzati in special modo nelle espressioni condizionali per controllare il flusso di esecuzione; inoltre possono essere manipolati con gli operatori booleani AND, OR, NOT e così via.
Anche se in teoria basterebbe un solo bit per memorizzare un valore booleano, per motivi di efficienza si usa in genere un' intera parola di memoria, come per i numeri interi "piccoli" (una parola di memoria a 8 bit, per esempio, può memorizzare numeri da 0 a 255, ma il tipo booleano utilizza solo i valori 0 e 1).
[modifica] Numeri
I tipi di dati numerici includono i numeri interi e i numeri razionali in virgola mobile, che sono astrazioni dei corrispondenti insiemi di numeri della matematica. Quasi tutti i linguaggi includono tipi di dati numerici come tipi predefiniti e forniscono un certo numero di operatori aritmetici e di confronto su di essi.
A differenza degli insiemi numerici della matematica, i tipi di dati numerici sono spesso limitati (includono cioè un massimo e un minimo numero rappresentabile), dovendo essere contenuti in una singola parola (word) di memoria.
[modifica] Caratteri e stringhe
Il tipo carattere contiene, per l'appunto, un carattere: generalmente si riferisce ad un carattere ASCII e viene memorizzato in un byte. Tuttavia in questi anni si sta affermando il nuovo standard Unicode per i caratteri, che prevede 16 bit (che generalmente corrisponde a una parola di memoria) per la rappresentazione di un singolo carattere. Molti linguaggi tradizionali si sono adattati a questo standard emergente introducendo, in aggiunta al tipo "carattere a 8 bit", un nuovo tipo "carattere a 16 bit", talvolta detto wide char (il linguaggio Java è invece un esempio di linguaggio moderno che gestisce direttamente tutti i caratteri nel formato Unicode).
Le stringhe sono sequenze di caratteri di lunghezza finita. I linguaggi possono fornire operazioni per la concatenazione di stringhe, la selezione di sottostringhe di una stringa data, ecc.
[modifica] Enumerazioni
Le enumerazioni sono insiemi finiti di identificatori, generalmente specificati dal programmatore. In linguaggi come C e C++ è possibile definire dei tipi enumerazione con una sintassi simile alla seguente:
enum Color { RED, GREEN, BLUE };
Una variabile di tipo "Color" potrà in tal caso assumere solo i valori "RED", "GREEN" e "BLUE". Rispetto alle tecniche tradizionali per gestire analoghi generi di dati, che prevedevano semplicemente di adottare una convenzione numerica implicita (per esempio scrivo "1" per intendere "rosso", "2" per intendere "verde" e così via), i tipi enumerati forniscono una maggiore leggibilità e una migliore astrazione sui dati.
[modifica] Puntatori
Per approfondire, vedi la voce Puntatore (programmazione). |
I valori di tipo puntatore sono indirizzi di memoria di variabili, oggetti (o altri elementi di programma). L'operatore con cui, dato un puntatore, si accede all'oggetto puntato viene detto operatore di dereferenziazione (dereferencing). Molti linguaggi offrono anche un operatore "inverso", spesso detto operatore indirizzo-di, che data una variabile consente di ricavarne l'indirizzo. Un insieme esteso di operazioni sui puntatori viene fornito dai linguaggi dotati di aritmetica dei puntatori.
L'uso di puntatori è spesso necessario per costruire strutture dati complesse e dalla forma non prevedibile a priori e/o variabile nel tempo come grafi, alberi, liste e così via; inoltre, i puntatori possono usati per realizzare il passaggio di parametri per riferimento nei linguaggi che non lo offrono come meccanismo nativo. Se usati in modo indiscriminato, tuttavia, i puntatori possono portare allo sviluppo di software molto complesso e, soprattutto, condurre a errori di programmazione molti difficili da individuare. Per questo motivo alcuni linguaggi tentano di limitarne l'uso (un esempio in questo senso è Java).
[modifica] riferimenti
Alcuni linguaggi (in particolare della famiglia del C++) forniscono un meccanismo simile ai puntatori, ma caratterizzato da dereferenziazione implicita; le variabili di questo tipo sono dette, nella terminologia C++, riferimenti.
Sintatticamente, i tipi riferimento non richiedono l'uso di un operatore di dereferenziazione, e non prevedono un operatore indirizzo-di. Di conseguenza, sui riferimenti non è possibile l'aritmetica dei puntatori.
[modifica] Array
Per approfondire, vedi la voce Array. |
Un array è una sequenza finita di elementi appartenenti a un determinato tipo, indicizzata mediante un numero intero.
[modifica] Record
I record, detti anche tuple o strutture, sono aggregati di tipi di dati più semplici, la cui composizione può essere definita dall'utente. Un record è necessario per mantenere informazioni eterogenee correlate: potrebbero ad esempio essere usati per modellare le schede dell'archivio di una biblioteca, che devono contenere stringhe per il titolo di un libro e il nome del suo autore, ma anche un valore numerico indicante la collocazione; ciascuna di queste informazioni (campi del record) può essere acceduta in modo indipendente specificandone il nome: in molti linguaggi, specialmente quelli derivati dal C, questa operazione è specificata mediante l'operatore . (punto):
cout << scheda.autore; // stampa il nome dell'autore cout << scheda.titolo; // stampa il titolo del libro
Poiché in molti casi è necessario imporre una coerenza ai dati contenuti in un record, certi linguaggi consentono di definire tipi di dati astratti, che sono essenzialmente record la cui struttura fisica non è visibile e che possono essere manipolati soltanto mediante certe operazioni fidate, specificate in un'apposita interfaccia. Sui tipi di dati astratti si basano anche i tipi classe tipici della programmazione orientata agli oggetti.
[modifica] Tipi funzione
Le funzioni possono restituire funzioni così come gli altri tipi di dati, le funzioni possono ricevere funzioni come argomenti, al pari degli altri tipi di dati. Un esempio in Perl:
sub generaAccumulatore { my $accumulator=0; return sub { $inc = shift; $accumulator += $inc; return $accumulator; } } $accumulatore1=generaAccumulatore(); $accumulatore2=generaAccumulatore(); $accumulatore1->(4); print $accumulatore2->($accumulatore1->(1)) . " e " . $accumulatore1->(3);
[modifica] Tipi generici
In certi linguaggi è possibile specificare anche tipi generici (detti anche parametrici o polimorfi). Questi tipi permettono di specificare classi di dati dipendenti da un tipo che deve essere passato come parametro. Sarà quindi possibile definire il tipo delle liste generiche, che può quindi essere istanziato sugli interi per ottenere liste di interi, o su booleani per ottenere liste di booleani. Il principale beneficio è quello di non essere costretti a dare una definizione diversa delle liste per ogni possibile tipo contenuto, in modo tale da ridurre il codice replicato.