Utilizzo avanzato di array e puntatori 1 Anno accademico 2010-2011 Sommario • Utilizzo avanzato di array e puntatori Gli array multidimensionali Gli array di puntatori I puntatori a puntatori Allocazione dinamica della memoria 2 Anno accademico 2010-2011 Gli array multidimensionali 1 • Un array di array è un array multidimensionale e viene dichiarato per mezzo di una sequenza di coppie di parentesi quadre /* x è un array di tre elementi costituiti * da array di cinque elementi (interi) */ int x[3][5]; • Anche se un array multidimensionale viene memorizzato come una sequenza di elementi, può essere manipolato come un array di array • Per accedere ad un elemento di un array multidimensionale occorre specificare tanti indici quante sono le dimensioni dell’array 3 Anno accademico 2010-2011 Gli array multidimensionali 2 • Gli array multidimensionali sono memorizzati con precedenza delle righe, cioè l’ultimo indice varia più velocemente • Esempio: int ar[2][3]{{0,1,2}, {3,4,5} }; Nell’inizializzazione, ogni riga di valori è racchiusa fra parentesi graffe (in questo caso, servono per migliorare la leggibilità) 1000 1004 1008 100C 1010 1014 ar[0][0] 0 1 2 ar[0][1] 3 4 5 ar[1][1] ar[0][2] ar[1][0] ar[1][2] 1018 4 Anno accademico 2010-2011 Gli array multidimensionali 3 • L’accesso all’elemento ar[1][2] viene interpretato come *(ar[1]2), ovvero *(*(ar1)2) • Poiché ar è un array di array, viene effettuato un doppio scaling: …quando si valuta *(ar1), “1” rappresenta un array di tre interi (12 byte sulla macchina di riferimento) …quando si valuta *(*(ar1)2), “2” rappresenta 2 interi (8 byte) complessivamente si ha uno spostamento di 20 byte rispetto all’indirizzo base (si ottiene l’indirizzo esadecimale 1014) • Se vengono specificati meno indici rispetto alle dimensioni, il risultato è un puntatore al tipo base dell’array; per esempio… ar[1] è equivalente a &ar[1][0] e fornisce come risultato un puntatore ad int • Lo standard ANSI non impone limiti al numero di dimensioni degli array; è comunque richiesto di gestire almeno array a sei dimensioni 5 Anno accademico 2010-2011 L’inizializzazione di array multidimensionali 1 • In fase di inizializzazione di un array multidimensionale, occorre specificare ogni riga tra parentesi graffe • Se i valori iniziali non sono sufficienti, e l’array è static, gli elementi mancanti vengono inizializzati a zero • Esempio: static int examp[5][3] { {1,2,3}, {4}, {5,6,7} }; ( ) 1 4 5 0 0 2 0 6 0 0 3 0 7 0 0 • In modo analogo al caso dei vettori, se viene “parzialmente” omessa la dichiarazione della dimensione di un array multidimensionale, il compilatore la calcola sulla base del numero dei valori iniziali specificati Anno accademico 2010-2011 6 L’inizializzazione di array multidimensionali 2 • Nel caso degli array multidimensionali, infatti, può essere omessa la dimensione dell’array più esterno, mentre è obbligatorio specificare le altre • Esempio: static int a_ar[][2] {{1,1},{0,0},{1,2}}; produce un array di dimensione 32, perché sono presenti 6 valori iniziali • Esempio: static int b_ar[][]{{1,2,3},{4,5,6}}; /*SCORRETTO*/ Non si può determinare se l’array è 32 o 23 (il raggruppamento dei dati di inizializzazione non è sufficiente!): specificare la seconda dimensione avrebbe risolto ogni ambiguità Anno accademico 2010-2011 7 Array multidimensionali come argomenti di funzione 1 • Per passare un array multidimensionale come argomento di funzione è sufficiente specificarne il nome: il valore passato è un puntatore all’elemento iniziale dell’array che è ancora un array • Nella funzione chiamata, l’argomento deve essere dichiarato in modo appropriato int f1() { static int ar[5][6]; … … … f2(ar); … … … } void f2(received_arg) int received_arg[][6]; { … … … } int (*received_arg)[6]; • È possibile omettere la dimensione dell’array che viene passato, ma è necessario specificare la dimensione di ogni elemento dell’array Anno accademico 2010-2011 8 Array multidimensionali come argomenti di funzione 2 • Una modalità alternativa consiste nel passare esplicitamente un puntatore al primo elemento e la dimensione dell’array int f1() { static int ar[5][6]; … … … f2(ar,5,6); … … … } void f2(received_arg,dim1,dim2) int **received_arg; int dim1,dim2; { … … … } È un puntatore a un puntatore a int • Il vantaggio di questo approccio è la flessibilità: non occorre conoscere a priori la dimensione degli elementi dell’array; occorre però calcolare manualmente l’aritmetica degli indici: received_arg[x][y] è memorizzato all’indirizzo received_arg xdim2 y Anno accademico 2010-2011 9 Esempio 1 • Scrivere una funzione che determina il tipo del risultato di un’espressione binaria in base ai tipi degli operandi include <stdio.h> typedef enum {SPECIAL2, ILLEGAL, INT, FLOAT, DOUBLE, POINTER, LAST} TYPES; TYPES type_needed(type1,type2) TYPES type1, type2; { static TYPES result_type[LAST][LAST] { /* int float double pointer /*int*/ /*float*/ /*double*/ /*pointer*/ }; TYPES INT, FLOAT, DOUBLE, POINTER, FLOAT, FLOAT, DOUBLE, ILLEGAL, DOUBLE, DOUBLE, DOUBLE, ILLEGAL, POINTER, ILLEGAL, ILLEGAL, SPECIAL */ La funzione riceve due argomenti interi che rappresentano i tipi degli operandi e fornisce un intero che rappresenta il tipo del risultato result result_type[type1][type2]; (result ILLEGAL) printf(“Operazione scorretta su puntatori\n”); return result; if } Anno accademico 2010-2011 10 Esempio 2 • La parte principale del programma è costituita dalla dichiarazione ed inizializzazione dell’array result_type: Ogni tipo di dati viene fatto corrispondere ad un valore intero per mezzo della dichiarazione enum • In base alle modalità di definizione dell’array bidimensionale, i due valori di ingresso sono indici, che individuano univocamente l’elemento dell’array corrispondente al tipo del risultato • La dichiarazione enum assicura che ogni costante venga associata ad un unico valore intero e che LAST rappresenti il numero totale di tipi (viene usato nella dichiarazione dell’array) 11 Anno accademico 2010-2011 Esempio 3 • La stessa dichiarazione (almeno per il compilatore) avrebbe potuto essere scritta per mezzo di interi char result_type[4][4]{0,1,2,3,1,1,2,1,2,2,2,1,3,1,1,2}; diminuendo sensibilmente la comprensibilità e la mantenibilità del programma • Nel caso SPECIAL, l’operazione è corretta solo se i puntatori riferiscono oggetti dello stesso tipo e l’operatore è il segno meno: il risultato è un int 12 Anno accademico 2010-2011 Errori di accesso ad array multidimensionali ar[1,2] 0; ar[1][2] 0; /* Lecito, ma probabilmente scorretto */ /* Corretto */ • Nella prima istruzione… …la virgola viene interpretata come operatore, si valuta quindi espressione1, che vale 1 ed il cui risultato non viene utilizzato e, successivamente, espressione2, che vale 2 (le due espressioni sono costanti) …si ottiene l’accesso ad ar[2] • Se ar è un array bidimensionale di int, ar[2] è un puntatore ad int (costante, ma potrebbe anche essere non valido) Viene segnalato un errore di incompatibilità di tipo: fuorviante dato che la causa dell’errore è l’uso della virgola 13 Anno accademico 2010-2011 Gli array di puntatori 1 • Si consideri la dichiarazione: char *ar_of_p[5]; La variabile ar_of_p è un array di cinque elementi di tipo puntatore a carattere e non un puntatore ad un array di cinque caratteri • L’operatore di accesso all’elemento di un array “[]” ha precedenza superiore all’operatore di accesso all’indirizzo contenuto in un puntatore • I puntatori non sono stati inizializzati, per cui puntano a posizioni di memoria qualsiasi 14 Anno accademico 2010-2011 Gli array di puntatori 2 996 • Esempio: char *ar_of_p[5]; char c0=‘a’; char c1=‘b’; ar_of_p[0] 2000 1000 ar_of_p[1] 2001 1004 ar_of_p[2] non definito 1008 ar_of_p[3] non definito 100C ar_of_p[4] non definito 1010 ar_of_p[0]=&c0; ar_of_p[1]=&c1; 1014 1FFF c0 a 2000 c1 b 2001 2002 15 Anno accademico 2010-2011 Esempio array di puntatori 1 • Gli array di puntatori vengono usati per gestire array di stringhe • Esempio: Realizzare una funzione che, dato un intero compreso fra 1 e 12, in ingresso, stampa il nome del mese corrispondente include <stdio.h> include <stdlib.h> char *month_text(m) int m; { static char *month[13] {“Badmonth”, “January”, “February”, “March”, “April”, “May”, “June”, “July”, “August”, “September”, “October”, “November”, “December” }; if (m>12) { printf(“Valore scorretto”); exit(1); } return month[m]; } 16 Anno accademico 2010-2011 Esempio array di puntatori 2 • La variabile month è un array di puntatori a char costituito da 13 elementi: come conseguenza dell’inizializzazione, ogni puntatore fa riferimento all’elemento iniziale di una stringa • La motivazione dell’uso di un puntatore aggiuntivo con un valore inutile consiste nel non voler effettuare sottrazioni dall’indice: è comune non utilizzare l’elemento iniziale di un array quando il valore dell’indice comincia logicamente da 1 • Non definendo “Badmonth”, si dovrebbe cambiare l’istruzione di ritorno al chiamante in return month[m1]; 17 Anno accademico 2010-2011 Esempio array di puntatori 3 Nota: • • I caratteri che costituiscono una stringa devono essere consecutivi Le stringhe corrispondenti ai nomi dei mesi vengono memorizzate dal compilatore in qualunque posizione libera della memoria ‘B’ 2000 ‘F’ 2011 ‘a’ 2001 ‘e’ 2012 ‘d’ 2002 ‘b’ 2013 ‘m’ 2003 ‘r’ 2014 ‘o’ 2004 ‘u’ 2015 ‘n’ 2005 ‘a’ 2016 month[0] 2000 1000 month[1] 2009 1004 month[2] 2011 1008 month[3] 2500 100C month[4] 2800 1010 ‘t’ 2006 ‘r’ 2017 month[5] 3000 1014 ‘h’ 2007 ‘y’ 2018 month[6] 3006 1018 ‘\0’ 2008 ‘\0’ 2019 month[7] 300A 101C ‘J’ 2009 month[8] 300F 1020 ‘a’ 200A ‘M’ 2500 month[9] 4000 1024 ‘n’ 200B ‘a’ 2501 month[10] 400A 1028 ‘u’ 200C ‘r’ 2502 ‘a’ 200D ‘c’ 4011 102C 2503 ‘r’ 200E ‘h’ 2504 401A 1030 ‘y’ 200F ‘\0’ 2505 ‘\0’ 2010 month[11] month[12] 18 Anno accademico 2009-2010 I puntatori a puntatori 1 • I puntatori a puntatori sono costrutti usati in programmi sofisticati: per dichiarare un puntatore a puntatore occorre far precedere il nome della variabile da due asterischi consecutivi int **p; dichiara p come puntatore ad un puntatore ad int • Per accedere al valore dell’int, è necessario utilizzare i doppi asterischi: assegna un intero a j j **p; 19 Anno accademico 2010-2011 I puntatori a puntatori 2 • Esempio: 4 byte int r 5; int *q &r; int **p &q; r 5 99C q 99C 1004 p 1004 100C È possibile assegnare valori ad r come: r10; /*Assegnamento diretto*/ *q10; /*Assegnamento con un livello di indirezione*/ **p10; /*Assegnamento con due livelli di indirezione*/ 20 Anno accademico 2010-2011 Esempio 1: Matrice identità /* Codice per la costruzione di I (matrice identità) */ #include<stdlib.h> #include<stdio.h> main() { int id[100][100]; int n, i, j; printf(“Introdurre la dimensione della matrice identità\n”); scanf(“%d”, &n); for (i0; in; i) for (j0; jn; j) id[i][j] (ij)?1:0; exit(0); } 21 Anno accademico 2010-2011 Esempio 2: Matrice simmetrica /* Si legge una matrice quadrata ** composta da numeri reali ** e si stabilisce se è simmetrica */ #include <stdio.h> #include <stdlib.h> #define DIM 100 main() { char r, c, sim; float a[DIM][DIM]; /* Lettura della matrice a */ for(r0;rDIM;r) { for(c0;c<DIM;c) scanf(“%f”,&a[r][c]); } /* Si valuta se a è simmetrica… */ sim 1; r 0; do { c r 1; do { if (a[r][c] ! a[c][r]) sim 0; c; } while (c<DIM && sim1); r; } while (r<DIM1 && sim1); if (sim1) printf(“Matrice simmetrica”); else printf(“Matrice non simmetrica”); exit(0); } Anno accademico 2010-2011 22 Esempio 3: Prodotto matricevettore /* ** ** ** ** */ Funzione per il calcolo di Ab, con… A: matrice mn di float (in input) b: vettore di dimensione n di float (in input) m, n: interi, numero di righe e colonne di A (in input) x: vettore di float di dimensione m, risultato (in output) float *prod_mv(a, b, m, n, x) float a[][100], b[], x[]; /* ma, nella funzione, sono puntatori */ int m, n; { int i, j; for (i0; im; i) { x[i] 0.0; for (j0; jn; j) x[i] a[i][j]b[j]; } return x; } Anno accademico 2010-2011 23 Esempio 4: Prodotto matricematrice • Problema Calcolo della matrice prodotto AB, con A e B matrici rettangolari di dimensioni, rispettivamente, mn e nc 24 Anno accademico 2010-2011 L’allocazione dinamica della memoria 1 • Dichiarare array equivale a fare un’ipotesi (una stima) sulla dimensione massima possibile dei vettori che potranno essere fonte di elaborazione per il particolare programma Si suppone di conoscere la quantità di memoria da allocare nel momento in cui si scrive il codice sorgente Generalmente si sovrastima, quindi si spreca memoria Se la stima è sufficientemente stringente per la situazione corrente, può essere una sottostima per il futuro (occorre aggiornare il sorgente) • Di norma, l’occupazione di memoria dipende strettamente dai dati in ingresso 25 Anno accademico 2010-2011 L’allocazione dinamica della memoria 2 • In C, esistono quattro funzioni della libreria di runtime (stdlib.h) che permettono l’allocazione dinamica della memoria malloc() alloca un numero specificato di byte in memoria e restituisce un puntatore all’inizio del blocco allocato calloc() come malloc(), ma inizializza a zero i byte allocati; consente di allocare la memoria per più di un oggetto alla volta realloc() cambia la dimensione di un blocco precedentemente allocato free() libera la memoria che era stata allocata con malloc(), calloc() o realloc() 26 Anno accademico 2010-2011 L’allocazione dinamica della memoria 3 • Esempio: include <stdio.h> include <stdlib.h> • main() { extern void bubble_sort(); int *list, j, sort_num; • printf(“Numero dei valori da introdurre:”); scanf(“%d”, &sort_num); list(int *)malloc(sort_numsizeof(int)); for (j0; j<sort_num; j) scanf(“%d”, listj); bubble_sort(list, sort_num); exit(0); L’argomento di malloc() è la dimensione in byte del blocco da allocare Usando calloc(), l’istruzione di allocazione della memoria sarebbe list=(int*)calloc(sort_num,sizeof(int)); • • La funzione calloc() accetta due argomenti: il primo è il numero di oggetti a cui riservare memoria, il secondo è la dimensione di ciascun oggetto Le funzioni malloc() e calloc() memorizzano gli elementi in modo contiguo in un singolo blocco } 27 Anno accademico 2010-2011