Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Puntatori e Riferimenti

559 views

Published on

Introduzione ai puntatori e ai riferimenti in C++. Viene presentato 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
  • Be the first to comment

  • Be the first to like this

Puntatori e Riferimenti

  1. 1. Puntatori & Riferimenti Ilio Catallo – info@iliocatallo.it
  2. 2. Indice ¤ Il problema dello swap ¤ Puntatori ¤ Reference 2
  3. 3. Il problema dallo swap
  4. 4. Il problema dello swap ¤ Supponiamo che in un programma avvenga frequentemente lo scambio (swap) tra due interi 4 2 inta: 5 intb:
  5. 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. 6. Il problema dello swap ¤ Come scrivere int_swap? 6
  7. 7. Il problema dello swap ¤ Un possibile tentativo potrebbe essere: ¤ La soluzione sembra immediata…ma funziona? void int_swap(int x, int y) { int temp = x; x = y; y = temp; } 7
  8. 8. Il problema dello swap 8
  9. 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. 10. Il problema dello swap ¤ Non è possibile scrivere la funzione int_swap passando le variabili per copia 10 #@?$!
  11. 11. Puntatori
  12. 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. 13. Organizzazione della memoria ¤ Ad ogni cella è associato un indirizzo crescente 13
  14. 14. Organizzazione della memoria ¤ L’indirizzo di una cella è pari a quella della precedente più uno 14
  15. 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. 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. 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 17771776 1778 1779 1780 int a
  18. 18. Variabili e memoria ¤ Ciononostante, una variabile occupa una o più celle di memoria 18 1775 17771776 1778 1779 1780 int a
  19. 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 17771776 1778 1779 1780 int a&a
  20. 20. Indirizzo di una variabile ¤ L’espressione &a può essere letta come “indirizzo di a” 20 1775 17771776 1778 1779 1780 int a&a
  21. 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. 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. 23. Indirizzo di una variabile ¤ Quando il simbolo & precede un’espressione, questo assume il ruolo di operatore (address-of operator) 23 int a = 5; std::cout << "the address of a is " << &a << std::endl;
  24. 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 17771776 1778 1779 1780 int a&a 24
  25. 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. 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. 27. Memorizzare un indirizzo ¤ 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 int a = 5; std::cout << "the address of a is " << &a << std::endl; 27
  28. 28. Memorizzare un indirizzo ? address_of_a = &a; &aaddress_of_a:5a: int ? 28
  29. 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. 30. Memorizzare un indirizzo ¤ Qual è il tipo dato di address_of_a? 30 ? address_of_a = &a;
  31. 31. Memorizzare un indirizzo ¤ Il tipo dato di una variabile che contiene l’indirizzo di un intero è int* ¤ Il tipo dato int* si chiama puntatore a intero int* address_of_a = &a; 31
  32. 32. Puntatori ¤ Variabili che contengono indirizzi sono chiamate puntatori ¤ 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 Per un tipo dato T, T* è il tipo dato puntatore a T * Purchè il tipo dato non sia più piccolo di 1 byte 32
  33. 33. Esempi di puntatori ¤ 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 int* a; // pointer to int char* b; // pointer to char float* c; // pointer to float int** d; // pointer to pointer to int Per un tipo dato T, T* è il tipo dato puntatore a T 33
  34. 34. XKCD 34
  35. 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. 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. 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. 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. 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. 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. 41. Indirezione &aaddress_of_a: 5a: int int* 41
  42. 42. Geek&Poke 42
  43. 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. 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. 45. Puntatori nulli ¤ Il puntatore potrebbero contenere un qualsiasi valore 45 int* ptr; // uninitialized pointer
  46. 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. 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. 48. Puntatori nulli ¤ È molto facile trovarsi ad utilizzare un puntatore non inizializzato senza nemmeno rendersene conto 48 int* ptr; // uninitialized pointer
  49. 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. 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. 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. 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. 53. Il problema dello swap: soluzione ¤ Come possiamo usare i puntatori per riuscire a scrivere la funzione int_swap? 53
  54. 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. 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. 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. 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. 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. 59. Problema risolto? ¤ 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? int_swap(&a, &b); 59
  60. 60. Riferimenti
  61. 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. 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. 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. 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. 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. 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. 67. Esempi di riferimenti ¤ 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 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*) 67
  68. 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” 68
  69. 69. Limitazione dei riferimenti ¤ I riferimenti devono essere inizializzati ¤ I riferimenti non possono essere ri-assegnati: ¤ Non sono ammessi* riferimenti a riferimenti int& b; //ERROR: b is a new name of which variable? 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” * Dal C++11, esiste un unico strappo alla regola al fine di permettere il perfect forwarding 69
  70. 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. 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. 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. 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. 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. 75. Bibliografia
  76. 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

×