Introduzione al linguaggio - di.unito.itdeligu/didattica/algo/Linguaggio_C.pdf · Deitel & Deitel,...

Post on 19-Feb-2019

251 views 3 download

Transcript of Introduzione al linguaggio - di.unito.itdeligu/didattica/algo/Linguaggio_C.pdf · Deitel & Deitel,...

CCIntroduzione al linguaggio

Ugo de’Liguoro

Storia e bibliografia

Il C evolve dal BCPL e dal B. E’stato implementato da DennisRitchie sul sistema operativo UNIX per il PDP-11.

Nel 1973 Ritchie e Thompson hanno riscritto il nucleo di UNIXin C; da allora il compilatore C è nativo nel sistema UNIX, etutte le estensioni e le utility del sistema sono scritte in C.

Nel 1983 un comitato ha definito una versione non ambigua dellinguaggio tuttoggi in uso: l’ANSI C.

B. W. Kernighan, D. M. Ritchie, The C programming language(trad. it. Linguaggio C, Jackson Libri)

Deitel & Deitel, C Corso completo di programmazione, Apogeo.

Il C è un linguaggio imperativo:

• strutturato, ma senza gerarchia di funzioni• tipato, ma con type casting• consente operazioni a basso livello

Il C ha dunque solo alcune caratteristiche dei linguaggi dialto livello, ma in realtà è piuttosto vicino alla macchina,adatto per programmare anche a livello molto basso,controllando indirizzi e singoli bit.

/* Programma per il calcolo del MCD */

#include <stdio.h>

void main(void){ int a, b;

a = b = 0; while (((a == 0) && (b == 0)) || (a < 0) || (b < 0)) { printf("Inserire due interi positivi non entrambi nulli\n"); scanf("%d %d", &a, &b); } printf("MCD(%d,%d) = %d\n", a, b, MCD(a,b));}

int MCD (int a, int b){ int c;

if (b == 0){c = b; b = a; a = c;} while (b > 0) { c = b; b = a % b; a = c; } return a;}

Un esempio di programma in C

Il file “stdio.h”

Il C non possiede primitive per l’I/O; queste sono definite nelmodulo “Standard I/O”; le dichiarazioni dei nomi e dei tipi dellefunzioni sono contenute in un file di intestazione (header, .h) cheviene importato dal comando per il preprocessore:

#include <stdio.h>

Tra le funzioni definite in stdio.h vi sono:

• printf funzione di stampa (PASCAL: write)

• scanf funzione di lettura (PASCAL: read)

# è il simbolo con cuicominciano i comandidel preprocessore

i comandi del preprocessorenon sono terminati da ;

La funzione “main”La funzione main è la parte principale del programma: la suaesecuzione coincide con quella del programma.

void main (void)

Indica che la funzione mainnon ritorna (in questo caso)alcun valore

Indica che la funzione mainnon ha (in questo caso)argomenti

La dichiarazione delle variabili;Le variabili si dichiarano indicando prima il tipo e poi il nomedegli identificatori:

int a, b; (PASCAL: var a, b: integer;)

Se una variabile viene dichiarata all’interno di un blocco:

{...}

allora è locale (visibile in quel blocco); altrimenti è globale.Nota: le variabili dichiarate nel blocco che segue il main sonolocali nel main, non globali.

Le strutture di controllo “while” e “if”La sintassi del while e dell’if è simile al Pascal, tuttavia conqualche differenza:

while(condizione) istruzione;

if (condizione) istruzione_1;else

istruzione_2;

while condizione do istruzione ;

if condizione then istruzione_1else

istruzione_2 ;

C Pascal

Il comando “return”Il C non ha la distinzione tra funzioni e procedure: tutti isottoprogrammi parametrizzati sono funzioni.

Tuttavia tra le funzioni vi sono quelle che ritornano un valore(e che si possono dunque impiegare come espressioni):

return espressione ;

Nota: l’espressione deve avere lo stesso tipo del valore di ritornodella funzione, ad esempio la funzione mcd deve ritornare interi:

int mcd (…)

Le funzioni che non ritornano valori hanno tipo void.

Il comando return interrompe il flusso nella funzione.

Struttura di un (semplice) programma C/* commenti: nome del programma, sue funzionalità ecc. */

istruzioni per il preprocessore

dichiarazione di tipi, variabili, costanti

tipo di ritorno main (elenco argomenti){

dichiarazione variabili localisequenza di istruzioni

}

tipo di ritorno funzione_1 (elenco argomenti){

dichiarazione variabili localisequenza di istruzioni

} ….

tipo di ritorno funzione_n (elenco argomenti){ … }

begin

end

blocco

programma principale: main

sottoprogrammi

Le funzioniSintassi per la dichiarazione di una funzione:

tipo_valore_ritornato nome_funzione (lista_argomenti){

dichirazione variabili esterne ;definizione variabili automatiche ;

corpo_della_funzione ;}

lista_argomenti ha la forma

tipo1 var1 , … , tipon varn

Funzioni: passaggio di parametri e valori diritorno

• I paremetri in C sono sempre passati per valore.

• Il passaggio per riferimento si simula passando eseplicitamente l'indirizzo delparametro attuale: quindi il parametro formale sarà di tipo puntatore.

• Non è possibile passare/ritornare parametri di tipo non elementare se nonattraverso puntatori (con l'eccezione, per l'ANSI-C, delle struttture).

• Il valore della funzione viene ritornato attraverso l'istruzione return, la cuiesecuzione termina, comunque, l'esecuzione del corpo della funzione.

• In C non esiste distinzione tra funzioni e procedure: semplicemente le secondenon contegono l'istruzione return; in tal caso (ANSI-C) il tipo di ritornodella funzione è void.

La ricorsioneIl C è un linguaggio ricorsivo che permette definizioni implicite:

int fact (int n){ if (n == 0) return 1;

return n * fact(n-1);};

In caso di mutua ricorsione la dichiarazione delle funzioni chiamate dovrebbeprecedere la chiamata (il compilatore accetta comunque la definizione, masegnala un WARNING). E’ buona pratica dichiarare prima tutte le funzionimutuamente ricorsive :

void p (int n); void q (int n);

void p (int n){

if (n > 0) {...q(n-1);}}

void q (int n){

if (n > 0) {...p(n-1);}

}

Il preprocessorePrima della compilazione il codice di un programma C viene trattato da unpreprocessore, il quale ha il compito di modificarlo prima della compilazionevera e propria.

L’azione del preprocessore è determinata dall’uso di comandi dei quali i piùcomuni sono #include e #define.

Esempi:

#define PI = 3.14159

ha l’effetto di sostituire la stringa “PI”con la stringa “3.14159” ovunque nelcodice (che la tratterà correttamente come una costante numerica con virgola);

#include “myfile”

causa la copia del contenuto del file myfile nel punto esatto in cui apparel’istruzione #include.

obsoleta: meglio usare const

Struttura di un programma su più file (1)Normalmente un programma C si articola in diversi file, i quali concorrono allaformazione del codice di un unico programma in due modi:1. Per inclusione in fase di preprocessing.2. Nella fase di linking.

Per favorire la compilazione separata è opportuno suddividere ciascun moduloin due file:

myfile_header.h myfile_code.c

In myfile_header.h saranno contenute le dichiarazioni di tipi, variabili efunzioni che si desidera siano visibili agli altri moduli (ed al modulo checontiene il main): si tratta di una interfaccia.

In myfile_code.c vi saranno le definizioni (quindi il codice) delle funzionipubbliche e le dichiarazioni di tipi, variabili e funzioni private, ossia conosciutesolo all’interno del modulo.

Struttura di un programma su più file (2)

int numero_di_serie:

int serie(void);

...

#include Serie.h

int serie(void) {...};

...

Serie.c

Serie.h

Struttura di un programma su più file (3)

Header_generale.h

Header1.h

Modulo1.c Main.c

Problema. Poiché l’inclusione consiste nella copia del codice nel punto in cuisi trova il comando #include, le due inclusioni nel file Main.c provocanoun messaggio di errore causato dalla ridefinizione degli identificatori nel fileHeader_generale.h

Header2.h

Modulo2.c

Struttura di un programma su più file (4)

Per ovviare al problema di possibili ridefinizioni di identificatori causati dainclusioni multiple si può usare il comando del preprocessore #ifindef :

#ifindef identificatorecodice

#endif

Il suo effetto è quello di includere il codice racchiuso tra i due comandi#ifindef e #endif solo se l’identificatore non è stato definito da uncomando #define.

Allora la struttura di un file che contenga delle inclusioni multiple dovrebbeessere:

#ifindef HEADER /* id. del file */

#define HEADER

/* codice del file */

#endif

Tipi di base

char carattereint interofloat virgola mobile, singola precisionedouble virgola mobile, doppia precisionevoid tipo di ritorno di una procedura,

tipo generico di puntatore

Modificatori:

unsigned (unsigned int)short (short int)long (long int, long double)

Dichiarazione delle variabili

tipo elenco_variabili ;

int i, j, l;short int si;unsigned int ui;double bilancio, profitto, perdita;

Dichiarazioni e inizializzazioni:

char car = 'a';int primo = 0;float importo = 1230000.0;

definizione del formato delledichiarazioni delle variabili

La virgola (punto decimale)si aggiunge per indicareche la costante 1230000deve essere memorizzata invirgola mobile.

Classi di memorizzazioneLe dichiarazioni delle variabili e delle funzioni le classificasecondo due dimensioni ortogonali:

• Visibilità

• globali: sono visibili a tutte le funzioni del programma;

• locali: visibili solo all’interno del blocco didichiarazione.

• Persistenza

• statiche: restano allocate per tutta la duratadell’esecuzione del programma;

• automatiche: restano allocate solo durante l’esecuzionedel blocco in cui sono dichiarate.

Tre classi di memorizzazionestatiche automatiche

globali ✔

locali ✔ ✔

Variabili static locali: sono variabili automatiche che, tuttavia, mantengono illoro valore (e dunque continuano ad esistere) attraverso successive chiamatedella funzione in cui si trovano.

int serie(void){

static int num_serie = 100;/* iniz. che viene eseguita una sola volta */

return(num_serie++);}

La struttura della memoria

Il funzionamento delle variabilistatiche/automatiche e della partedinamica della memoria èillustrato dalla ripartizione dellamemoria in tre areefondamentali: statica, dinamicaed automatica (pila dei record diattivazione).

Variabili esterneUn programma C si articola usualmente in più file. Ciò consente di introdurre unagerarchia di visibilità tra variabili. Essa si basa sulla definizione di varibili esterne:

Variabili extern: una dichirazione extern di una variabile in un file (nell'es. File2) dice al compilatore che la variabile in questione è stata definita altrove (File 1), ene estende la visibilità alle funzioni definite in quel file.

File 1 File 2int x, y;char car;

voidmain(void){…}

extern int x, y;extern char car;

int funz(void){…}

Costanti

Nome delle costanti Tipo'a' '\n' '\0' char1 123 - 234 int35000L -34L long int10000U 987U unsigned123.23F 4.34e-3F float123.23 12312333 double1001.2L long double

•\0 = fine stringa, e \n = fine riga, sono singoli caratteri;

•L abbrevia long, U unsigned, F float,

•e separa la mantissa dall’esponente

Definizione di identificatori perle costanti

Con comandi per il preprocessore (cioè come alias):

#define nome della costante valore della costante

#define MAXLINEE 100

Usando il modificatore const:

const tipo nome_costante = valore ;

Esempi:const double e = 2.71828182845905;

const char tm[] = “type missmatch”;

Espressioni e assegnazioniLe espressioni sono definite dalla grammatica:

espressione ::= variabile | costante | operatore(lista_espressioni)

Gli operatori aritmetici binari si scrivono in notazione infissa, parentesizzatisecondo le usuali convenzioni di precedenza. Ad esempio:

x + 2 5 * (y - 1) x % y (PASCAL x mod y) …

Le assegnazioni hanno la forma:

variabile = espresione ;Un' assegnazione in C è un'espressione, il cui valore è il valoreassegnato alla variabile a sinstra del simbolo `=´

if ((n = strlen(s)) > 10) ... equivale al Pascaln:= streln(s); if n > 10 then ...

Ciò permette di fare assegnazioni multiple simultanee:x = y = z = 0;

I booleani

Il C non ha il tipo dei booleani. Al loro posto si usano gli interi:

0 (false) qualunque intero > 0 (true)

onde è preferibile introdurre le costanti:

#define TRUE 1 ovvero const int true = 1;#define FALSE 0 const int false = 0;

Il metodo migliore per introdurre ibooleani usa l’istruzione typedef

Espressioni booleane

Le espressioni booleane si formano utilizzando:• Simboli relazionali: > >= < <=• Simboli di eguaglianza e diseguaglianza: == !=• Connettivi: && (and) || (or) ! (not)

Esempi:x > 3 (y == 1) || (y > 0) !(even(n))

(even() deve essere definita)

Attenzione:if (n == m) equivale al Pascal if n = m then ...if (n = m) equivale al Pascal n:= m; if n > 0 then ...

Conversione automatica del tipoImplicite nelle espressioni (promotion)Quando in un'espressione sono utilizzati tipi differenti il compilatore liconverte tuti nello stesso tipo, scegliendo quello che occupa più memoria.

char c; int i; float f; double d;

tot = (c / i) + (f * d) - (f + i);

int double float

double

double

Nota: poiché un argomento di funzione è un'espressione, le conversioni di tipoavvengono anche con il passaggio di parametri.

Conversione esplicita del tipo

Negli assegnamenti

int i; char c;

i = c;/* un carattere e´ identificato col suo codice ASCII */

c = i; /* il valore di c e' inalterato */

Nota: la conversione float ⇒ int provoca troncatura; la conversione double ⇒ float provoca arrotondamento.

Nelle espressioni (cast)(tipo) espressione

int n;printf("la radice di %d risulta %f",n,sqrt((double) n));

Iterazione con whilewhile (condizione) comando ;

Esecuzione: la condizione viene valutata; se il valore ottenuto è true alloraviene eseguito il comando e l’istruzione while viene eseguita nuovamente;altrimenti il controllo passa all’istruzione successiva.

Esempio:int a, b, c; /* pre: a, b interi positivi */

while (b > 0) {

c = b;

b = a % b;

a = c;

}

return a; /* post: a e’ l’MCD tra a e b */

Iterazione con do while

do istruzione while (condizione);

equivale al Pascal

repeat istruzione until not condizione;

Si osservi che la condizione inPASCAL è negata: questo perché untilesce se la condizione è vera, mentre dowhile esce se la condizione è falsa

Incrementi, decrementi, operazioni riflessiveIncremento di due specie:

preincremento ++ipostincremento i++ significano i:= i+1 in entrambi ì casi ma:

x = ++y equivale a y = y + 1; x = y; x = y++ equivale a x = y; y = y + 1;

Esistono anche i decrementi in questa forma:

--i ovvero i-- equivalenti a i = i - 1;

Operazioni riflessive: i += 7 significa i := i +7

utile in contesti del tipo:

numero_addetti += 7; invece dinumero_addetti = numero_addetti + 7;

Il ciclo forfor (inizializzazioni; condizione; operazioni) corpo;

La semantica di for è spiegata dalla seguente formulazione equivalente conil while:

inizializzazioni ;while (condizione) {

corpo ;operazioni ;

}

Non essendovi restrizioni sulle condizioni e sulle operazioni che vengonoeseguite al termine di ciascun ciclo, il for ed il while sono in Cequivalenti (a differenza del PASCAL).

Esempio: fibonacciano

/* Fibonacciano:

fib(0) = 0fib(1) = 1fib(n+2) = fib(n) + fib(n+1)

*/

int fib (int n)/* pre: n intero positivo */{

int a = 0, b = 1, i;

if (n == 0) return 0;for(i = 1; i < n; i++) /* a = fib(i-1), b = fib(i) */{

b = a+b;a = b-a;

}return b;

}

Esempio: somma di 10 interi

/* legge 10 numeri e ne stampa la somma */

void main (void){ int i, num, somma;

for (i = 0, somma = 0; i < 10; i++) { scanf("%d", &num); somma += num; } printf("la somma vale: %d \n", somma);}

Esempio: inversione di una stringa

void inversione (char s[])/* post: inverte la stringa s sul posto */{ int c, i, j;

for (i = 0, j = strlen(s) - 1; i < j; i++, j--){

c = s[i]; s[i] = s[j]; s[j] = c; }}

Nota. i = 0, j = strlen(s) - 1 sono due comandi chevengono eseguiti in sequenza; analogamente i++, j--.

If annidati

Attenzione: else si riferisce all'ultimo if che ne è privo

if (n > 0) for (i = 0; i < n; i++) if (s + i > 0) { … }

else … /* errore: else non si riferisce all'if esterno */

Correzione:if (n > 0)

for (i = 0; i < n; i++) { if (s + i > 0) { … } }

else ….

Annidamento if (condizione) istruzione_1; else if (condizione) istruzione_2; else istruzione_3

Forma generale if (condizione) istruzione ;

if (condizione) istruzione_1; else istruzione_2;

Selezione con switch (Pascal case)

switch c { case 'a': case 'b': case 'c': abc_num = abc_num + 1; break; case 'd': d_num = d_num +1; break; default: altri_num = altri_num +1;}

Il comando break serve ad impedire l’esecuzionesequenziale dei test successivi (tra i quali default avrebbesicuramente successo).

PuntatoriUn puntatore è una variabile il cui campo di valori è costituito da indirizzi.

Dichiarazione di un puntatore:

tipo *nome_variabile;

Esempio: int *p; /* p e' un puntatore ad interi */

Operatori su puntatori:

Operatore Semantica Esempio& var ritorna l'indirizzo di var p = &n;* var si riferisce al valore nella

locazione puntata da varn = *p;

La costante NULL è un valore comune per i puntatori di qualunque tipo edenota convenzionalmente una locazione indefinita (PASCAL nil).

Esempio/* esempio dell’uso degli operatori sui puntatori*/

#include <stdio.h>void main(void){

int x;int *p1, *p2;

p1 = &x;p2 = p1;printf("%p", p2);

/* stampa il valore esadecimale dell'ind. di x */}

Condivisione (sharing)I puntatori possono condividere l’area di memoria cui puntano:

int *p, *q; int n = 44; p = q = &n;

44n

p q

Ogni modifica del valore di n che avvenga per assegnazione su *p siriflette su n e su *q. Questa caratteristica viene sfruttata per ottonere effetticollaterali sui valori dei parametri attauli, passandoli cioè per indirizzo.

Esempio: passaggio di parametriPoiché in C i parametri delle funzioni sono sempre passati per valore, siimpiegano i puntatori per ottenere effetti collaterali sui parametri attuali(passati alla chiamata):

void scambia (int *p, int *q){

int temp;

temp = *p; *p = *q; *q = temp;

}

che, nel contesto di chiamata, si deve usare con l'operatore &:

int n, m; ...scambia (&n, &m);

VettoriIn C i vettori sono puntatori il cui valore è costante (cioè un indirizzofissato); la loro dichiarazione è però anche una definizione, nel senso che ilcompilatore alloca la memoria per le componenti del vettore:

tipo_elementi nome_vettore [dimensione];

L’accesso in lettura scrittura ha la forma

nome_vettore [indice];

Esempi:int v[10];v[3] = 7; printf(“%d”, v[0]);

Nota: in C gli indici variano tra 0 e la dimensione del vett. - 1.

Esempio: dichiarazione e inizializzazione

int giorni_mese[12];/* vettore di 12 interi */

La definizione può inizializzare gli elementi del vettore (quando siano unnumero ragionevole):

int giorni_mese[12] ={31,28,31,30,31,30,31,31,30,31,30,31};

Poiché in C il range di un array di n elementi è da 0 a n-1 abbiamo:

giorni_mese[3] sono i giorni del quarto mese

Esempio: massimo in un vettoreint maxvett (int n, int v[])/* pre: v è un vettore di n > 0 interi positivi post: ritorna il massimo in v[0..n-1],*/{

int max = 0, i;

for (i = 0; i < n; i++)if (max < v[i]) max = v[i];

return max;}

Un tipico errore sui vettori

Un vettore non può essere copiato su un altro, di egual tipo edimensione, con un’unica operazione di assegnamento:

int v[3] = {0,1,2};

int w[3];

w = v; /* errore */

la copia deve essere effettuata componente per componente:for (i = 0; i < 3; i++) w[i] = v[i];

Funzioni con vettori quali argomentiUn array non può essere passato per valore, ma sempre attraverso un puntatore, edunque per indirizzo (riferimento). Vi sono tre modi per ottenere questo risultato:

void stampa(int num[10]);/* stampa i dieci elementi dell'array num *//* num ha una dimesnione fissata */{

int i;

for (i = 0; i < 10; i++)printf("%d", num[i]);

}

void stampa(int num[]);/* stampa i dieci elementi dell'array num *//* num non ha una dimensione fissata */{

int i;

for (i = 0; i < 10; i++)printf("%d", num[i]);

}

È preferibileaggiungere unparametro chespcifichi lalunghezza del vettore

Aritmetica dei puntatori:Sui puntatori sono definite operazioni di• incremento e decremento: int *p; p = &n; p++;

/* p punta a &n + sizeof(int) */• somma e sottrazione di un intero:

int n, m, *p; p = &n; p = p + m;

/* p punta a &n + m * sizeof(int) */

• differenze tra puntatori:int n, a, b, *p, *q; p = &a, q = &b; n = p - q;

/* n è il numero degli interi allocabili tra

gli indirizzi di a e di b */

• confronto tra puntatori:int n, m, *p; p = &n; q = &m;

if (p < q) … /* eseguito se l’indirizzo di n

è minore di quello di m */

EsempiSi può puntare ad un singolo elemento di un array:

int a[10]; int *p; p = &a[2];

Tuttavia il nome di un array è una costante di tipo puntatore, il cui valore èl'indirizzo del primo elemento dell'array (offset 0). Dunque quanto segue èlecito:

p = a; che equivale a p = &a[0];

E' anche possibile accedere all'i+1-esimo elemento di un array usando unpuntatore al primo e l'offset i: se p == a allora

n = *(p + i); che equivale an = a[i]; che equivale an = *(a + i); (!!!)

Funziona perchéindirizzo di a[i] = indirizzo di a[0] + (i × sizeof(tipo el. di a))

e perchévalore di p + i = indirizzo di p + (i × sizeof(tipo di *p))

dimensione inbyte

StringheUna stringa è un vettore di caratteri non vuoto, il cui ultimo elemento è ilterminatore `\0’: quindi una stringa di lunghezza n occupa n+1 byte.

“abc” = [`a’,`b’,`c’,`\0’]

In particolare “” = [`\0’] è la stringa vuota.

Il tipo delle stringhe è char*, ossia il tipo dei puntatori a char; le seguentidichiarazioni sono corrette, ma hanno significato diverso:

char* s; s è una variabile e può puntare ad una stringachar s[]; s è un parametro di stringa nelle funzionichar s[80]; s è una stringa di 80 caratteri indefinitichar* s = “ciao”; s è la stringa “ciao”

Esempi: lunghezza e copia di una stringaint lunghezza (char s[]){

int i = 0;while (s[i] != ‘\0’) ++i;return i;

}

void copia(char s[], char t[]){ int i = 0; while ((s[i] = t[i]) != '\0') ++i;}

Nota: queste ed altre funzioni sono nella libreria string, dovesono chiamate strlen e strcpy rispettivamente.

Le strutture (record)Un struttura è un insieme finito di valori, possibilmente di tipo differente,raccolti sotto un'unico nome. Ciascun valore è associato ad un campoetichettato da un nome-campo, ed è accessibile attraverso di esso.

struct nome_struttura {tipo nome_campo;…tipo nome_campo;

} variabili_struttura;

L’istruzione struct definisce un tipo, che può essere impiegato nelladichiarazione di varabili, purché la parola chiave struct sia ripetuta:

struct point {double x; double y;

};

struct point p;

facoltativo

Strutture: accesso

Una struttura può essere:

• copiata: struct point p, struct point q;… p = q;

• indirizzata mediante l’uso dell’operatore &:

struct point *r; r = &p;

• acceduta attraverso i suoi membri:

p.x, r->x

• passata per parametro e ritornata come valore da una funzione:

struct point move(struct point p, double dx, double dy)

A differenza dei vettorile strutture possonoessere copiate con unasola assegnazione

Esempiostruct point {

double x; double y;} p, q;

struct point move (struct point p, double dx, double dy){

p.x += dx; p.y += dy;return p;

}

void showpoint (struct point p){

printf("(%f,%f)\n", p.x, p.y);}

void main (void){

p.x = 1.0; p.y = 2.0;showpoint(p);

q = move(p,1.5,3.1);showpoint(p); showpoint(q);

}

p resta invariato, mentre qviene assegnato al valore dimove, che è un puntoottenuto traslando p di dx, dy

Dichiarazioni e definizioniDichiarazione: consiste nell’associare un tipo ad un identificatore.

Esempi: int n; int fact(int n);

Definizione: consiste nell’associare un valore ad un identificatore.

Esempi: n = 12; int fact(int n) {…}

Dichiarazioni e definizioni spesso coincidono (e.g. la definizione di fact èanche una dichiarazione).

La distinzione tra dichiarazioni e definizioni è importante per capire l’uso dellamemoria. Nel caso dei puntatori, ad esempio, una dichiarazione ha l’effetto diriservare uno spazio in memoria che possa contenere un indirizzo; unadefinizione invece può (quando non vi sia sharing) comportare un’allocazione,riservando spazio in memoria, il cui indirizzo iniziale diventa il valore delpuntatore.

Gestione dinamica della memoriaSia data la dichiarazione: T* p; (T sia un tipo)

Per definire *p (cioe’ associargli un valore) si può procedere in tre modi:1. p = q; /* supponendo che *q sia definito */2. p = &x; /* se x e’ definita */

3. p = (T*) malloc(sizeof(T)); /* allocando un oggetto di tipo T */

Allocazione: è il processo con cui una porzione della memoria dinamicaviene riservata per ospitare dati di una certa dimensione; la dimensione,espressa in byte, dipende dal tipo ed è calcolata dalla funzione sizeof.

La funzione malloc ritorna l’indirizzo del primo byte dell’area allocata: taleindirizzo deve essere qualificato mediante un tipo, per cui si impiega ilcasting (T*).

AllocatoriE’ opportuno dedicare a ciascun tipo di struttura dati utilizzatodinamicamente una funzione per l’allocazione:char* StrAlloc(int len){ char *p;

p = (char*) malloc((len+1)*sizeof(char)); return p;}

Questa funzione alloca una stringa di lunghezza len: deve perciò allocarelen+1 caratteri, per tener conto del terminatore \0.

Se l’allocazione non va a buon fine (memoria insufficiente) malloc equindi StrAlloc, ritorna NULL: pertanto l’uso corretto di un allocatoreè il seguente:if ((s = StrAlloc(n)) != NULL) … ;

else StrError(); /* avendo definito tale fun. */

Deallocazione

Per deallocare ciò che è stato allocato da malloc si usa la funzione dilibreria:

void free (void *p)

Il puntatore void *p è generico (polimorfo); l’informazione sulladimensione del blocco puntato da p è stata memorizzata da mallocnell’intestazione del blocco (header), che è a sua volta parte della free listgestita da malloc; grazie ad essa la funzione free può calcolare ledimensioni del blocco da deallocare.

Tipi definiti dall’utente

Il C consente all’utente di definire propri tipi mediante il costrutto typedef(PASCAL type):

typedef definizione_del_tipo nome_del_tipo ;

I tipi così dichiarati possono essere impiegati esattamente come i tipi di base:typedef unsigned int Length;

Length len, maxlen;

Length* lengths[]; /* vettore di puntatori a Length */

Un simile esempio è la definizione del tipo String come puntatore a char:

typedef char* String;

Tipi enumeratiUn tipico impiego della definizione di tipi è il caso dei tipi enumerati, ossia cheammettono un numero finito di valori ordinati linearmente:

typedef enum {lun,mar,mer,gio,ven,sab,dom} Giorni;

In realtà il compilatore attribuisce ai valori di un tipio enumerato un numerointero progressivo a partire da 0. Sfruttando questa particolarità possiamointrodurre il tipo dei booleani:

typedef enum {False, True} Boolean;

Questa dichiarazione attribuisce a False il valore 0 e a True il valore 1: diconseguenza un’istruzione come

if (True) ...

avrà il comportamento atteso.

Tipi e struttureSebbene il costrutto struct definisca dei tipi, l’uso del typedf (introdottonell’ANSI C) consente una scrittura più perspicua del codice. Quindi ladichiarazione:

typedef struct {double x;double y;

} Point;

è preferibile alla dichiarazione:struct point {

double x;double y;

};

Si rammenti che in quest’ultimo caso la dichiarzione di una variabile avrebbeavuto la forma:

struct point p;

Tipi unioneL’insieme dei valori di un tipo unione è esattamente l’unione dell’insieme deivalori delle componenti. Tecnicamente una unione è implementata da unasovrapposizione di formati la cui dimensione è il massimo delle dimensioni:

union nome_tipo { tipo1 flag1; … tipon flagn; };

L’informazione sul tipo del valore attuale deve essere mantenuta in una variabiledistinta; il flag serve esclusivamente per l’accesso in lettura/scrittura.

typedef union {int ival; char cval;} U_int_char;

typedef enum {Ival, Cval} U_int_char_flag;

U_int_char u;

U_int_char_flag uf;

if (uf == Ival) u.ival = 0; else u.cval = `0’;

La sintassi per accedere alle componenti di una unione è la stessa usata per lestrutture (punto e freccia).

Tipi ricorsiviUn tipo è ricorsivo quando l’insieme dei sui valori sia definito induttivamente; adesempio un albero binario con etichette intere ha valori nell’insieme:

EmptyTree ∈ BinTree,

n ∈ Int ∧ tl, tr ∈ BinTree ⇒ ConsTree(n,tl,tr) ∈ BinTree.

La sua definizione in C richiede l’uso delle stutture e dei puntatori:

typedef struct tnode *BinTree;

typedef struct tnode {int Info;BinTree left;BinTree right;

} Tnode;

const BinTree EmptyTree = NULL;

Allocazione di tipi ricorsiviNel definire l’allocatore per gli alberi binari bisogna fare attenzione che ciò chesi alloca è la struttura di tipo Node, non il puntatore:BinTree BinTreeAlloc (void){ BinTree bt;

bt = (BinTree) malloc(sizeof(BinTree));/* errore */

return bt;}

La versione corretta è:BinTree BinTreeAlloc (void){ BinTree bt;

bt = (BinTree) malloc(sizeof(Tnode));return bt;

}

In generale si deve scrivere: variabile_puntatore = (T*) malloc (sizeof(T));

Nel caso dell’esempio, BinTree è infatti definito come Tnode*.

numero di byte per unindirizzo di memoria

numero di byte per unastruttura che rappresenta unnodo dell’albero

I/O da terminale (1)I/O formattato. Si basa sulle seguenti funzioni, definite in stdio

int printf(char stringa_di_controllo, espressioni_argomenti)

stampa il valore delle espressioni argomenti utilizzando la stringa di controlloquale maschera per la formattazione. Retsituisce il numero dei caratteri scritti,oppure un numero negativo in caso di errore.

int scanf(char stringa_di_controllo, puntatori_argomenti)

legge stringhe da tastiera, considerando spazi, tabultori e ENTER qualiseparatori; effettua la conversione secondo la stringa di controllo, e nememorizza i valori agli indirizzi che sono valore dei puntatori argomenti.La funzione ritorna il numero degli argomenti cui è stato assegnato unvalore corretto; ritorna EOF (ossia -1) in caso d'errore.

I/O da terminale (2)La stringa di controllo di printf e di scanf contiene, oltre a caratteri chevengono semplicemente riprodotti (solo per printf), caratteri detti specificatori,preceduti dal carattere %. I principali sono:

d numero decimaleo numero ottalex numero esadecimalef numero in virgola mobilee numero in mantissa ed esponentec caratteres stringa

Esempio#include <stdio.h>

void main(void){ char str[80]; int i;

printf("inserisci una stringa ed un intero: ");scanf("%s %d", str, &i);if (i < strlen(str)) printf("%c risulta %d esimo carattere di %s\n", str[i], i, str);

}

I/O da file (1)

Si basa su due concetti:Flusso: dispositivo logico, cratterizzato da una sequenza di informazionibufferizzate; è di due tipi:

testo = sequenza di caratteri,binario = sequenza di byte.

File: dispositivo fisico, quale un file su disco, un terminale, una stampante ecc.

La libreria stdio definisce un tipo FILE, che consente le operazioni diapertura, lettura, scrittura e chiusura su di un file, mediante un puntatore:

FILE *fp; /* fp e' un puntatore a file */

I/O da file (2)

Ritorna un puntatore a FILE (inizio del flusso) ovvero NULL in caso di errore.

int fclose(FILE *fp);chiude il file puntato da fp, effettuando il flushing del buffer (ossia ricopiando suldispositivo fisico quanto fosse rimasto sul buffer).

int feof(FILE *fp);ritorna TRUE (ossia 1) se fp punta alla fine del file, FALSE (0) altrimenti.

Inoltre sono disponibili funzioni I/O verso file di tipo formattato:

int fprintf(FILE *fp, const char *stringa_di_controllo, elenco_espressioni);int fscanf(FILE *fp, const char *stringa_di_controllo, elenco_puntatori);

"r" Apre file di testo in lettura"w" Apre file di testo in scrittura"a" Apre file di testo in scrittura in coda

FILE *fopen(const char *nome_file, const char *modo);associa un file ad un flusso, fissandone la modalità (elenco parziale):

Argomenti non trattati

In questo corso non sono stati trattati i seguenti aspetti dellinguaggio C (per cui si rinvia, ad esempio, al manuale diKernighan e Ritchie):

• primitive di basso livello

• puntatori generici

• passaggio di funzioni per puntatore

• argomenti del main nelle funzioni di linea

• il C nel sistema UNIX