Capitolo precedente. Indice del capitolo. Indice analitico globale. Capitolo successivo.

Capitolo 5

Puntatori, vettori, stringhe

5.1 Puntatori.

Un puntatore è una variabile predisposta per contenere un indirizzo di memoria. Attraverso i puntatori diventa possibile manipolare gli indirizzi di memoria. Si tenga presente che ogni variabile occupa uno spazio di memoria, e che quindi è possibile ottenerne l'indirizzo di memoria.

Una variabile di tipo puntatore si dichiara specificando il tipo di dato a cui si punta ed il carattere *.

La dichiarazione

int *pint;
indica che pint è una variabile predisposta per contenere indirizzi di numeri int.

I valori di pint sono tutti numeri interi positivi, compreso il numero 0, che saranno interpretati come indirizzi di memoria. La costante 0 viene in genere utilizzata per inizializzare variabili di tipo puntatore.

Alcuni esempi di assegnazione per variabili di tipo puntatore sono

pint = 0;
pint = &i;   /* & operatore indirizzo
p = NULL;
Insieme ai puntatori si utilizza un operatore unario, *, che serve per indicare il dato puntato dal puntatore. In pratica è l'operatore inverso all'operatore &. Se
int *p;
allora con *p si indica l'intero il cui indirizzo è contenuto in p. Supponiamo di avre dichiarato
float x, y, *p;
Le seguenti due istruzioni
p = &x;
y=*p;
sono equivalenti a
y = *&x;
che è equivalente a
y = x;
Ci sono alcuni indirizzi di memoria che non sono accessibili. I seguenti esempi ne mostrano alcuni.

&5; non si può far riferimento all'indirizzo di una costante
int v[10];  
&v; v è una costante che indica l'indirizzo del vettore
&(i+3); non si può fare riferimento ad una espressione

mentre se v è un vettore allora

 
&v[3], &v[i+2]; sono tutte espressioni corrette.


Esempio.

int x = 4, y = 5, z[20];  
int *ip;   /* ip è un puntatore a intero */
ip = &x; /* ip punta a x */
y = *ip; y = 4
*ip = 0; x = 0
ip = &z[0]; ip punta a z[0]


i puntatori devono essere utilizzati con estrema cautela perchè rendono il programma poco leggibile, e possono generare errori in esecuzione difficilmente rintracciabili.

Se ip punta ad un intero x allora *ip può essere utilizzato ovunque è possibile utilizzare x. Ad esempio

*ip = *ip +10; incrementa *ip di 10.
y = *ip + 1; incrementa di 1 la variabile a cui punta ip e l'assegna a y
*ip += 1; incrementa ciò a cui punta ip di 1
++*ip; incrementa ciò a cui punta ip di 1
(*ip)++; incrementa ciò a cui punta ip di 1
*ip++; incrementa l'indirizzo di ip di 1 e poi ne considera il contenuto


5.2 I puntatori come parametri in una function.

Nei capitoli precedenti abbiamo visto come il passaggio dei parametri per una funzione avvenga per valore. Questo significa che le eventuali modifiche effettuate nella function non alterano i valori originali. Ora con i puntatori è possibile realizzare la chiamata di una funzione passando i parametri per indirizzo: è sufficiente dichiarare i parametri della funzione di tipo puntatore. Ad esempio

void swap( int a, int b) {
int c;
c=a; a=b; b=c;
}

effettua lo scambio di a e b solo localmente, non modificando i dati originali.

Mentre con

void swap( int* a, int* b ) {
int c;
c=*a; *a=*b; *b=c
}

la chiamata

int x, y;
....
....
swap( &x, &y );
provoca lo scambio tra x e y.


5.3 Relazione tra puntatori e vettori.

In molti casi puntatori e vettori sono utilizzati indifferentemente per accedere alla memoria. La differenza sostanziale tra i due è che un puntatore è una variabile predisposta per contenere un indirizzo, il nome di un vettore è un indirizzo.

Supponiamo di avere la seguente dichiarazione:

int v[100], *p;
Le istruzioni:
p=v; p=&v[0]; sono equivalenti
p=v+1; p=&v[1]; sono equivalenti

Supponiamo che tutto il vettore v sia utilizzato e che si voglia farne la somma degli elementi.
somma=0;
for( i=0; i<100; i++)
somma+=v[i];
oppure
somma=0;
for(p=v; p<&v[100]; p++)
somma+=*p;
oppure
somma:=0;
for(i=0; i<100; i++)
somma+=*(v+i);
sono programmi del tutto equivalenti.

Esercizio: Siano

char* ps="Cara Elena devi studiare.";
char s[]="Cara Elisa devi colorare."
Che differenza c'è tra le due dichiarazioni?


5.4 Aritmetica dei puntatori.

È questa una delle caratteristiche più potenti e più pericolose del C: la possibilità di operare sugli indirizzi di memoria con operazioni aritmetiche. Sia p una variabile di tipo puntatore ad un particolare tipo t. Con

p+1
si indica la variabile successiva di tipo t. Allo stesso modo è possibile utilizzare
p++;
++p;
p+=i;
Se p e q sono due variabili di tipo puntatore a t, con p - q si ottiene il numero di elementi di tipo t compresi tra p e q. Tutte le operazioni aritmetiche avvengono correttamente poichè per ogni variabile di tipo puntatore deve essere indicato anche il tipo di dato a cui punta. Il compilatore è in grado quindi di incrementare un indirizzo di memoria, passando al dato successivo, correttamente.

In Ansi C esiste anche il puntatore generico, dichiarato

void *p;
Le operazioni aritmetiche sui puntatori non sono effettuabili su un puntatore generico.

Nella dichiarazione di function che operano su vettori è possibile utilizzare indifferentemente la simbologia con gli indici o le variabili puntatore. Ad esempio è possibile scrivere:

int strlen( char* s ) {
int i;
for(i=0; s[i]; i++)
;
return i;
}
oppure
int strlen( char* s ) {
int i;
for(i=0; *(s+i); i++)
;
return i;
}
oppure
int strlen( char* s ) {
char* x = s;
for(; *s; s++)
;
return s - x;
}

5.5 cast.

L'operatore di cast permette di forzare particolari conversioni di espressioni. La sintassi è la seguente:

(tipo-del-dato) espressione

Ad esempio sia

int i;
Con
(double) i
si effettua un'operazione di cast sulla variabile i. Il valore contenuto in i non viene modificato, ma il risultato dell'espressione è un double.

Ad esempio l'espressione

i + 3
è di tipo int, mentre
(double) i + 3
è di tipo double.

Una operazione di cast equivale ad assegnare il risultato dell'espressione ad una variabile del tipo specificato dal cast ed utilizzarne il risultato.

L'operatore di cast, unario, è molto utile quando si opera con i puntatori, per modificare il tipo delle variabili di tipo puntatore.

Sia

void * p;
con
(char*)p
si utilizza il contenuto di un puntatore generico come se fosse un puntatore a char. Questo permette di realizzare funzioni generiche che operano su dati generici restituendo puntatori generici. Sarà compito del programmatore effettuare le opportune operazioni di cast.

5.7 Operazioni sulle stringhe.

Le funzioni che operano sulle stringhe, cioè su vettori di caratteri terminati da \0, permettono di confrontare, copiare, ricercare stringhe e caratteri, e permettono tante altre operazioni.

char *strchr( const char *string, int c )

Dove

string Stringa sorgente
c Carattere da ricercare

La funzione restituisce un puntatore alla prima occorrenza di c, convertito in un carattere, in string. Il carattere c può essere il carattere \0, poichè il carattere nullo può essere inserito nella ricerca. La funzione restituisce NULL se il carattere non è trovato. La funzione opera su stringhe terminate dal carattere \0.

int strcmp( const char *string1, const char *string2 );

Dove


string1, string2 sono le stringhe da confrontare

La funzione strcmp confronta le due stringhe in base al codice ashii e restituisce un intero secondo le seguenti condizioni:

< string1 minore di string2
0 string1 uguale a string2
> string1 più grande di string2

Si suppone che sia string1 che string2 siano terminate da \0.

char *strcpy( char *string1, const char *string2 );

Dove


string1 Stringa di destinazione
string2 Stringa sorgente

La funzione strcpy copia string2, incluso il carattere terminatore, nella posizione indicata da string1 e ritorna string1. Opera su stringa contenente il carattere terminatore. Non effettua nessun controllo di overflow su string1.

size_t strlen( const char *string );

Dove

string è una stringa terminata da \0.

Restituisce la lunghezza in byte di string, non calcolando il carattere \0.

char *strncat( char *string1, const char *string2, size_t count );

Dove


string1 Stringa di destinazione
string2 Stringa sorgente
count Numero di carattere da aggiungere

La funzione strncat aggiunge al massimo count caratteri di string2 in fondo a string1, terminandoli con \0 e restituisce string1. Se count è maggiore della lunghezza di string2 vengono aggiunti solo i caratteri di string2.

int strncmp( const char *string1, const char *string2, size_t count );

Dove


string1, string2 Stringhe da confrontare
count Numero di caratteri da confrontare

La funzione strncmp confronta alfabeticamente al massimo i primi count caratteri di string1 e string2 e restituisce un intero con il seguente significato:


< string1 minore di string2
0 string1 uguale a string2
> string1 più grande di string2

char *strncpy( char *string1, const char *string2, size_t count );

Dove


string1 Stringa di destinazione
string2 Stringa sorgente
count Numero di caratteri da copiare

La funzione strncpy copia count caratteri da string2 a string1. Se count è maggiore della lunghezza di string1 allora sono copiati solo i caratteri di string2 ma il carattere \0 non viene aggiunto in fondo a string1, mentre se count è minore della lunghezza di string2 allora viene aggiunto il carattere \0 alla fine in string1. Potrebbero sorgere problemi se le stringhe si sovrappongono parzialmente. Viene restituito string1.

char *strpbrk( const char *string1, const char *string2 );

Dove

string1 Stringa sorgente
string2 Insieme di caratteri

La funzione strpbrk ricerca in string1 la prima occorrenza di un qualsiasi carattere di string2. Il carattere \0 non viene ricercato. La funzione restituisce NULL se le due stringhe non hanno alcun carattere in comune.

char *strstr( const char *string1, const char *string2 );

Dove

string1 Stringa dove ricercare
string2 Stringa da ricercare

La funzione strstr restituisce un puntatore alla rpima occorrenza di string2 in string1. Restituisce NULL se string2 non è contenuta in string1.

char *strtok( char *string1, const char *string2 );

Dove

string1 Stringa contenente i token
string2 Caratteri di delimitazione dei token

La funzione strtok considera string1 come una sequenza di 0 o più token, mentre string2 come un insieme di caratteri che servono per delimitare un token dal successivo. I token di string1 possono essere estratti con una serie di chiamate alla funzione strtok. Dopo la prima chiamata strtok ricerca in string1 la prima sequenza di caratteri indicata da string2, scartando i delimitatori e restituendo il puntatore al primo token. Per ottenere gli altri token basta richiamare la funzione, passando come string1 la stringa nulla. È importante notare che le chiamate a questa funzione modificano string1 perchè viene inserito il carattere \0 al posto del delimitatore. Quando non ci sono più token viene restituito NULL.

char *strcat( char *string1, const char *string2 );

Dove

string1 Stringa di destinazione
string2 Stringa sorgente

La funzione strcat aggiunge alla string1 la string2. Entrambe le stringhe devono essere terminate da \0. La funzione restituisce il puntatore alla stringa concatenata, cioè a string1. Non viene effettuato alcun controllo di overflow.

char *strrchr( const char *string, int c );

Dove

string Stringa utilizzata per la ricerca
c Carattere da ricercare

La funzione strrchr ricerca l'ultima occorrenza di c, convertito in carattere, in string. String è una stringa terminata da \0, che può essere incluso nella ricerca. La funzione restituisce un puntatore all'ultima occorrenza di c in string. NULL se il carattere non è stato trovato.

size_t strspn( const char *string1, const char *string2 );

Dove

string1 Stringa su cui effettuare la ricerca
string2 Insieme di caratteri.

La funzione strspn restituisce l'indice del primo carattere di string1 che non appartiene a string2. Questo valore equivale alla lunghezza della sottostringa iniziale contenente alcuni caratteri di string2. Se il primo carattere di string1 non appartiene a string2 la funzione restituisce 0.

size_t strcspn( const char *string1, const char *string2 );

Dove

string1 Stinga utilizzata per la ricerca
string2 Insieme di caratteri

La funzione strcspn restituisce l'indice del primo carattere in string1 appartenente all'insieme di caratteri indicato da string2. Il carattere \0 non è considerato nella ricerca. Se string1 inizia con un carattere appartenente a string2 viene restituito 0. Sia string1 che string2 devono terminare con \0.

Esempio. STRTOK.C: In questo programma si utilizza un ciclo per stampare tutti i token di una stringa. I caratteri separatori per i token sono lo spazio o i caratteri di punteggiatura.

#include <string.h>
void main( void ){
char string[] = "Questa stringa, come ben sai,servirà come prova, o no!.";
char seps[] = " ,;!?:";
char *token;
 
printf( "%s\n\nTokens:\n", string );
token = strtok( string, seps );
while( token != NULL ) {
printf( " %s\n", token );
token = strtok( NULL, seps );
}
}
tipo data come stringa record come stringa