Your SlideShare is downloading. ×
  • Like
  • Save
Puntatori e Riferimenti
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Now you can save presentations on your phone or tablet

Available for both IPhone and Android

Text the download link to your phone

Standard text messaging rates apply

Puntatori e Riferimenti

  • 163 views
Published

Introduzione ai puntatori e ai riferimenti in C++. Viene introdotto il problema dello swap, come esempio motivante per l'introduzione della semantica di riferimento. Si procede con l'introduzione del …

Introduzione ai puntatori e ai riferimenti in C++. Viene introdotto il problema dello swap, come esempio motivante per l'introduzione della semantica di riferimento. Si procede con l'introduzione del concetto di puntatore, a cui segue una spiegazione dei basilare operatori di referenziazione e dereferenziazione. Il problema dello swap viene risolto mediante puntatori. Si procede con l'introduzione dei riferimenti, come alias di variabili esistenti. Il problema dello swap viene in ultimo risolto mediante riferimenti.

Published in Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
163
On SlideShare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
2
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Puntatori & Riferimenti Ilio Catallo – Politecnico di Milano Ilio.catallo@polimi.it
  • 2. Indice ¤ Il problema dello swap ¤ Puntatori ¤ Reference 2
  • 3. Il problema dallo swap
  • 4. Il problema dello swap ¤ Supponiamo che in un programma avvenga frequentemente lo scambio (swap) tra due interi 4 a: 2 int b: 5 int
  • 5. Il problema dello swap ¤ IDEA: Scrivere una funzione int_swap e richiamarla ovunque necessario int main() { ... int a = 2, b = 5; ... int_swap(a, b); // expected result: a = 5, b = 2 } 5
  • 6. Il problema dello swap ¤ Come scrivere int_swap? 6
  • 7. Il problema dello swap ¤ Un possibile tentativo potrebbe essere: void int_swap(int x, int y) { int temp = x; x = y; y = temp; } ¤ La soluzione sembra immediata…ma funziona? 7
  • 8. Il problema dello swap 8
  • 9. Il problema dello swap ¤ All’invocazione di int_swap: ¤ I valori di a e b nella funzione chiamante vengono copiati nelle variabili locali x e y ¤ i valori di x e y vengono scambiati, ma… ¤ Le variabili a e b nel chiamante rimangono intoccate ... int a = 2, b = 5; int_swap(a, b); // x and y are copies of a and b, // hence a and b remain unchanged 9
  • 10. Il problema dello swap ¤ Non è possibile scrivere la funzione int_swap passando le variabili per copia 10 #@?$!
  • 11. Puntatori
  • 12. Organizzazione della memoria ¤ La memoria può essere immaginata come una successione di celle di memoria ¤ Ogni cella occupa la minima dimensione che il calcolatore è in grado di gestire (1 byte*) *1 byte è il quantitativo più comune 12
  • 13. Organizzazione della memoria ¤ Ad ogni cella è associato un indirizzo crescente 13
  • 14. Organizzazione della memoria ¤ L’indirizzo di una cella è pari a quella della precedente più uno 14
  • 15. Organizzazione della memoria ¤ In questo modo: ¤ Ogni cella ha un indirizzo univoco ¤ È nota una regola per passare da una cella alle successive 15
  • 16. Nome di una variabile ¤ Il nome della variabile è lo strumento usato per accedere al contenuto della memoria riservata a tale variabile int a = 5; int b = a + 5; // we use the data contained in ‘a’ // without knowing its physical // location in memory 16
  • 17. Variabili e memoria ¤ In virtù del fatto che ogni variabile ha un nome, non dobbiamo solitamente preoccuparci di dove i dati vengano memorizzati 17 1775 1776 1777 1778 1779 1780 int a
  • 18. Variabili e memoria ¤ Ciononostante, una variabile occupa una o più celle di memoria 18 1775 1776 1777 1778 1779 1780 int a
  • 19. Indirizzo di una variabile ¤ Quando vogliamo riferirci all’indirizzo di una variabile, piuttosto che il suo valore, utilizziamo la sintassi &nome_variabile 19 1775 1776 1777 1778 1779 1780 &a int a
  • 20. Indirizzo di una variabile ¤ L’espressione &a può essere letta come “indirizzo di a” 20 1775 1776 1777 1778 1779 1780 &a int a
  • 21. Indirizzo di una variabile ¤ Data dunque la definizione: ¤ Si ha che: ¤ a si riferisce al contenuto della variabile (5 in questo caso) ¤ &a si riferisce all’indirizzo dove tale contenuto risiede in memoria 21 int a = 5;
  • 22. Indirizzo di una variabile ¤ Esempio: stampare a video l’indirizzo di una variabile 22 int a = 5; std::cout << "the address of a is " << &a << std::endl;
  • 23. Indirizzo di una variabile ¤ Quando il simbolo & precede un’espressione, questo assume il ruole di operatore (address-of operator) 23 int a = 5; std::cout << "the address of a is " << &a << std::endl;
  • 24. Indirizzo di una variabile ¤ Nota: se una variabile occupa più di una cella di memoria, l’operatore & restituisce l’indirizzo della prima cella di memoria utilizzata 1775 1776 1777 1778 1779 1780 &a int a 24
  • 25. Dimensione dei tipi dati in C++ ¤ Non esistono dimensioni prefissate dei tipi dati ¤ La dimensione in byte di ogni tipo dato è dipendente dall’architettura del calcolatore e dal compilatore ¤ Lo standard C+ specifica unicamente il minimo intervallo di valori memorizzabili 25
  • 26. Dimensione dei tipi dati in C++ ¤ Esempio: un int deve permettere di memorizzare numeri interi tra -32’767 e +32’767 ¤ Tipica dimensione su architettura Intel 16-bit: 2 byte ¤ Tipica dimensione su architettura Intel 32-bit: 4 byte 26
  • 27. Memorizzare un indirizzo int a = 5; std::cout << "the address of a is " << &a << std::endl; ¤ La stampa di un indirizzo non è particolarmente utile ¤ Supponiamo invece di voler memorizzare l’indirizzo di a, in modo da poterlo riutilizzare successivamente ¤ Abbiamo bisogno di una variabile address_of_a il cui contenuto sia l’indirizzo di a 27
  • 28. Memorizzare un indirizzo ? address_of_a = &a; a: 5 int address_of_a: &a ? 28
  • 29. Memorizzare un indirizzo ¤ La variabile address_of_a deve contenere “l’indirizzo di una variabile di tipo int” 29 ? address_of_a = &a;
  • 30. Memorizzare un indirizzo ¤ Qual è il tipo dato di address_of_a? 30 ? address_of_a = &a;
  • 31. Memorizzare un indirizzo ¤ Il tipo dato di una variabile che contiene l’indirizzo di un intero è int* int* address_of_a = &a; ¤ Il tipo dato int* si chiama puntatore a intero 31
  • 32. Puntatori ¤ Variabili che contengono indirizzi sono chiamate puntatori Per un tipo dato T, T* è il tipo dato puntatore a T ¤ Una variable di tipo T* può contenere l’indirizzo di una variabile di tipo T ¤ È possibile creare puntatori a qualsiasi tipo dato*, indipendentemente che questo sia predefinito o creato dall’utente * Purchè il tipo dato non sia più piccolo di 1 byte 32
  • 33. Esempi di puntatori Per un tipo dato T, T* è il tipo dato puntatore a T int* a; // pointer to int char* b; // pointer to char float* c; // pointer to float int** d; // pointer to pointer to int ¤ Regola (del pollice) per interpretare il tipo dato: ¤ Il più a destra degli asterischi * si legge “puntatore a…” ¤ Tutto ciò che segue da destra a sinistra è il tipo puntato 33
  • 34. XKCD 34
  • 35. Accedere al contenuto di una variabile ¤ Supponiamo che qualcuno ci fornisca un puntatore a intero ptr contenente l’indirizzo di una variabile intera 35 int* ptr = ... // somehow initialized
  • 36. Accedere al contenuto di una variabile ¤ Possiamo usare l’indirizzo memorizzato in ptr per accedere al contenuto della variabile puntata? 36 int* ptr = ... // somehow initialized
  • 37. Indirezione ¤ Dato un puntatore, è possibile accedere al contenuto della variabile puntata attraverso l’operatore * (indirection operator) int* ptr = ... // somehow initialized std::cout << "the value of the variable pointed to by ptr is " << *ptr << std::endl; 37
  • 38. Indirezione ¤ L’espressione *ptr può essere letta come “il valore della variabile puntata da ptr” int* ptr = ... // somehow initialized std::cout << "the value of the variable pointed to by ptr is " << *ptr << std::endl; 38
  • 39. Indirezione ¤ L’operatore di indirezione può essere usato sia per leggere che per modificare il valore della variabile puntata int* ptr = ... // somehow initialized *ptr = 7; int b = 5 + *ptr; // b contains 12 39
  • 40. Indirezione ¤ In altre parole, l’espressione *ptr può apparire a sinistra dell’operatore di assegnamento 40 int* ptr = ... // somehow initialized *ptr = 7; int b = 5 + *ptr; // b contains 12
  • 41. Indirezione address_of_a: &a int* a: 5 int 41
  • 42. Geek & Poke 42
  • 43. Metodi d’accesso ¤ È possibile quindi accedere al contenuto di una variabile in due modi: ¤ Attraverso il suo nome ¤ Attraverso il suo indirizzo (memorizzato in un puntatore) ¤ L’accesso mediante puntatore è indiretto ¤ per accedere al contenuto della variabile puntata è necessario prima accedere al contenuto del puntatore 43
  • 44. Puntatori nulli ¤ Come per tutte le variabili locali, non è possibile conoscere il valore di un puntatore non inizializzato 44 int* ptr; // uninitialized pointer
  • 45. Puntatori nulli ¤ Il puntatore potrebbero contenere un qualsiasi valore 45 int* ptr; // uninitialized pointer
  • 46. Puntatori nulli ¤ Tale valore casuale potrebbe essere interpretato come l’indirizzo di un ipotetica variabile puntata dal puntatore 46 int* ptr; // uninitialized pointer
  • 47. Puntatori nulli ¤ Non esiste alcun modo di capire se il valore contenuto in un puntatore rappresenti o meno un indirizzo valido 47 int* ptr; // uninitialized pointer
  • 48. Puntatori nulli ¤ È molto facile trovarsi ad utilizzare un puntatore non inizializzato senza nemmeno rendersene conto 48 int* ptr; // uninitialized pointer
  • 49. Puntatori nulli ¤ Per evitare il rischio di avere puntatori non inizializzati sarebbe sufficiente inizializzare un puntatore solo dopo aver definito la variabile da puntare 49 int a = 5; int* ptr = &a;
  • 50. Puntatori nulli ¤ Purtroppo, ciò non è sempre realizzabile ¤ in alcuni casi non è effettivamente possibile creare la variabile da puntare prima dell’inizializzazione del puntatore ¤ Anche senza una variabile da puntare, sarebbe preferibile evitare rischi inizializzando il puntatore. 50
  • 51. Puntatori nulli ¤ È possibile inizializzare un puntatore e al contempo indicare che non è ancora disponibile la corrispettiva variabile a cui puntare ¤ Per farlo si inizializza il puntatore al valore: ¤ NULL (fino al C++03) o ¤ nullptr (dal C++11) 51
  • 52. Puntatori nulli ¤ L’espressione può essere letta come “il puntatore ptr non punta a nulla” int* ptr = nullptr; // 'ptr’ is not // pointing to anything 52
  • 53. Il problema dello swap: soluzione ¤ Come possiamo usare i puntatori per riuscire a scrivere la funzione int_swap? 53
  • 54. Il problema dello swap: soluzione ¤ IDEA: invece di passare una copia del contenuto delle variabili, passare una copia del loro indirizzo void int_swap(int* x, int* y) { int temp = *x; *x = *y; *y = temp; } 54
  • 55. Il problema dello swap: soluzione ¤ All’invocazione di int_swap: ¤ Gli indirizzi di a e b nella funzione chiamante vengono copiati nelle variabili puntatore locali x e y ¤ Si scambiano i valori delle variabili puntate da x e y (ovvero a e b) ¤ Al termine della funzione int_swap le variabili locali x e y vengono distrutte ¤ Come invocare la nuova funzione int_swap? 55
  • 56. Il problema dello swap: soluzione ¤ Sappiamo che: ¤ La funzione int_swap si aspetta in ingresso gli indirizzi delle variabili da scambiare ¤ Per ottenere l’indirizzo di una variabile si usa l’operatore & 56
  • 57. Il problema dello swap: soluzione ¤ Se vogliamo scambiare il contenuto di a e b dobbiamo quindi fornire l’indirizzo di a (&a) e l’indirizzo di b (&b) int main() { ... int a = 2, b = 5; ... int_swap(&a, &b); // now: a = 5, b = 2 } 57
  • 58. Passaggio per indirizzo ¤ Se una funzione riceve una copia delll’indirizzo di una variabile piuttosto che un copia del suo valore, si parla di passaggio per indirizzo ¤ Il passaggio per indirizzo permette di: ¤ Riflettere le modifiche attutate nella funziona chiamata anche alla funzione chiamante ¤ Accedere rapidamente a dati corposi, dato che nessuna copia del dato viene creata ¤ Simulare il ritorno di più variabili da una funzione 58
  • 59. Problema risolto? int_swap(&a, &b); ¤ Siamo soddisfatti della nostra soluzione? ¤ Il metodo funziona, ma… ¤ Il codice è diventato difficile da leggere ¤ dovremmo gestire il caso di particolare in cui viene passato nullptr come indirizzo ¤ Possiamo fare di meglio? 59
  • 60. Riferimenti
  • 61. Inizializzare una variabile ¤ Al momento dell’inizializzazione di una variabile, il valore della variabile inizializzatrice viene copiato nella nuova variabile ¤ Al termine dell’inizializzazione, ognuna delle due variabili contiene la propria copia dello stesso valore ¤ La nuova variabile vive una vita indipendente, il cambiamento del suo valore non altera quello della variabile inizializzatrice 61
  • 62. Dichiarare una variabile ¤ Quando una variabile viene dichiarata, se ne specifica il tipo e l’identificativo (nome) ¤ Entrambi questi aspetti non sono modificabili in un secondo momento ¤ Non è possibile tramutare una variabile di tipo T in una variabile di tipo T’* ¤ Non è possibile mutare l’identificativo (cambiare nome ad una variabile) * ricorda che il casting crea una nuova variabile, non cambia la natura di una variabile esistente 62
  • 63. Alias di una variabile ¤ Il C++ permette però di inizializzare nomi aggiuntivi (alias) per una variabile: int a = 5; int& b = a; // b is a new name (alias) for a 63
  • 64. Alias di una variabile ¤ b non indica una nuova area di memoria, b è solo un altro nome per a int a = 5; int& b = a; // b is a new name (alias) for a 64
  • 65. Alias di una variabile ¤ b è una variabile di tipo int&, ovvero un alias di una variabile di tipo int int a = 5; int& b = a; // b is a new name (alias) for a 65
  • 66. Riferimenti ¤ Gli alias di una variabile sono chiamati riferimenti (reference*) ¤ L’inizializzazione di un riferimento non comporta la copia del valore della variabile inizializzatrice, bensì l’associazione (binding) del nuovo nome alla variabile * Dal C++11 sono stati rinominati lvalue reference Per un tipo dato T, T& è il tipo dato riferimento a T 66
  • 67. Esempi di riferimenti Per un tipo dato T, T& è il tipo dato riferimento a T int& a = b; // a is a new name for b (where b is of type int) float& c = d; // c is a new name for d (where d is of type float) int& e = b; // e is a new name for b, hence a new name for a int*& f = g; // f is a new name for g (where g is of type int*) ¤ Regola (del pollice) per interpretare il tipo dato: ¤ L’ampersand & più a destra si legge “riferimento a…” ¤ Tutto ciò che segue da destra a sinistra è il tipo a cui ci si riferisce 67
  • 68. Nuovo vs. vecchio nome ¤ Una volta inizializzato, usare il nuovo nome o il vecchio nome è indifferente* int a = 5; int& b = a; // b is a new name (alias) for a b = 7; std::cout << a << std::endl; // the output will be '7' std::cout << &a << &b << std::endl; //'a' and 'b' are the same thing, // so the address is the same * concetto riassumibile nella massima: “the reference is the referent” 6 8
  • 69. Limitazione dei riferimenti ¤ I riferimenti devono essere inizializzati int& b; //ERROR: b is a new name of which variable? ¤ I riferimenti non possono essere ri-assegnati: int a = 5, c = 10; int& b = a; b = c; // this does not mean that b is now a // new name for c, the expression amounts to // say “a = c” ¤ Non sono ammessi* riferimenti a riferimenti * Dal C++11, esiste un unico strappo alla regola al fine di permettere il perfect forwarding 69
  • 70. Il problema dello swap: soluzione ¤ I riferimenti ci permettono di scrivere la funzione int_swap in maniera semplice e sicura void swap(int& x, int& y) { int temp = x; x = y; y = temp; } Il corpo della funzione è uguale a quanto scritto nella prima (errata) versione 70
  • 71. Il problema dello swap: soluzione ¤ I riferimenti ci permettono di scrivere la funzione int_swap in maniera semplice e sicura: ¤ Semplice: non dobbiamo usare l’operatore di indirezione ovunque ¤ Sicura: non possiamo per errore passare nullptr 71
  • 72. Il problema dello swap: soluzione ¤ Per scambiare i valori di a e b, invochiamo la funzione int_swap esattamente come una qualsiasi altra funzione 72 int main() { ... int a = 2, b = 5; ... int_swap(a, b); // now: a = 5, b = 2 }
  • 73. Il problema dello swap: soluzione ¤ In questo modo: ¤ x è un alias per a ¤ y è un alias per b int main() { ... int a = 2, b = 5; ... int_swap(a, b); // now: a = 5, b = 2 } 73
  • 74. Il problema dello swap: soluzione ¤ Modificare x e y è la stessa cosa che modificare a e b 74 int main() { ... int a = 2, b = 5; ... int_swap(a, b); // now: a = 5, b = 2 }
  • 75. Bibliografia
  • 76. Bibliografia ¤ S. B. Lippman, J. Lajoie, B. E. Moo, C++ Primer (5th Ed.) ¤ B. Stroustrup, The C++ Programming Language (4th Ed.) ¤ R. Lafore, Object Oriented Programming in C++ (4th Ed.) ¤ C++FAQ, Section 8 http://www.parashift.com/c++-faq/references.html 76