Introduzione al linguaggio C Importanza del tipo base
Transcript
Introduzione al linguaggio C Importanza del tipo base
Introduzione al linguaggio C Corso di Informatica di Base Lezione VI Riccardo Veraldi – Servizio Calcolo e Reti INFN sez. Firenze [email protected] Importanza del tipo base Abbiamo visto che e’ possibile assegnare direttamente a c il valore di b attraverso un puntatore. In che modo il compilatore C puo’ sapere quanti byte copiare in c partendo dall’indirizzo puntato da a ? Il tipo di base del puntatore determina il tipo di dato al quale si assume che stia puntando il puntatore, in questo caso visto che a e’ un puntatore a intero il C copia due byte di dati in c a partire dall’indirizzo puntato da a. I puntatori devono sempre puntare al tipo corretto di dato. Se per esempio si dichiara un puntatore int allora il puntatore presuppone che qualunque elemento a cui punta sia una variabile intera. Codice sbagliato Codice corretto int main() { int *p float f; p=&f; return 0; } int main() { int *p int f; p=&f; return 0; } Assegnamento di valori attraverso un puntatore E’ possibile utilizzare un puntatore alla sinistra di un’istruzione di assegnamento per assegnare un valore alla locazione di memoria a cui punta il puntatore. Riprendendo l’esempio precedente: int b; int *a; a=&b *a=10; Viene assegnato il valore 10 alla locazione puntata da a che e’ il contenuto della variabile b. Quindi b sara’ uguale a 10. (*a)++; Ora b sara’ uguale a 11. Aritmetica dei puntatori I puntatori possono essere utilizzati nella maggior parte delle espressioni C. Con I puntatori e’ possibilie utilizzare soltanto quattro operatori aritmetici, +,-,++,--. Quando si applica l’operatore di incremento ad un puntatore, questo puntera’ in memoria alla locazione successiva, nel caso di un puntatore ad intero: int b; int *a; a=&b; a++; Immaginiamo che a contenga il valore 100, dopo l’incremento diventera’ 102 e non 101, questo perche’ dopo l’incremento puntera’ alla locazione di memoria dell’elemento succesivo del suo tipo base (un intero occupa 2 byte). Nel caso di puntatori a caratteri l’incremento apparira’ algebricamante “normale” dal momento che un carattere occupa 1 solo byte in memoria. E’ anche possibile aggiungere o sottrarre interi ad un puntatore, non e’ possibile addizionare due puntatori fra loro mentre e’ possibile sottrarli e il risultato di questa operazione sara’ il numero di elementi del tipo base che separano i due puntatori. Confronto di puntatori Il confronto fra puntatori puo’ avvenire utilizzando gli operatori relazionali, ==,>,<, tuttavia perche’ il risultato abbia significato tra i puntatori deve intercorrere una qualche relazione valida. Se due puntatori puntano a variabili separate e non correlate, qualsiasi confronto fra i due e’ generalmente senza significato, Se invece puntano a variabili che sono in rapporto l’una con l’altra (ad esempio gli elementi di uno stesso array) allora e’ possibile fare un confronto significativo. Puntatori e Array (1) In C esiste un rapporto molto stretto fra puntatori e array e sono frequentemente intercambiabili.Si consideri il seguente esempio: char str[80]; char *p1; p1 = str; str e’ un araray di 80 caratteri e p1 e’ un puntatore a carattere. Alla terza linea viene assegnato a p1 l’indirizzo del primo elemento dell’array str ed e’ equivalente a scrivere: p1 = &str[0]; Pertanto dopo l’assegnamento p1 puntera’ a str[0]. In C e’ possibilefare questo perche’ l’uso del nome di un array senza indice genera un puntatore al primo elemento di quell’array. Internamente il C tratta gli array come puntatori. Pertanto l’assegnamento p1 = str assegna l’indirizzo di str[0] a p1. Quando in un’espressione viene utilizzato il nome di un array non indicizzato, viene generato un puntatore al primo elemento dell’array. Dal momento che dopo l’assegnamento p1 punta al primo elemento di str e’ naturale che p1 possa essere utilizzato per accedere agli elementi dell’array. Se per esempio vogliamo accedere al quinto elemento di str possiamo scrivere str[4] oppure *(p1+4) ed entrambe le istruzioni restituiranno il quinto elemento. Puntatori e Array (2) Le parentesi che racchiudono p1+4 sono necessarie perche’ l’operatore * ha un livello di priorita’ piu’ elevato rispetto all’operatore + e senza di esse l’espressione troverebbe prima il valore a cui punta p1 e poi aggiungerebbe ad esso il valore 4. Il C in definitiva consente di utilizzare due metodi per accedere agli elementi di un array, l’aritmetica con i puntatori e l’indicizzazione dell’array: l’aritmetica dei puntatori a volte puo’ rivelarsi piu’ veloce in particolar modo quando l’accesso ad un array avviene in ordine strettamente sequenziale. In genere l’indicizzazione di un array richiede piu’ istruzioni macchina rispetto all’applicazione dell’aritmetica su un puntatore. In C si ricorre in modo molto diffuso ai puntatori per accedere agli elementi di un array. Inoltre l’utilizzo dei puntatori consente di scrivere un codice piu’ sintetico rispetto all’indicizzazione dell’array. In C inoltre e’ possibile indicizzare un puntatore come se fosse un array, il che e’ una dimostrazione dello stretto rapporto che intercorre tra puntatori ed array, quindi ornando all’esempio precfedente ne consegue che le tre istruzioni che seguono sono funzionalmente equivalenti: *(p1+4) , p1[4] , str[4] . Puntatori ed array sono intercambiabili? Puntatori ed array sono entita’ fortemente correlate, in molti casi infatti puntatori ed array sono intercambiabili. Ad esempio un puntatore che punta all’inizio di un array puo’ accedere ad esso sia attraverso l’aritmetica con i puntatori sia con l’indicizzazione. Tuttavia l’intercambiabilita’ fra puntatori ed array non e’ completa. Puntatori ed array sono equivalenti ma non uguali. Si consideriad esempio: int num[10]; int i; for(i=0;i<10;i++) { *num=i; /* istruzione legale */ num++; /* ERRORE: istruzione sbagliata */ } Num e’ un array di interi, mentre e’ corretto applicare l’operatore * a num, poiche’ si tratta di un’operazione con un puntatore e’ illegale modificare il valore di num. Questo perche’ num e’ una costante che punta all’inizio di un array e pertanto non puo’ essere incrementata. Un nome di array senza indice genera un puntatore all’inizio dell’array, questo pero’ non puo’ essere modificato. Quindi un nome di array genera una costante puntatore che pero’ puo’ fare parte di un’espressione con puntatori a patto che non venga modificato. Ad esempio la seguente istruzione che assegna a num[3] il valore 100 e’ perfettamente valida: *(num+3)=100; Costanti stringa int main() { char *str=“ciao”; printf(“%s\n”); return 0; } Quando il compilatore C incontra una costante stringa la memorizza nella tabella delle stringhe del programma e genera un puntatore alla stringa. In questo programma i caratteri che formano una costante stringa sono memorizzati in una tabella di stringhe e a str e’ assegnato un puntatore alla stringa in quella tabella. Il programma e’ perfettamente legale, quello che non e’ consentito e’ scrivere una riga di codice tipo *(str+1)=‘C’; Questo perche’ non possiamo utilizzare il puntatore per andare a modificare il contenuto della tabella delle stringhe del programma. La modifica di una stringa tramite un puntatore che punta all’interno della tabella delle stringhe del programma ha un risultato indefinito e generalemente pericoloso per il programma. Array di puntatori (1) I punatori possono essere riuniti in array come qualsiasi altro tipo di dato. Per esempio la dichiarazione di un array di puntatori interi di dimensione 10 e’: int *p[10]; Per assegnare l’indirizzo di una variabile intera chiamata var al terzo elemento dell’array di puntatori occorre scrivere int var; p[2]=&var; Occorre ricordare che si sta lavorando con un array di puntatori. Gli unici valori che gli elementi dell’array possono contenere sono gli indirizzi di variabili intere. Per trovare il valore di var e’ necessario scrivere: *p[2]; Array di puntatori (2) Anche gli array di puntatori possono essere inizializzati: char *previsioni[]={ “domani sara’ nuvoloso”, “sabato sara’ poco-nuvoloso”, “domenica sara’ sereno”}; Per ottenere il secondo messaggio occorrera’ scrivere: printf(“%s”,previsioni[1]); Un’array di stringhe e’ equivalente ad un array di puntatori a caratteri. argc e argv In alcuni casi potrebbe esserci la necessita’ di passare informazioni a un programma quando lo si esegue. Cio’ e’ possibile passando degli argomenti sulla linea di comando a main(). In C esistono due argomenti facoltativi di main(). La forma generale della funzione main() e’: int main(int argc, char *argv[]); Il parametro argc e’ un intero che contiene il numero di argomenti sulla linea di comando il cui valore sara’ sempre almeno 1, perche’ il nome del programma si considera come primo argomento. Il parametro argv e’ un array di puntatori a carattere cioe’ e’ un puntatore ad un puntatore a carattere. Ogni puntatore nell’array argv punta ad una stringa (un array di caratteri che e’ anche un puntatore a carattere) che contiene un argomento della linea di comando. argv[0] punta al nome del programma, argv[1] al primo argomento argv[2] al secondo e cosi’ via. Tutti gli argomenti sulla linea di comando sono passati al programma come stringhe, quindi gli argomenti numerici dovranno essere convertiti dal programma nel loro formato appropriato. Puntatori non inizializzati Prima di potere utilizzare un puntatore e’ necessario che questo punti a qualche elemento valido. Ad esempio: main() { int x,*p; *p=x; } p contiene un indirizzo sconosciuto perche’ non e’ mai stato definito, questo programma e’ sbagliato, per potere utilizzare p bisogna prima di tutto inizializzarlo. Chiamata di funzioni con puntatori In C e’ possibile passare un puntatore ad una funzione, per farlo e’ sufficiente dichiarare il parametro come tipo puntatore. Quando passiamo una variabile ad una funzione attraverso un puntatore si dice che la variabile e’ passata “per indirizzo”. Nel momento in cui si esegue un’operazione all’interno della funzione che utilizza il puntatore che gli e’ stato passato dalla funzione chiamante, l’operazione avviene sulla variabile a cui punta il puntatore. Pertanto la funzione sara’ in grado di modificare il valore dell’oggetto a cui punta il parametro. Chiamata di funzioni con array Quando l’argomento di una funzione e’ un array, cio’ che viene passato e’ soltanto l’indirizzo al primo elemento dell’array, ossia un puntatore e non una copia dell’intero array. Infatti in C un nome di array senza alcun indice e’ un puntatore al primo elemento dell’array. Passaggio di stringhe Quando si passa una stringa ad una funzione viene passato soltanto un puntatore all primo elemento della stringa, cioe’ al primo carattere della stringa, il tutto e’ analogo al passaggio di un array ad una funzione.