Linguaggio C - Puntatori

Похожие документы
Unità Didattica 4 Linguaggio C. Vettori. Puntatori. Funzioni: passaggio di parametri per indirizzo.

Ogni variabile in C è una astrazione di una cella di memoria a cui corrisponde un nome, un contenuto e un indirizzo.

Il linguaggio C. Puntatori e dintorni

Uso avanzato dei puntatori Allocazione dinamica della memoria

L Allocazione Dinamica della Memoria

Gestione dinamica della memoria

La gestione della memoria dinamica Heap

Puntatori. Un puntatore contiene un numero che indica la locazione di memoria dove è presente la variabile puntata

Strutture Dati Dinamiche

Puntatori (in C) Emilio Di Giacomo

Fondamenti di Informatica T. Linguaggio C: i puntatori

Introduzione al linguaggio C Puntatori

Linguaggio C: PUNTATORI

Stringhe e allocazione dinamica della memoria

I puntatori. Un puntatore è una variabile che contiene l indirizzo di un altra variabile. puntatore

Allocazione dinamica della memoria

Implementazione di Liste puntate

Una stringa di caratteri in C è un array di caratteri terminato dal carattere '\0' a p e \0

Definizione Allocazione e deallocazione di variabili Allocazione e deallocazione di vettori

Esercizio 1: funzione con valore di ritorno di tipo puntatore

Allocazione statica della memoria

Linguaggio C: puntatori

Il linguaggio C. Puntatori e Array

Lezione 8 Struct e qsort

Informatica 1. Corso di Laurea Triennale in Matematica. Gianluca Rossi

I puntatori e l allocazione dinamica di memoria

Input/output da file I/O ANSI e I/O UNIX FLUSSI E FILE FLUSSI FLUSSI di TESTO FLUSSI BINARI FILE

Non ci sono vincoli sul tipo degli elementi di un vettore Possiamo dunque avere anche vettori di

Perché il linguaggio C?

Lezione 9: Puntatori a funzioni. Tipi enumerativi e orientati ai bit

La gestione della memoria

ERRATA CORRIGE. void SvuotaBuffer(void); void SvuotaBuffer(void) { if(getchar()!=10) {svuotabuffer();} }

Array multidimensionali e stringhe

STRINGHE: ARRAY DI CARATTERI! a p e \0

Gli array. Gli array. Gli array. Classi di memorizzazione per array. Inizializzazione esplicita degli array. Array e puntatori

Tipi di dati strutturati e Linguaggio C. Record o strutture Il costruttore struct in C

Array Tipi di dato semplici e strutturati strutturati array elementi omogenei numero d ordine indice lunghezza dimensione

Linguaggio C. strutture di controllo: strutture iterative. Università degli Studi di Brescia. Docente: Massimiliano Giacomin

Funzioni, Stack e Visibilità delle Variabili in C

Consideriamo un vettore allocato dinamicamente

Linguaggio C - sezione dichiarativa: costanti e variabili

Strutture Dinamiche. Fondamenti di Informatica

Inside C : Puntatori. Indirizzo: operatore & p = &v; x = a; Puntatori Referenziazione e Dereferenziazione Arrays

Parametri Formali di una Funzione e Record di Attivazione

Транскрипт:

Puntatori: I puntatori sono uno dei costrutti più potenti del linguaggio C; permettono ai programmi di realizzare il passaggio per riferimento, di passare funzioni alle funzioni, di accedere direttamente a locazioni di memoria e di gestire strutture dinamiche di dati. I puntatori sono delle variabili i cui valori sono degli indirizzi di memoria. Una variabile fa riferimento direttamene a un valore; mentre un puntatore fa indirettamente riferimento a un valore. Es.: int x=6; x memoria:... 6 indirizzi: 5000 5001 5002 L operatore & ( visto nella scanf ) restituisce l indirizzo di memoria di una variabile; che può essere assegnato solo a una speciale categoria di variabili: i puntatori. Sintassi per dichiarare un puntatore: tipo* nome_puntatore; o in modo equivalente: tipo *nome_puntatore; La dichiarazione di un puntatore include il tipo dell'oggetto a cui il puntatore punta; ovvero un puntatore è in grado di memorizzare indirizzi di memoria di variabili di tipo: tipo. Ad esempio non è possibile che un puntatore a int punti a un float e così via.

Esempi: int *pointer; // pointer è un puntatore a intero char *ptr; double *x_ptr; Nota : con la semplice dichiarazione, un puntatore a cosa punta? Inizializzazione: int a=10; int *ptr; ptr = &a; // ptr punta ad a ptr a memoria:... indirizzi: 5000 5001 5002 L'operatore di indirezione * : 5002 10 si applica a una variabile di tipo puntatore e restituisce il contenuto della variabile puntata. Es.: int x = 10; int* ptr = &x; printf("valore di x = %d", x); *ptr += 20; printf("valore di x= %d", x); // x e *ptr sono degli alias. Quanto vale x?

ancora: char a = 'a', b = 'b' ; char* ptr = &a; printf("valore di a = %c e di b = %c", a, b); b=*ptr; printf( Valore di a = %c e di b = %c", a, b); errore tipico: int *ptr; *ptr = 100; // ptr non punta ad alcuna locazione di memoria. In relazione alla inizializzazione dei puntatori abbiamo 3 possibilità: assegnare l indirizzo di una variabile dichiarata. assegnare un altro puntatore esistente (stesso tipo). assegnare valore NULL.

Array e Puntatori: In C gli array e i puntatori sono strettamente correlati. Si noti che il nome di un array è una costante è funge da puntatore al suo primo elemento. Ad esempio: char stringa[10]; char *s; scrivere: s = stringa oppure s = &stringa[0] è equivalente Nota: gli elementi di un array possono essere scanditi per mezzo dell indice associato; una cosa equivalente si può ottenere tramite puntatore. Cosicchè: s[0] : indica il valore della prima componente dell array (s + i) : punta alla (i+1)-esima componente del vettore (equivale a &stringa[i]). Es.: s=stringa; stringa[3] = 'c'; printf("stringa[3]= %c\n", stringa[3]); // stampa: c *(s+3) = 'C'; printf("stringa[3]= %c\n", stringa[3]); // stampa: C Nota: essendo il nome dell array una costante non possiamo scrivere: stringa++; // errore! stringa = stringa+2; // errore!

Esempio: int buffer[5]; int s*; s = buffer; equivale a: for(i=0; i<5; i++) buffer[i] = i; for(i=0; i<5; i++) { *s = i; s = s+1; for(i=0; i<5; i++) { *s++ = i; for(i=0; i<5; i++) { s[i] = i; Nota: fare attenzione che (s+i) non vada fuori range. Es.: con s = buffer; s -= 2; oppure s = s+6; // errore! Aritmetica dei Puntatori: Siccome un puntatore contiene indirizzi di memoria, le operazioni consentite sono soltanto quelle che hanno senso per un indirizzo; gli operatori sono: + ++ - -- // incremento e decremento Nota: l incremento o il decremento di un puntatore dipende dal tipo di dato puntato; poiché ogni tipo di dato occupa un numero diverso di byte in memoria.

Ad es. supponiamo che ogni locazione di memoria sia di 1 byte; char occupa 1 byte e int 4 byte: char c; char* s=&c; con s++ // sposta s in avanti di un byte int i=1; int* p = &i; con p++ // s avanza di 4 byte Si noti la differenza: (*s)++ : incrementa di uno il valore della variabile puntata da s. (s++) : incrementa l indirizzo contenuto in s. E possibile effettuare anche sottrazioni tra puntatori che puntano elementi dello stesso array: es.: int v1[5]; int v2[5]; int i; int *ptr; for(i=0; (v1[i] = (i+1)) <= 5; i++); i = &v1[4]-&v1[2]; printf("%d\n", i); // scrive 2 ovvero: (v1 + 4) (v1 + 2) i = &v2[4] - &v1[2]; printf("%d\n", i); // v1 è diverso da v2, risultato imprevedibile! ptr = v2-3; // non corretto, probabilmente punta a elem. di v1.

Puntatori e funzioni (passaggio parametri per indirizzo): Il linguaggio C di default implementa il passaggio di parametri per valore. Si ricordi le difficoltà incontrate con la funzione scambio per scambiare il valore di due variabili passate come parametri. In realtà lo scambio avveniva solo tra parametri formali e no attuali. Si può ovviare a questo problema, passando alla funzione gli indirizzi dei parametri attuali. Passare un indirizzo ad una funzione, significa renderle nota la locazione di memoria che contiene l oggetto; cosicchè le modifiche si effettueranno direttamente nei parametri attuali. Es. (funzione scambio): /* Funzione scambio non funzionante */ #include <stdio.h> void scambio(int, int); int main() { int a=5, b=7; printf("prima dello scambio: a= %d e b= %d\n", a, b); scambio(a,b); printf("dopo lo scambio: a= %d e b= %d\n", a, b); return 0; void scambio(int x, int y) { int tmp = x; x = y; y = tmp;

Con i Puntatori: /* Funzione scambio OK */ #include <stdio.h> void scambio(int*, int*); int main() { int a=5, b=7; printf("prima dello scambio: a= %d e b= %d\n", a, b); scambio(&a, &b); printf("dopo lo scambio: a= %d e b= %d\n", a, b); return 0; void scambio(int *x, int *y) { int tmp; tmp = *x; *x = *y; *y = tmp; Altra versione, con solo puntatori - da rivedere più avanti: /* Funzione scambio - solo puntatori */ #include <stdio.h> #include <stdlib.h> void scambio(int**, int**); int main() { // int a=5, b=7; int *a = (int*) malloc(sizeof(int)); *a = 5; int *b = (int*) malloc(sizeof(int)); *b = 7; printf("prima dello scambio: *a= %d e *b= %d\n", *a, *b); scambio(&a,&b); printf("dopo lo scambio: *a= %d e *b= %d\n", *a, *b); return 0; void scambio(int **x, int **y) { int *tmp; tmp = *x; *x = *y; *y = tmp; La stessa tecnica (passaggio indirizzo) si può usare con gli array come parametri. Le stringhe in C sono array di caratteri che terminano con \0; pertanto possono essere manipolate con puntatori.

Es.: #include<stdio.h> int my_strlen(char* s); int main() { char str[] = "Una stringa di prova"; printf("la lunghezza di: %s e' %d\n ", str, my_strlen(str)); return 0; int my_strlen(char * s){ int i=0; while(*s++) i++; // oppure while(s[i++]); return i-1; return i; Si noti che: int my_strlen(char *s); è equivalente a: int my_strlen(char s[]); La funzione my_strlen si può scrivere anche: int my_strlen(char * s){ char *p=s; while(*p++); return (p-s-1);

Le funzioni (predefinite) per la manipolazione delle stringhe (incluse in string.h), operano sempre attraverso i puntatori a carattere. Si noti il prototipo di alcune funzioni: char* strcat(char *string1, const char *string2); // concatena string1 e string2 -> string1 int strlen(const char *string); // restituisce la lunghezza di string escluso il terminatore. Altro esempio: sostituzione di stringhe: #include <stdio.h> void funzione(char *, char *); int main() { char a[] = "stringa da sostituire"; char b[] = "stringa di prova"; printf("'%s'\n'%s'\n\n", a, b); funzione(a, b); printf("'%s'\n'%s'\n\n",a, b); return 0; void funzione(char *p, char *q) { while((*p=*q)!= '\0') { p++; q++; // in forma più compatta: while((*p++=*q++)!= '\0');

Esempio con array di altro tipo: #include <stdio.h> #define NUM 10 void carica(int *, int); int main() { int i, c[num]; carica(c, NUM); // perchè passiamo c e non &c? // stampa vettore: for(i=0; i<num; i++) printf("elemento %d = %d\n", i+1, c[i]); return 0; void carica(int* pt, int n) { int i; for(i=0; i< n; i++) { printf("inserisci elemento: "); scanf("%d", &pt[i]); // in alternativa: scanf("%d", (pt + i)); Una funzione può ritornare un puntatore. Questo permette di restituire una struttura dati più complessa rispetto ai tipi di dato semplici come int, char, ecc.

Esempio: #include <stdio.h> char* trova(char *str, char c); int main() { char car = 'd'; // carattere da cercare char str[] = "stringa di testo"; if(trova(str, car)) printf("%s", trova(str, car)); else printf("carattere non trovato"); return 0; char* trova(char *str, char c) { while(*str++) if(*str == c) return str; return NULL; Info: esistono anche i puntatori a funzioni; contengono l indirizzo della funzione. Analogamente al nome dell array che punta al primo elemento, il nome della funzione è in realtà l indirizzo di memoria iniziale del codice (blocco di istruzioni) che esegue il compito della funzione. Es.: int (*nomefunc)(parametri) da non confondere con int *nomefunc(parametri) Per approfondimenti ed esempi si rimanda alla documentazione ufficiale.

Esercizi: (implementare le seguenti funzioni) char* fromfirstupper(char *); /* ritorna la stringa dal primo carattere maiuscolo, null se non c'è */ char* fromfirstdigit(char *); /* ritorna la stringa dal primo numero, null se non c'è */ char* parsedigits(char *); /* ritorna la stringa di soli numeri, null se non ci sono */ char* parsechars(char* s1, char* s2); /* ritorna una stringa a partire da s1 senza i caratteri compresi in s2 */ char* mysubstring(char* s1, char* s2); /* ritorna la parte di stringa in s1 che fa "match" con s2, null se non c'è */ int* greatesvalues(int* vet, int limit); /* ritorna i valori di vet i cui elementi sono > limit, null se non ci sono */ int firstoccurrenceof(char* s1, char c); /* ritorna la posizione della prima occorrenza di c in s1, -1 se non c'è */

Allocazione dinamica della memoria: Grazie all utilizzo dei puntatori, il linguaggio C permette la creazione e la gestione di oggetti dinamici. Mentre gli oggetti statici vengono creati nella fase di definizione, quelli dinamici sono creati durante l esecuzione del programma; ovvero vengono creati o distrutti in fase di esecuzione del programma e non durante la compilazione. La memoria (in C) è divisa in due parti: - Stack: contiene tutto ciò che è statico ( es. int, float, ecc ). - Heap: riservata per gli oggetti dinamici. Funzioni utilizzate per la gestione dinamica della memoria: malloc() : allocazione della memoria free() : rilascio della memoria realloc() : modifica dello spazio allocato Nota: per usare le suddette funzioni, includere il file malloc.h e/o stdlib.h. Particolarmente utile risulta essere l operatore sizeof : restituisce la dimensione del tipo di dato da allocare. void* malloc(size_t size); // prototipo Sintassi: tipodato* ptr = (tipodato *) malloc(sizeof(tipodato)); tipodato: indica il tipo di dato per cui allochiamo memoria ptr: (puntatore) è una variabile di tipo: tipodato * sizeof: restituisce il numero di byte che occupa: tipodato

Nota: generalmente si usa il casting (tipodato *), poichè la funzione malloc() restituisce un puntatore generico (void *) che può puntare qualsiasi tipo di dato; con il cast viene assegnato il tipo richiesto. Se la richiesta di allocazione va a buon fine, ptr punta all inizio dello spazio allocato in memoria; altrimenti punta a NULL ( es. memoria esaurita ). Es.: int* ptr = (int *) malloc(sizeof(int)); Abbiamo allocato uno spazio di memoria sufficiente a contenere un intero; esso è raggiungibile soltanto attraverso il puntatore ptr. #include <stdio.h> #include <malloc.h> int main() { int *ptr; ptr = (int *) malloc(sizeof(int)); if(ptr == NULL) { printf("errore in fase di allocazione! \n"); return(-1); // oppure exit(-1); *ptr = 12; (*ptr)++; printf("*ptr vale %d\n", *ptr); // cosa stampa? return 0;

Nota: per determinare la dimensione di un tipo di dato non affidarsi a valori di default, ma usare sempre sizeof. Es.: int* ptr = (int *) malloc(4); // non è detto che sia corretto; poiché sizeof(int) potrebbe non essere 4 byte. Esempio: allochiamo più di un elemento; int i, *p; /* allochiamo 10 elementi. */ p = (int *) malloc(10*sizeof(int)); if(p == NULL) exit(-1); // errore in allocazione! for(i = 0; i < 10; i++) *(p + i) = i; // possiamo scorrere le 10 posizioni. for(i = 0; i < 10; i++) printf("*(p + %d) vale %d\n", i, *(p + i)); // cosa stampa?

Interessante esempio su stringhe dinamiche: #include <stdio.h> #include <stdlib.h> //#include<malloc.h> #include <string.h> char* my_concat(const char *, const char *); int main (void) { char *p; p = my_concat("abcd","efghi"); printf ("Risultato : %s \n", p); // stampa? return 0; char* my_concat(const char *s1, const char *s2) { char * result; result = (char *) malloc((strlen(s1) + strlen(s2) + 1) * sizeof(char)); if(result == NULL) { printf("out of memory\n"); exit (-1); strcpy(result, s1); strcat(result, s2); return result;

Nota: La keyword const non permette modifiche sui parametri attuali. Nota bene: gli oggetti allocati dinamicamente, vengono indirizzati tramite puntatore. Attenzione a non perdere il riferimento, altrimenti gli oggetti non sono più reperibili: int* p = (int *) malloc(sizeof(int)); *p= 20; int i=0; se scriviamo: p = &i; -> ora *p vale 0, perdendo la vecchia locazione. Deallocazione: free(): rilascia o dealloca la memoria precedentemente allocata da una malloc(). Ha effetto solo nei puntatori creati con allocazione dinamica. void free(void* ptr); // prototipo es.: int* ptr = (int *) malloc(sizeof(int)); *ptr= 10; //.. free(ptr); ptr = NULL; // abbiamo rilasciato lo spazio, ma ptr non si sa a cosa punti, allora: Nota: la funzione free non si applica agli array.

realloc() Questa funzione permette di modificare (in fase di esecuzione) lo spazio di memoria occupato da un oggetto dinamico già esistente. Consente ad esempio, di definire gli array flessibili. Definito un array, se lo spazio non è sufficiente a contenere i dati, la realloc consente di aggiungere altri blocchi. Il prototipo: void* realloc( void* ptr, size_t size ); Si veda esempio: #include <stdio.h> #include <stdlib.h> int main() { int input, n, count=0; int *numbers = NULL; do { printf("inserire un numero intero (0 - uscita): "); scanf("%d", &input); count++; numbers = (int*) realloc(numbers, count * sizeof(int)); if(numbers==null) { printf("error (re)allocating memory"); exit (1); numbers[count-1]=input; while(input!=0); printf("numeri inseriti: "); for(n=0; n<count; n++) printf("%d ",numbers[n]); free(numbers); return 0;

Array di Puntatori: In questo caso, gli elementi dell array sono di tipo puntatore. es.: int* vett[10]; int i=1; vett[5]= &i; // la componente vett[i] è un puntatore *vett[5] = 2; // anche la variabile i varrà 2. char* colori[4] = {"Giallo", "Blu", "Rosso", "Verde"; // Array bidimensionale (4 puntatori a carattere) *colori[2] = "Arancio"; float* a[10]; float x=10.5f; a[6] = &x; *a[6] = 0; // quanto vale x?

Puntatori a Puntatori (indirizzamento multiplo): Un puntatore è una variabile che contiene l indirizzo di memoria di un altra variabile; perciò è possibile far uso di puntatori che puntano ad altri puntatori. Sintassi: tipo **variabile; es.: int a, *p, **pp; p = &a; pp=&p; **pp = 10; // avremo che a = 10; e *pp: non è un valore ma un indirizzo PP P A 10 // vedere la stampa: printf("indirizzo di pp= %p, valore= %p\n", &pp, pp); printf("indirizzo di p= %p, valore= %p\n", &p, p); printf("indirizzo di a= %p, valore= %d \n", &a, a);

L indirizzamento multiplo, permette di creare dinamicamente array bidimensionali: Ad esempio, creiamo un array 3*4: int **a, i; a = (int **) malloc(3 * sizeof(int *)); for(i=0; i<3; i++) a[i] = (int *) malloc(4 * sizeof(int));. a[1][2]= 10; Esercizi: - Implementare la funzione per allocazione dinamica di matrici (tutte le caselle inizializzate a 0); prototipo: float **matrice(int nr, int nc); - Scrivere una funzione che restituisca il valore max di una matrice passata come parametro: prototipo: int trova( int **matr, int nr, int nc);

Linguaggio C Funzione main() Completamento funzione main() La funzione main(), viene eseguita all inizio del programma. Finora l abbiamo vista ed utilizzata nella sua forma più semplice; non abbiamo passato parametri e non abbiamo tenuto conto del valore di ritorno. Lo standard definisce tre dichiarazioni (prototipi) per la funzione main: int main(void) int main(int argc, char *argv[]) int main(int argc, char *argv[], char *envp[]) int argc: indica il numero di parametri passati al programma in fase di avvio, compreso il nome del programma. char *argv[]: contiene tutti gli argomenti sotto forma di stringhe (il nome del programma corrisponde ad argv[0]). char *envp[]: contiene le variabili di ambiente del sistema operativo. La funzione main, ritorna un valore int, che indica l esito dell esecuzione: 0: esecuzione completata correttamente. -1: si sono verificati problemi. Nota: Per passare i parametri ad un programma basta, alla riga di comando, specificarli di seguito al nome del programma (separati da uno spazio).

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i; printf("nome del programma: '%s'\n",argv[0]); // argv[0] esiste sempre. printf("al programma sono stati passati %d parametri.\n", argc-1); if(argc > 1) for(i = 1; i < argc; i++) printf("parametro argv[%d] = %s\n", i, argv[i]); else printf("nessun parametro passato.\n"); return 0; Esecuzione: c:\> nomefile parametro1 parametro2. Nota: I nomi argc, argv sono convenzionali, si possono cambiare. Si noti l equivalenza: int main(int argc, char *argv[]); int main(int argc, char **argv); Utilizzo: se abbiamo un programma che fa operazioni di I/O su file, il nome di quest ultimo lo possiamo specificare come parametro al momento del lancio.