Fondamenti di Informatica 2 Ingegneria Informatica Docente: Giovanni Macchia a.a. 2002-2003 Puntatori Una variabile di tipo puntatore contiene un indirizzo di memoria con la locazione di una variabile(detta variabile puntata). La variabile puntata può essere di qualsiasi tipo (non solo quindi di tipo fondamentale ) e può quindi essere un puntatore stesso. Indirizzo Memoria 100 220 puntatore variabile puntata 220 10 Dichiarazione di variabile puntatore In C++, la sintassi per la dichiarazione di una variabile puntatore è effettuata tramite il qualificatore di tipo * (suffisso *): T *var_puntatore dove T è il tipo del puntatore e var_puntatore è il nome della variabile puntatore (puntatore a T) Es: int *p; p è puntatore ad una variabile int double **vel vel è puntatore a puntatore di variabile double Un puntatore può puntare solo a variabili di tipo specificato nella dichiarazione, cioè di tipo T. Dichiarazione di variabile puntatore ATTENZIONE!!! La dichiarazione di un puntatore comporta l’allocazione di memoria del puntatore, non della variabile puntata. Nell’esempio precedente, int* p comporta l’allocazione di memoria p (p.e. 100) ma non vi è allocazione per *p. Dichiarazione di variabile puntatore ATTENZIONE!! E’ corretta anche la seguente sintassi T* var_puntatore ma ricordate che in una lista di variabili , con questa sintassi, solo la prima variabile è un puntatore. Es: int* p, quota; p è un puntatore ad int, mentre quota è una variabile di tipo int, non un puntatore !! Operatori per puntatori Gli operatori non aritmetici usati in espressioni in cui sono coinvolti puntatori sono i seguenti operatori unari: • Operatore di indirizzo & • Operatore di dereferenza * Operatore di indirizzo L’operatore unario di indirizzo & restituisce l’indirizzo di memoria dell’operando. L’operando è un oggetto. Es: &a restituisce l’indirizzo di a int* p, a; p=&a; p contiene l’indirizzo di memoria di a; Operatore di indirizzo L’operatore di indirizzo & non può mai essere usato come lvalue, in quanto l’indirizzo di memoria di una variabile è predeterminato e non può essere modificato Es: &a=p; &(a++); Errato Errato Operatore di dereferenza L’operatore unario di dereferenza * restituisce il valore della variabile puntata dall’operando. L’operando è un puntatore. L’operatore * può essere usato: •come rvalue ed esegue operazioni di estrazione a=*p; assegna ad a il valore della variabile puntata da p •come lvalue ed esegue operazioni di inserimento *p=a; assegna alla variabile puntata da p il valore della variabile a Operatore di dereferenza Nel caso di puntatori a puntatori (puntatori a indirizzamento multiplo), l’operatore * si può applicare più volte: int ***p, *p1,**p2, p3; ….. p2 = *p; p1 = **p; p3 = ***p; DOMANDA: E’ POSSIBILE APPLICARE PIU’ VOLTE L’OPERATORE & NELLA STESSA ISTRUZIONE? Operatore di dereferenza L’operazione di dereferenza * è l’inversa dell’operazione di indirizzamento &: Es. p = &a; // assegna a p l’indirizzo di a allora l’espressione booleana (*p ==a ) è sempre vera, cioè: la dereferenza di p coincide con a Operatore di dereferenza ATTENZIONE!!! Non è vero il contrario!! Nell’istruzione *p = a; il valore di p non diventa l’indirizzo di a, ma il valore della variabile puntata da p coincide con a. Dichiarazione e Operatori: un esempio int* p, val, n; 100 220 400 Indirizzo ... p ... ... val ... n Memoria val = 12; ... p ... ... val ... n 12 p= &val; ... p ... ... val ... n 12 220 n = *p; ... p ... ... val ... n 12 12 220 Aritmetica dei puntatori Il valore di un puntatore è un numero intero che rappresenta in byte un indirizzo di memoria. Gli operatori aritmetici che è possibile usare con i puntatori sono: ++ -+ += -= Aritmetica dei puntatori Vale la seguente regola: Sia i un intero e p un puntatore ad un oggetto di tipo T che occupa n locazioni di memoria. Sia addr il valore di p. L’espressione p + i ha come risultato addr + i x n L’espressione p - i ha come risultato addr - i x n Aritmetica dei puntatori Esempi: float *v; int *p; p= p + 5; p++; p-v++; v = v+ 6; // int è un intero a 2 byte // p viene incrementato di 10 byte // p viene incrementato di 2 byte // p viene decrementato di 2 byte // v viene incrementato di 4 byte // v viene incrementato di 24 byte Aritmetica dei puntatori ATTENZIONE!! E’ possibile sottrarre puntatori dello stesso tipo ma NON è possibile sommare dei puntatori !!! Assegnazione di valore a puntatori E’ possibile assegnare un valore ad un puntatore SOLAMENTE nei seguenti casi: • assegnazione al puntatore del valore NULL (il puntatore “punta a niente”) • assegnazione al puntatore, tramite l’operatore unario &, dell’indirizzo di una variabile definita precedentemente (p.e. int i, *p=&i;) •assegnazione tramite la allocazione dinamica della memoria Ulteriori operazioni con i puntatori • Confronto tra puntatori ( p.e. = = , !=) • E’ possibile, tramite il qualificatore const, definire un puntatore in modo che non cambi il proprio valore Es: int i; int *const p = &i ; // p è un puntatore costante all’intero i Tentativi di modificare il valore di p portano ad errore. Ulteriori operazioni con i puntatori E’ possibile, tramite il qualificatore const , definire un puntatore in modo che non possa cambiare il valore dell’oggetto puntato Es: int i; const int *p = &i ; // p non può modificare i ; Tentativi di modificare il valore di i tramite p portano ad errore. Puntatori e Reference • Un riferimento (reference indipendente) è un nome alternativo per un oggetto e deve essere sempre inizializzato. • Un riferimento è un puntatore costante ad un oggetto e viene dereferenziato ogni volta che viene richiamato int x, &ret = x; //ret è un riferimento a x int* p, y =10; ret = y; // x = y ret++ // x viene incrementato di 1 p = &ret; // p punta ad x Puntatore void Un puntatore void è un puntatore che accetta come valore un indirizzo senza puntare ad un tipo determinato. Il puntatore void può riferirsi ad oggetti di tipo differente int x, *pi=&x; float f, *pf=&f; void *p; p=pi; *(int *)p = 3; *(float *)p = 3.23; Puntatori e Array Il nome di un array senza indice è un puntatore costante all’indirizzo del primo elemento dell’array. Pertanto, tale valore non può essere modificato Es: str contiene l’indirizzo dell’elemento str[0] dell’array str[80] Puntatori e Array La dichiarazione di un array comporta l’allocazione di memoria di una variabile puntatore (il nome dell’array) e dell’area puntata, di lunghezza predefinita. Il puntatore è definito const e inizializzato con l’indirizzo dell’area puntata Puntatori e Array Es: int v [4] • alloca memoria per il puntatore costante v • alloca memoria per 4 elementi int • inizializza v con l’indirizzo di v[0] Per un int a 32 bit : Indirizzo Memoria 100 220 224 228 232 ... v ... v[0] v[1] v[2] v[3] ... 220 valore COSTANTE Puntatori e Array L’aritmetica dei puntatori può essere pertanto usata per accedere agli elementi di in array. Si ottiene un elemento di un array tramite l’operatore di dereferenza * applicato al puntatore-array incrementato di una quantità pari all’indice dell’elemento cercato. Es: int v[20]; *(v+i) è equivalente a v[i] Indicizzazione di Puntatori E’ possibile indicizzare un puntatore come se fosse un array per ottenere il valore dell’elemento dell’array desiderato Es: int *p, v[20]; p=v; … cout << p[5] // visualizza il 6° elemento dell’array Array di Puntatori E’ possibile raggruppare i puntatori in array, come una qualsiasi variable. Es: int *v[20]; dichiara un array di 20 puntatori a int. Il nome di un array equivale ad un puntatore, pertanto nel caso di array a puntatori equivale ad un puntatore a puntatore. Array di Puntatori L’allocazione di memoria per un array di puntatori è analoga a quella di un array qualsiasi. Le regole di inizializzazione di ogni singolo elemento di un array sono le stesse usate per l’assegnazione di valori a puntatori Es: int p, i, *v[2]={&p, NULL}; Stringhe Le stringhe sono degli array di tipo char con ‘\0’ come elemento che segue l’ultimo carattere dell’array. In questo modo gli operatori e le funzioni distinguono le stringhe dagli array di tipo char. Si dice pertanto che una stringa è un “array di tipo char null terminated” L’inserimento di ‘\0’ è eseguito automaticamente dal compilatore nel caso di costanti stringa. Es: char str[6]="Torre” Il compilatore valorizza str[0]=‘T’, str[1]=‘o’, str[2]=‘r’, str[3]=‘r’, str[4]=‘e’, str[5]= ‘\0’ Stringhe Es.: char val[20]; Viene allocata memoria per 20 elementi di tipo char. Affinchè val sia riconosciuta da operatori e funzioni come una stringa occorre inserire il carattere ‘\0’;. Es.: val[0]=‘c’,val[1]=‘i’, val[2]=‘a’, val[3]=‘o’, val[4]=‘\0’; vengono impegnati 5 elementi di val[20] e i rimanenti sono disponibili. Stringhe Se nella dichiarazione-inizializzazione di un array di char a stringa viene omessa la dimensione dell’array, allora viene allocata memoria per un numero di byte pari alla lunghezza della stringa aumentata di uno per considerare il carattere per ‘\0’. Es.: char str[ ]="Torre”; alloca in memoria 6 elementi di tipo char. Array di Stringhe L’uso più frequente di array di puntatori è quello degli array di stringhe , che vengono inizializzati in maniera atipica per array di puntatori. Es: char *str[5]={"Torre", "Alfiere", "Cavallo", "Re", "Regina"}; Le stringhe possono essere di lunghezza diversa. In memoria vengono allocate consecutivamente delle locazioni pari al numero di bytes della stringa, terminatore compreso. Array di Stringhe char *str[5]={"Torre", "Alfiere", "Cavallo", "Re", "Regina"}; • str[5] è un array di 5 elementi • Ogni elemento dell’array è un puntatore al primo elemento della stringa. Il compilatore infatti memorizza tutte le costanti stringa in una tabella apposita e pertanto è necessario conoscere solo il puntatore per accedervi.