Dal C al C++: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
m Linguaggio C++ spostata a Dal C al C++: Questo wikibook insegna il linguaggio C++ solo a chi conosce già il linguaggio C.
Nuova organizzazione
Riga 1:
# [[Dal C al C++/Introduzione|Introduzione]]
Il '''C++''' nasce all'inizio degli [[w:anni '80|anni '80]], ma si diffonde nell'industria solo negli [[w:anni '90|anni '90]]: si tratta della più diffusa estensione del [[linguaggio C]], con l'aggiunta di numerose caratteristiche per consentire la programmazione orientata agli oggetti, la programmazione generica e la metaprogrammazione. Questi tre paradigmi di programmazione si uniscono alla programmazione procedurale propria del C, per formare un linguaggio multi-paradigma.
## I destinatari del libro
 
==## ILa destinataristruttura del libro ==
## Impieghi del linguaggio
 
## Panoramica
Siccome il linguaggio C++ deriva dal linguaggio C, di cui è sostanzialmente un soprainsieme, alcuni autori ritengono utile imparare prima il linguaggio C, anche se lo scopo finale è solamente l'apprendimento del linguaggio C++.
# Utilizzo basilare di librerie
Altri autori non sono d'accordo, cioè ritengono preferibile imparare direttamente il linguaggio C++, senza perdere tempo e rischiare di confondersi le idee con l'apprendimento di un altro linguaggio.
## [[Dal C al C++/Utilizzo basilare di librerie/L'uso di classi e oggetti|L'uso di classi e oggetti]]
 
## [[Dal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni|Dichiarazioni e definizioni]]
Questo testo segue la prima filosofia, cioè '''si rivolge a persone che abbiano già una discreta conoscenza del linguaggio C'''.
### Le variabili riferimento
Chi volesse imparare tale linguaggio, può leggere il libro [[Linguaggio C]].
### Il modificatore "const"
 
### L'inizializzazione degli oggetti
Le motivazioni che inducono a scrivere un libro per programmatori C sono i seguenti:
### L'allocazione degli oggetti
* Esistono molti programmatori C che vogliono imparare il C++.
## [[Dal C al C++/Utilizzo basilare di librerie/L'uso dei template|L'uso dei template]]
* Esiste nell'industria molto codice C++ che è in realtà un incapsulamento di vecchio codice C. Se non si conosce il linguaggio C, si fatica a comprendere tale codice.
## [[Dal C al C++/Utilizzo basilare di librerie/L'uso dei namespace|L'uso dei namespace]]
* Un libro che presuma la conoscenza del linguaggio C è più compatto.
## [[Dal C al C++/Utilizzo basilare di librerie/I file di intestazione|I file di intestazione]]
* È comunque consigliabile studiare il linguaggio C, sia perché ha una grande importanza storica, sia perché è tuttora molto usato.
## [[Dal C al C++/Utilizzo basilare di librerie/L'uso delle stringhe|L'uso delle stringhe]]
 
## [[Dal C al C++/Utilizzo basilare di librerie/I canali di ingresso/uscita|I canali di ingresso/uscita]]
== Le caratteristiche principali del C++ ==
## [[Dal C al C++/Utilizzo basilare di librerie/La gestione delle eccezioni|La gestione delle eccezioni]]
 
## [[Dal C al C++/Utilizzo basilare di librerie/I contenitori standard|I contenitori standard]]
Le caratteristiche più importanti che il C++ aggiunge al C sono le seguenti:
# Utilizzo avanzato di librerie
* Classi, con funzioni virtuali ed ereditarietà multipla
## Ridefinizione di funzioni membro
* Eccezioni
## Parametri di default
* Namespace
## Gli iteratori sui contenitori
* Template di classe
## Gli algoritmi sui contenitori
* Template di funzione
# Lo sviluppo di librerie
* Libreria di input/output
## Classi concrete
* Libreria per gestire stringhe
## Gerarchie di classi
* Libreria per gestire contenitori
## Proprietari di risorse
 
## Eccezioni
Ecco un programma C++ che esemplifica l'utilizzo delle suddette caratteristiche.
## Namespace
#include <iostream>
## Template di funzione
#include <vector>
## Template di classe
#include <complex>
## Algoritmi su sequenze
using namespace std;
# [[Dal C al C++/La metaprogrammazione|La metaprogrammazione]]
int main() {
# [[Dal C al C++/La progettazione del software|La progettazione del software]]
complex<double> c1(2, 3); // c1 è un numero complesso
complex<double> *pc2; // pc2 è un puntatore a un numero complesso
const string s = "Risultato: "; // s è un oggetto stringa
try {
pc2 = new complex<double>(5, min(17, 12));
} catch (...) {
cerr << "Memoria insufficiente\n";
return 1;
}
vector<complex<double> > vcd;
vcd.push_back(c1 + *pc2);
cout << s << vcd.front();
delete pc2;
}
 
Diciamo subito che cosa fa questo programma.
Se non si riesce ad allocare una manciata di byte di memoria, il che è praticamente impossibile, viene emesso sulla console il messaggio d'errore "Memoria insufficente" e il programma termina.
Altrimenti, cioè quasi sicuramente, viene emessa sulla console la scritta "Risultato: (7,15)".
 
Esaminiamo il significato di ogni riga.
 
#include <iostream>
Questa riga include le dichiarazioni della libreria di ingresso/uscita su canali (stream input/output), nonché gli oggetti su cui si basa l'ingresso/uscita su console. Per finalità, corrisponde al file "stdio.h" della libreria standard del linguaggio C.
Si noti che manca l'estensione ".h" dopo "iostream".
 
#include <vector>
Questa riga include le dichiarazioni della libreria di contenitore dinamico ad array, chiamato appunto "vector".
 
#include <complex>
Questa riga include le dichiarazioni della libreria di gestione dei numeri complessi.
 
using namespace std;
Questa riga dichiara che d'ora in poi in questa unità di compilazione, ogni identificatore verrà cercato non solo nello spazio di nomi globale (cioè quello del linguaggio C), ma anche nello spazio dei nomi "std", cioè quello della libreria standard del C++.
 
int main() {
Questa riga definisce il punto di ingresso del programma, analogamente ai programmi in linguaggio C.
La differenza sta nel fatto che il valore di ritorno è obbligatoriamente "int", ma, a differenza di tutte le altre funzioni che rendono un "int" è consentito omettere l'istruzione di return in fondo alla funzione, sottintendendo "return 0;".
 
complex<double> c1(2, 3);
Questa riga definisce sullo stack una variabile identificata dal nome "c1", il cui tipo è "complex<double>", e che è inizializzata con la coppia di valori "2, 3".
Il tipo "complex<double>" rappresenta un numero complesso in cui sia la parte reale che la parte immaginaria sono memorizzate in numeri a virgola mobile di tipo "double".
è l'istanziazione di un template di classe.
L'identificatore "complex" rappresenta il tipo di un oggetto che rappresenta un numero complesso, ma tale definizione è parametrizzata dal tipo dei campi contenenti la parte reale e la parte immaginaria.
Un tale tipo parametrizzato, o tipo generico, in C++ viene chiamato ''template di classe'', cioè modello di classe.
Specificando un tipo tra parentesi angolari, si ''istanzia'' il template, cioè si genera un tipo concreto definito dall'utente, che può essere usato in modo analogo ai tipi predefiniti.
I tipi definiti dall'utente sono detti ''classi''.
L'inizializzazione di un numero complesso richiede due valori, uno per la parte reale e uno per la parte immaginaria.
Pertanto, passando i valori di inizializzazione all'oggetto in corso di creazione, questo riceve tali valori e li usa per inizializzare i propri campi.
La sintassi è analoga a quella di una chiamata di funzione.
 
complex<double> *pc2;
Questa riga definisce sullo stach un puntatore di nome "pc2", che può contenere l'indirizzo di un oggetto del tipo "complex<double>", appena visto.
Tale variabile (puntatore) non è inizializzata, pertanto il suo contenuto è indefinito, e sarebbe un errore logico usarne il valore prima di assegnargli un valore valido.
 
const string s = "Risultato: ";
Questa riga definisce sullo stack una variabile di nome "s" e di tipo "const string", inizializzata dalla costante di stringa "Risultato: ".
Il tipo "string" non è un tipo predefinito ma una classe standard, cioè un tipo definito dall'utente, facente parte della libreria standard del C++.
Il modificatore "const" significa ''non modificabile'' o ''a sola lettura''.
Le variabili il cui tipo non ha il modificatore "const" possono essere sia lette che scritte, mentre per quelle il cui tipo ha il modificatore "const" (quasi) ogni tentativo, diretto o indiretto, di scriverle produce un errore di compilazione.
L'inizializzazione segue lo stile delle inizializzazioni in linguaggio C.
Una sintassi alternativa ma equivalente sarebbe stata la seguente:
const string s("Risultato: ");
Se l'inizializzatore è uno solo, è possibile scegliere tra le due notazioni; mentre se se ci sono più inizializzatori, come nel caso della variabile "c1" sopra, è ammessa solo la notazione funzionale.
 
try {
Questa riga inizia un blocco "try", utilizzato per la gestione delle eccezioni.
Per ''eccezione'' si intende una situazione anomala rilevata all'interno di una funzione di libreria e a causa della quale la funzione stessa non è in grado di completare il suo compito. Per segnalare al chiamante il proprio fallimento, la funzione termina ''sollevando'' un'eccezione.
Quando in codice contenuto all'interno di un blocco "try" solleva un'eccezione, il controllo passa immediatamente alla fine del blocco "try" stesso, dove c'è il blocco "catch".
 
pc2 = new complex<double>(5, min(17, 12));
Questa riga contiene una chiamata all'operatore "new", che è l'operatore predefinito per allocare dinamicamente oggetti nella memoria libera.
È simile alla funzione "malloc", ma mentre quest'ultima alloca una sequenza di byte, l'operatore new alloca lo spazio necessario per contenere un oggetto del tipo specificato, ed eventualmente inizializza tale oggetto.
Inoltre, il puntatore reso da "new" non è di tipo puntatore a ''void'', bensì di tipo puntatore a un oggetto della classe specificata.
La riga sopra alloca lo spazio per un oggetto di tipo "complex<double>", inizializza tale oggetto con i valori passati tra parentesi, e assegna alla variabile "pc2" un puntatore a tale oggetto.
L'inizializzatore ha due argomenti; il primo è la costante "5", che diventerà la parte reale del numero complesso; il secondo è ottenuto dalla valutazione dell'espressione "min(17, 12)".
La funzione della libreria standard "min" prende due argomenti dello stesso tipo e rende quello dei due di valore inferiore.
Non si tratta di una semplice funzione, ma di un template di funzione, in quanto, al variare del tipo degli argomenti, rappresenta una funzione diversa. In questo caso, è una funzione che prende due espressioni di tipo "int" e rende un valore di tipo "int".
 
} catch (...) {
Questa riga inizia il blocco "catch" dell'istruzione "try/catch".
Se all'interno del blocco "try" precedente viene sollevata un'eccezione, il controllo passa all'interno di questo blocco che quindi costituisce il gestore dell'eccezione.
 
cerr << "Memoria insufficiente\n";
return 1;
Queste due righe costituiscono il corpo del blocco "catch" e vengono eseguita solo se nell'esecuzione del blocco "try" viene sollevata un'eccezione.
Siccome l'unica eccezione che poteva essere sollevata dal codice contenuto nel blocco "try" era l'eccezione di fallimento di allocazione della memoria, qui si avvisa l'utente che non è stato possibile allocare la memoria necessaria alla prosecuzione del programma, e si termina il programma.
L'oggetto "cerr" rappresenta lo stream di uscita dei messaggi d'errore su console ("CERR" sta per Console ERRor stream).
Corrisponde allo stream "stderr" della libreria standard del linuaggio C, dichiarato nel file di intestazione "stdio.h".
L'operatore "<<", oltre ad essere l'operatore di scorrimento a sinistra dei bit in una variabile intera, se usata su uno stream è l'operatore di inserimento, cioè di uscita.
usando tale operatore tra uno stream di uscita e un'espressione di tipo stringa, tale stringa viene inserita nello stream.
Siccome "cerr" è uno stream collegato alla console, l'effetto è di stampare la stringa sulla console.
 
vector<complex<double> > vcd;
Questa riga definisce sullo stack una variabile di nome "vcd" e di tipo "vector<complex<double> >", senza inizializzarla esplicitamente.
Il template di classe "vector" definisce un array dinamico, cioè di lunghezza variabile nel corso della sua esistenza.
Se non specificato diversamente, come in questo caso, la sua lunghezza iniziale è zero.
Il tipo degli elementi contenuti in un ''vector'' è specificato tra parentesi angolari.
In questo caso, gli elementi contenuti sono di tipo "complex<double>".
Sebbene non ci sia un inizializzatore esplicito, gli oggetti di un tipo istanziato da ''vector'' vengono inizializzati implicitamente.
Pertanto, dopo questa dichiarazione, l'oggetto rappresentato dalla variabile "vcd" contiene un array dinamico di numeri complessi a precisione ''double'', che inizialmente non contiene nessun elemento.
 
vcd.push_back(c1 + *pc2);
Questa riga applica all'oggetto "vcd" la ''funzione membro'' (chiamata anche ''metodo'') "push_back", passandole un parametro.
La funzione "push_back", serve ad aggiungere un elemento in fondo a un contenitore sequenziale.
L'elemento che verrà aggiunto al ''vector'' è il parametro della funzione.
In questo caso è un'espressione, che dovrà essere prim valutata.
Sia "c1" che "*pc2" sono oggetti di tipo "complex<double>".
L'operatore "+" oltre che essere l'operatore di somma tra numerosi tipi predefiniti (come "int" e "double"), è stato ridefinito come operatore di somma tra due oggetti di un tipo istanziato da "complex".
Pertanto l'espressione "c1 + *pc2" effettua la somma tra due numeri complessi e rende un altro numero complesso temporaneo.
Il numero complesso generato dalla somma viene subito dopo aggiunto in fondo al ''vector'' "vcd".
 
cout << s << vcd.front();
Questa riga usa un altro oggetto di tipo stream su console, "cout", che rappresenta lo stream di uscita su console ("COUT" sta per Console OUTput stream).
Corrisponde allo stream "stdout" della libreria standard del linuaggio C, dichiarato nel file di intestazione "stdio.h".
L'espressione "cout << s" ha l'effetto di stampare sulla console la stringa contenuta nella variabile "s".
Il valore di tale espressione non è "void", ma è l'oggetto "cout" stesso.
Pertanto la successiva espressione "<< vcd.front()" esegue un'altra operazione di uscita sulla console.
Questa volta l'espressione inviata in uscita non è una semplice stringa, bensì il valore reso dall'applicazione della funzione membro "front" all'oggetto "vcd".
La funzione membro "front" non richiede parametri e rende il primo valore di una collezione.
Nel nostro caso, rende il numero complesso che è stato inserito nell'istruzione precedente.
Come si vede, l'operatore di inserimento in stream ("<<") non accetta solo stringhe, ma anche altri tipi; in questo caso un'espressione di tipo "complex<double>".
 
delete pc2;
Questa riga distrugge dalla memoria l'oggetto il cui indirizzo si trova nel puntatore "pc2".
Corrisponde alla funzione "free" della libreria standard del linguaggio C, ma è applicabile solo a indirizzi resi da chiamate a "new".
 
== Le classi e gli oggetti ==
 
Consideriamo la seguente porzione di codice in linguaggio C:
struct S1 {
int i;
double x;
};
struct S1 a1 = { 2, 3.14 };
Le prime quattro righe definiscono un nuovo ''tipo'', chiamato "struct S1".
L'ultima riga definisce una variabile, di nome "a1", di tipo "struct S1", e inizializzata con i due valori "2" e "3.14".
 
Nel linguaggio C++ la possibilità di definire nuovi tipi è stata estesa con l'aggiunta delle "funzioni membro".
Consideriamo la seguente porzione di codice in linguaggio C++:
struct S2 {
int i;
double x;
double f() { return x * 2; }
};
struct S2 a2 = { 2, 3.14 };
Rispetto al codice precedente, è stata aggiunta solo la quarta riga, in cui si definisce una funzione di nome "f", che non prende parametri e che rende un valore di tipo "double".
Tale funzione è stata definita all'interno della struttura "struct S2".
Così come le variabili definite all'interno di una struttura sono dette essere le sue ''variabili membro'', così le funzioni definite all'interno di una struttura sono dette essere le sue ''funzioni membro''.
In generale, variabili membro e funzioni membro sono i ''membri'' di una struttura.
 
Si noti che "f" non è un puntatore a funzione, è proprio una funzione, con tanto di corpo.
Forse qualcuno si chiederà: "Ma allora la struttura contiene il codice macchina della funzione o solo il suo indirizzo?".
La risposta è che non contiene nessuno dei due.
La funzione "f" è memorizzata nel segmento di codice del programma, quindi ben lontano dalla variabile a2 e da ogni altra istanza del tipo "struct S2".
 
A fronte di una tale dichiarazione di funzione, il compilatore genera pressapoco lo stesso codice che genererebbe quando compilasse il seguente codice in linguaggio C:
double S2_f(const struct S2 * t) { return t->x * 2; }
Il nome della funzione "S2_f" indica semplicemente che tale funzione è distinta da eventuali altre funzioni di nome "f" dichiarate fuori dalla struttura.
Il parametro "t" è un puntatore a una variabile avente lo stesso tipo della struttura.
La variabile "x" indicata nel corpo originale della funzione, è diventata un membro della struttura puntata da "t".
 
La variabile "a2" ha la stessa dimensione e lo stesso contenuto della variabile "a1".
È solo il ''tipo'' a essere diverso, in quanto l'insieme delle funzioni definite all'interno di una struttura definiscono l'insieme delle operazioni applicabili a tale struttura.
Vediamo infine come si invoca la funzione "f":
double y = a2.f();
L'operatore ".", come consente di accedere alle variabili membro, così contente di accedere alle funzioni membro.
Il codice generato dalla riga qui sopra è equivalente a quello generato dalla seguente riga:
double y = S2_f(&a2);
Quindi chiamare una funzione membro su una variabile di tipo struttura, significa chiamare la funzione passandole come primo parametro un puntatore alla variabile stessa.
 
Vediamo un esempio realistico basato sulla classe "string".
string s = "ABCDEFG";
cout << s.substr(4, 2);
Questa porzione di codice stampa sulla console la stringa "EF", in quanto la funzione membro "substr" estrae dalla stringa "s" una sottostringa saltando 4 caratteri da sinistra e prendendone 2.
 
"substr" è una funzione membro della classe "string", quindi riceve implicitamente come primo parametro nascosto un puntatore alla stringa a cui la funzione viene applicata.
Inoltre, tale funzione accetta due parametri interi; il primo è il numero di caratteri da contare a partire dall'inizio della stringa, e il secondo è il numero di caratteri da prelevare.
Il valore reso dalla funzione è ovviamente un altro valore di tipo "string".
 
Il C++ introduce rispetto al C alcune comodità sintattiche:
* Il nome del tipo della variabile a2 può essere "struct S2", ma può essere abbreviato in "S2".
* La parola-chiave "class" è ''quasi'' sinonima di "struct", ed è molto più usata per strutture contenenti funzioni membro. D'ora in poi, invece di "struct", e ''struttura'' useremo prevalentemente "class" e ''classe''. Quindi una variabile "x" di tipo "class C" potrà essere definita semplicemente con la seguente riga:
C x;
 
Un termine molto usato (e abusato) è la parola "oggetto".
La si usa impropriamente a volte per indicare una classe, altre per indicare una variabile.
In realtà non è nessuna delle due cose.
Un oggetto è una cosa che esiste anche in C, sotto il nome di "r-value".
 
L'espressione "r-value", abbreviazione di "right-hand value" (cioè "valore di destra"), significa ''espressione che può stare a destra dell'operatore "="''.
Cioè si tratta di un'espressione che rappresenta una sequenza di bit che può essere copiata in una locazione di memoria tramite un assegnamento.
Un oggetto può risiedere in memoria, oppure essere solo teorico, cioè esiste concettualmente ma l'ottimizzatore di codice ne evita la creazione.
Facciamo qualche esempio valido sia in C che in C++:
char x;
char * px = &x;
Se le due righe precedenti sono poste fuori da qualunque funzione e struttura, costituiscono altrettante definizioni di variabili globali "x" e "px", la prima di tipo "char" e la seconda di tipo "char *", ossia puntatore a "char".
Entrambe le variabili rappresentano anche degli oggetti, il primo lungo "sizeof (char)" byte, cioè tipicamente 1 byte, e il secondo lungo "sizeof (char *)" byte cioè tipicamente 2 byte su una macchina a 16 bit, 4 byte su una macchina a 32 bit, e 8 byte su una macchina a 64 bit.
 
La variabile "px" è inizializzata a puntare all'oggetto rappresentato dalla variabile "x".
Pertanto l'espressione "*px" rappresenta ''lo stesso oggetto'' rappresentato da "x".
Adesso aggiungiamo la seguente riga in una funzione.
px = malloc(7);
Dopo aver eseguito questa istruzione, "px" o è nullo (se la malloc fallisce) o punta a un array di sette oggetti di tipo carattere.
Tali oggetti non sono associati a nessuna variabile, eppure possono essere assegnati. Per esempio, con la seguente istruzione:
px[2] = 'a';
 
Consideriamo la seguente istruzione:
int a = 3 + 5;
Il numero "3" in quanto tale non è un oggetto, in quanto è un valore letterale, ma quando entra a far parte di una espressione come questa, concettualmente viene istanziato un oggetto che contiene una sequenza di bit che rappresenta il valore "3".
Chiaramente qualunque compilatore ottimizzante a fronte di questa riga di codice genera un'istruzione che assegna alla variabile "a" il valore 8.
 
Tuttavia, concettualmente, avvengono le seguenti operazioni:
* Viene allocato sullo stack un oggetto di tipo "int", non inizializzato, che rappresenta la variabile "a".
* Viene allocato sullo stack un oggetto di tipo "int", e viene posto il valore "3" in tale oggetto.
* Viene allocato sullo stack un oggetto di tipo "int", e viene posto il valore "5" in tale oggetto.
* Viene allocato sullo stack un oggetto di tipo "int", non inizializzato, che serve come area temporanea per memorizzare il risultato della somma.
* Viene eseguita l'operazione di somma tra il secondo e il terzo oggetto, assegnando il risultato al quarto oggetto.
* Viene copiato il contenuto del quarto oggetto nel primo oggetto.
* Vengono deallocati tutti gli oggetti tranne il primo.
 
Quindi, nel corso dell'esecuzione di un programma scritto in C++, concettualmente vengono allocati e deallocati moltissimi oggetti, anche se in pratica a fronte di tali allocazioni e deallocazioni concettuali il compilatore ottimizzante genera molte meno istruzioni di quante sembrerebbero necessarie.
 
In C++, ma non in C, è anche possibile, come vedremo in seguito, definire una variabile senza allocare nessun oggetto.
Si tratta delle variabili di tipo "riferimento" ("reference" in inglese), usate in alcuni linguaggi, come BASIC e Pascal, per passare parametri a funzioni.
 
== Le variabili riferimento ==
 
Nel linguaggio C, si possono definire variabili che non sono puntatori, come in "int a;", oppure variabili che sono puntatori a oggetti che non sono puntatori, come in "int * a;", oppure variabili che sono puntatori a oggetti che sono puntatori a oggetti che non sono puntatori, come in "int ** a;", e così via.
 
In C++ rimangono tutte queste possibilità, ma si aggiunge una nuova tipologia di variabile, il "riferimento".
Consideriamo le seguenti righe, di cui le prime due sono valide anche in C:
int a = 34;
int * b = & a;
int & c = a;
La prima riga definisce una variabile di nome "a", di tipo "int", e inizializzata al valore intero "34".
La seconda riga definisce una variabile di nome "b", di tipo puntatore a "int", e inizializzata all'indirizzo dell'oggetto rappresentato dalla variabile "a".
La terza riga, non valida in linguaggio C, definisce una variabile di nome "c", di tipo ''riferimento'' a "int", e inizializzata alla variabile "a".
La variabile "c" non ha un suo oggetto, ma rappresenta lo stesso oggetto rappresentato dalla variabile "a", nonché dall'espressione "* b".
In seguito si può usare la variabile "c" con lo stesso significato della variabile "a".
Tale fatto si esprime dicendo che "c" è un ''alias'' di "a".
Per esempio, eseguendo il seguente codice.
c = 7;
cout << a;
Viene stampato il numero "7", in quanto l'oggetto riferito da "c" è lo stesso oggetto riferito da "a".
 
Tale associazione è permanente per la vita della variabile.
Come non si può fare in modo che "a" rappresenti un oggetto diverso da quello iniziale, così anche "c", da quando viene inizializzata, è vincolata a rappresentare sempre lo stesso oggetto.
 
Consideriamo le seguenti righe.
int a2;
int * b2;
int & c2;
Le prime due continuano a definire variabili a cui corrispondono oggetti, anche se tali oggetti non sono inizializzati e quindi hanno valori indefiniti, ossia il loro utilizzo come r-value ha effetto impredicibile.
Inoltre, l'espressione "* b2" non rappresenta più un oggetto valido.
La dichiarazione di variabili non inizializzate è pericolosa e andrebbe evitata.
Alcuni hanno proposto di vietarle in C++, ma siccome questo avrebbe reso non validi molti vecchi programmi, sono state mantenute.
Per fortuna, molti compilatori emettono un avvertimento quando si usa come l-value una variabile non inizializzata e a cui non è ancora stato assegnato un valore.
 
La terza riga è invece illegale sia in C che in C++.
Infatti, tenterebbe di dichiarare una variabile che costituisca un riferimento alternativo a un oggetto, ma non specifica a quale oggetto.
Siccome il collegamento di una variabile riferimento al suo oggetto può avvenire solo all'atto della definizione della variabile, tale variabile rimarrebbe per sempre non legata a un oggetto, e quindi completamente inutile.
 
L'uso principale delle variabili riferimento, è però per dichiarare parametri di funzione.
Consideriamo il seguente programma:
#include <iostream>
using namespace std;
double doppio1(double a) { return a * 2; }
double doppio2(double * a) { return *a * 2; }
double doppio3(double & a) { return a * 2; }
int main() {
double x = 1;
cout << doppio1(x);
cout << doppio2(&x);
cout << doppio3(x);
}
 
Le funzioni "doppioN" sono equivalenti, tanto che il programma stamperà "222".
Ma consideriamo il seguente programma:
 
#include <iostream>
using namespace std;
void raddoppia1(double a) { a *= 2; }
void raddoppia2(double * a) { *a *= 2; }
void raddoppia3(double & a) { a *= 2; }
int main() {
double x = 1;
raddoppia1(x);
cout << x;
raddoppia2(&x);
cout << x;
raddoppia3(x);
cout << x;
}
 
Le funzioni "raddoppiaN" ''non'' sono equivalenti, tanto che il programma stamperà "124".
 
Chiamando la funzione "raddoppia1", il valore dell'oggetto riferito dalla variabile "x" viene copiato sullo stack.
All'interno della funzione tale valore viene riferito dal parametro "a".
Il corpo della funzione modifica tale valore allocato sullo stack, e quando la funzione termina l'oggetto viene deallocato dallo stack, senza che ci siano effetti sul mondo esterno.
Quindi l'oggetto rappresentato dalla variabile "x" continua a valere "1".
 
Chiamando la funzione "raddoppia2", l'indirizzo dell'oggetto riferito dalla variabile "x" viene copiato sullo stack.
All'interno della funzione tale valore viene riferito dal parametro "a".
Il corpo della funzione modifica il valore puntato da tale valore allocato sullo stack, che è poi lo stesso valore associato a "x", e quando la funzione termina l'oggetto puntatore viene deallocato dallo stack.
Quindi l'oggetto rappresentato dalla variabile "x" è stato raddoppiato, passando dal valore "1" al valore "2".
 
Chiamando la funzione "raddoppia3", si ''comunica'' alla funzione che il parametro "a" dovrà rappresentare lo stesso oggetto rappresentato dalla variabile "x".
Questa ''comunicazione'' può avvenire copiando sullo stack l'indirizzo dell'oggetto rappresentato dalla variabile "x".
Il corpo della funzione modifica il valore puntato da tale valore allocato sullo stack, e quando la funzione termina l'oggetto puntatore viene deallocato dallo stack.
Quindi l'oggetto rappresentato dalla variabile "x" è stato raddoppiato, passando dal valore "2" al valore "4".
 
Come si vede, il corpo della funzione "raddoppia3" è uguale a quello della funzione "raddoppia1", ma il comportamento è uguale a quello della funzione "raddoppia2".
Questi fatti si esprimono con la frase: '''I riferimenti hanno la sintassi dei valori, ma la semantica dei puntatori'''.
 
== Il modificatore "const" ==
 
In C++ esiste la parola-chiave "const" (abbreviazione di "constant", cioè "costante"), che serve a modificare la dichiarazione di variabili e funzioni.
Ecco alcuni esempi di utilizzo:
const int x1 = 0;
const int *x2;
int * const x3 = 0;
const int * const x4 = 0;
La variabile "x1" è tipo "int", ed è non modificabile.
L'inizializzazione è obbligatoria, in quanto non gli si potrà assegnare un valore in seguito.
 
La variabile "x2" è di tipo puntatore a un "int" non modificabile, ma la variabile "x2" è modificabile.
 
La variabile "x3" è di tipo puntatore non modificabile a un "int" modificabile.
 
La variabile "x4" è di tipo puntatore non modificabile a un "int" non modificabile.
 
Vediamo quali sono gli utilizzi ammessi e quali quelli vietati dal compilatore:
x1 = 1; // Illegale
x2 = 0; // OK
*x2 = 1; // Illegale
x3 = 0; // Illegale
*x3 = 1; // OK
x4 = 0; // Illegale
*x4 = 1; // Illegale
 
Il modificatore "const" si può applicare anche ai parametri di qualunque funzione, e alle funzioni membro stesse.
Per esempio:
void f1(const int &x5) { }
class C {
int x6;
void f2() const { }
};
 
Il parametro "x5" è di tipo riferimento a un "int" non modificabile.
Un riferimento non può mai essere reindirizzato a un altro oggetto, pertanto la seguente riga è erronea:
void f1(int & const x5) { } // Illegale
 
Nella classe "C", la funzione membro "f2" non prende argomenti, ma presenta la parola "const" appena prima del corpo.
Tale specificazione asserisce che quella funzione non può modificare le variabili membro dell'oggetto su cui è chiamata.
In quesl caso la parola "const" andrebbe letta come "che non modifica l'oggetto".
Per esempio, il seguente codice è illegale.
class C {
int x6;
void f2() const { x6 = 0; }
};
 
Gli scopi del modificatore "const" sono i seguenti:
* Rendere il codice autodocumentante e quindi più comprensibile. Il programmatore potrebbe scrivere un commento come "questa routine si limita a leggere l'oggetto", ma oltre a essere più verboso e meno standard, il compilatore non potrebbe assicurare la veridicità di tale commento.
* Evidenziare errori di programmazione. Se una variabile con deve essere modificata e invece lo è, si tratta di un errore logico che verrà segnalato dal compilatore.
* Rendere il codice più efficiente. Se il compilatore sa che una variabile ha un valore costante, può propagare il suo valore ovunque è usata la variabile. Per variabili locali, questo viene già fatto dai compilatori ottimizzanti, ma normalmente non viene fatto per le variabili globali. Questo fatto rende inutile e sconsigliato l'uso delle macro del preprocessore per definire costanti.
 
== L'uso dei template ==
 
In C++ si possono definire non solo classi e funzioni, ma anche template di classe e template di funzione.
 
Per esempio, nella libreria standard è definito il template di classe "vector", che è un'array dinamico.
Tale template di classe non definisce il tipo degli elementi contenuti nell'array.
Tale tipo è un parametro del template.
Per poter utilizzare il template si deve specificare il tipo degli elementi.
Vediamo qualche esempio:
vector a; // Illegale, parametro non specificato
vector<int> b; // Array dinamico di int
vector<int> c; // Altro array dinamico di int
b = c; // Copia un vector su un altro dello stesso tipo
vector<double> d; // Array dinamico di double
b = d; // Illegale, non si possono copiare array di double su array di int
La parola "vector" rappresenta la definizione di una classe generica, definita a meno di un parametro;
non è una classe, ma una famiglia parametrica di classi, e quindi non si può usare direttamente per dichiarare o definire una variabile o una funzione.
 
Se dopo la parola "vector" si pone tra parentesi angolari un'espressione che identifica un tipo, si ottiene un'''istanza'' del template, che è un tipo, utilizzabile come qualunque altro tipo.
 
L'istanziazione di un template di classe, cioè il passaggio dal template al tipo, viene eseguito in fase di compilazione, e pertanto il parametro deve essere una definito in fase di compilazione.
 
Un template può ammettere più parametri, e questi parametri non sono necessariamente dei tipi, in quanto sono ammessi come parametri anche numeri interi.
Consideriamo il seguente programma:
#include <iostream>
#include <bitset>
using namespace std;
int main() {
bitset<6> a;
a[0] = 1;
a[2] = 1;
cout << a;
}
La prima riga della funzione "main" è la dichiarazione della variabile "a", di tipo "bitset<6>".
Il template di classe "bitset" fa parte della libreria standard, ed è definito nel file di intestazione "bitset".
 
Questo template di classe rappresenta una sequenza di bit di lunghezza fissa, e richiede un parametro di tipo intero che indica il numero di bit contenuti nell'oggetto.
Nel nostro esempio, "a" è una sequenza di sei bit.
L'oggetto "a" non è inizializzato esplicitamente, ma la classe bitset<6> lo inizializza implicitamente impostando a zero tutti i suoi bit.
Le due istruzioni successive impostano a 1 il primo e il terzo bit, contando da zero, cioè quelli di indice zero e di indice due.
L'ultima istruzione emette sulla console una rappresentazione testuale della variabile.
Il risultato sarà la stampa della stringa "000101".
 
Chiaramente non si possono mescolare tipi con valori interi.
Nella definizione di "vector" è specificato che il parametro deve essere un tipo, mentre nella definizione di "bitset" è specificato che il parametro deve essere un intero.
Le seguenti due righe producono errori di compilazione:
vector<4> a; // Illegale, vector richiede un tipo
bitset<double> b; // Illegale, bitset richiede un intero
 
In C++, oltre ai template di classe esistono i template di funzione.
Ecco un esempio:
#include <iostream>
#include <string>
using namespace std;
int main() {
double a = max<double>(2.3, 6.8);
string b = min<string>(string("abc"), string("xyz"));
cout << a << " " << b;
}
Questo codice utilizza due template di funzione "max", che rende il valore maggiore tra i due suoi parametri, e "min", che rende il valore minore tra i suoi due argomenti.
 
Il programma stampa su console la stringa "6.8 abc", infatti il numero double "6.8" è maggiore del numero double "2.3", e la stringa "abc" è minore della stringa "xyz" in ordine alfabetico.
 
In modo analogo ai template di classe, anche questi template di funzione sono stati istanziati specificando tra parentesi angolari il parametro del template, che per questre funzioni è il tipo dei parametri della funzione.
Se si specifica un tipo e si passa un tipo diverso, si ottiene un errore sintattico.
Per esempio:
double a = max<double>(string("abc"), string("xyz")); // Illegale
string b = min<string>(2.3, 6.8); // Illegale
Nella prima riga si passano due stringhe alla funzione "max<double>", che si attende due numeri;
nella seconda riga si passano due numeri alla funzione "min<string>", che si attende due stringhe.
 
A differenza dei template di classe, i template di funzione possono sottintendere il parametro del template.
Per esempio, il seguente programma è equivalente al precedente:
#include <iostream>
#include <string>
using namespace std;
int main() {
double a = max(2.3, 6.8);
string b = min("abc", "xyz");
cout << a << " " << b;
}
Siccome al template di funzione "max" vengono passati come parametri di funzione due valori entrambi di tipo "double", il compilatore deduce che il tipo del template è "double", e istanzia la funzione "max<double>".
Analogamente, Siccome al template di funzione "min" vengono passati come parametri di funzione due valori entrambi di tipo "string", il compilatore deduce che il tipo del template è "string", e istanzia la funzione "min<string>".
 
Tale inferenza automatica permette spesso al programmatore di scrivere meno codice.
Tuttavia a volte l'inferenza non è possibile, in quanto c'è un'ambiguità.
Per esempio:
double a = min(2, 3.4); // Illegale
In questa riga il compilatore non sa se istanziare la funzione "min<double>" o "min<int>" e quindi si arrende.
L'uso che viene fatto del valore reso da una funzione non viene mai utilizzato per inferire il parametro di un template, quindi il fatto che il valore sia usato per inizializzare una variabile di tipo "double" è ininfluente.
Per risolvere l'ambiguità si può usare una delle quattro righe seguenti:
double a = min<double>(2, 3.4);
double a = min(2., 3.4);
double a = min((double)2, 3.4);
double a = min(double(2), 3.4);
La prima versione specifica esplicitamente il parametro del template, generando una funzione che si attende due valori double.
A questo punto, il parametro "2" viene automaticamente convertito in "double", come avviene quando in linguaggio C si passa un valore intero a una funzione che si attende un parametro di tipo "double".
 
Le altre tre versioni convertono il valore "2" nel corrispondente valore "double".
la seconda e la terza sono valide anche in linguaggio C.
La quarta versione usa una sintassi di type-cast introdotto dal C++, e equivalente a quella della terza versione.
 
== Uso dei namespace ==
 
Notoriamente, quando si vuole utilizzare una libreria, si devono prima chiarare le funzioni esportate (cioè pubblicate) da tale libreria.
 
Normalmente, invece di inserire a mano nel proprio codice le dichiarazioni delle funzioni che si intende utilizzare, ci si limita a include uno (o più) file di intestazione che contiene le dichiarazioni necessarie, usando la direttiva "#include" del preprocessore.
Tipicamente, tali file di intestazione non contengono solo dichiarazioni di funzioni, ma anche di variabili, costanti, tipi, strutture e, in C++, classi, template di classe e template di funzione.
Insomma, includendo un file di intestazione si mette a disposizione del programmatore una gran quantità di identificatori.
 
Quando il compilatore trova un identificatore, cerca tale identificatore tra tutti quelli che sono stati dichiarati in quella unità di compilazione e in quello ''scope''.
Dal punto di vista del compilatore, gli identificatori vengono cercati in una struttura dati che tradizionalmente si chiama "symbol table", anche se non ha necessariamente la forma di una tabella.
ma dal punto di vista del programmatore, gli identificatori vengono cercati nello "spazio dei nomi", o, in inglese, "namespace".
 
Nel linguaggio C, i nomi hanno uno scope, cioè possono essere locali o globali, ma gli oggetti globali appartengono tutti allo stesso spazio dei nomi.
 
Questo fatto crea problemi quando si vuole incominciare ad usare una nuova libreria in un programma esistente, o, peggio, quando si vogliono usare due o più librerie progettate indipendentemente.
 
Supponiamo che stiamo sviluppando un software che riguarda l'elettronica o l'elettrotecnica, e di usare per una funzione l'identificatore "socket" per indicare, in inglese, un dispositivo fisico a cui fa riferimento l'applicazione.
 
Ad un certo punto vogliamo aggiungere all'applicazione una funzionalità di comunicazione di rete, e utilizziamo una libreria che usa il medesimo nome "socket" per indicare un canale di comunicazione.
Abbiamo una collisione di nomi, dalla quale si può uscire effettuando una ricerca e sostituzione globale nella libreria o nella nostra applicazione.
Supponendo di non avere i sorgenti della libreria, l'unica possibilità è modificare l'applicazione.
 
La situazione è peggiore se abbiamo adottato due librerie che utilizzano lo stesso nome per concetti diversi, o comunque non intercambiabili.
In tal caso il conflitto di nomi è insanabile.
 
È per rimediare a tale situazione che in C++ sono stati introdotti i namespace.
 
Ogni libreria ben progettata dovrebbe inserire tutti i nomi che dichiara all'interno di un namespace.
Per esempio, la famosa libreria Boost, inserisce tutte le proprie dichiarazioni nel namespace "boost".
 
La stessa libreria standard del C++ inserisce gran parte delle proprie dichiarazioni nel namespace "std" (per "standard").
 
Per accedere a un nome contenuto in un namespace dall'interno del namespace stesso non è richiesta nessuna sintassi particolare.
 
Per accedere a un nome contenuto in un namespace dall'esterno del namespace, si deve invece ''qualificare'' il nome, facendolo precedere da nome del namespace, separato da due caratteri ":".
Per esempio, l'oggetto "cout" e la classe "string" possono essere usati nel seguente modo:
std::string s = "abc";
std::cout << s;
 
Per evitare di dover ripetere il nome del namespace ogni volta che si fa riferimento a un oggetto di libreria, si può usare l'apposita direttiva seguente, dove N rappresenta il nome di un namespace:
using namespace N;
Dopo tale direttiva, tutti i nomi contenuti nel namespace N possono essere utilizzati senza necessità di qualificazione.
Pertanto è buona regola porla all'inizio di ogni unità di compilazione, appena dopo le direttive di inclusione.
 
Ovviamente nei casi, non molto comuni, in cui in uno stesso file si usano due o più librerie che dichiarano uno stesso nome, non si potrà usare tale direttiva in tale file, in quanto provocherebbe un conflitto.
In tali casi si ricorre alla qualificazione esplicita.
 
Nei file di intestazione, invece è buona regola usare sempre la qualificazione esplicita, e non usare mai la direttiva "using namespace", in quanto tale direttiva si applicherebbe a tutte le unità di compilazione che, direttamente o indirettamente, includono tale file di intestazione.
 
== I file di intestazione standard ==
 
Anche lo standard del linguaggio C++, analogamente allo standard del linguaggio C, definisce una libreria standard.
Tale libreria è molto più grande di quella del C e, per compatibilità con tale linguaggio, comprende l'intera libreria standard del linguaggio C, anche per le funzionalità ormai obsolete.
 
Anche in C++, prima di utilizzare una funzionalità di libreria è necessario dichiarare tale funzionalità.
A differenza del C, però, è praticamente obbligatorio e comunque fortemente consigliato effettuare tali dichiarazioni tramite l'inclusione di un file di intestazione, usando la direttiva del preprocessore "#include <nome_del_file_di_intestazione>".
 
Un'altra differenza con il linguaggio C sta nel fatto che i file di intestazione standard hanno un nome di file privo di suffisso (o estensione). Per esempio:
#include <string>
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<string> vs;
vs.push_back("abc");
cout << vs.front();
}
Questo programma ha incluso il file di intestazione "string" per poter usare la classe "string"; ha incluso il file di intestazione "vector" per poter usare il template di classe "vector"; e ha incluso il file di intestazione "iostream" per poter usare lo stream di uscita "cout".
 
La parte di libreria standard ereditata dal linguaggio C può essere inclusa in due modi: o con gli stessi file di intestazione del C o con nuovi file di intestazione, privi di suffisso e con il prefisso "c".
Per esempio, per le funzioni di ingresso/uscita e per le funzioni matematiche, si ha:
// File di intestazione ereditati dal C:
#include <stdio.h>
#include <math.h>
 
//Nuovi file di intestazione:
#include <cstdio>
#include <cmath>
 
== L'allocazione e l'inizializzazione degli oggetti ==
 
Nel linguaggio C, si possono allocare oggetti nei seguenti modi:
* Definendo variabili statiche, che sono quelle definite con il qualificatore "static" o definite all'esterno da qualunque funzione. Tali oggetti sono creati prima che inizi la funzione "main", e sono deallocati dopo che il main è terminato.
* Chiamando una funzione che richiede dei parametri. Per ogni parametro, viene allocato un oggetto sullo stack all'atto della chiamata di funzione; tale oggetto viene deallocato quando la funzione termina.
* Facendo entrare il flusso di controllo del programma in un blocco delimitato da parentesi graffe, che può essere un'intera funzione o una sua parte, all'inizio della quale sono definite delle variabili locali non precedute dal qualificatore "static". Per ogni variabile locale, viene allocato un oggetto sullo stack all'atto dell'entrata nel blocco; tale oggetto viene deallocato quando il controllo esce dal blocco.
* Implicitamente, all'interno di espressioni. Tali oggetti temporanei vengono creati durante la valutazione dell'espressione e distrutti quando è finita la valutazione dell'intera espressione.
* Quando si chiama la funzione di libreria "malloc". Tale funzione alloca un array di caratteri. Tale array viene deallocato chiamando la funzione di libreria "free".
 
Si noti che non occorre fare discorsi particolari per strutture e array, in quanto tali oggetti composti sono comunque allocati in uno dei suddetti casi.
 
In C++, vale tutto quanto detto sopra, ma con le seguenti aggiunte.
 
All'interno di un blocco racchiuso tra parentesi graffe, è possibile definire variabili locali non solo all'inizio del blocco, ma in qualunque punto si possa inserire un'istruzione.
La variabile potrà essere utilizzata solo dopo il punto in cui è stata definita, fino alla chiusa del blocco più interno nel quale è stata definita.
Vediamo un esempio:
void f() {
int a;
a = 0;
int b; // Valido
b = 0;
c = 0; // Illegale, perché precede la definizione
int c;
{
int d;
d = 0;
static int e;
}
d = 0; // Illegale, qui "d" è già stata deallocata
e = 0; // Illegale, qui "e" esiste ancora, ma non è più accessibile
}
 
All'interno di una struttura o di una classe, è possibile usare il qualificatore "static" per una variabile membro.
Per esempio:
#include <iostream>
using namespace std;
struct C {
int a;
static int b;
};
int C::b;
int main() {
C c1;
c1.a = 1;
c1.b = 2;
C c2;
c2.a = 3;
c2.b = 4;
cout << c1.a << " " << c1.b << " " << c2.a << " " << c2.b;
}
 
Questo programma non stampa "1 2 3 4", come avrebbe fatto se la variabile membro "b" non fosse stata statica.
Stampa invece "1 4 3 4", perché dopo che la variabile membro "b" ha ricevuto il valore "2" come membro della struttura "c1", riceve il valore "4" come membro della struttura "c2".
 
Dichiarare statica una variabile membro di una struttura o classe significa dichiarare una variabile per cui viene istanziato un oggetto una sola volta, qualunque sia il numero di oggetti che vengono creati in esecuzione (anche nessuno).
Tale oggetto statico viene creato prima che sia chiamata la funzione "main", e viene distrutto dopo che la fine della funzione "main".
 
Si noti che dopo la definizione della struttura C, c'è la seguente riga:
int C::b;
Questa è la "definizione" della variabile membro "b", che era stata solo "dichiarata" nella struttura C.
 
Questa distinzione tra il concetto di "dichiarazione" e quello di "definizione" esiste anche in linguaggio C.
In quest'ultimo linguaggio è frequente, e in C++ un po' meno frequente, avere variabili globali accessibili da più unità di compilazione (che sono i file con estensione ".c").
 
Tutte le unità di compilazione che vogliono utilizzare una variabile globale la devono ''dichiarare'', ma siccome ci deve essere una sola istanza condivisa di tale variabile, una sola unità di compilazione la deve ''definire''.
Le dichiarazioni si distinguono dalle definizioni in quanto precedute dalla parola-chiave "extern".
Inoltre, tipicamente, le dichiarazioni sono contenuti in file separati (cosiddetti "header", cioè "intestazioni"), inclusi nell'unità di compilazione con la direttiva "#include".
Le definizioni sono invece poste direttamente nell'unità di compilazione.
Se si omette di definire una variabile dichiarata e non utilizzata, tale variabile è semplicemente ignorata.
Se invece si omette di definire una variabile dichiarata e utilizzata, non ci sono problemi solo in fase di compilazione, ma viene generato un errore in fase di collegamento (in inglese, "linking").
 
Una definizione di una struttura contiene le dichiarazioni delle sue variabili membro.
Le variabili membro non statiche, non hanno bisogno di definizione, in quanto vengono istanziate quando si istanzia la struttura stessa.
Invece, le variabili membro statiche devono essere definite analogamente alle variabili globali, in quanto si tratta di variabili che devono essere inserite dal linker nell'area statica.
 
Per definire una variabile membro statica, si usa una sintassi analoga a quella usata per definire una variabile globale, con la differenza che prima del nome della variabile si deve porre il nome della classe o struttura, separata dall'operatore "::".
 
L'operatore "::" non esiste in C. Si tratta dell'operatore di "risoluzione dell'ambito" (in inglese, "scope resolution").
Il suo fine è indicare al compilatore dove cercare il nome indicato alla sua destra.
Alla sinistra di tale operatore ci può essere il nome di una struttura o classe, oppure il nome di un namespace (come "std"), oppure anche niente, come nella seguente istruzione.
::a = 3;
Questa istruzione assegna il valore 3 alla variabile globale di nome "a".
L'operatore serve a indicare che tale il variabile non deve essere confusa con eventuali variabili locali o parametri di funzione con lo stesso nome.
Se esiste una variabile globale e una variabile locale con lo stesso nome, e non si usa l'operatore di risoluzione dell'ambito, il compilatore intente che si intenda riferirsi alla variabile locale.
 
== I contenitori standard ==
 
In linguaggio C, l'unico contenitore di oggetti predefinito è l'array di lunghezza fissa, che può essere multidimensionale.
 
Praticamente in qualunque programma non banale c'è l'esigenza di organizzare i dati in strutture dinamiche, cioè che occupano uno spazio definito in fase di esecuzione.
 
La libreria standard del C++ definisce alcune tipologie di contenitori, che possono soddisfare varie esigenze, anche se non tutte.
 
Si tratta di contenitori tipizzati, cioè, a differenza di molti linguaggi di programmazione orientati agli oggetti, un oggetto contenitore può contenere oggetti di un solo tipo.
 
Per implementare un contenitore tipizzato si deve usare un template di classe, in cui il parametro del template è il tipo degli oggetti contenuti.
 
Per esempio la seguente istruzione:
vector<int> a;
definisce la variabile "a" di tipo "vector<int>".
Il tipo "vector<int>" non è definito nella libreria standard. Quest'ultima definisce solo il template di classe "vector", ed è l'istruzione citata che ''istanzia'' il template creando in fase di compilazione la classe "vector<int>".
 
I contenitori principali definiti dalla libreria standard sono i segneti template di classe:
* '''vector''': è un array dinamico. L'oggetto contenitore contiene un puntatore a un buffer allocato dinamicamente. Gli elementi sono allineati in tale buffer. Aggiungendo oggetti in fondo al vector, vengono posti in tale buffer. Quando lo spazio del buffer è esaurito, automaticamente viene allocato un buffer più grande, l'intero vecchi buffer viene copiato nel nuovo buffer e si dealloca il vecchio buffer. Il suo scopo è permettere accessi indicizzati estremamente veloci e aggiunte di elementi in fondo abbastanza veloci.
* '''list''': è una lista a collegamento doppio (in inglese, "doubly-linked list"). Ogni nodo della lista è allocato autonomamente, e contiene un puntatore all'elemento successivo e un puntatore all'elemento precedente. Aggiungendo un elemento in cima, in fondo o in posizione intermedia, viene allocato un nuovo elemento e si impostano i puntatori in modo da mantenere la struttura di lista. Il suo scopo è permettere aggiunte ed eliminazioni veloci in qualunque punto della sequenza. Non consente accessi diretti, ma solo sequenziali in entrambe le direzioni.
* '''deque''': questa parola è l'abbreviazione dell'espressione inglese "Double-Ended QUEue", cioè coda a due estremità. Il suo scopo è permettere accessi diretti abbastanza veloci, e aggiunte ed eliminazioni veloci all'inizio o alla fine della sequenza.
* '''set''': è un insieme matematico ordinato di oggetti, cioè è una collezione ordinata priva di doppioni. È rappresentato da un albero di ricerca i cui nodi sono gli elementi contenuti. Quando si tenta di inserire un nuovo elemento, questo viene cercato nell'albero; se trovato, non viene in realtà inserito; se non trovato, viene inserito nella posizione che compete al suo valore. La ricerca per valore ha complessità logaritmica, a confronto della complessità libeare dei tre contenitori già presentati. Per poter ordinare gli elementi, è necessario che dati due elementi "a" e "b", sia definita l'espressione "a < b". Per esempio, questo è vero sia per i numeri predefiniti che per le stringhe, ma non per i numeri complessi.
* '''multiset''': è una struttura simile al "set", ma può contenere più volte lo stesso oggetto.
* '''map''': è un array associativo ordinato, chiamato anche "dizionario". Tale template è parametrizzato da due tipi: il tipo della chiave e il tipo del valore. È un "set" di associazioni chiave-valore, in cui il criterio di ricerca e di uguaglianza si basa solo sulla chiave.
* '''multimap''': è una struttura simile al "map", ma per ogni chiave ci possono essere più valori.
 
== I canali di ingresso/uscita ==
 
== Le stringhe ==
 
{{wikipedia|titolopedia=C plus plus|titolobooks=il linguaggio C++}}