Che materia stai cercando?

Anteprima

ESTRATTO DOCUMENTO

13. Puntatori

13.01. Cos'e' un puntatore

13.02. Puntatori e funzioni

13.03. Puntatori e array

13.04. Array di puntatori

13.05. Array multidimensionali e puntatori

13.06. Inizializzazione statica degli array di puntatori

13.07. Puntatori e strutture

13.08. Le "trappole" piu' comuni dei puntatori

13.08.01. Non assegnare un puntatore ad un indirizzo di

memoria prima di utilizzarlo

13.08.02. Assegnazione indiretta illegale

14. Allocazione dinamica della memoria

14.01. Malloc

14.02. Linked Lists

15. Input ed output

15.01. Streams

15.01.01. Streams predefinite

15.01.01.01. Redirezione

15.02. Funzioni comuni di I/O

15.03. Formattazione di I/O

15.03.01. Printf

15.04. Scanf

15.05. Files

15.05.01. Lettura e scrittura su files

15.06. Sprintf ed Sscanf

15.07. Input dalla linea di comando

15.08. I/O di basso livello

16. Il preprocessore C

16.01. #define

16.02. #undef

16.03. #include

16.04. #if - Inclusione condizionale

17. Scrittura di grossi programmi

17.01. File header

17.02. Variabili esterne e funzioni

17.02.01. Scopo delle variabili esterne

17.03. L'utility Make

17.04. Programmazione di Make

17.05. Creazione di un makefile

17.06. Macro di Make

17.07. Esecuzione di Make

18. UNIX e il C

18.01. Vantaggi di usare UNIX con il C

18.02. Utilizzo delle chiamate di sistema UNIX e delle funzioni

di libreria

18.03. Trattamento di file e directory

18.03.01. Funzioni di trattamento delle directory

18.03.02. Routine di trattamento dei file

18.03.03. errno

18.04. Controllo e gestione dei processi 2

18.04.01. Esecuzione di comandi UNIX da C

18.04.01.01. execl()

18.04.01.02. fork()

18.04.01.03. wait()

18.04.01.04. exit()

18.04.02. Utilizzo di pipe in un programma C

18.04.02.01. popen() - Piping formattato

18.04.02.02. pipe() - Piping di basso livello

18.04.03. Interruzioni e segnali

18.04.03.01. Invio di segnali - kill()

18.04.03.02. Ricezione di segnali - signal()

18.05. Times Up!!

19. Opzioni comuni del compilatore C

19.01. Opzioni di compilazione

20. Funzioni della libreria standard C

20.01. Manipolazione dei buffer

20.02. Classificazione dei caratteri e conversione

20.03. Conversione dei dati

20.04. Manipolazione delle directory

20.05. Manipolazione dei file

20.06. Input e Output

20.06.01. Stream I/O

20.06.02. I/O di basso livello

20.07. Matematica

20.08. Allocazione di memoria

20.09. Controllo dei processi

20.10. Ricerca e ordinamento

20.11. Manipolazione di stringhe

20.12. Time

======================================================================

========

------------------------------------------------------------------------------

01. Caratteristiche del linguaggio C

------------------------------------------------------------------------------

Qui di seguito verranno elencate brevemente alcune delle caratteristiche del

C che definiscono il linguaggio stesso e che hanno contribuito alla

popolarita' che ha raggiunto come linguaggio di programmazione:

- dimensioni ridotte

- utilizzo frequente di chiamate a funzioni

- loose typing (a differenza del Pascal)

- linguaggio strutturato

- programmazione a basso livello facilmente disponibile

- implementazione dei puntatori (ampio uso di puntatori per memoria,

vettori, strutture e funzioni)

Il C e' ora diventato un linguaggio professionale ampiamente utilizzato per

varie ragioni: 3

- ha strutture di alto livello

- puo' maneggiare attivita' di basso livello

- produce programmi efficienti

- puo' essere compilato su un'ampia gamma di computers

Il suo principale inconveniente e' quello di avere un metodo scadente per

l'identificazione di errori, che puo' escluderne l'utilizzo ai principianti.

Comunque con un minimo di diligenza si puo' risolvere elegantemente questo

problema, in quanto si possono violare le regole del C non appena si sono

imparate (non molti linguaggi lo permettono). Nel caso in cui venga fatto

correttamente e con attenzione, questo porta a sfruttare le potenzialita'

della programmazione C.

Lo standard per i programmi C in origine era dato dalle caratteristiche

messe a punto da Brian Kernighan. Al fine di rendere il linguaggio piu'

accettabile a livello internazionale, venne messo a punto uno standard

internazionale chiamato ANSI C (American National Standards Institute).

------------------------------------------------------------------------------

02. Storia del linguaggio C

------------------------------------------------------------------------------

Le pietre miliari nel corso dell'evoluzione del C come linguaggio sono

elencate di seguito:

- UNIX developed c. 1969 - DEC PDP-7 Assembly Language

- BCPL - un OS facilmente accessibile che fornisce potenti strumenti di

sviluppo prodotti a partire da BCPL. Si tratta di un assemblatore noioso,

lungo ed incline agli errori

- Un nuovo linguaggio "B" come secondo tentativo c. 1970

- Un linguaggio "C" totalmente nuovo come successore di "B" c. 1971

- Dal 1973 UNIX OS, quasi totalmente scritto in C

------------------------------------------------------------------------------

03. Primo approccio

------------------------------------------------------------------------------

Un minimo programma in C e':

main()

{

}

che corrisponde a un programma in Pascal:

program minimum;

begin

end 4

Ogni programma C deve contenere una e una sola funzione main().

Per ogni parentesi graffa aperta (che corrisponde al begin in pascal)

deve essercene una chiusa (che corrisponde all'end in pascal).

I commenti possono essere posti ovunque utilizzando /* (inizio commento)

e */ (fine commento), ma non si puo' inserire un commento in un altro.

Ad esempio:

/* Esempio di programma in C */

main()

{ /* Un ulteriore commento */ ESATTO

/* Commento /* Ancora un commento */ */ ERRATO

}

Il seguente esempio e' un programma che produce l'output sullo

schermo della frase "Hello World":

main()

{ printf("Hello World \n");

exit(0);

}

L'istruzione "printf" e' una funzione C che visualizza cio' che gli

viene passato come argomento.

Per creare un file contenente uno dei precedenti programmi si puo'

utilizzare un qualsiasi text editor disponibile sulla macchina (vi, emacs,

xedit, ...).

Il nome del file deve avere l'estensione ".c", cioe' chiamarsi, ad esempio,

prog.c. Il contenuto, ovviamente, deve rispettare la sintassi C; per quanto

riguarda gli esempi sopra riportati, potrebbero iniziare con una riga del

tipo

/* Esempio ... */ (anche con una linea vuota che la precede)

e terminare con la linea

} /* Fine del programma */ (anche con una linea vuota che la segue)

------------------------------------------------------------------------------

04. Compilazione di un programma C

------------------------------------------------------------------------------

Per compilare il programma si utilizza il comando cc seguito dal nome del

programma C sorgente, dove "cc" e' il nome del compilatore C.

Ad esempio: cc prog.c

Se il compilatore trova errori (in genere syntax error, come errori di

battitura, errori di sintassi delle parole chiave o ";" omessi), questi

vengono identificati e visualizzati; in caso contrario, viene creato il 5

file eseguibile a.out. Il compilatore non identifica eventuali errori di

logica del programma, quindi potrebbero essere eseguite delle operazioni

errate, ed e' compito dell'utente trovarle (anche con l'ausilio di appositi

programmi di debugging).

In fase di compilazione possono essere specificate anche ulteriori opzioni:

la piu' utilizzata e' "-o nome-file", che crea l'eseguibile con il nome

nome-file invece di a.out, ma ne esistono altre come ad esempio "-c"

(opzione senza argomenti, per la soppressione di link).

Altra opzione possibile e' "-g", con cui e' necessario compilare per poter

utilizzare il debugger "dbx".

Ad esempio: cc prog.c -o prog (oppure cc -o prog.c prog)

cc -c prog.c -o prog

cc -g prog.c -o prog

Per far eseguire il programma e' sufficiente scrivere il nome

dell'eseguibile creato (e' ovvio che il file eseguibile deve avere i

permessi per l'esecuzione, solitamente assegnati automaticamente in fase di

compilazione):

si avranno visualizzati sullo schermo gli eventuali risultati.

Nel momento dell'esecuzione e' possibile osservare ed identificare eventuali

errori di run-time, come ad esempio le divisioni per zero; in tal caso

l'esecuzione termina irregolarmente e viene generato un file core con lo

stato del programma in esecuzione al momento del verificarsi dell'errore.

Se il programma in esecuzione non rilascia errori ma produce output errati,

e' evidente che contiene errori logici; questi andranno corretti editando

il programma sorgente, questo dovra' essere ricompilato e si potra' lanciare

nuovamente l'esecuzione.

La compilazione del programma C avviene attraverso le seguenti fasi:

- un preprocessore che accetta il codice sorgente come input

ed e' resposabile della:

- rimozione di commenti

- interpretazione di speciali direttive per il preprocessore

denotate da "#".

Ad esempio:

#include - include il contenuto di un determinato file

(solitamente chiamato header, con suffisso ".h").

#include <math.h> - standard library maths file.

#include <stdio.h> - standard library I/O file

#define - definisce un nome simbolico o una costante

(sostituzione di una macro).

#define MAX_ARRAY_SIZE 100

- il compilatore C che traduce il codice sorgente ricevuto dal

preprocessore in codice assembly.

- l' assembler che crea il codice oggetto (in UNIX i file con il suffisso

.o sono i file in codice oggetto, che corrispondono ai file .obj in 6

MSDOS).

- il link editor che combina le funzioni definite in altri file sorgenti

o definite in librerie, con la funzione main() per creare il file

eseguibile.

Infatti molte delle funzioni presenti in altri linguaggi non sono incluse

nel C (ad esempio, funzioni di I/O, di manipolazione di stringhe o

matematiche), ma il C fornisce tali funzionalita' attraverso un ricco

insieme di librerie di funzioni. Molte applicazioni C includono librerie

standard di funzioni per coprire le utilita' mancanti.

In questa fase vengono anche ricostruiti i riferimenti alle variabili

esterne utilizzate nei sorgenti C.

------------------------------------------------------------------------------

05. Struttura di un programma C

------------------------------------------------------------------------------

Un programma C ha in linea di principio la seguente forma:

Comandi per il preprocessore

Definizione di tipi

Prototipi di funzioni (dichiarazione dei tipi delle funzioni e delle

variabili passate alle funzioni)

Variabili

Funzioni

Vediamo l'esempio di un programma:

main()

{ printf("I like C\n");

exit(0);

}

Note:

- Il C richiede un punto e virgola alla fine di ogni statement.

- printf() e' una funzione standard richiamata da main.

- \n significa una nuova linea (a capo).

- exit() e' anch'essa una funzione standard che fa terminare il programma

(qui non sarebbe necessaria in quanto e' l'ultima linea di main e il

programma terminerebbe comunque).

------------------------------------------------------------------------------

06. Variabili

------------------------------------------------------------------------------

Il C ha i seguenti tipi di dati:

Tipo Size (byte)

char 1 7

unsigned char 1

short int 2

unsigned short int 2

(long) int 4

float 4

double 8

Sui sistemi UNIX tutte le variabili dichiarate "int" sono considerate

"long int", mentre "short int" deve essere dichiarato esplicitamente.

E' importante notare che in C non esiste un tipo di variabile booleano,

quindi si possono utilizzare variabili "char", "int" o meglio "unsigned

char". "unsigned" puo' essere utilizzato con tutti i tipi "char" e "int".

Per dichiarare una varibile si scrive:

var_tipo elenco-variabili-separate-da-virgole ;

Le variabili globali si definiscono al di sopra della funzione main(),

nel seguente modo:

short number,sum;

int bignumber,bigsum;

char letter;

main()

{

...

}

E' possibile preinizializzare una variabile utilizzando = (operatore di

assegnazione).

Ad esempio:

int i,j,k=1;

float x=2.6,y;

char a;

Vediamo due esempi di inizializzazione di variabili che si equivalgono,

senza pero dimenticare che il metodo utilizzato nel primo esempio

risulta piu' efficiente:

Esempio 1: float sum=0.0;

int bigsum=0;

char letter='A';

main()

{

...

}

Esempio2: float sum;

int bigsum;

char letter;

main()

{

sum=0.0; 8

bigsum=0;

letter='A';

...

}

E' possibile effettuare assegnazioni multiple purche' le variabili

siano dello stesso tipo.

Ad esempio:

int somma;

char letter="A";

main()

{ int a,b,c=3;

a=b=c;

}

dove l'istruzione a=b=c (con c=3) corrisponde ad a=3, b=3 e c=3, ma

anche in questo caso risulta piu' efficiente il primo metodo.

Si possono definire nuovi propri tipi di variabili utilizzando "typedef"

(questo risulta utile quando si creano strutture complesse di dati).

Come esempio di utilizzo semplice consideriamo come sia possibile creare

i due nuovi tipi di variabile "real" e "letter", che potranno successiva-

mente essere utilizzati alla stessa maniera dei tipi predefiniti del C.

Ad esempio:

typedef float real;

typedef char letter;

variabili dichiarate:

real sum=0.0;

letter nextletter;

------------------------------------------------------------------------------

06.01. Stampa ed input di variabili

------------------------------------------------------------------------------

Il C sfrutta l'output formattato.

Per stampare il contenuto di una variabile si utilizza la funzione printf().

Bisogna pero' specificare il formato della variabile utilizzando il

carattere speciale di formattazione "%" seguito dal carattere che definisce

un certo formato per una variabile:

%c - char

%d - int

%f - float

Ad esempio: printf("%c%d%f",letter,somma,z);

Nota: l'istruzione di formattazione e' racchiusa tra "", e le variabili

vengono esposte di seguito; assicurarsi che l'ordine dei formati ed

il tipo di dato delle variabili coincidano. 9

Sempre a proposito della funzione "printf", vediamo il seguente esempio di

una istruzione di stampa:

printf(".\n.1\n..2\n...3\n");

per la quale l'output sara':

.

.1

..2

...3

scanf() e' la funzione per l'input di valori a strutture di dati.

Il suo formato e' simile a quello di printf():

scanf("%c%d%f",&ch,&i,&x);

Nota: "&" si riferisce all'indirizzo della variabile, e va sempre messo

davanti ai nomi di variabili in acquisizione; il motivo verra'

spiegato nel paragrafo dei "puntatori".

------------------------------------------------------------------------------

07. Operatori

------------------------------------------------------------------------------

------------------------------------------------------------------------------

07.01. Operatori aritmetici

------------------------------------------------------------------------------

Come gia' accennato, le assegnazioni in C vengono effettuate utilizzando

"=". Oltre agli operatori arimetici standard +,-,*,/ e all'operatore %

(modulo) per gli interi, in C si hanno anche gli operatori incremento ++

e decremento --, che possono essere preposti o posposti all'argomento. Se

sono preposti il valore e' calcolato prima che l'espessione sia valutata,

mentre se sono posposti il valore viene calcolato dopo la valutazione della

espressione.

Ad esempio: int x,z=2;

1) x=(++z)-1;

A questo punto x=2 e z=3

int x,z=2;

2) x=(z++)-1;

A questo punto x=1 e z=3

Riportiamo un ulteriore esempio:

int x,y,w;

main()

{

x=((++z)-(w--))%100; 10

}

che equivale alle seguenti istruzioni:

int x,y,w;

main()

{

z++;

x=(z-w)%100;

w--;

}

E' importante sottolineare che un'istruzione del tipo

x++

e' piu' veloce della corrispondente

x=x+1

L'operatore "%" (modulo) puo' essere utilizzato solamente con le variabili

di tipo integer; la divisione "/" e' utilizzata sia per gli integer che

per i float.

A proposito della divisione riportiamo un altro esempio:

z=3/2

dove z avra' valore 1, anche se e' stato dichiarato come float

(di regola, se entrambi gli argomenti della divisione sono integer,

allora verra' effettuata una divisione integer);

per avere un risultato corretto sara' necessario scrivere:

z=3.0/2 oppure

z=3/2.0 o, ancora meglio,

z=3.0/2.0

Inoltre esiste una forma contratta per espressioni del tipo

expr1 = expr1 op expr2

(ad esempio: i=i+2 oppure x=x*(y+3))

che diventano:

expr1 op = expr2

Per cui i=i+2 puo' essere scritta nel modo contratto come i+=2

od x=x*(y+3) diventare x*=y+3.

Nota: l'espressione x*=y+3 corrisponde a x=x*(y+3) e non a x=x*y+3.

------------------------------------------------------------------------------

07.02. Operatori di confronto

------------------------------------------------------------------------------

Per testare l'ugualianza si usa "==" mentre per la disugualianza "!=".

Ci sono poi gli operatori "<" (minore), ">" (maggiore), "<=" (minore o

uguale), ">=" (maggiore o uguale).

NB. if (i==j) ... esegue il contenuto dell'if se i e' uguale a j, ma 11

if (i=j) ... e' ancora sintatticamente esatto ma effettua l'assegnazione

del valore di j ad i e procede se j e' diverso da zero in quanto

viene interpretato il valore TRUE (e' come scrivere if i ... con i

diverso da zero). In questo caso si tratterebbe di una "assegnazione di

valore", una caratteristica chiave del C.

------------------------------------------------------------------------------

07.03. Operatori logici

------------------------------------------------------------------------------

Gli operatori logici, solitamente utilizzati con le istruzioni condizionali

che vedremo piu' avanti, sono "&&" per AND logico e "||" per OR logico.

Nota: "&" e "|" hanno un significato diverso, poiche' sono bitwise AND e OR.

------------------------------------------------------------------------------

07.04. Operatori di basso livello

------------------------------------------------------------------------------

Nel capitolo relativo ai puntatori si vedra' come questi permettano il

controllo delle operazioni di memoria di basso livello.

Molti programmi (in particolare le applicazioni di gestione del sistema)

devono realmente operare a basso livello, poiche' lavorano su bytes

individuali.

E' importante notare che la combinazione di puntatori e di operatori

bit-level rendono il C utilizzabile per molte applicazioni a basso livello

e possono quasi sempre sostituire il codice assembly (ricordiamo che

solamente circa il 10% di UNIX e' un codice assembly, mentre il resto e' C).

------------------------------------------------------------------------------

07.04.01. Operatori di bitwise

------------------------------------------------------------------------------

Gli operatori di bitwise (che operano sui singoli bit) sono i seguenti:

"&" AND

"|" OR

"^" XOR

"~" Complemento a 1 (0=>1, 1=>0)

"<<" shift a sinistra

">>" shift a destra

Nota: fare attenzione, come gia' detto in precedenza, a non confondere

& con && (& e' "bitwise and", mentre && e' "logical and"); la stessa

cosa vale per | e ||.

"~" e' un operatore unario, cioe' opera su un solo argomento indicato a

destra dell'operatore.

Gli operatori di shift eseguono un appropriato shift dall'operatore indicato

a destra a quello indicato a sinistra. L'operatore destro deve essere 12

positivo. I bits liberati vengono riempiti con zero (cioe' non si tratta di

una rotazione, con recupero sul lato opposto dei bit shiftati).

Ad esempio: z<<2 shifta i bit in z di due posti verso sinistra

cosi', se z=00000010 (binario) o 2 (decimale)

allora, z>>=2 => z=00000000 (binario) o 0 (decimale)

inoltre, z<<=2 => z=00001000 (binario) o 8 (decimale)

Quindi, uno shift a sinistra e' equivalente ad una moltiplicazione per 2;

similmente, uno shift a destra equivale ad una divisione per 2.

Nota: l'operazione di shift e' molto piu' veloce della reale moltiplicazione

(*) o divisione (/); cosi', se occorrono veloci moltiplicazioni o

divisioni per 2 si puo' utilizzare lo shift.

Per illustrare le molteplici caratteristiche degli operatori di bitwise,

riportiamo una funzione (bitcount) che somma 2 bit settati ad 1 un un numero

ad 8 bit (unsigned char) passato come argomento alla funzione:

int bitcount(unsigned char x)

{int count;

for (count=0; x!=0; x>>=1);

if (x&01)

count++;

return count;

}

Questa funzione mostra molti punti del programma C:

- il loop "for" non viene usato per semplici operazioni di somma

- x>>=1 => x=x>>1

- il loop "for" shifta ripetutamente a destra x, finche' x diventa 0

- il controllo "if" utilizza la valutazione dell'espressione x &01

- x &01 controlla il primo bit di x, ed esegue count++ se questo e' 1

------------------------------------------------------------------------------

07.04.02. Bit Fields

------------------------------------------------------------------------------

I Bit Fields permettono il raggruppamento dei dati in una struttura. Questa

tecnica viene usata soprattutto quando la gestione della memoria o la

memorizzazione dei dati sono uno meta molto ambita.

Tipici esempi sono costituiti da:

- raggruppamento di parecchi oggetti in una parola macchinai (i flag di un

bit possono essere compattati); ad esempio, la tabella dei simboli

nell'ambito dei compilatori;

- lettura di formati di file esterni (formati di file non standard possono

essere importati); ad esempio, gli interi di 9 bit. 13

Il C permette di fare questo in una definizione di struttura, mettendo

":lunghezza-bit" dopo la variabile stessa, e cioe':

struct packed-struct {

unsigned int f1:1;

unsigned int f2:1;

unsigned int f3:1;

unsigned int f4:1;

unsigned int type:4;

unsigned int funny_int:9;

} pack;

Qui la struttura packed-struct contiene 6 elementi: 4 flag da 1 bit (f1,...

f4) e funny_int da 9 bit.

Il C automaticamente raggruppa assieme i campi di bit elencati nell'esempio

appena riportato.

Solitamente si accede ai membri della struttura nel seguente modo:

pack.type = 7;

Notiamo che:

- solamente "n" bit di basso livello possono essere assegnati ad un numero

di "n" bit. Cosi' il campo "type" non puo' assumere valori maggiori di 15

(4 bits long);

- i bit fields vengono sempre convertiti al tipo intero prima di eseguirvi

delle operazioni;

- e' permesso "mescolare" tipi normali con bit fields;

- la definizione di "unsigned" e' importante, per assicurarsi che per i

flags non venga usato nessun bit per il segno.

------------------------------------------------------------------------------

07.05. Ordine di precedenza degli operatori

------------------------------------------------------------------------------

E' necessario fare attenzione al significato di un'espressione come

a + b * c

dove potremmo volere sia l'effetto di

(a + b) * c

sia quello di

a + (b * c)

Tutti gli operatori hanno una propria priorita', e gli operatori ad alta

priorita' sono valutati prima di quelli a bassa priorita'.

Gli operatori con la stessa priorita' sono valutati da sinistra a destra;

Cosi' a - b - c

e' valutato

(a - b) - c

come ci si puo' aspettare. 14

L'ordine di priorita', dalla piu' alta alla piu' bassa, degli operatori

in C e':

()[]->.

!~-*& sizeof cast ++ --

(these are rigth -> left)

*/%

+-

< <= >= >

== !=

&

^

|

&&

||

?: (right -> left)

= += -= (right -> left)

,(comma)

Quindi:

"a < 10 &&2 * b < c"

e' interpretato come:

"(a < 10) &&((2 * b) < c)".

ed anche:

a=

b= spokes / spokes_per_wheel

+ spares;

e' valutato come:

a=

(b= (spokes / spokes_per_wheel)

+ spares

);

------------------------------------------------------------------------------

08. Strutture di controllo

------------------------------------------------------------------------------

Quelli che seguono sono i vari metodi con cui il C puo' controllare il

flusso logico di un programma. A parte alcune minime differenze sintattiche,

queste istruzioni sono simili a quelle che si possono trovare negli altri

linguaggi.

Come abbiamo visto, in C esistono le seguenti operazioni logiche:

==,!=,||,&&.

Un altro operatore e' il not "!" unario (ha un solo argomento).

Questi operatori sono utilizzati congiuntamente alle istruzioni di seguito

riportate. 15

------------------------------------------------------------------------------

08.01. If

------------------------------------------------------------------------------

L'istruzione "if" ha le stesse funzioni degli altri linguaggi. Puo' avere

tre forme di base:

if (expression)

statement

if (expression)

statement1

else

statement2

if (expression1)

statement1

else if (expression2)

statement2

else

statement3

Ad esempio:

int x,y,z;

main()

{ int w;

...

if (x<0)

{ z=w;

...

}

else

{ z=y;

...

}

}

------------------------------------------------------------------------------

08.02. Operatore "?"

------------------------------------------------------------------------------

L' operatore ? (ternary condition) e' la forma piu' efficente per esprimere

semplici if statements. Ha la seguente forma:

expression1 ? expression2 : exprssion3

che equivale a: 16

if expression1 then expression2 else expression3

Ad esempio:

z=(a>b) ? a : b

cioe'

if (a>b)

z=a;

else

z=b;

assegna a z il massimo tra a e b.

------------------------------------------------------------------------------

08.03. Switch

------------------------------------------------------------------------------

Permette scelte multiple tra un insieme di items.

La sua forma generale e':

switch (expression) {

case item1: statement1;

break;

case item2: statement2;

break;

.

.

.

case itemn: statementn;

break;

case default:

statement;

break;

}

Il valore degli item deve essere una costante (le variabili non sono

permesse).

Il break serve per terminare lo switch dopo l'esecuzione di una scelta,

altrimenti verra' valutato anche il caso successivo (questo, a differenza

di molti altri linguaggi).

E' possibile anche avere un'istruzione nulla, includendo solamente un ";"

oppure lasciando fallire l'istruzione di switch omettendo qualsiasi frase

(come nell'esempio di seguito). 17

Il caso "default" e' facoltativo e raggruppa tutti gli altri casi.

Ad esempio:

switch (letter) {

case 'A':

case 'E':

case 'I':

case 'O':

case 'U':

numerovocali++;

break;

case " ":

numerospazi++;

break;

default:

numerocostanti++;

break;

}

In questo caso se letter e' una vocale ('A','E','I','O','U') viene

incrementato il valore della varibile numerovocali, se e' uno spazio (" ")

si incrementa numerospazi e altrimenti (se nessuno dei casi precedenti e'

vero) viene eseguita la condizione di default e quindi viene incrementato

numerocostanti.

------------------------------------------------------------------------------

08.04. For

------------------------------------------------------------------------------

L'istruzione C "for" ha la seguente forma:

for (expression1; expression2; expression3)

statement;

{or block of statements}

dove expression1 inizializza, expression2 e' il test di termine e

expression3 e' il modificatore (che puo' anche essere piu' di un semplice

incremento).

Nota: fondamentalmente il C tratta le istruzioni "for" come i cicli di

tipo "while".

Ad esempio:

int x;

main()

{ for (i=0;i<3;i++)

printf("x=%d\n",x);

} 18

che genera come output sullo schermo:

x=0

x=1

x=2

Gli esempi che seguono sono tre forme valide delle istruzioni "for" in C:

for (x=0;((x<3)&&(x>9));x++)

for (x=0,y=4;((x<3)&&(y>9));x++,y+=2)

in cui si puo' notare che le espressioni multiple possono essere separate

da una ",";

for (x=0,y=4,z=4000;z;z/=10)

in cui si puo' notare che il loop continua l'iterazione fino a quanto z

diventa 0.

------------------------------------------------------------------------------

08.05. While

------------------------------------------------------------------------------

L'istruzione "while" ha la seguente forma:

while (expression)

statement;

Ad esempio:

int x=3;

main()

{ while (x>0)

{ printf("x=%d\n",x);

x--;

}

}

che genera come output sullo schermo:

x=3

x=2

x=1

While puo' accettare non solo condizioni ma anche espressioni, per cui

risultano corrette le seguenti istruzioni:

while (x-);

while (x=x+1);

while (x+=5);

Utilizzando questo tipo di espressioni, solo quando il risultato di x--, 19

x=x+1 oppure x+=5 ha valore 0 la condizione di while fallisce e si esce

dal loop.

E' possibile avere anche complete operazioni di esecuzione nelle espressioni

"while":

while (i++<10)

che incrementa i fino a raggiungere il valore 10;

while ((ch=getchar())!'q')

putchar(ch);

che usa le funzioni getchar() e putchar() delle librerie standard, che

rispettivamente leggono un carattere dalla tastiera e scrivono un

determinato carattere sullo schermo. Il loop while continua a leggere dalla

tastiera e a visualizzare sullo schermo il carattere digitato, fino a quando

non venga battuto il carattere "q".

------------------------------------------------------------------------------

08.06. Do-While

------------------------------------------------------------------------------

L'istruzione C "do-while" ha la seguente forma:

do

statement;

while (expression);

(e' simile al Pascal repeat ... until, eccetto il fatto che l'espressione

dell'istruzione do-while e' vera)

Ad esempio:

int x=3;

main()

{ do { /* le graffe sono superflue, visto */

printf("x=%d\n",x-); /* che racchiudono solamente una */

} /* istruzione */

while (x>0);

}

Il cui output e':

x=3

x=2

x=1

Nota: l'operatore finale "x-" indica che viene usato il valore corrente

di x mentre stampa, e poi viene decrementato x.

------------------------------------------------------------------------------

08.07. Break e Continue 20

------------------------------------------------------------------------------

Il C fornisce due comandi per controllare i loop:

break - esce da un loop o da uno switch

continue - salta una iterazione del loop

Consideriamo il seguente esempio, dove leggiamo un valore integer e lo

elaboriamo in accordo con le seguenti condizioni. Se il valore che abbiamo

letto e' negativo, dovremo stampare un messaggio di errore ed abbandonare

il loop. Se il valore letto e' maggiore di 100, dovremo ignorarlo e

continuare con il successivo valore in input. Se il valore e' 0, dovremo

terminare il loop.

/* Viene letto un valore intero ed elaborato purche'

sia maggiore di 0 e minore di 100 */

while (scanf("%d".&value) == 1 && value !=0) {

if (value<0) {

printf("Valore non ammesso\n");

break; /* Abbandona il loop */

}

if (value>100) {

printf("Valore non ammesso\n");

continue; /Torna nuovamente all'inizio del loop */

}

/*Elabora il valore letto*/

/*che e' sicuramente tra 0 e 100 */

.

.

.

}

------------------------------------------------------------------------------

09. Arrays

------------------------------------------------------------------------------

------------------------------------------------------------------------------

09.01. Array singoli e multidimensionali

------------------------------------------------------------------------------

Un esempio di definizione di un array in C e' :

int elenco_numeri[50];

e si accede agli elementi dell'array nel seguente modo:

terzo_numero= elenco_numeri[2];

elenco_numeri[5]=100;

NB. In C gli Array subscripts iniziano da 0 e finiscono alla dimensione 21

dell'array meno uno. Nell'esempio precedente il range e' 0-49, cioe'

elenco_numeri e' un array di 50 elementi e si ha:

elenco_numeri[0],elenco_numeri[1],....elenco_numeri[49].

Questa e' una grossa differenza fra il C e gli altri linguaggi e

richiede un po' di pratica per raggiungere "la giusta disposizione

d'animo".

Array multidimensionali sono cosi definiti:

int tabella_numeri[50][50] => per due dimensioni

int big_D[20][30][10][40] => per piu' di due dimensioni

e si accede agli elementi nel seguente modo:

numero=tabella_numeri[5][32];

tabella_numeri[1][23]=100;

------------------------------------------------------------------------------

09.02. Stringhe

------------------------------------------------------------------------------

In C le stringhe sono definite come array di caratteri. Ad esempio, la

seguente istruzione definisce una stringa di 50 caratteri:

char name[50];

Il C non ha pero' un sistema maneggevole per costruire le stringhe, cosi

le seguenti assegnazioni non sono valide:

char firstname[50], lastname[50], fullname[50];

firstname = "Mario" /* illegale */

lastname = "Rossi" /* illegale */

fullname = "Sig."+firstname+lastname /* illegale */

Esiste pero' una libreria di routines per il trattamento delle stringhe

("< string.h >").

Per maneggiare le stringhe si possono usare puntatori ad array di char

(come vedremo piu' avanti).

Per stampare una stringa si usa printf() con lo speciale carattere di

controllo %s:

printf("%s",nome);

Nota: e' sufficiente avere il nome della stringa.

Al fine di permettere l'utilizzo di stringhe con lunghezza variabile,

il carattere \0 viene utilizzato per indicare la fine di una stringa.

In questo modo, se abbiamo una stringa dichiarata di 50 caratteri

(char name[50];), e la utilizziamo per memorizzare il nome "Dave", il

suo contenuto (a partire da sinistra) sara' la parola Dave immediatamente 22

seguita dal segno di fine stringa \0, e quindi tutti gli altri caratteri

(fino ad arrivare alla lunghezza di 50) risulteranno vuoti.

------------------------------------------------------------------------------

10. Funzioni

------------------------------------------------------------------------------

Il C fornisce delle funzioni anch'esse simili alla maggior parte degli

altri linguaggi. Una differenza e' che il C considera "main()" come una

funzione. A differenza di alcuni linguaggi, come il Pascal, il C non ha

procedure poiche' usa le funzioni per soddisfare entrambe le esigenze.

La forma generale di una funzione e':

returntype function_name (parameterdef1, parameterdef2, ...)

{ local variables

function code (C statements)

}

Se manca la definizione del tipo della funzione ("returntype", tipo della

variabile di ritorno della funzione), il C assume che il ritorno della

funzione e' di tipo integer; questo puo' essere una delle cause di problemi

nei programmi.

Esempio di una funzione che calcola la media tra due valori:

float calcolamedia(float a, float b)

{ float media;

media=(a+b)/2;

return(media);

}

Per richiamare tale funzione si procede nel seguente modo:

main()

{ float a=10, b=25, risultato;

risultato=calcolamedia(a,b);

printf("Valore medio= %f\n",risultato);

}

Nota: l'istruzione "return" ritorna il risultato della funzione al

programma principale.

------------------------------------------------------------------------------

10.01. Funzioni "void"

------------------------------------------------------------------------------

Se non si vuole ritornare alcun valore da una funzione e' sufficiente 23

dichiararla di tipo void ed omettere il return.

Ad esempio:

void quadrati()

{int loop;

for (loop = 1; loop < 10; loop++);

printf("%d\n",loop*loop);

}

main()

{quadrati()

}

Nota: e' obbligatorio mettere le parentesi () dopo il nome della funzione

anche se non ci sono parametri, a differenza di altri linguaggi.

------------------------------------------------------------------------------

10.02. Funzioni ed array

------------------------------------------------------------------------------

Possono essere passati alle funzioni come parametri anche array singoli o

multidimensionali.

Gli array monodimensionali possono essere passati nel seguente modo:

float trovamedia(int size,float list[])

{int i;

float sum=0.0;

for (i = 0; i < size; i++)

sum+=list[i];

return(sum/size);

}

In questo esempio la dichiarazione "float list[]" dichiara al C che

"list" e' un array di float. Non viene specificata la dimensione di un

array quando e' un parametro di una funzione.

Array multidimensionali possono essere passati alle funzioni nel seguente

modo:

void stampatabella(int xsize, int ysize,float tabella[][5])

{int x,y;

for (x = 0; x < xsize; x++) {

for (y = 0; y < ysize; y++)

printf("\t%f"tabella[x][y]);

printf("\n");

}

} 24

In questo esempio "float tabella[][5]" dichiara al C che tabella e' un

array di float di dimensioni Nx5. E' importante notare che dobbiamo

specificare la seconda dimensione (e le successive) del vettore, ma non la

prima dimensione.

Quindi, riepilogando, nel caso di array singoli non e' necessario

specificare la dimensione dell'array nella definizione come parametro della

funzione, mentre nel caso di array multidimensionali si puo' non specificare

solo la prima dimensione.

------------------------------------------------------------------------------

10.03. Prototipi di funzioni

------------------------------------------------------------------------------

Prima di usare una funzione, il C deve riconoscere il tipo di ritorno e il

tipo dei parametri che la funzione si aspetta.

Lo standard ANSI del C ha introdotto un nuovo e migliore metodo per fare

questa dichiarazione rispetto alle precedenti versioni di C (ricordiamo

che tutte le nuove versioni del C aderiscono ora allo standard ANSI).

L'importanza della dichiarazione e' doppia:

- viene fatta per avere un codice sorgente piu' strutturato e percio'

facile da leggere ed interpretare;

- permette al compilatore C di controllare la sintassi delle chiamate di

funzioni.

Il modo in cui questo viene fatto dipende dallo scopo della funzione.

Fondamentalmente, se una funzione e' stata definita prima di essere usata

(call) allora e' possibile semplicemente usare la funzione. Nel caso

contrario, e' obbligatorio dichiarare la funzione; la dichiarazione

stabilisce in modo semplice il ritorno della funzione ed il tipo dei

parametri utilizzati da questa.

E' buona norma (e solitamente viene fatto) dichiarare tutte le funzioni

all'inizio del programma, sebbene non sia strettamente necessario.

Per dichiarare un prototipo di funzione bisogna semplicemente stabilire il

ritorno della funzione, il nome della funzione e tra le parentesi elencare

il tipo dei parametri nell'ordine in cui compaiono nella definizione di

funzione.

Ad esempio:

int strlen(char[]);

Questo dichiara che una funzione di nome "strlen" ritorna un valore integer

ed accetta una singola stringa come parametro.

Nota: le funzioni e le variabili possono essere dichiarate sulla stessa

linea di codice sorgente. Questa procedura era molto piu' diffusa 25

nei giorni del pre-ANSI C; da allora le funzioni solitamente vengono

dichiarate separatamente all'inizio del programma. La prima procedura

risulta ancora perfettamente valida, purche' venga rispettato l'ordine

in cui gli oggetti compaiono nella definizione della funzione.

Ad esempio:

int length, strlen(char[]);

dove "length" e' una variabile, e "strlen" e' la funzione (come

nell'esempio precedente).

------------------------------------------------------------------------------

11. Ulteriori tipi di dati

------------------------------------------------------------------------------

------------------------------------------------------------------------------

11.01. Strutture

------------------------------------------------------------------------------

Le strutture in C sono simili ai records in Pascal.

Ad esempio:

struct gun

{ char name[50];

int magazinesize;

float calibre;

};

struct gun arnies;

Viene cosi definita una nuova struttura gun e definita arnies di tipo struct

gun.

Nota: "gun" e' un'etichetta (tag) per la struttura che serve come

abbreviazione per le successive dichiarazioni. E' necessario solamente

dichiarare "struct gun" e il corpo della struttura e' implicito come

viene fatto per creare la struttura "arnies"; il tag e' opzionale.

Le variabili possono anche essere dichiarate tra "}" e ";" di una

dichiarazione di struttura; ad esempio:

struct gun

{ char name[50];

int magazinesize;

float calibre;

} arnies;

che equivale al precedente esempio di definizione di una nuova variabile 26

strutturata di nome "arnies".

Una struttura puo' essere pre-inizializzata al momento della dichiarazione:

struct gun arnies={"Uzi",30,7};

Per accedere ai membri (o campi) di una struttura il C fornisce l'operatore

".".

Ad esempio:

arnies.magazinesize=100;

Anche con le strutture si puo utilizzare typedef. La seguente istruzione

crea un nuovo tipo "agun" che e' di tipo "struct gun" e puo' essere

inizializzato come al solito:

typedef struct gun

{ char name[50];

int magazinesize;

float calibre;

} agun;

agun arnies= {"Uzi",30,7};

Qui "gun" e' ancora un'etichetta della struttura ed e' opzionale; agun e'

un nuovo tipo di dato e arnies e' una variabile di tipo agun (che e' una

struttura).

Il C permette anche la definizione array di strutture:

agun arniesguns[1000];

che si possono utilizzare nel seguente modo:

arniesguns[50].calibre=5;

dove il campo "calibre" del record 50 di arniesguns assume valore 5;

itscalibre= arniesguns[50].calibre;

dove viene assegnato alla variabile itscalibre il valore del campo

calibre del record 50 di arniesguns.

------------------------------------------------------------------------------

11.02. Unioni

------------------------------------------------------------------------------

Un'unione e' una variabile che puo' tenere (in momenti diversi) oggetti di

diversa dimensione e tipo.

Il C usa l'istruzione "union" per creare unioni; ad esempio: 27

union number

{ short shortnumber;

long longnumber;

double doublenumber;

} anumber

In questo modo viene definita un'unione chiamata number e un riferimento ad

essa chiamato anumber. "number" e' un'etichetta (tag) di unione e funziona

alla stessa maniera del tag delle strutture.

Si accede ai membri dell'unione come per i membri delle strutture.

Ad esempio:

printf("%ld\n",anumber.longnumber);

Questa istruzione visualizza semplicemente il valore di longnumber.

Quando il compilatore C alloca la memoria per le unioni, riserva sempre lo

spazio necessario per il membro piu' grande (nell'esempio sopra riportato,

sono 8 bytes per il tipo "double").

Per fare si che il programma possa tenere traccia del tipo della variabile

di unione usata in un determinato momento, e' diffusa l'abitudine di avere

una struttura (con registrate le unioni) e una variabile che indica il

tipo dell'unione.

Ad esempio:

typedef struct

{int maxpassengers;

} jet;

typedef struct

{int liftcapacity;

} helicopter;

typedef struct

{int maxpayload;

} cargoplane;

typedef struct

{jet jetu;

helicopter helicopteru;

cargoplane cargoplaneu;

} aircraft;

typedef struct

{aircrafttype kind;

int speed;

aircraft description; 28

} an_aircraft;

Questo esempio definisce una unione di base aircraft, che puo' essere sia

jet, helicopter o cargoplane.

Nella struttura an_aircraft c'e' un tipo di elemento che indica quale

struttura e' contenuta in quel momento.

------------------------------------------------------------------------------

11.03. Type-casting

------------------------------------------------------------------------------

Il C e' uno dei pochi linguaggi che permette la coercizione, e cioe'

permette di forzare una variabile di un tipo ad essere una variabile di

un'altro tipo utilizzando l'operatore "()".

Ad esempio:

int numerointero;

int numerointero2=10;

float numerofloat=6.34;

float numerofloat2;

char lettera='A';

numerointero=(int)numerofloat; /* assegna il valore 6 (parte intera) */

numerointero=(int)lettera; /* assegna il valore 65 (codice ASCII)*/

numerofloat2=(float)numerointero2 /* assegna 10.0 (valore float) */

Alcuni type-casting vengono fatti automaticamente, principalmente in

relazione alle capacita' dei numeri integer.

E' buona regola eseguire il type-casting tutte le volte che si e' in dubbio

sulla corrispondenza degli operatori nelle assegnazioni.

Altro uso che ne viene fatto e' all'interno delle divisioni, per assicurarsi

che dia il risultato voluto; se abbiamo due numeri integer come operatori e

vogliamo che il risultato sia un float, allora dovremo agire come segue:

int intnumber,anotherint;

float floatnumber;

floatnumber=(float)intnumber/(float)anotherint

Questa operazione assicura una divisione in floating-point.

------------------------------------------------------------------------------

11.04. Enumerated Types

------------------------------------------------------------------------------

Gli enumerated types contengono un elenco di costanti che possono essere

indirizzate con valori integer.

Per dichiarare tali tipi si utilizza "enum"; vengono dichiarati i tipi e le 29

variabili come nell'esempio che segue:

enum colori {rosso, giallo, verde, blu} pennarello;

enum giorni (lun,mar,mer,gio,ven,sab,dom} settimana;

enum colori pulsante, nastro;

In tale esempio viene dichiarato colori come enumerated type e la variabile

pennarello con 4 valori accettabili definiti, mentre la variabile settimana

di tipo giorni ha 7 valori accettabili definiti. Le variabili pulsante e

nastro sono di tipo colori.

Ogni item nell'elenco di valori accettabili e' detto enumeration constant.

Il C mappa ogni enumeration constant ad un'intero, per cui e' ad esempio

possible scrivere:

settimana=verde;

che come risultato fa si che settimana abbia valore 2, perche' di default a

ogni membro dell'elenco di variabili e' assegnato un valore incrementale

partendo da 0 per il primo valore (come gia' visto per gli array).

E' possibile definire valori diversi agli elementi:

enum colori {rosso=10, giallo=30, verde, blu=giallo};

Un ulteriore esempio relativo all'assegnazione di valori diversi e' il

seguente:

enum escapes {bell='\a',

backspace='\b', tab='\t',

newline='\n', vtab='\v',

return='\r'};

E' anche possibile annullare il valore iniziale 0:

enum months (jan=1,feb,mar,...dec);

dove e' implicito che febbraio=2, marzo=3 e cosi' via.

------------------------------------------------------------------------------

11.05. Variabili statiche

------------------------------------------------------------------------------

Una variabile statica e' locale ad una particolare funzione. E'

inizializzata una sola volta, la prima volta che tale funzione viene

chiamata e il suo valore resta inalterato quando si esce dalla funzione, per

cui quando si richiama nuovamente la funzione tale variabile ha ancora il

valore assegnatogli precedentemente.

Per definire statica una variabile e' sufficente anteporre la parola static

alla dichiarazione della variabile.

Ad esempio: 30

void stat(); /* prototype function */

main()

{int i;

for (i=0; i<5; ++i)

stat()

}

stat()

{int auto_var=0;

static int static_var=0;

printf("auto=%d, static=%d \n", auto_var, static_var);

++ auto_var;

++ static_var;

}

Il cui output sara':

auto_var=0, static_var=0

auto_var=0, static_var=1

auto_var=0, static_var=2

auto_var=0, static_var=3

auto_var=0, static_var=4

La variabile auto_var viene creata ogni volta, mentre la variabile

static_var e' creata una sola volta ed il suo valore memorizzato.

------------------------------------------------------------------------------

12. Errori comuni in C

------------------------------------------------------------------------------

Prima di procedere con l'analisi delle caratteristiche piu' avanzate del C,

e' importante analizzare le cause dei possibili errori nei programmi C.

Alcuni di questi errori vengono fatti facilmente, alcuni altri vengono fatti

nel caso in cui si conosca un linguaggio con una sintassi a volte simile al

C (come ad esempio il Pascal).

------------------------------------------------------------------------------

12.01. Assegnazione (=) al posto di confronto (==)

------------------------------------------------------------------------------

Il C utilizza l'assegnazione per valore, cosi':

if (a=b)

e' sintatticamente corretta.

Nota: b e' assegnato ad a,

l'espressione a=b prende il valore di b, 31

if (a=b) => if (b),

True se b != 0, False se b == 0.

------------------------------------------------------------------------------

12.02. Passaggio dell'indirizzo di puntatori

------------------------------------------------------------------------------

Vedremo meglio di cosa si tratta nel prossimo capitolo, anche se ne abbiamo

gia' accennato in relazione al scanf(). E' comunque fondamentale ricordare

di mettere la "&" nella funzione.

------------------------------------------------------------------------------

12.03. Mancanza di () per una funzione

------------------------------------------------------------------------------

Arrivando da precedenti esperienze con altri linguaggi, spesso ci si

dimentica di mettere () dopo una funzione; e' necessario farlo, anche se non

ci sono parametri passati alla funzione stessa.

------------------------------------------------------------------------------

12.04. Indici di array

------------------------------------------------------------------------------

E' importante ricordare che gli array in C sono dichiarati in maniera

diversa: gli array multidimensionali vengono dichiarati alla stessa maniera

di quelli semplici, ma con l'elenco dei valori massimi associati ad ogni

dimensione; la notazione che viene utilizzata per gli array

multidimensionali e' la seguente:

[][] ...

dove tra le [] va specificata la dimensione massima per ognuna delle

dimensioni dell'array stesso.

Un vettore di "n" elementi ha un intervallo indice che va da 0 a "n-1".

------------------------------------------------------------------------------

12.05. Array di caratteri e puntatori

------------------------------------------------------------------------------

Verranno trattati in maniera dettagliata nel prossimo capitolo.

------------------------------------------------------------------------------

12.06. C e' case-sensitive

------------------------------------------------------------------------------

Una regola fondamentale per l'utilizzo del C e' quella di ricordare che le

lettere maiuscole e quelle minuscole vengono trattate come fossero caratteri

diversi.

------------------------------------------------------------------------------ 32

12.07. ";" chiude ogni istruzione

------------------------------------------------------------------------------

E' facile dimenticarsene, ma il compilatore riscontrera' questa mancanza e

la segnalera'.

------------------------------------------------------------------------------

13. Puntatori

------------------------------------------------------------------------------

I puntatori sono una delle piu' importanti caratteristiche del C. Se non si

e' in grado di usare i puntatori in maniera appropriata, non si riusciranno

a sfruttare completamente la potenza e la flessibilita' che il C permette;

infatti, il segreto del linguaggio C sta proprio nel modo in cui utilizza i

puntatori.

Il C usa molto i puntatori. Perche'?

- e' l'unico modo per esprimere alcune operazioni;

- produce codici sorgenti compatti ed efficienti;

- rappresenta uno strumento molto efficace.

Il C utilizza molto i puntatori in maniera esplicita con:

- vettori;

- strutture;

- funzioni.

Nota: i puntatori probabilmente sono la parte del C piu' difficile da

capire; le implementazioni in C sono leggermente diverse rispetto

agli altri linguaggi.

------------------------------------------------------------------------------

13.01. Cos'e' un puntatore

------------------------------------------------------------------------------

Un puntatore e' un tipo di dato, una variabile che contiene l'indirizzo

in memoria di un'altra variabile. Si possono avere puntatori a qualsiasi

tipo di variabile.

La dichiarazione di un puntatore include il tipo dell'oggetto a cui il

puntatore punta.

In C ogni variabile ha due tipi di valori: una locazione e un valore

contenuto in quella locazione.

L' operatore & (operatore unario, o monadico) fornisce l'indirizzo di una

variabile.

L' operatore * (operatore indiretto, o non referenziato) da' il contenuto

dell'oggetto a cui punta un puntatore.

Per dichiarare un puntatore ad una variabile, l'istruzione e': 33

int *pointer;

Nota: e' obbligatorio associare un puntatore ad un tipo particolare; per

esempio, non e' possibile assegnare l'indirizzo di uno short int ad

un long int.

Consideriamo gli effetti del seguente codice:

int *pointer; /* dichiara pointer come un puntatore a int */

int x=1,y=2;

(1) pointer= &x; /* assegna a pointer l'indirizzo di x */

(2) y=*pointer; /* assegna a y il contenuto di pointer */

(3) x=pointer /* assegna ad x l'indirizzo contenuto in pointer */

(4) *pointer=3; /* assegna al contenuto di pointer il valore 3 */

Vale la pena considerare cosa succede al "livello macchina" in memoria per

capire completamente come funzionano i puntatori.

Supponiamo che la variabile x si trovi nella locazione di memoria 100, y

nella 200 e pointer nella 1000 (ricordiamo che pointer e' una variabile a

tutti gli effetti, e cosi' il suo valore necessita di essere memorizzato da

qualche parte; e' la caratteristica del valore dei puntatori che risulta

nuova).

L'istruzione (1) fa si che pointer punti alla locazione di memoria 100

(quella di x).

La (2) fa si che y assuma valore 1 (il valore di x).

La (3) fa si che x assuma valore 100 (cioe' il valore di pointer).

La (4) fa si che il valore del contenuto di pointer sia 3 (quindi x=3).

Notate che le assegnazioni x=1 e y=2 ovviamente caricano questi valori nelle

variabili; pointer e' dichiarato come puntatore ad un intero e vi e'

assegnato l'indirizzo di x (&x), cosi' pointer verra' caricato con il valore

100.

Successivamente, y prende l'assegnazione del contenuto di pointer. In questo

esempio, pointer punta attualmente alla locazione di memoria 100 (la

locazione di x). Cosi' ad y viene assegnato il valore di x (che' e' 1).

Abbiamo gia' visto che il C non e' molto meticoloso riguardo

all'assegnazione di valori di tipo differente. Cosi' e' perfettamente legale

(sebbene non sia comune a tutti) assegnare il valore corrente di pointer ad

x; in questo momento il valore di pointer e' 100.

Alla fine possiamo assegnare un valore al contenuto di pointer (*ip).

Quindi in merito ai puntatori possiamo avre tre possibili valori:

pointer contenuto o valore della variabile pointer

(indirizzo della locazione di memoria a cui punta)

&pointer indirizzo fisico della locazione di memoria del puntatore

*pointer contenuto della locazione di memoria a cui punta

NB. Quando un puntatore viene dichiarato non punta a nulla!

Per poterlo utilizzare deve puntare a qualcosa! 34

E' infatti un errore comune non assegnare un indirizzo di memoria a un

puntatore prima di usarlo.

Cosi':

int *ip;

*ip=100;

generera' un errore (crash di programma).

L'utilizzo corretto e' il seguente:

int *ip;

int x;

ip=&x;

*ip=100;

Un metodo comune per ovviare al problema dell'assegnazione

dell'indirizzo e' quello di utilizzare la funzione di libreria standard

malloc(), che permette un'allocazione dinamica della memoria; e'

definita come char *malloc(int number_of_bytes).

Ad esempio:

int *p;

p = (int *) malloc(100);

oppure:

p= (int *) malloc(100*sizeof(int))

Si possono fare operazioni aritmetiche intere con i puntatori:

float *flp, *flq;

*flp=*flp+10;

++*flp;

(*flp)++;

flq=flp;

Nota: un puntatore ad una variabile di qualsiasi tipo e' un indirizzo in

memoria (il quale e' un indirizzo intero). Un puntatore per

definizione NON e' un intero.

La ragione per cui associamo un puntatore ad un tipo di dato e' quella per

cui e' possibile riconoscere quanti bytes contiene il dato. Quando si

incrementa un puntatore si cresce il puntatore di un "blocco" di

memoria.

Cosi' per un puntatore a char ++ch_ptr aggiunge 1 byte all'indirizzo,

per un intero o un float ++ip aggiunge 4 byte all'indirizzo.

Consideriamo una variabile float (fl) ed un puntatore ad un float (flp);

ricordiamo che ad un float corrispondono 4 bytes.

Assumiamo che flp punti ad fl; se poi incrementiamo il puntatore (++flp), 35

questo si sposta dalla posizione a cui puntava originariamente di 4 bytes in

avanti, e puntera' quindi al float successivo. D'altra parte, se aggiungiamo

2 al puntatore (flp+2), questo si sposta di due posizioni float, cioe' di 8

bytes.

------------------------------------------------------------------------------

13.02. Puntatori e funzioni

------------------------------------------------------------------------------

Esamineremo ora la stretta relazione tra i puntatori e le altre parti

principali del C, incominciando con le funzioni.

Il C passa argomenti alle funzioni per valore.

Ci sono molti casi in cui possiamo avere la necessita' di variare un

argomento passato in una funzione e ricevere di ritorno il nuovo valore una

volta che la funzione e' terminata. Gli altri linguaggi sono in grado di

fare questa operazione internamente (come ad esempio i parametri "var" in

PASCAL), mentre il C utilizza esplicitamente i puntatori per farlo.

Il miglior metodo per comprenderne il funzionamento e' quello di fare un

esempio in cui dobbiamo essere in grado di ricevere parametri cambiati.

Proviamo ad esempio a trovare un modo per effettuare uno scambio di

variabili (swap). La consueta chiamata di funzione:

swap(a, b)

non funziona. I puntatori forniscono quindi la possibile soluzione: passare

l'indirizzo delle variabili alla funzione ed accedere all'indirizzo della

funzione stessa. Cosi' la chiamata di funzione nel nostro programma potra'

apparire come segue:

swap(&a, &b)

Il codice sorgente della funzione swap e' abbastanza lineare:

void swap(int *px, int *py)

{ int temp;

temp=*px; /* contenuto di pointer */

*px=*py;

*py=temp;

}

main()

{ int a=10,b=20;

...

swap(&a,&b);

...

} 36

Possiamo ritornare un puntatore dalle funzioni. Un esempio frequente e'

quello di ritornare strutture:

typedef struct {float x,y,z;} COORD;

main()

{COORD p1, *coord_fn();/*dichiara fn come return pointer di tipo COORD*/

...

p1=*coord_fn(...); /*assegna il contenuto dell'indir. restituito*/

...

}

COORD *coord_fn(...)

{COORD p;

...

p=...; /* assegna un valore alla struttura */

return &p; /* ritorna l'indirizzo di p */

}

In questo esempio ritorniamo un puntatore il cui contenuto e' immediatamente

tradotto in una variabile. Dobbiamo pero' farlo contestualmente all'uscita

del valore dalla funzione, poiche' la variabile a cui puntiamo e' locale

alla funzione stessa che e' appena terminata. Questo significa che lo spazio

dell'indirizzo si rende subito libero e puo' essere sovrascritto.

------------------------------------------------------------------------------

13.03. Puntatori e array

------------------------------------------------------------------------------

Un'array di elementi puo' essere pensato come disposto in un insieme di

locazioni di memoria consecutive.

Consideriamo il seguente esempio:

int a[10],x;

int *ptr;

ptr=&a[0]; /* ptr punta all'indirizzo di a[0] */

x=*ptr; /* x = contenuto di ptr (in questo caso, a[0]) */

A questo punto potremo incrementare ptr con successive istruzioni

++ptr

ma potremo anche avere

(ptr + i)

che e' equivalente ad a[i], con i=0,1,2,3...9 .

Quindi per raggiungere un elemento qualsiasi dell'array utilizzando un

puntatore, l'istruzione puo' essere:

ptr + i = a[i]

Attenzione: non c'e alcun limite di controllo per array e pointer, cosi' e'

facilmente possibile oltrepassare la memoria prevista per un 37

array e sovrascrivere altre cose.

Il C comunque e' molto piu' sottile nei propri collegamenti tra vettori e

puntatori.

Ad esempio e' possibile scrivere

ptr=a;

invece di

ptr=&a[0];

ed a[i] puo' essere scritto come

*(a+i)

cioe' &a[i] = a+i.

Inoltre si possono esprimere puntatori nel seguente modo

ptr[i] = *(ptr+i)

Va comunque ricordato che puntatori e vettori sono diversi:

- un puntatore e' una variabile, per cui possiamo scrivere:

ptr=a ed ptr++

- un array non e' una variabile quindi:

a=ptr ed a++ sono istruzioni non valide

Ora siamo in grado di comprendere in che maniera gli array vengono passati

alle funzioni. Quando un array e' passato ad una funzione, quello che viene

effettivamente passato e' la locazione in memoria del suo elemento iniziale.

Cosi':

strlen(s)=strlen(&s[0])

Questo e' il motivo per cui dichiariamo la funzione:

int strlen(char s[]);

Una dichiarazione equivalente e':

int strlen(char *s);

poiche' char s[]=char *s.

strlen() e' una funzione della standard library che ritorna la lunghezza di

una stringa.

Vediamo ora come possiamo scrivere una funzione:

int strlen(char *s)

{ char *p=s;

while (*p != '\0);

p++

return p-s;

} 38

Ora scriviamo una funzione per copiare una stringa in un'altra stringa.

strcpy() e' una funzione della standard library che compie questa

operazione.

void strcpy(char *s, char *t)

{ while ((*s++ = *t++) != '\0);i }

In questo esempio vengono utilizzati puntatori ed assegnazioni per valore.

E' interessante notare l'utilizzo della frase "null" con while.

------------------------------------------------------------------------------

13.04. Array di puntatori

------------------------------------------------------------------------------

Visto che i puntatori sono variabili, si possono avere array di puntatori.

Ad esempio:

main(argc,argv)

int argc;

char *argv[];

{

}

utilizzato per passare argomenti dalla linea di comando.

Gli array di puntatori sono una rappresentazione di dati che puo' essere

convenientemente utilizzata per far fronte in maniera efficiente ai problemi

di trattamento di linee di testo con lunghezza variabile (ad esempio, nel

caso dell'ordinamento); va ricordato che un testo non puo' essere spostato o

confrontato in una singola operazione.

E' possibile risolvere questi problemi con le seguenti operazioni:

- memorizzare le linee end-to-end in un unico array char (\n va utilizzato

come separatore delle linee);

- memorizzare i puntatori in un diverso array dove ogni puntatore punta al

primo carattere di ogni linea nuova;

- confrontare due linee utilizzando la funzione strcmp() della libreria

standard;

- se due linee non sono ordinate, swappare il puntatore nell'array dei

puntatori (non in quello del testo).

Questa procedura elimina gli aspetti complicati della gestione della

memorizzazione e la dispendiosita' dell'operazione di spostamento di linee

di testo.

------------------------------------------------------------------------------

13.05. Array multidimensionali e puntatori

------------------------------------------------------------------------------ 39

In C dobbiamo pensare agli array multidimensionali in un modo diverso: un

array a due dimensioni e' un array monodimensionale i cui elementi sono

a loro volta degli array.

Gli elementi degli array vengono memorizzati riga per riga.

Avevamo visto che per passare un array a una funzione si deve specificare il

numero di colonne, mentre non e' necessario specificare il numero di righe.

La ragione di questo e' dovuta ai puntatori, in quanto il C deve sapere il

numero di colonne per saltare di riga in riga in memoria.

Si consideri ad esempio di passare l'array a[5][35] ad una funzione f;

si puo' dichiarare:

f(int a[][35]){...}

oppure

f(int(*a)[35]){...}

Necessitano le parentesi per (*a) perche' il vettore abbia una precedenza

maggiore rispetto ad *.

Si noti cosi' la differenza tra:

int (*a)[35]; dichiara un puntatore ad un array di 35 int

int *a[35]; dichiara un array di 35 puntatori a int

Consideriamo ora la sottile differenza tra puntatori ed array.

Ad esempio:

char *name[10];

char Aname[10][20];

in C e' possibile dichiarare legalmente sia name[3][4] che Aname[3][4].

Comunque:

- "Aname" e' un vero array di char a due dimensioni, con 200 elementi;

- l'accesso agli elementi in memoria viene attuato tramite l'istruzione

20*riga+colonna+indirizzo_base;

- "name" ha 10 elementi pointer (quindi e' un array di puntatori).

Se ogni puntatore nel vettore "name" e' settato per puntare ad un array di

20 elementi, solo in quel caso verranno riservati 200 chars (+ 10 elementi).

Il vantaggio di una dichiarazione fatta nel secondo modo e' quello che ogni

pointer puo' puntare a vettori di lunghezza diversa.

Un tipico esempio di puntatore ad un array sono le stringhe.

Consideriamo un esempio:

#include <stdio.h>

main()

{ 40

char *s[5];

*s="ciao";

printf("%s\n",*s);

}

------------------------------------------------------------------------------

13.06. Inizializzazione statica degli array di puntatori

------------------------------------------------------------------------------

L'inizializzazione degli array di puntatori e' una delle ideali applicazioni

per un array interno statico.

Esempio:

some_fn()

{static char *months = { "no month", "jan", "feb", ...};

}

Un array statico riserva un bit di memoria privato permanente.

------------------------------------------------------------------------------

13.07. Puntatori e strutture

------------------------------------------------------------------------------

Si tratta di strutture abbastanza lineari e facilmente definibili.

Consideriamo ad esempio:

struct COORD {float x,y,z;}pt;

struct COORD *pt_ptr;

pt_ptr=&pt; /* assegna un puntatore a pt*/

L'operatore "->" permette l'accesso a un membro della struttura puntata

dal puntatore, cioe':

pt_ptr->x=1.0;

pt_ptr->y=pt_ptr->y - 3.1;

mentre avevamo visto che l'accesso ai membri di una struttra era dato

dall'operatore "." , e cioe':

pt.x=2.73;

Un esempio puo' essere costituito dalle Linked Lists:

typedef struct { int value;

ELEMENT *next;

} ELEMENT;

ELEMENT n1, n2; 41

n1.next = &n2;

con cui viene rappresentato il link tra due nodi (n1 ed n2) della struttura

ELEMENT; all'interno di ogni nodo di quest'ultima, oltre al valore c'e' un

puntatore "next" che viene settato all'indirizzo del nodo successivo.

E' importante notare che possiamo dichiarare "next" solo come un puntatore

ad ELEMENT; non e' possibile avere "next" come elemento del tipo della

variabile, poiche' questo creerebbe una definizione ricorsiva che non e'

permessa.

E' invece possibile settare una referenza del pointer poiche' vengono messi

da parte 4 bytes per ogni puntatore.

Nel prossimo capitolo verra' analizzato ulteriormente questo problema.

------------------------------------------------------------------------------

13.08. Le "trappole" piu' comuni dei puntatori

------------------------------------------------------------------------------

Vogliamo ora puntualizzare due errori solitamente riscontrati nell'utilizzo

dei puntatori.

------------------------------------------------------------------------------

13.08.01. Non assegnare un puntatore ad un indirizzo di memoria prima

di utilizzarlo

------------------------------------------------------------------------------

Un esempio di questo errore:

int *x;

*x=100;

E' necessario pero' dichiarare una locazione fisica, quindi avremo:

int *x;

int y;

x=&y;

*x=100;

Puo' essere difficile individuare questo tipo di errore, poiche' nessun

compilatore lo segnala. Comunque "x" potrebbe anche avere degli indirizzi

random come inizializzazione.

------------------------------------------------------------------------------

13.08.02. Assegnazione indiretta illegale

------------------------------------------------------------------------------

Supponiamo di avere una funzione malloc() che prova ad allocare

dinamicamente la memoria (in fase di esecuzione) e ritorna un puntatore al

blocco di memoria richiesto nel caso in cui termini con successo, oppure un

puntatore nullo nell'altro caso. 42


PAGINE

92

PESO

220.37 KB

AUTORE

Sara F

PUBBLICATO

+1 anno fa


DETTAGLI
Corso di laurea: Corso di laurea in ingegneria informatica
SSD:
Università: Firenze - Unifi
A.A.: 2013-2014

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher Sara F di informazioni apprese con la frequenza delle lezioni di Fondamenti di Informatica I e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Firenze - Unifi o del prof Vicario Enrico.

Acquista con carta o conto PayPal

Scarica il file tutte le volte che vuoi

Paga con un conto PayPal per usufruire della garanzia Soddisfatto o rimborsato

Recensioni
Ti è piaciuto questo appunto? Valutalo!

Altri appunti di Fondamenti di informatica i

Fondamenti di Informatica I – Manuale Linguaggio C
Dispensa
Fondamenti di Informatica I - Tutorial linguaggio c
Appunto
Fondamamenti di Informatica I - Programma
Dispensa
Appunti di Geometria e Algebra Lineare
Appunto