Sei sulla pagina 1di 45

“Libri” di “testo”

Bjarne Stroustrup “The C++ Programming Language”, Addison


Wesley, 1997
Oppure:
Bjarne Stroustrup “Il linguaggio di Programmazione C++”, Addison
Wesley, ???

Reference online:
http://www.cppreference.com/index.html
http://www.cplusplus.com/ref/
Il primo programma C++
#include <iostream>//direttiva
preprocessore <,> o “”
using namespace std;//abbreviazione
utilizza variabili etc definite in std

int main()
{
cout << “Ciao mondo” <<
endl;//std::cout prompt dei comando
//<< operatore di scrittura
return 0; //programma termina correttamente
solo se viene restituito 0
}
CiaoMondo.cpp
Il primo programma C++
L'esecuzione dei programmi inizia dalla funzione
main()
Le graffe{ e } delimitano blocchi di codice (in questo
caso il corpo della funzione main()
All'interno di un blocco i singoli statement sono
terminati da un ;
/* e */ delimitano i commenti. Si può usare
anche //
Il primo programma C++

#include importa funzionalità tramite il


preprocessore
using namespace ci evita di scrivere per intero i
nomi degli oggetti
cout è la console, vista come file di output “normale”
0 per convenzione indica “nessun errore”
Controllo di flusso: if
//condizione vera sel il suo valore è diverso da 0

if (cond) {
blocco 1
} else {
blocco 2 con == 0
} d

!= 0

if (vendite > media) { blocco 1 blocco 2


bonus = 0.1;
guadagno = quota * bonus;
} else {
guadagno = 0;
}

La clausola else è opzionale ed associa sempre con l'if più vicino!


Controllo di flusso: switch
switch (variabile) { Le espressioni degli switch devono
case valore1:
istruzioni essere costanti
break; break può mancare
case valore2:
istruzioni; al massimo una clausola default
break;
default:
istruzioni;
}

char C = ‘n’; //singolo carattere


switch (C) {
case ‘s’: cout << “Sconto” << endl;
break;
case ‘n’: cout << “Niente sconto” << endl;
break;
default: cout << “Mah!” << endl;
}
Controllo di flusso: while
Ciclo while
== 0
cond
while (cond) {
blocco
} != 0

while (true) { blocco


cout << “Infinito” << endl;
}

Ciclo do...while blocco

do {
blocco
} while (cond); con
!= 0
d

== 0
Controllo di flusso: for
for (inizializzazione; cond; step) {
blocco
}

for (int i = 0; i < 5; i++) {


cout << i << endl;
inizializzazione }

for (;;) {
cout << “Infinito” << endl;
con == 0 }
d
!= 0 Equivale a
inizializzazione;
blocco while (cond) {
blocco;
incremento;
}
step
Controllo di flusso: break e
continue
L’istruzione break interrompe l’attuale istruzione
di iterazione (for, while, do...while).

L’istruzione continue interrompe la corrente


iterazione e ritorna all’inizio del ciclo, iniziandone
un’altra. //ignoro ciò che viene dopo
for (int i = 0, j = 10; i < 15; i++, j--) {
cout << i << endl;
if (i % 2 == 0)
continue;
if (j < 0)
break;
cout << j << endl;
}

StdioTests.cpp
I tipi di dato base: interi
int Intero “standard della macchina”, in genere 32 bit
short Intero “corto”, in genere 16 bit
long Intero “lungo”, in genere 32 bit
char Carattere, in genere 8 bit
long long Intero “molto lungo”, in genere 64 bit (non standard)
I tipi interi possono essere con o senza segno. Il default è con segno
(tranne che per i char dove è dipendente dal compilatore). Per
specificare un intero senza segno si usa la keyword unsigned. Ad es:
unsigned long intero lungo senza segno
Per indicare esplicitamente che un intero è con segno si usa la keyword
signed. //default con segno
Anche bool, che può assumere solo i valori true e false è “quasi” un
intero
I tipi di dato base: virgola mobile
float precisione singola IEEE (32 bit, 23 di mantissa e 8 di
esponente) //1 bit di segno
double precisione doppia IEEE (64 bit, 52 di mantissa e 11 di
esponente)
long double precisione estesa (non standard, 80 bit, 68 di mantissa
e 11 di esponente)

Per una discussione sul floating point, vedi:

http://www.math.grin.edu/~stone/courses/fundamentals/IEEE-reals.html
I tipi di dato base: enumeration
Permette di definire delle costanti intere con nome simbolico
enum colori { //tipo di dato
nero, //a ciascuna etichetta viene assegnato
//un valore numerico dal compilatore
rosso = 1,
verde,
giallo,
bianco = 255
};

Il valore può essere implicito, nel qual caso viene assunto essere uguale
al valore precedente più 1 o a zero nel caso del primo valore.
Definizione di variabili
In C++ le variabili si definiscono facendole precedere dal loro tipo
Si possono definire più variabili dello stesso tipo assieme, separandole
con una virgola
Si può inizializzare una variabile assegnandole un valore al momento
della definizione.
int contatore;
double importo1, importo2;
unsigned long zero = 0, due = 2;
// contatore, importo, importo2 = ??

Attenzione: il valore delle variabili non inizializzate non è


definito (ovvero è casuale)!
I tipi di dato aggregati: array
Permettono di aggregare più dati dello stesso tipo, indicandoli in base al
loro indice.

int vettore[71];

La dimensione dichiarata deve essere costante


Gli indici iniziano da zero!

int vettore[3 + 10]; // Ok


int vettore[i]; // ERRORE!
vettore[71] = 3; // ERRORE! //non esiste
l'elemento 71 solo 70
I tipi di dato aggregati: strutture
Permettono di aggregare più dati in un solo tipo, assegnando ad ogni dato
un nome
struct conto {
string intestatario;
double saldo;
}

Per accedere ai membri della struct si usa l'operatore “punto”

conto ilMioConto;
// ...
ilMioConto.saldo = 1000000; // Magari :)
I puntatori Pointers.cpp
Un puntatore è un riferimento, ovvero punta ad un indirizzo di memoria che
può contenere un dato di base o un dato aggregato
In C++ l'uso dei puntatori è sempre esplicito, attraverso l'operatore asterisco
(*) o l'operatore freccia (->)
Per ricavare l'indirizzo di una variabile si usa l'operatore e commerciale (&)
I nomi degli array sono già implicitamente dei puntatori
int *vptr = vettore; //vettore di 70 elementi dichiarato
precedentemente, vptr punta al primo elemento del vettore

conto *contoPtr = &ilMioConto; //puntatore al tipo


aggregato conto, assegno l'indirizzo di ilMioConto

*vptr = 0; // vettore[0] = 0
vptr = &vettore[12]; //sposto il puntatore
*vptr = 144; // vettore[12] = 144
contoPtr->saldo = 1000000; //accedo all'elemento saldo e
scrivo
NB (*contoptr).saldo == contoptr->saldo
int *pv = &v[0]; // Stessa cosa che int *pv = v;
Aritmetica dei puntatori
Ai puntatori si possono sommare e sottrarre quantità intere per spostarli
di un certo numero di posizioni in memoria

int *vptr = vettore;//il puntatore punta al primo elemento


vptr += 11; // Ora vptr punta a vettore[11]
vptr++; // Ora vptr punta a vettore[12]. Ahio!

//rispetto al Java in C++ è possibile eseguire operazioni


aritmetiche con i puntatori

La relazione tra vettori e puntatori è duplice:


il nome di un vettore è l'indirizzo del suo primo elemento
l'operazione di indicizzazione di un elemento del vettore equivale ad
un'operazione di somma sul puntatore:
int *vptr = vettore;
// Questo e` sempre vero, per ogni i intero:
(vptr + i) == (vettore + i) == &vettore[i]
//entrambi puntano all'elemento i esimo di vettore
//se volessi rifermi all'oggeto dovrei fare *(vptr+i)==
*(vettore + i) == vettore[i]
Il puntatore 0 (NULL)
Il puntatore 0 o NULL è un caso particolare e si riferisce a “nessun
oggetto”. È un caso particolare in quanto non ha tipo: può essere
assegnato ad una variabile puntatore di qualunque tipo.
Usarlo (dereferenziandolo) causa errori a runtime
È molto usato come valore speciale->usato per restituire situazioni di
errore

int *vptr = NULL;


vptr += 11; // Errore
Le reference //esistono solo in C++
Le reference assomigliano a dei puntatori dereferenziati
automaticamente->è applicata automaticamente l'operazione di asterisco implicito

Si dichiarano con & e si usano implicitamente


Una volta inizializzate non possono più essere fatte “puntare” ad un altro
oggetto. Caso particolare: non esiste una “aritmetica di reference”
Non esiste una “reference nulla”

int &vref = vettore[0];//assegno la locazione di memoria 0


del vettore
vref += 11; // Somma 11 a vettore[0], non sposto il puntatore
vref = vettore[7]; // Equivale a vettore[0] = vettore[7]
assegna all'oggetto a cui la reference si riferisce un valore
Programma e unità di
compilazione
L'unità di compilazione è il singolo file .c che “diamo in pasto” al
compilatore per produrre i moduli oggetto.->file sorgente
Il programma è il risultato del linking di uno o più moduli oggetto

pippo.c pluto.c
extern bool foo(int a, int
b); bool foo(int a, int b)
int main() {
{ return true;
return foo() ? 0 : 1; }
}
g++/gcc

Programma (o eseguibile)
Classi di memorizzazione: auto
Le variabili di classe auto (il default) sono allocate sullo stack

Tutte le variabili che abbiamo visto finora erano dunque auto


In caso di funzioni ricorsive ogni livello di chiamata ha la sua istanza delle
variabili auto

I parametri passati alle funzioni sono sempre di tipo auto

long fact(long i)//calcolo del fattoriale


{
if (i == 0)
return 1;
long ret = i * fact(i - 1);
return ret;
}
Classi di memorizzazione: static

Le variabili di classe static sono allocate in una singola istanza per


unità di compilazione. //è presente una sola variabile per ogni file cpp/c

Unità di compilazione diverse possono avere quindi variabili static con


lo stesso nome che rimangono distinte
Vengono inizializzate una volta per tutte prima dell'esecuzione di
main(), quindi prima dell'inizio del programma vero e proprio

static int sum = 3;//può essere usata da tutte le


funzioni che stanno in un’unità di compilazione

int main() {
return sum; // Restituisce 3
}
Dichiarazione e definizione
La dichiarazione indica soltanto il tipo dell'oggetto dichiarato (variabile,
tipo dati o funzione)
La definizione invece fornisce al compilatore tutte le informazioni
necessarie
In particolare la definizione di una variabile riserva lo spazio di memoria
per la variabile
Nota: la sintassi di dichiarazione e di definizione è un po' confusa
extern int i; // Dichiarazione di variabile che sarà definita
successivamente, di solito usata nei file .h (esempio funzioni di sistema cin o
cout)
int j; // Definizione
int k = 3; // Definizione con inizializzazione

extern bool fun(int a, int b); /* Dichiarazione di funzione (detta anche


prototype) */
bool fun(int a, int b) { return a < b; } // Definizione di funzione
seguita dal corpo della funzione

struct foo; // Dichiarazione di tipo di dati strutturato in questo caso posso


usare solo puntatori (senza deferenziarli) o reference a struct foo ma non
posso accedere ai singoli membri poichè non conosco la definizione
struct foo { int bar; } // Definizione di tipo di dati strutturato
Dichiarazione e definizione
Nello stesso programma si possono avere più dichiarazioni, purché
consistenti, per lo stesso nome (variabile, funzione, tipo dati), ma una sola
definizione
Non sempre il compilatore/linker è in grado di rilevare dichiarazioni
inconsistenti in unità di compilazione diverse! //esempio diversa
dichiarazione in file .h diversi
Con la sola dichiarazione di un tipo dato aggregato si possono definire
variabili che contengono puntatori e reference ad oggetti di quel tipo ma
non variabili che contengono gli oggetti stessi, ne allocarli dinamicamente.
Per queste operazioni è necessario avere la definizione
Classi di memorizzazione: extern
Le variabili di classe extern sono allocate in una singola istanza per
programma.->condivise tra tutte le unità di compilazioni del programma, NB diverso
dalle variabili static condivise da una stessa unità di compilazione

Unità di compilazione diverse non possono avere quindi variabili extern


con lo stesso nome (causano errori in linking)

La parola chiave extern viene usata solo per la dichiarazione, non per la
definizione.
Vengono anch'esse inizializzate una volta per tutte prima dell'esecuzione
di main()
extern int bigsum; // dichiarazione bigsum è definita da qualche altra parte
int sum = 3; // Definizione di sum

int main() {
return sum + bigsum; // Restituisce 3 + bigsum
}
Allocazione dinamica: l'heap
(mucchio)
È possibile utilizzare oggetti allocati in modo dinamico, ovvero
esplicitamente dal programmatore.

Per farlo si utilizza la parola chiave new che restituisce un puntatore ad


un oggetto del tipo specificato
Quando non serve più l'oggetto deve essere esplicitamente rilasciato
usando la parola chiave delete.
La sintassi per i vettori è leggermente diversa
persona *ester = new persona;//variabile puntatore di nome
ester di tipo persona a cui assegno un nuovo elemento persona
persona *tante = new persona[5];
//variabile puntatore di nome tante di tipo persona a cui
assegno un vettore di elementi persona[5]
delete ester;
delete[] tante; decl_def
Il tipo void
Il tipo void significa “nessun tipo”
// Funzione che non restituisce nulla
void f(int c);

Il puntatore a void però significa “puntatore a qualunque cosa” (opaque


pointer)
/* Funzione che restituisce un
* puntatore a chissa` cosa */
void *g(int c);

È spesso usato nelle librerie “di sistema” per passare al chiamante


qualcosa che non deve toccare, ma che può solo passare di nuovo alla
libreria stessa
È molto usato nelle librerie C (non C++) anche per restituire un puntatore
ad un tipo non specificato. Ad es. la funzione malloc()della libreria C,
equivalente dell'istruzione new del C++, restituisce un void *
Il modificatore const
Introduce una variabile che, una volta inizializzata, non può essere
modificata
const int zero = 0;
//...
zero = 1; // Errore

Si può riferire anche ad un oggetto puntato da un puntatore

/* Funzione che accetta un puntatore


* ad una persona e garantisce di non
* modificare l'oggetto puntato */
int g(const persona *p); //il puntatore p punta ad un oggetto
persona che è costante, il puntatore può essere modificato, ma non
l'oggetto puntato, questo ci garantisce che l'oggetto non venga
modificato
Divagazione: leggere i parametri
passati al programma
La funzione main() può avere 2 argomenti, tradizionalmente chiamati
argc e argv.

argc è il numero di elementi di argv

argv è un vettore contenente://char pointer

in posizione 0 il nome del programma


nelle posizioni successive gli argomenti passati da riga di comando
//esempio da riga di comando il file IPconfig

CommandLine.cc
io.cc
Object Oriented Programming: classi

L'idea è di unire codice e dati in un unico tipo che chiamiamo class


(ovvero classe)->dati e funzioni metodi che lavorano sui dati unite nella definizione di classe
In questo modo si può limitare l'accesso ai dati al solo codice della
classe, garantendo così il mantenimento di certi invarianti: proprietà che
sono sempre verificate dalla classe->incapsulamento
I dati che fanno parte di una classe si chiamano [data] members (ovvero
membri [dato]) ed il codice methods (metodi) (funzioni)

In C++ le classi si definiscono con la parola chiave class (incredibile!)


che altro non è che un tipo particolare di struct->in un tipo struct tutti i membri
sono visibili dall'esterno, negli oggetti di tipo class i campi sono nascosti di default

È particolare perché di default i membri di una class non sono visibili


dall'esterno della class mentre quelli di una struct si
class e visibilità
Questa è una struct che contiene dei metodi...
struct Persona
{ //metodi
Persona(string nome_, string cognome_, Persona *padre_, Persona *madre_)
: nome(nome_), cognome(cognome_), padre(padre_), madre(madre_)
{
orfano = (!padre || !madre);

}
//metodo particolare che si chiama come la classe è detto costruttore inizializza
l'oggetto
//il codice dopo : sono i membri mentre i valori nome_ sono passati dall'esterno,
inizializzo i membri con i valori passati dall'esterno
//orfano lo inizializzo all'interno della funzione
//il costruttore consente di inizializzare i dati nell'ordine in cui compaiono
nella definizione del tipo

bool is_orfano() const { return orfano; }


//dalla main non posso accedere ad orfano ma solo alla funzione is_orfano il
metodo tipo di ritorno const non modifica lo stato dell'oggetto

private://Dati
string nome;
string cognome;
Persona *padre;
Persona *madre;
bool orfano;
};
class e visibilità
...e questa è una class perfettamente equivalente. Cambia soltanto
l'uso di public/private
class Persona
{
public:
Persona(string nome_, string cognome_, Persona *padre_, Persona *madre_)
: nome(nome_), cognome(cognome_), padre(padre_), madre(madre_)
{
orfano = (!padre || !madre);
}

bool is_orfano() const { return orfano; }

private:
string nome;
string cognome;
const Persona *padre;
const Persona *madre;
const bool orfano;
};
Molta carne al fuoco
public significa che tutti i membri e metodi che seguono sono
accessibili anche da oggetti di altre classi o da codice non facente parte
di alcuna classe (ad es. dalla funzione main())

private invece significa che i membri e metodi che seguono non sono
accessibili da oggetti di altre classi o codice non facente parte di una
classe->sono accessibili dai metodi di una classe

const dopo le parentesi di un metodo significa che il metodo non


modifica i membri dato della classe
Il metodo che si chiama come la classe è detto costruttore ed ha il
compito di creare l'oggetto

Class0.cc
Ridefinire gli operatori
In C++ gli operatori del linguaggio (+, -, /, *, !, ecc) sono funzioni
come le altre
Si può quindi definire il codice che viene eseguito quando vengono
usati gli operatori.
Per far questo basta definire un metodo o una funzione di nome
operatorop dove op è uno degli operatori del linguaggio
class List {
public:
void append(const string &el) { /* ... */ }//metodo
List &operator += (const string &el) { append(el); return *this; }
//accetta una reference a stringa costante, appende la stringa alla lista
restituisce l'oggetto puntato da this cioè quello in cui si sta lavorando
};

template List &operator += (List &l, const string &el) {


l.append(el);
return l;
}//passo esplicitamente come argomento l'oggetto lista
//attenzione alla chiarezza se un operatore è redefinito o meno
//Nota: gli operatori cin o cout dono definiti tramite template
Costruttori e distruttore
//costruttore crea un oggetto l'altro lo elimina
Una classe può avere più di un costruttore, differenziati in base agli
argomenti. È un caso particolare di overloading di un metodo
class Persona
{
Persona()
: nome(“”), cognome(“”), padre(0), madre(0), orfano(false)
{ }
Persona(string nome_, string cognome_)
: nome(nome_), cognome(cognome_), padre(0), madre(0), orfano(false)
{ }
//: nome() questa sintassi è un elenco di assegnamento equivalente a scrivere
all'interno nome=string nome; il compilatore garantisce che l'assegnazione avviene
nell'ordine con cui i dati sono definiti nella parte private
};
Una classe può avere un distruttore (uno solo!) che viene eseguito
quando un oggetto esce di visibilità
class Persona
{
// ...
~Persona() { cout << “Il tristo mietitore si porta via “ << nome << endl; }
}; //occorre poter elimiare gli oggetti senza specificare i parametri, non esiste una
sintassi per i parametri del distruttore
Costruttori e distruttore (2)
Un costruttore senza argomenti si dice costruttore di default e si può
usare per costruire oggetti senza specificare parametri //si può utilizzare per
inizializzare una classe senza valori

Un costruttore che accetta come unico argomento un oggetto del tipo


che viene costruito (es. Persona) si dice copy constructor (perché può
essere usato per costruire una copia dell'oggetto passato come
argomento).
Quando si usa un copy constructor è buona norma definire anche
l'operatore copia (Persona & operator = (const &Persona))

//l'operatore copy constructor e il costruttore di copia devono corrispondere

costr_destr.cc
Ereditarietà
Una classe può derivare da un'altra. Caratteristiche della classe derivata:
La classe derivata comprende automaticamente tutti i membri dato e tutti
i metodi della classe base
Un puntatore (o reference) ad oggetto di classe derivata può essere
passato dovunque è richiesto un puntatore (o reference) alla classe base
(implicit downcast).//la classe derivata può essere usata in tutti i posti dove uso la
classe padre

La classe derivata può modificare la definizione dei metodi della classe


base
Una classe può derivare da più classi base contemporaneamente
class Lavoratore : public Persona //classe lavoratore caso particolare della
classe persona
{
// ...
};
Ereditarietà
Quando si usa un oggetto tramite un puntatore la scelta del metodo da
invocare (ovvero, se invocare quello della classe base o quello della
classe derivata) può avvenire:
a tempo di compilazione (è il default)//statico, per motivi storici e di efficienza
a tempo di esecuzione, se il metodo è dichiarato con il modificatore
virtual//binding dinamico in java esiste solo il secondo modo
Se implementare un metodo non ha senso si può dichiararlo virtuale
puro, in questo caso non si possono creare oggetti della classe.//il
metodo è definito senza vederne l'implementazione, serve nel caso in cui il metodo
servirà nelle classi derivate (concrete) ma non serve nella classe base
class Persona
{
virtual bool lavora() const = 0;
};

class Lavoratore : public Persona


{
bool lavora() const { return true; }
//definisco il metodo Class1
};
Esercizio 1
Modificare l'esempio Class0 in modo da gestire diversi tipi di Persone,
come visto nell'esempio Class1
Template //le mappe liste in c++ sono template
Il template è un tipo parametrico (basato su uno o più altri tipi)

In C++ si possono avere sia tipi sia funzioni template

Si definiscono con la parola chiave template

Quando si usano si devono specificare i tipi su cui si basa il template


template<class C> C max(C a, C b) { return (a >= b) ? a : b; }
//C nome di un tipo, funzione template

template<class K, class V> class Mappa //classe template


{
public:
V get(K k);//definisco due metodi
void set (K k, V v);//nel caso di un tipo devono essere
specificati, dichiara una variabile mappa di tipo mappa
};

//...
int m = max<int>(1, 2);//specifico che tipo di parametro deve
ricevere la funzione
int m = max(1, 2); // Solo in certi casi, nel caso passo 2.3 il
compilatore non sa quale tipo di cast effettuare
Mappa<string, int> mappa;
Standard Template Library

La libreria standard C++ mette a disposizione alcuni tipi template di


uso generale

I principali tipi sono vector, string, map (memoria associativa) e


list.
Tutti questi tipi sono detti anche contenitori

string s;//non è un tipo parametrizzato


vector<int> v;
map<string, Persona> persone; //mappa da stringa a persona
STL: gli iteratori

Tutti i tipi della Standard Template Library definiscono un iteratore


L'iteratore permette di enumerare tutti gli elementi di un contenitore
vector<int> v;
// ...
for (vector<int>::iterator i = v.begin(); i != v.end(); ++i) {
cout << *i << “ “;

//vector<int>::iterator i è l'iteratore che vine inizializzato al primo


elemento di v fino alla fine di v

//operatore * ridefinito per gli iteratori e rappresenta l'oggetto a cui


l'iteratore fa riferimento
//l'iteratore è una specie di puntatore in cui non vale l'aritmetica dei
punatori, non mi sposto nlle celle di memoria, ma mi sposto all'interno del
contenitore, operazione ++ ridefinita
STL: requisiti per i tipi
Per poter inserire elementi di un certo tipo in un contenitore STL il
tipo deve soddisfare alcuni requisiti
Deve avere un costruttore di default e un copy constructor (e quindi
è consigliabile ridefinire anche l'operatore =)
In certi casi (il tipo-chiave della mappa) deve sottostare ad altri
requisiti (ad es. definire l'operatore < //poichè nella mappa occorre
confrontare gli elementi)
vector<int> v;
// ...
for (vector<int>::iterator i = v.begin(); i != v.end(); ++i) {
cout << *i << “ “;
}
STL: il tipo vector

È simile all'array ma dell'allocazione e deallocazione si occupa l'STL


Si può usare l'operatore-indice “n-esimo elemento” [n]

vector<string> v(10);//dichiarazione
v[9] = “Ultimo”;// assegnazione
STL: il tipo map

È una mappa associativa ovvero un vettore il cui “indice” (detto


chiave) non è obbligatoriamente un intero, ma può essere un
qualunque tipo su cui è definita la relazione < (minore di)
L'iteratore sul tipo map<K, V> restituisce oggetti di tipo std::pair<K,
V> //k key, v value, restituisce la coppia pair<k,v>

map<string, int> stipendio;//è definito l'operatore parentesi quadra


stipendio[“Paolo”] = 1;
stipendio[“Piero”] = 15;
stipendio[“Anselmo”] = 30;
for (map<string, int>::const_iterator i = stipendio.begin();
i != stipendio.end; ++i) {
string nome = i->first;//puntatore al primo elemento dell'iteratore
string rapporto = i->second;
// ...
}

Potrebbero piacerti anche