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.