Linguaggio C: Generalità Il C è un linguaggio procedurale

Transcript

Linguaggio C: Generalità Il C è un linguaggio procedurale
Univ. degli Studi di Cagliari
u
Linguaggio C: Generalità
Il C è un linguaggio procedurale-sequenziale, appartiene cioè alla stessa
classe di linguaggi a cui appartengono Pascal e Fortran.
Il C è stato definito nei primi anni 70 da Brian W. Kernighan e Dennis M.
Ritchie con l'obiettivo di fornire da supporto per l'implementazione del
sistema operativo UNIX.
Il sistema operativo (eccetto il nucleo), il compilatore C ed essenzialmente
tutti i programmi applicativi di UNIX sono scritti in C.
Molte delle caratteristiche del C discendono dal linguaggio BCPL
(Richards), attraverso il linguaggio B (Thompson) sviluppato nel 1970 per il
primo sistema operativo UNIX su un calcolatore DEC PDP-11.
Come molti altri linguaggi, il C non si presenta sempre nella stessa forma,
ha subito cioè modifiche nel corso della sua esistenza.
In particolare, il linguaggio si può presentare ...
ð
nella forma che risale alla sua definizione da parte degli autori, che
chiameremo per brevità K&R-C.
ð
nella forma proposta dall'ANSI (American National Standards
Institute), che nel 1983 ha costituito un comitato con l'obiettivo di dare
una definizione del linguaggio C non ambigua e non dipendente dalla
macchina, che chiameremo ANSI-C.
Docente G. Armano
-1-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
Il C ha estensioni standard anche verso paradigmi di programmazione
alternativi. Ad esempio:
ð
Parallel-C, per la programmazione concorrente
ð
C++, per la programmazione object-oriented (OOP)
Nel seguito, non verrà fatto alcun accenno alle possibili estensioni del
linguaggio per la programmazione concorrente, mentre ricordiamo una volta
per tutte che il linguaggio C++ ha avuto una notevole importanza per la
definizione dello standard ANSI-C.
Infatti, molte innovazioni introdotte dal C++ sono state recepite dal comitato
ANSI-C ed incorporate nello standard corrispondente.
L'ANSI-C sarà il linguaggio di riferimento anche per questi appunti.
Docente G. Armano
-2-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Che cosa si intende quando si dice che il C è un linguaggio proceduralesequenziale ?
ð
procedurale
le istruzioni del programma specificano cosa il programma deve fare
Esempio:
A = 22 ;
ð
/* Assegna 22 alla var. A */
sequenziale
le istruzioni del programma specificano vengono fornite ed eseguite in
sequenza
Esempio:
A = 22 ;
B = -3 ;
Docente G. Armano
/* PRIMA, assegna 22 ad A */
/* POI, assegna -3 a B */
-3-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Un semplice esempio di programma C: Hello, world !
Vediamo come si fa in C a visualizzare sul monitor del computer la fatidica
frase “Hello, world !”
#include <stdio.h>
main()
{ printf("Hello, world !\n") ; }
Docente G. Armano
-4-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Hello, world !
[II]
Qualche nota sul programma che stampa “Hello, world !”
-
#include <stdio.h>
è una direttiva per il compilatore. Si richiede di includere (nel file
corrente) il file di libreria "stdio.h" che contiene la specificazione dei
prototipi di funzione e delle costanti che gestiscono l'I/O
-
main ()
è una tra le possibili specificazioni − la più semplice− dell'interfaccia
della funzione main (da cui parte l'esecuzione del programma). Il corpo
della funzione segue immediatamente ed è contenuto tra parentesi
graffe
-
printf("Hello, world !\n") ;
è una chiamata alla funzione di sistema printf, che scrive una stringa
sullo standard-output (tipicamente il video).
Vedremo in seguito come la funzione printf può essere utilizzata per
effettuare output generalizzato (formattazione e scrittura di costanti,
espressioni e variabili di vario tipo)
Docente G. Armano
-5-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Hello, world !
[III]
Supponiamo di aver salvato il programma nel file Hello.c. Il suffisso “.c”
serve per ricordarci che il contenuto del file è un programma sorgente scritto
in C.
Per compilare il programma sorgente, sotto il prompt del sistema operativo
(sia ad esempio il carattere “$”), dovremo attivare il compilatore C
digitando:
$
cc Hello.c
In assenza di errori, il programma cc tenta di generare anche l'eseguibile,
che − per default− si chiamerà a.out.
Per dare al file eseguibile un nome diverso, ad esempio Hello, dovremo
scrivere:
$
cc -o Hello Hello.c
A questo punto, digitando:
$
Hello
si ottiene sul video la scritta:
Hello, world !
Docente G. Armano
-6-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Un altro esempio: azzeramento del contenuto di un vettore
Supponiamo di dover scrivere una funzione che azzera il contenuto di un
vettore di numeri interi (vett). La dimensione del vettore (NMAX) viene
passata come parametro alla funzione.
void azzeraVettore ( int vett[], int NMAX )
{ int i=0 ;
while ( i < NMAX )
{ vett[i] = 0 ; i = i+1 ; } }
Risultato dell'applicazione della funzione ad un vettore di interi qualunque:
tutti gli elementi del vettore (dall'elemento di indice 0 a quello di indice
NMAX-1) vengono azzerati.
Docente G. Armano
-7-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Azzeramento del contenuto di un vettore
[II]
Qualche nota sulla funzione che azzera il contenuto di un vettore di interi:
-
void azzeraVettore ( int vett[ ], int NMAX )
è l'interfaccia della funzione azzeraVettore. Tale interfaccia specifica
che la funzione non ritorna nulla (void), si chiama azzeraVettore, e i
suoi parametri sono − nell'ordine− un vettore di interi (vett) e la
dimensione del vettore (NMAX).
-
int i=0 ;
definisce una variabile intera i, locale alla funzione azzeraVettore, che
assume come valore iniziale zero.
-
while ( i < NMAX ) { ... }
forza la ripetizione del codice tra parentesi graffe finché è verificata la
condizione i < NMAX.
-
while ( ... ) { vett[i]=0 ; i = i + 1 ; }
finché la condizione del test tra parentesi rotonde è verificata, assegna
zero all'elemento vett[i] (elemento del vettore con indice i) e poi
incrementa i.
Docente G. Armano
-8-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Azzeramento del contenuto di un vettore
[III]
Un esempio di programma che utilizza la funzione azzeraVettore:
#include <stdio.h>
void azzeraVettore ( int vett[], int NMAX )
{ int i=0 ;
while ( i < NMAX )
{ vett[i] = 0 ; i = i+1 ; } }
void main ( void )
{ int V1[10] ; /* Vettore V1 di 10 interi */
int V2[50] ; /* Vettore V2 di 50 interi */
... /* Altre dichiarazioni */
azzeraVettore(V1,10) ; /* Azzera gli elem. di V1 */
azzeraVettore(V2,50) ; /* Azzera gli elem. di V2 */
... /* Altre istruzioni */ }
Docente G. Armano
-9-
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Azzeramento del contenuto di un vettore
[IV]
Si noti che nella chiamata azzeraVettore(V1,10):
-
V1 è un parametro attuale che viene fatto corrispondere con il
parametro formale vett
-
10 è un parametro attuale che viene fatto corrispondere con il
parametro formale NMAX
Per il momento, annotiamo che, in C:
-
i vettori vengono passati per indirizzo (ovvero tutto va come se la
funzione lavorasse direttamente sul parametro attuale).
-
tutti gli altri tipi di parametri vengono passati per valore (ovvero si
calcola il valore del parametro attuale e si usa tale valore per
inizializzare la variabile locale equivalente che ha il nome del
parametro formale).
Docente G. Armano
- 10 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Un esame più dettagliato delle caratteristiche del linguaggio C
PARTE I:
1.
2.
3.
4.
5.
strutture dati
operatori
strutture di controllo
procedure e funzioni
input/output
PARTE II:
6. gestione della memoria dinamica
7. funzioni di libreria
8. altre caratteristiche del linguaggio (macro e direttive)
Docente G. Armano
- 11 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
LINGUAGGIO C: PARTE I
Docente G. Armano
- 12 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati
Per i linguaggi delle classe del C, una struttura dati è caratterizzata dalla sua
specificazione (tipo), ed è rappresentabile tramite le sue istanze, che
possono essere costanti o variabili.
u
Strutture Dati: Tipi, Costanti e Variabili
Un tipo di dato (concreto) è la specificazione delle caratteristiche fisiche che
tutte le istanze che apparterranno al tipo devono avere.
Una costante (di un certo tipo) è un elemento che appartiene al dominio
definito dal tipo corrispondente. Una costante non cambierà il suo valore nel
corso dell'esecuzione del programma.
Una variabile (di un certo tipo) è un identificatore a cui è associato un
indirizzo di memoria e che può ospitare valori appartenenti al dominio
definito dal tipo corrispondente. Una variabile potrà assumere diversi valori
nel corso dell'esecuzione del programma (o meglio durante il suo tempo di
vita). Vedremo in seguito una definizione più dettagliata del concetto di
variabile.
Docente G. Armano
- 13 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi
I tipi possono essere:
-
built-in (predefiniti) / user-defined (derivati)
-
semplici / strutturati
I tipi built-in sono già noti al compilatore del linguaggio utilizzato per la
codifica del programma.
I tipi user-defined sono definiti dall'utente sulla base di regole di
composizione prefissate (array, records, enumerazioni, ecc.).
I tipi semplici sono caratterizzati dal fatto di avere un dominio non
strutturato (ad esempio: interi, caratteri, enumerazioni).
I tipi strutturati sono caratterizzati dal fatto di avere un dominio strutturato
(ad esempio: array e records). La definizione di un tipo strutturato viene
mappata sui tipi semplici, direttamente (facendo ricorso a tipi built-in)
oppure indirettamente (facendo riferimento ad identificatori di tipi definiti
dall'utente).
Docente G. Armano
- 14 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Built-In
In C esiste un ristretto numero di tipi predefiniti fondamentali:
-
char, carattere
int, intero
float, floating-point in singola precisione
double, floating point in doppia precisione
Docente G. Armano
- 15 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Qualificatori di Tipi Built-In
Alcuni qualificatori (short / long, signed / unsigned) possono essere
applicati ai tipi predefiniti fondamentali, generando restrizioni / estensioni di
tali tipi.
Tabella di applicabilità dei qualificatori short e long ai tipi predefiniti
fondamentali:
short
long
char
int
float
double
no
si
no
no
no
si
no
si
Le uniche restrizioni imposte dallo standard ANSI sono le seguenti:
-
short ≥ 16 bit
int ≥ 16 bit
long ≥ 32 bit
Normalmente l'ampiezza di un int rispecchia quella degli interi nella
macchina utilizzata. Spesso short indica un intero a 16 bit, long uno di 32, e
int occupa 16-32 bit. Ogni compilatore è comunque libero di scegliere la
dimensione degli interi in relazione all'hardware sul quale opera.
Il tipo long double caratterizza una precisione tipicamente superiore a
quella fornita dal tipo double.
Le notazioni short int e long int possono essere rispettivamente abbreviate
in short e long.
L'aritmetica corrispondente è quella binaria pura per i numeri non segnati e
quella complemento a due per i numeri con segno.
signed
unsigned
Docente G. Armano
char
int
short int
long int
si
(default)
(default)
(default)
(default)
si
si
si
- 16 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Costanti Built-In: Caratteri
Le costanti built-in di tipo char costituiscono il dominio del tipo di dato
char.
La notazione tipica utilizzata è quella di porre il carattere tra apici. In
alternativa si possono specificare (sempre tra apici) il valore ottale /
esadecimale (entrambi preceduti dal carattere “\”) della codifica ASCII
corrispondente.
Esempi: 'A', 'a', '\065', '\0x41'
Alcuni caratteri speciali sono codificati a parte (sequenze di escape):
\0
\a
\b
\f
\n
\r
\t
\v
\\
\?
\'
\"
Docente G. Armano
NULL (carattere nullo)
bell (allarme)
BS backspace
FF Form Feed (salto pagina)
LF Line Feed (salto riga)
CR Carriage Return (ritorno di carrello)
HT Horizontal TAB (tabulazione orizzontale)
VT Vertical TAB (tabulazione verticale)
\
?
'
"
- 17 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Costanti Built-In: Numeri Interi
Le costanti built-in numeriche intere costituiscono il dominio dei tipi di dato
int / short / long (eventualmente unsigned).
La notazione tipica utilizzata è la sequenza di cifre decimali, opzionalmente
preceduta dagli operatori unari “+” e “-”. Alcuni prefissi e suffissi predefiniti
consentono di specificare rispettivamente il sistema di numerazione
utilizzato e il tipo di riferimento. In assenza di suffissi espliciti, il tipo di
riferimento dipende dal numero rappresentato (int / long, unsigned int /
long).
Esempi: 0, 1, -1, +32512, -37
-
prefisso 0x / 0X, utilizzato per specificare costanti esadecimali
esempi: 0xA925, 0x1B, 0x2154
-
prefisso 0, utilizzato per specificare costanti ottali
esempi: 072, 0515
-
suffisso l / L, utilizzato per specificare costanti di tipo long
esempi: 0L, -1l, 072L, 0x453AL
-
suffisso u / U, utilizzato per specificare costanti unsigned
esempi: 3589u, 190U, 034U, 0x49B6U
-
suffissi ul / UL, utilizzati congiuntamente per specificare costanti
unsigned long.
esempi: 1882591UL, 32ul, 06UL, 0xFFFF0000ul
Docente G. Armano
- 18 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Costanti Built-In: Numeri Reali
Le costanti built-in numeriche reali costituiscono il dominio dei tipi di dato
float / double / long double.
La notazione tipica utilizzata è la sequenza di cifre decimali, separata dal
punto decimale, ed opzionalmente preceduta dagli operatori unari “+” e “-”.
Anche la notazione esponenziale è ammessa. L'esponente presuppone una
base 10.
Alcuni prefissi e suffissi predefiniti consentono di specificare
rispettivamente il tipo di riferimento. Il tipo di default è sempre double
(infatti nel calcolo delle espressioni floating, tutto viene trasformato in
double).
Esempi: 0., .1, -1.357, +325e-4, -37.1e2
-
suffisso f / F, utilizzato per specificare costanti float
esempi: -72.85F, 32e-3f
-
suffisso l / L, utilizzato per specificare costanti di tipo long double
esempi: 0.1L, -1547.38e-2l
Docente G. Armano
- 19 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Costanti di Tipo Espressione e di Tipo Stringa
Oltre alle costanti built-in, altri tipi di costanti meritano immediata
attenzione:
-
costanti di tipo espressione
sono espressioni calcolabili in tempo di compilazione, e possono essere
inserite in ogni punto in cui può trovarsi una costante
Esempi: 10*(356-31), -82e-3+71
-
costanti di tipo stringa
sono costanti costituite da sequenze di caratteri stampabili, con
eventuali sequenze di escape (\n, \t, ecc.), il tutto racchiuso tra doppi
apici.
Esempi: “Hello world !\n”, “Pippo”, “Pluto”, “Pippo, Pluto, Paperino”
Le costanti di tipo stringa sono memorizzate come vettori di caratteri e
terminate con il carattere '\0'.
Docente G. Armano
- 20 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Costanti Simboliche (User-Defined)
Il K&R-C mette a disposizione la direttiva #define. Esempi:
#define pigreca 3.1415
#define NMAX 100
In pratica #define stabilisce una regola di riscrittura per cui il compilatore
sostituisce ogni occorrenza di un certo simbolo con la corrispondente
definizione (nell'esempio sopra, rispettivamente, ogni occorrenza di pigreca
e NMAX con 3.1415 e 100).
L'ANSI-C mette anche a disposizione costruttori di costanti che utilizzano le
definizioni di tipo e la parola chiave const. Ad esempio, per i tipi semplici:
const
const
const
const
double pigreca = 3.1515 ;
double e = 2.71828182845905;
int NMAX = 100 ;
short NumChars = 255 ;
Vedremo poi qualche esempio di uso dell'uso di const applicata anche a tipi
derivati.
Docente G. Armano
- 21 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili
In generale, ad una variabile vengono associati:
- un nome
- un tipo
- una visibilità (scope)
- un tempo di vita (extent)
- un indirizzo di memoria
- un valore
Docente G. Armano
- 22 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Nome e Tipo
Il nome e il tipo di una variabile vengono specificati durante la sua
dichiarazione. Una dichiarazione di variabile viene fatta specificando
innanzitutto il tipo di riferimento, seguito da un nome (con associata
un'eventuale espressione di inizializzazione).
Esempio:
int K ; char ch='A' ;
Più variabili dello stesso tipo possono essere dichiarate insieme. In tal caso,
alla specificazione del tipo di riferimento, segue una lista di nomi (con
associate eventuali espressioni di inizializzazione) separati da virgole.
Esempio:
int X, Y = -1;
/* Inizializzo soltanto Y */
float F1, F2 = 1.0e-2, F3 ; /* Inizializzo soltanto F2 */
Docente G. Armano
- 23 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Visibilità
La visibilità di una variabile è lo spazio di programma nel quale la variabile
è accessibile. In C la visibilità di una variabile può essere relativa al blocco
di istruzioni / alla funzione / al file / al programma nel quale la variabile
stessa è stata dichiarata.
Esempio:
/* File pippo.c */
int X, Y=-1;
/* X ed Y sono variabili globali */
static float F1 = 1.0 ; /* F1 nota solo in pippo.c */
void simpleF ( int NMAX )
{
int I=0 ; /* I visibile in simpleF */
while ( I < NMAX )
{ int J = I+1 ; /* J visibile nel ciclo while */
... ; /* Istruzioni del ciclo while */ }
}
/* File pluto.c */
extern int X, Y;
/* X ed Y sono definite in pippo.c */
static float F1 = 1.0 ; /* un'altra F1 in pluto.c ! */
void complexF ( int K, int NMAX )
{
int I=0 ; /* I visibile in complexF */
int X=-1 ; /* Questa def.ne di X oscura l'altra */
if ( NMAX > 0 )
{ int J = 0 ;
/* J visibile solo in questo blocco */
... ; /* Istruzioni del blocco */ }
}
Docente G. Armano
- 24 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Visibilità
[II]
Dall'esempio precedente:
X e Y sono variabili globali al programma (statiche e potenzialmente visibili
ovunque). Nel file pippo.c sono visibili direttamente, mentre in pluto.c
vanno dichiarate extern per renderle visibili.
F1 è una variabile statica con visibilità all'interno del file nel quale è definita.
Identiche dichiarazioni (con il qualificatore static) in file diversi identificano
variabili diverse !
Le variabili I definite in simpleF e complexF sono automatiche. La loro
visibilità è all'interno della funzione nella quale sono definite.
Le variabili J definite in simpleF e complexF sono automatiche. La loro
visibilità è all'interno del blocco nel quale sono definite.
La dichiarazione della variabile X all'interno della funzione complexF
oscura quella della variabile globale (extern) con lo stesso nome.
Docente G. Armano
- 25 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Tempo di Vita
Il tempo di vita di una variabile è l'intervallo di tempo in cui la variabile
esiste all'interno del programma. Può essere coincidente con l'intero periodo
in cui il programma è in esecuzione (variabili statiche), oppure limitato al
tempo in cui viene eseguito codice appartenente al blocco in cui è stata
definita la variabile (variabili automatiche).
Ogni volta che si entra in un blocco / funzione vengono dunque create nuove
istanze delle variabili automatiche, mentre all’uscita del blocco / funzione
tali istanze vengono distrutte.
Esempio:
void azzeraVettore ( int vett[], int NMAX )
{
int i=0 ; while ( i < NMAX ) { ... ; }
}
Ogni volta che la funzione azzeraVettore viene chiamata viene creata una
variabile automatica i (in questo caso, con valore iniziale zero).
NB in realtà, ogni volta che si entra in azzeraVettore, oltre ad i, vengono
create 2 variabili locali “equivalenti” addizionali: vett e NMAX. Come
vedremo meglio in seguito, vett viene inizializzata con l'indirizzo del
parametro attuale, mentre NMAX viene inizializzata con il valore del
corrispondente parametro attuale (in generale un'espressione).
Docente G. Armano
- 26 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Tempo di Vita
[II]
Per default, le variabili definite all'interno di un blocco / una funzione sono
automatiche, mentre quelle definite al di fuori sono statiche.
Per modificare la scelta di default relativa ad una variabile locale ad un
blocco / funzione, si può usare la parola chiave static che, senza cambiarne
le regole di visibilità, le associa un extent statico.
Esempio:
/* Sequenza di Fibonacci */
int Fibonacci( void )
{ static int F0=0, F1=0; int Fnext ;
if ( F0 == 0 ) { F0 = 1; return F0 ; }
if ( F1 == 0 ) { F1 = 1; return F1 ; }
Fnext = F1 + F0 ; F0 = F1 ; F1 = Fnext ;
return Fnext ; }
La funzione Fibonacci utilizza due variabili statiche (F0 ed F1) per
memorizzare gli ultimi due numeri della sequenza Fn-1 ed Fn-2.
Le variabili F0 ed F1 hanno un tempo di vita che coincide con quello del
programma e vengono esplicitamente inizializzate a zero (in realta − come
vedremo− l'inizializzazione a zero è la scelta di default, quindi potrebbe
essere omessa). Concettualmente, l'inizializzazione viene effettuata alla
prima chiamata, concretamente − come tutte le variabili statiche− F0 ed F1
vengono inizializzate nel momento in cui il programma viene mandato in
esecuzione.
Il loro valore iniziale è stato posto a zero per poter effettuare il test relativo
ai primi due valori. Alla prima chiamata il test sul valore di F0 ha esito
positivo e la funzione ritorna 1. Alla seconda chiamata il test sul valore di
F1 ha esito positivo e la funzione ritorna ancora 1. Alle successive chiamate
la funzione ritorna Fn-1+Fn-2.
Docente G. Armano
- 27 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Indirizzo di Memoria
L'indirizzo di memoria di una variabile non viene gestito dal
programmatore. Le variabili statiche (globali o no) vengono create una sola
volta e quindi il loro indirizzo è fisso. Le variabili automatiche vengono
create ogni volta che si entra nel blocco in cui sono definite, e quindi ad ogni
creazione riceveranno -in generale- un indirizzo di memoria diverso.
Tipicamente, le variabili statiche vengono allocate in un'area di memoria
riservata, mentre le variabili automatiche vengono allocate nell'area di stack
del programma, oppure in un registro del processore. La decisione di
allocare nello stack o in un registro viene presa dal compilatore (tipicamente
in fase di ottimizzazione), anche se si può suggerire al compilatore stesso
l'allocazione in un registro tramite il qualificatore register.
Esempio:
int azzeraVettore( int vett[], int NMAX )
{ register int i=0 ; while ( i < NMAX ) { ... ; }}
NB Nonostante il suggerimento, il compilatore rimane comunque libero di
decidere la reale allocazione della variabile.
Docente G. Armano
- 28 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Valore (Inizializzazione)
Il valore iniziale può essere specificato esplicitamente nella dichiarazione,
oppure seguire delle regole di default.
Le regole di default sono le seguenti:
-
le variabili globali o statiche vengono inizializzate a zero
-
le variabili automatiche vengono lasciate indefinite.
Docente G. Armano
- 29 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Variabili: Valore (Aggiornamento)
Il valore di una variabile può essere aggiornato tramite un'istruzione di
assegnazione o un'istruzione specializzata di incremento / decremento.
La più semplice forma di assegnazione è quella che assegna un valore ad
una variabile.
Sintassi di una semplice assegnazione:
<varIdentifier> = <expr>
Semantica corrispondente:
store E V(<expr>) into E A(<varIdentifier>)
Dove E A(.) ed E V(.) sono due funzionali che restituiscono -rispettivamentel'indirizzo e il valore del loro argomento. I due funzionali sono stati da noi
definiti per poter trattare in modo esplicito la semantica delle assegnazioni, e
-più in generale- quella delle istruzioni del linguaggio.
Vedremo in seguito che in realtà esiste una stretta correlazione tra tali
funzionali e -rispettivamente- gli operatori & (indirizzo) e * (indirezione).
Esempio:
int azzeraVettore( int vett[], int NMAX )
{ register int i=0 ;
while ( i < NMAX ) { vett[i] = 0 ; i++ ; } }
vett[i] = 0 assegna zero all'elemento del vettore vett di indice i.
i++ incrementa il valore della variabile i (più semplicemente, diremo che
incrementa i). Per il momento, possiamo considerarla equivalente
all'istruzione di assegnazione i=i+1. Vedremo meglio in seguito la semantica
delle istruzioni di incremento / decremento.
Docente G. Armano
- 30 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Dichiarazione di Variabili Definite su Tipi Built-In
Esempi:
int X, Y = -1;
/* Inizial. soltanto Y */
short S1 = 25, S2 = -1 ;
unsigned long distanza ;
float F1 = -12.58e-3 ;
float F2 = F1 * 2 ;
long double raggio = 1.0 ;
Docente G. Armano
- 31 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati (User-Defined)
In C i tipi derivati sono sostanzialmente:
ð
enumerazioni
ð
ð
ð
array (mono / multi-dimensionali)
record
puntatori
Docente G. Armano
[semplice]
- 32 -
[strutturato]
[strutturato]
[strutturato]
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Enumerazioni
Il C mette a disposizione il costruttore di tipi enumerativi enum per definire
costanti di tipo simbolico. Esempi:
enum
enum
enum
enum
bool {
Giorno
Mese {
Escape
false, true } ;
{ dom, lun, mar, mer, gio, ven, sab } ;
Gen=1, Feb, Mar, Apr, ..., Dic } ;
{ BELL = '\a', BACKSPACE = '\b',
TAB = '\t', NEWLINE = '\n' } ;
In pratica, enum stabilisce una regola di riscrittura e mappa i suoi valori
sugli interi.
Nel tipo enumerativo bool, false e true valgono rispettivamente 0 e 1, poiché
l'assegnazione di default parte da 0 e procede per incrementi unitari.
Nel tipo enumerativo Giorno, dom, lun, mar, ..., sab valgono
rispettivamente 0, 1, 2, ..., 6.
Nel tipo enumerativo Mese, Gen, Feb, Mar, ..., Dic valgono rispettivamente
1, 2, 3, ..., 12.
Nel tipo enumerativo (sequenza di) Escape, a BELL, BACKSPACE, TAB e
NEWLINE sono esplicitamente assegnati i valori dei corrispondenti caratteri
speciali.
Docente G. Armano
- 33 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Array Monodimensionali
Il C mette a disposizione il costruttore di strutture omogenee (vettori) monodimensionali.
La definizione di strutture omogenee multidimensionali (matrici) avviene
specificando la dimensione tra parentesi quadre.
Esempi:
#define FMAX 1000
typedef int vettInt [100] ;
vettInt V1 ; float F[FMAX] ;
vettInt è un tipo di dato che caratterizza un array di 100 int.
V1 è una variabile di tipo vettInt array di int (di dimensione 100).
F è una variabile di tipo array di float (di dimensione 1000).
Gli indici di un array di dimensione N sono 0,1, ..., N-1.
Per accedere all'elemento di indice i di un array, basta specificare l'indice tra
parentesi quadre. Esempio:
void copiaVettore ( int DST[], int SRC[], int NMAX )
{ int i=0 ;
while ( i < NMAX )
{ DST[i] = SRC[i] ; i = i + 1 ; } }
Docente G. Armano
- 34 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Array Multidimensionali
Il C mette a disposizione il costruttore di strutture omogenee (vettori) multidimensionali.
La definizione di strutture omogenee multidimensionali (matrici) avviene
specificando, nell'ordine, le varie dimensioni tra parentesi quadre.
Esempi:
#define RMAX 30
#define CMAX 10
typedef int matrInt [RMAX][CMAX] ;
int V[30][10] ; matrInt V1 ;
Per accedere all'elemento di indice i di un array, basta specificare l'indice tra
parentesi quadre. Esempio:
void azzeraMatrice ( int matr[][100], int NMAX )
{ int i ; /* righe */
int j ; /* colonne */
i=0 ;
while ( i < NMAX )
{ j=0 ;
while ( j < 100 )
{ matr[i][j] = 0 ; j = j+1 ; }
i = i+1 ; } }
NB Come si può notare, nel passare una matrice come parametro, la prima
dimensione può essere omessa. Infatti le matrici vengono memorizzate per
righe, e quindi è necessario conoscere soltanto la dimensione di una riga
(ovvero il numero di colonne).
Docente G. Armano
- 35 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Array Multidimensionali
[II]
Una dichiarazione alternativa del prototipo della funzione azzeraMatrice
potrebbe essere:
void azzeraMatrice ( int (*matr)[100], int NMAX )
che specifica che il parametro matr è un puntatore ad un vettore di interi di
100 posizioni. Si noti invece che la dichiarazione:
void azzeraMatrice ( int *matr[100], int NMAX )
specifica che mat è un vettore di puntatori ad un intero ! Questo perche' la
priorità dell'operatore [] è superiore a quella dell'operatore di * indirezione.
Docente G. Armano
- 36 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Record
Il C mette a disposizione il costruttore di strutture composite (record).
Esempi:
/* K&R-C */
enum Mese {
... } ;
struct Data {
unsigned short giorno; enum Mese mese;
unsigned short anno };
struct Persona { char cognome[25] ; char nome[20] ;
struct Data dataNascita ;
char codiceFiscale[16+1] ; } ;
typedef struct persona PERSONA ;
struct Persona P1 ; PERSONA P2, dataBase[100] ;
Docente G. Armano
- 37 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Record
[II]
Mese è un type-TAG che caratterizza un tipo enumerativo con i seguenti
valori: Gen = 1, Feb = 2, ..., Dic = 12.
Data è un type-TAG che caratterizza un record con i campi giorno (1-31),
mese (1-12), anno.
Persona è un type-TAG che caratterizza un record con i campi cognome,
nome, dataNascita e codiceFiscale.
PERSONA è un identificatore di tipo che corrisponde alla struttura di tipo
(struct) Persona.
P1 è una variabile di tipo (struct) Persona.
P2 è una variabile di tipo PERSONA.
dataBase è una variabile di tipo array di PERSONA (di dimensione 100).
Docente G. Armano
- 38 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Record
[II]
Per accedere ad un elemento di un record (struct), occorre specificare la
variabile coinvolta ed il nome del campo. Esempi:
/* ANSI-C */
#include <string.h>
enum Mese { ... } ;
struct Data { ... } ;
struct Persona { ... } ;
...
Persona P1, dataBase[100] ;
...
strcpy(P1.cognome, "Dessi") ;
strcpy(P1.nome, "Sandro") ;
P1.dataNascita.giorno = 15;
P1.dataNascita.mese = Nov ;
P1.dataNascita.anno = 1968
strcpy(P1.codiceFiscale, "DSS SND 68S15 C006V") ;
Dove strcpy è una funzione di libreria del C che copia una stringa sorgente
in una destinazione, la cui interfaccia è definita (nel file string.h) come
segue:
char * strcpy ( char DST[], const char SRC[] ) ;
La funzione restituisce il puntatore al primo carattere della stringa
destinazione, e copia il contenuto del vettore SRC nel vettore DST (sino al
carattere di fine stringa, ovvero '\0').
Docente G. Armano
- 39 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Puntatori
Un puntatore è un indirizzo di memoria. Tipicamente sono sufficienti 32 bit
per caratterizzare un puntatore, anche se numero di bit riservati ad un
indirizzo di memoria dipende ovviamente dall'hardware della macchina (e a
volte dal compilatore utilizzato).
Una variabile di tipo puntatore è una variabile il cui valore è un indirizzo di
memoria, ma è caratterizzata anche dal tipo di dato puntato.
Esempio:
...
int X = 35, *Xptr = &X ;
...
printf("Scrivo X: %d\n",X) ;
printf("Scrivo ancora X: %d\n",*Xptr) ;
...
Definisco una variabile intera X con valore iniziale 35.
Definisco una variabile puntatore (ad int) di nome Xptr il cui valore iniziale
è l'indirizzo della variabile X.
Le istruzioni printf scrivono entrambe sullo standard-output il valore della
variabile X.
Scrivo X: 35
Scrivo ancora X: 35
Docente G. Armano
- 40 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Puntatori
[II]
Per capire bene il meccanismo della dichiarazione ed inizializazione della
variabile Xptr occorre conoscere la semantica degli operatori unari “&” ed
“*”.
L'operatore & (indirizzo) fornisce l'indirizzo del suo argomento. Cosi' &X
fornisce l'indirizzo della variabile X.
L'operatore * (indirezione) forza un livello di valutazione in più rispetto al
suo argomento. Cosi' *Xptr non fornisce il valore di Xptr (che è un
puntatore), bensì il valore intero allocato all'indirizzo di memoria il cui
valore è contenuto in Xptr.
Esempio:
Xptr
1931E4 0A2248
X
35
0A2248
con riferimento alla figura, possiamo scrivere:
E A(Xptr) = 0x1931E4
E V(Xptr) = 0x0A2248
/* indirizzo della variabile Xptr */
/* valore della variabile Xptr */
E A(X) = 0x0A2248 = E V(Xptr)
E V(X) = 35 = E V(*Xptr)
/* indirizzo della variabile X */
/* valore della variabile X */
NB Esiste uno stretto legame tra i funzionali E A(.) ed E V(.) da una parte, e
gli operatori & ed * dall'altra. Con riferimento alle variabili X ed Xptr sopra
definite, tale legame può essere esplicitato dalle seguenti uguaglianze:
E A(*Xptr) = E V(Xptr)
E V(&X) = E A(X)
Docente G. Armano
- 41 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Inizializzazione
L'ANSI-C consente di inizializzare − in fase di definizione− non solo
costanti e variabili appartenenti a tipi semplici, ma anche a tipi derivati.
In particolare, per quanto riguarda i tipi strutturati, l'inizializzazione procede
secondo la struttura dati di riferimento. Ad ogni livello di annidamento della
struttura dati corrisponde un livello in più di parentesi graffe.
L'inizializzazione può avvenire per definire il valore iniziale di una variabile
o quello di una costante di programma (in tal caso dovremo premettere la
parola chiave const).
Docente G. Armano
- 42 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Inizializzazione di Vettori
Per inizializzare una variabile di tipo vettore, in fase di definizione,
scriveremo, ad esempio:
/* ANSI-C */
int V1[5] = { -2, -1, 0, 1, 2 } ;
Inizializza V1[0], V1[1], ..., V1[4] rispettivamente a -2, -1, 0, 1, 2. Non è
necessario specificare un valore iniziale per tutti gli elementi del vettore. Gli
elementi di cui non viene specificato il valore iniziale seguono le regole di
inizializzazione di default per le variabili.
Per inizializzare una costante di tipo vettore scriveremo, ad esempio:
/* ANSI-C */
const int nullVector[5] = { 0, 0, 0, 0, 0 } ;
Inizializza tutti gli elementi del vettore a zero.
Se la dimensione del vettore viene omessa, il compilatore ne calcola le
dimensioni sulla base del numero di valori iniziali.
/* ANSI-C */
int V1[] = { -2, -1, 0, 1, 2 } ;
Dichiara un vettore di 5 elementi con i valori iniziali specificati tra parentesi
graffe.
Docente G. Armano
- 43 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture Dati: Tipi Derivati: Inizializzazione di Record
Per inizializzare una variabile di tipo record, in fase di definizione,
scriveremo, ad esempio:
/* ANSI-C */
enum Mese { ... } ;
struct Data { ... } ;
struct Persona { ... } ;
...
Persona P1 = { "Dessi","Sandro", { 15, Nov, 1968 },
"DSS SND 68R15 C006V") ;
Per inizializzare una costante di tipo record scriveremo, ad esempio:
/* ANSI-C */
enum Mese { ... } ;
struct Data { ... } ;
struct Persona { ... } ;
...
const Persona emptyPerson = { "", "", { 0,0,0 }, "") ;
Se alcuni campi della struttura vengono omessi, il compilatore
l'inizializzazione procede seguendo le regole di inzializzazione di default per
le variabili. Ad esempio:
/* ANSI-C */
struct Persona P1 = { "Dessi", "Sandro" }
Alla variabile P1 di tipo (struct) Persona vengono assegnati esplicitamente
soltanto i campi cognome e nome. Se la variabile è statica (globale o no),
gli altri campi vengono inizializzati a zero, altrimenti il loro contenuto sarà
− in generale− imprecisato.
Docente G. Armano
- 44 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori
Gli operatori in C si dividono nei seguenti gruppi principali:
ð
ð
ð
ð
ð
operatori aritmetici
operatori relazionali e logici
operatori di incremento e decremento
operatori che operano sui bit
operatori di assegnamento ed espressioni
Docente G. Armano
- 45 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori aritmetici
ð
operatori aritmetici unari: “+”, “-”
“+” introdotto dall’ANSI-C per motivi di “simmetria” rispetto alla
notazione con il “-” unario
“-” già presente anche nel K&R-C per specificare l’inversione di segno
applicata a costanti / variabili / espressioni aritmetiche.
ð
operatori aritmetici binari: “+”, “-”, “*”, “/”, “%”
sono tutti operatori infissi.
“*” e “/” sono prioritari rispetto al “+” e “-” e rappresentano gli usuali
operatori aritmetici binari.
“%” è l’operatore di modulo che restituisce il resto della divisione tra
il primo e il secondo operando.
Esempio: X % 4 => 1 se X = 5 oppure 9 oppure 13 oppure ...
Tabella di applicabilità degli operatori binari
“+”, “-”
“*”, “/”
“%”
int, short, long
si
si
si
float, double
si
si
no
Docente G. Armano
- 46 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori relazionali e logici
Sono operatori che si integrano perfettamente all’interno del meccanismo di
valutazione di espressioni aritmetiche.
Infatti, come vedremo, i test realizzati in linguaggio C sono considerati falliti
(esito negativo) se e solo se il risultato della valutazione dell’espressione
corrispondente e’ zero. Un risultato non nullo determina l’esito positivo del
test corrispondente.
I valori “logici” false e true ottenuti come risultato della valutazione di
espressioni che coinvolgono operatori relazionali e/o logici vengono
mappati, rispettivamente, su 0 e 1.
ð
operatori relazionali sono: “>“, “>=“,“<“,“<=“,“==“,“!=“
Si noti che l’operatore di (verifica di) uguaglianza e’ “==“, mentre
quello di (verifica di) disuguaglianza e’ “!=“
“>“, “>=“, “<“, “<=“ hanno priorità superiore rispetto a “==“ e “!=“
Tutti gli operatori relazionali hanno priorità inferiore a quelli aritmetici.
Esempio: l’espressione i < lim-1 equivale a i < (lim-1)
ð
operatori logici unari: “!” (negazione)
ð
operatori logici binari: “&&” (and) e “||” (or)
le espressioni connesse da “&&” e/o “||” vengono valutate da sinistra a
destra e la valutazione si blocca non appena si determina la verità o
falsità dell’intera espressione
Docente G. Armano
- 47 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di incremento e decremento
Il C offre operatori specializzati che realizzano l’operazione di incremento o
decremento.
Gli operatori sono “++” (incremento) e “--” (decremento) e possono essere
applicati prima di valutare la variabile corrispondente (pre-incremento /
decremento) o dopo (post-incremento / decremento).
L’operazione di incremento o decremento va vista come un effetto
collaterale che si scatena durante la valutazione di espressioni in cui sono
coinvolte le variabili / locazioni di memoria corrispondenti.
Esempio (uso dell’operatore di post-incremento):
int N = 10 ; int X[10] ;
...
while ( N-- > 0 ) X[N] = 0 ;
...
Come risultato dell’esecuzione del ciclo while, tutte le componenti del
vettore X sono state poste a zero. Perché ?
R.: l’istruzione N-- specifica un’operazione di post-decremento, ovvero:
prima si valuta N e poi la si decrementa.
Si noti che la prima assegnazione viene fatta sull’elemento di indice 9 del
vettore e l’ultima sull’elemento di indice 0.
Docente G. Armano
- 48 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di incremento e decremento
[II]
Un altro esempio (copia di un vettore):
versione #1:
void copiaVettore( int DST[], int SRC[], int NMAX )
{ int i=-1 ;
while ( ++i < NMAX ) DST[i] = SRC[i] ; }
versione #2:
void copiaVettore( int DST[], int SRC[], int NMAX )
{ while ( NMAX-- ) *DST++ = *SRC++ ; }
Si noti l’assegnazione: *DST++ = *SRC++ che sarà presto usata in molti
esercizi.
La semantica associata all’istruzione e’: “prima estrai il valore contenuto
nella locazione puntata da SRC e poi incrementa SRC. Deposita tale valore
nella locazione puntata da DST e poi incrementa DST”.
Docente G. Armano
- 49 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di incremento e decremento
[III]
Alcune osservazioni:
-
un'istruzione di assegnazione ritorna sempre come valore quello della
sua parte sinistra. In questo caso, viene restituito l’indirizzo della
locazione di memoria puntata da DST. Le operazioni di assegnazione e
di incremento vanno considerate come effetti collaterali (side-effect);
-
DST e SRC sono trattati come se fossero dei puntatori. Questo e’
sempre possibile in C (almeno quando stiamo considerando parametri
formali di una funzione);
-
sono i puntatori e non le locazioni puntate ad essere (post-)incrementati
poiché l’operatore ++ (incremento) si applica soltanto a quello che
compare immediatamente alla sua sinistra. Per incrementare le
locazioni puntate avremmo dovuto scrivere (*DST)++ oppure
(*SRC)++.
Semantica dell'assegnazione:
Dopo ogni operazione in cui si verifica un side-effect inseriremo in alto il
valore ritornato e in basso il side-effect.
Quando necessario si possono usare le indicazioni {before} e {after} per
sottolineare che il side-effect interviene − rispettivamente− prima o dopo la
corrispondente valutazione. Assumiamo che se non viene indicato nulla il
side-effect intervenga dopo.
E A (* DST + + )
 store E V (* SRC + + ) into E A (* DST + + )

EV (* DST + + =* SRC + + )= 
EV (* SRC + + )= 
EV (* SRC ) = EV (EV (SRC ))
 inc SRC
E A (* DST + + )= 
E A (* DST ) = EV (DST )
 inc
Docente G. Armano
DST
- 50 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori che operano sui bit
Sono 6 operatori che si possono applicare soltanto ad operandi interi
(ovvero char, short, int, e long segnati o no).
ð
operatori logici bit-a-bit unari: “~” (inversione, ovvero complem. a 1)
ð
operatori logici bit-a-bit binari: “&” (and), “|” (or), “^” (xor)
ð
operatori di shift bit-a-bit binari “<<” (shift sx), “>>” (shift dx)
Esempi:
int X1, X2 = -1 ; /* ogni bit di X2 è posto ad 1 */
char ch1 = '9' ; /* ch1 vale 0x39 = codif. ASCII di 9 */
X1
X1
X1
X1
X1
X1
=
=
=
=
=
=
~X2 ; /* risultato 0 */
X1 | X2 ; /* risultato -1 */
ch1 & 0x0F ; /* risultato 9 */
ch1 ^ 0x1C ; /* risultato 0x25 */
X1 << 2 ; /* risultato 0x94 */
X1 >> 4 ; /* risultato 9 */
Docente G. Armano
- 51 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di assegnazione ed espressioni
L'operatore di assegnazione è stato illustrato in una forma semplificata
trattando l'aggiornamento delle variabili.
Più in generale la sintassi di un'assegnazione è la seguente:
<multipleAssign> ← <mem> = <multipleExpr>
<multipleExpr> ← <expr> | <multipleAssign>
Si noti che la definizione è ricorsiva.
Semantica corrispondente:
store E V(<multipleExpr>) into E A(<mem>)
Rispetto alla versione semplificata dell'assegnazione, sono stati introdotti
due elementi di novità:
-
la "parte sinistra" dell'assegnazione può contenere riferimenti a variabili
/ indirizzi di memoria multipli
-
nella "parte sinistra" non è più necessario che ci siano riferimenti a
variabili singole. È sufficiente che ci siano riferimenti ad indirizzi di
memoria.
Docente G. Armano
- 52 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di assegnazione ed espressioni
[II]
Un esempio:
...
int V[10], X, Y, Z=5 ;
...
V[0] = X = Y = 3*Z - 1 ; /* Inizializzazione multipla */
...
/* A questo punto, V[0], X e Y valgono tutti 14 = 3*5-1 */
Semantica:
EV (V [ 0 ] =
EV ( X
=
E A ( X = Y = 3* Z − 1)
 store EV ( X = Y = 3 * Z − 1) intoE A (V [ 0 ] )

X = Y = 3* Z − 1)= 
E A (Y = 3* Z − 1)
 store EV (Y = 3 * Z − 1) intoE A ( X )

Y = 3* Z − 1)= 
EV (3* Z − 1) = 3* EV (Z ) − 1 = 14
 store EV (3 * Z − 1) intoE A (Y )

EV (Y = 3* Z − 1)= 
Si noti che la semantica del C restituisce l’indirizzo della parte sinistra di
un’espressione di assegnazione.
È comunque opportuno non sfruttare –salvo rarissimi casi– questa
caratteristica del linguaggio, per evitare di incorrere in forme difficili da
interpretare. Ad esempio, il C consentirebbe di scrivere:
(x=y=1)++ ;
che come risultato finale assegna 1 alla variabile y e 2 alla variabile x !
L’espressione risulta però di difficile lettura e inutilmente complicata. Se ne
sconsiglia quindi l’uso.
Come regola pratica, tutto va come se la valutazione della parte più a destra
di un’assegnazione multipla venisse effettivamente propagata a sinistra,
effettuando separatamente lo store su tutti gli indirizzi di memoria specificati
dall’assegnazione multipla.
Docente G. Armano
- 53 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di assegnazione ed espressioni
[III]
Un altro esempio:
int X, Y, *Yptr = &Y ; int Z = 0;
X = *Yptr = ++Z - 2 ;
Semantica:
ad X e Y viene assegnato il valore E V(++Z - 2) = -1. C'è un side-effect su Z
che viene pre-incrementata. L'inizializzazione di Y avviene tramite l'uso
della variabile puntatore Yptr, inizializzata all'indirizzo di Y.
E V ( X = *Yptr =
+ +Z −
 E V (*Yptr = + +
2) = 
 store E V (*Yptr = + +
E V (+ + Z − 2 ) = E V (Z ) −
E V (* Yptr = + + Z − 2 )= 
 inc Z
Docente G. Armano
Z − 2)
Z − 2 ) into E A ( X )
2 = 1− 2 = −1
{before}, store E V (+ + Z − 2 ) into E A (* Yptr )
- 54 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di assegnazione ed espressioni
[IV]
Ci sono poi altri 10 operatori che costituiscono delle forme contratte per le
assegnazioni che utilizzano operatori aritmetici o bit-a-bit binari.
ð
ð
operatori aritmetici di assegnazione: “+=“, “-=“, “*=“, “/=“, “%=“
operatori bit-a-bit di assegnazione: “&=“, “|=“, “^=“, “<<=“, “>>=“
La loro semantica è quella della forma corripondente espansa. Ad esempio:
X1 += 2 corrisponde a X1 = X1 + 2
Docente G. Armano
- 55 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori di assegnazione ed espressioni
[V]
Importanti note sugli effetti collaterali:
il linguaggio C non specifica l'ordine di valutazione degli argomenti di una
funzione e neppure quello relativo all'esecuzione degli effetti collaterali.
Diversi compilatori possono quindi produrre risultati diversi eseguendo − ad
esempio− le seguenti istruzioni:
int n=3; int i=0; int A[10];
...
printf("%d %d\n",++n,power(2,n)) ; /* power(2,3) o power(2,4) ? */
...
A[i] = i++ ; /* A[0] o A[1] ? */
...
Docente G. Armano
- 56 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Espressioni Condizionali
Sintassi:
<expr> ? <expr1> : <expr0>
Semantica:
E V(<expr> ? <expr1> : <expr0>) = E V(<expr1>) se E V(<expr>) ≠ 0
= E V(<expr0>) altrimenti
Esempi:
int X, Y, Z ; char ch1 ;
...
Z = ( X < Y ) ? X : Y ; /* assegna a Z min(X,Y) */
...
ch1 = ( ch1 >= 'a' && ch1 <= 'z' ) ? ch1-'a'+'A' : ch1 ;
/* trasforma ch1 in char maiuscolo */
Docente G. Armano
- 57 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Operatori: Tabella delle Priorità
Operatore
() [] -> .
! ~ ++ -- (tipo) * & sizeof
*/%
+<< >>
<<= >>=
== !=
& bit-a-bit
^
|
&&
||
?:
= += -= ecc.
,
Docente G. Armano
- 58 -
Associatività
da SX a DX
da DX a SX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da SX a DX
da DX a SX
da DX a SX
da SX a DX
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo
Le strutture di controllo sono:
-
sequenza
if_then_else
switch
while_do
do_while
for
goto
Docente G. Armano
- 59 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: Sequenza
In C le istruzioni vengono eseguite in sequenza. Il carattere ";" è il
separatore di istruzioni semplici.
Esempio di istruzioni semplici in sequenza:
int X, Y, Z ;
...
Z = X + Y ;
X = 2 * Y ;
Istruzioni complesse (blocchi) sono delimitate dai caratteri {} e vanno
considerate a tutti gli effetti come istruzioni "singole". All'interno di un
blocco possono essere dichiarate variabili la cui visibilità rimane confinata
all'interno del blocco stesso (inoltre, variabili esterne al blocco con lo stesso
nome vengono oscurate).
Esempio di istruzione complessa:
int X, Y ;
...
{ int tmp = X; X = Y; Y = tmp; }
...
/* scambio X con Y */
NB Nel seguito, con istruzione denoteremo istruzioni semplici o complesse.
Si ricordi che soltanto le istruzioni semplici devono essere sempre terminate
dal carattere ";". Anche l'istruzione vuota (ovvero un semplice ";") è
ammessa.
Docente G. Armano
- 60 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: if_then_else
Sintassi:
if ( <expr> ) <instr1>
oppure
if ( <expr> ) <instr1> else <instr0>
Dove:
<instr1> e <instr0> sono istruzioni.
Semantica:
se E V(<expr>) ≠ 0 allora E V(<instr1>) altrimenti E V(<instr0>)
Esempio:
int X, Y ;
...
if ( X < Y ) X = Y ; else { int tmp=X; X=Y; Y=tmp; }
...
Docente G. Armano
- 61 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: switch
Sintassi (semplificata):
switch ( <expr> )
{ case <expr-const1> : <instrList1>
case <expr-const2> : <instrList2>
...
case <expr-constN> : <instrListN>
default : <instrListN+1> }
Dove:
<expr> è un'espressione intera (char, short, int).
<expr-const> è un'espressione di costanti.
<instrList> è una lista di istruzioni.
Semantica:
Si valuta E V(<expr>) e si cerca sequenzialmente la prima espressione
costante <expr-const> (in corripondenza delle label case) tale che
E V(<expr-const>) = E V(<expr>).
Se tale espressione esiste, allora si eseguono le istruzioni associate, e tutte
quelle successive sino (i) alla fine del blocco dello switch o (ii) al primo
break (o return) incontrato. Altrimenti, se esiste l'opzione di default, si
eseguono le istruzioni corrispondenti. Altrimenti si esce dal blocco senza
eseguire nulla.
Docente G. Armano
- 62 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: switch
[II]
Esempio:
int X, Y ;
...
switch ( X )
{ case 10
case 11
case 12
default
...
: X++ ; Y = 2 * X ; break ;
:
: Y = X ;
: Y++ ; }
NB quando X = 10 eseguo le istruzioni corrispondenti e poi esco dal blocco
switch (per la presenza dell'istruzione break). Quando invece X = 11
oppure X = 12 eseguo l'assegnazione Y = X e poi incremento Y (ovvero,
dato che il “case 12” non è chiuso da un break, eseguo anche l'istruzione
associata alla label default). Altrimenti incremento Y.
Docente G. Armano
- 63 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: while_do
Sintassi:
while ( <expr> ) <instr>
Dove:
<expr> è un'espressione, <instr> è un'istruzione.
Semantica:
Se E V(<expr>) ≠ 0 allora E V(<instr>). Si ripetono tali operazioni finche' non
si verifica una delle due eventualità seguenti: (i) E V(<expr>) = 0, oppure
(ii) nel corpo dell'istruzione <instr> viene eseguito un break (o un return).
Una delle due eventualità sopra riportate forza l'uscita dal ciclo.
Esempio:
/* cerca un char in una stringa. Se trovato,
ne restituisce l'indice, altrimenti -1 */
int cercaChar ( char *str, int ch)
{ int i=0 ;
while ( str[i] != '\0' )
{ if ( str[i] == ch ) break ; else i++ ; }
return ( str[i] != '\0' ) ? i : -1 ; }
Finche' non sono arrivato al fine stringa ('\0') verifico se il carattere corrente '
uguale a quello cercato. Se uguale, allora esco dal ciclo (break). Ritorno
l'indice i del char cercato oppure -1 (se non trovato).
Docente G. Armano
- 64 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: while_do
[II]
Esiste anche la possibilità di forzare la ripetizione anticipata del test
E V(<expr>). In tal caso occorre usare l'istruzione continue che − eseguita
all'interno del blocco di istruzioni del ciclo while− rimanda ad effettuare il
test senza eseguire le eventuali istruzioni successive.
Esempio:
/* cerca un char in una stringa. Se trovato,
ne restituisce l'indice, altrimenti -1 */
int cercaChar ( char *str, int ch)
{ int i=-1 ;
while ( str[++i] )
{ if ( str[i] != ch ) continue ;
return i ; }
return -1 ; /* non trovato */ }
Docente G. Armano
- 65 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: do_while
Sintassi:
do <instr> while ( <expr> )
Dove:
<expr> è un'espressione, <instr> è un'istruzione.
Semantica:
Come lo while_do, con la differenza che qui il test viene eseguito dopo.
Docente G. Armano
- 66 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: for
Sintassi:
for ( <init> ; <expr> ; <update> ) <instr>
Dove:
<init>
è un'espressione (o una sequenza di espress. separate da virgole)
<expr> è un'espressione.
<update> è un'espressione (o una sequenza di espress. separate da virgole).
<instr> è un'istruzione.
Semantica (fornita in termini dello while_do):
<init>
while ( <expr> ) { <instr> ; <update> ; }
Esempio:
void azzeraVettore ( int vett[], int NMAX)
{ int i ;
for ( i=0; i < NMAX; i++ ) vett[i] = 0 ; }
NB In <init> e <update> è possibile specificare anche più di una istruzione
semplice. In tal caso, le virgole sono usate come separatori.
Docente G. Armano
- 67 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Strutture di Controllo: goto
Sintassi:
goto label ;
Semantica:
È l'istruzione di salto incondizionato. L'etichetta (label) deve ovviamente
essere stata definita.
Esempio:
/* cerca un char in una stringa. Se trovato,
ne restituisce l'indice, altrimenti -1 */
int cercaChar ( char *str, int ch)
{ int i=0 ;
while ( str[i] != '\0' )
{ if ( str[i] == ch ) goto trovato ; else i++ ; }
return -1 ; /* non trovato */
trovato:
return i ; /* trovato */ }
NB Si consiglia di utilizzare l'istruzione di goto soltanto per implementare
strutture di controllo che il linguaggio non offre esplicitamente (ad es.
repeat-exit a più livelli).
Docente G. Armano
- 68 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni
Le procedure storicamente sono nate (anni 60) per evitare la duplicazione di
codice, per poi diventare (anni 80) strumenti per realizzare delle astrazioni
funzionali.
Una procedura è un costrutto linguistico che specifica una trasformazione tipicamente non disponibile a livello del linguaggio scelto.
La trasformazione è caratterizzata da un nome e da eventuali parametri, che
possono essere di ingresso, di ingresso-uscita, o di uscita.
I parametri presenti nella specificazione della procedura sono detti
parametri formali e consentono di esplicitare operazioni senza dover
anticipare su quali dati "effettivi" tali operazioni devono essere eseguite.
L'esecuzione di una procedura procede attraverso la sua invocazione (call),
in cui si specificano il nome della procedura e i suoi parametri attuali, cioè
quei dati sui quali verrà eseguita la trasformazione.
Docente G. Armano
- 69 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni
[II]
Esempio:
/* incrementa tutti gli elementi di un vettore */
void incVettore ( int vett[], int NMAX)
{ while ( NMAX-- > 0 ) vett[NMAX]++ ; }
void main ( void )
{ int V1[100], V2[10] ;
...
incVettore(V1,100) ; /* call su V1 */
incVettore(V2,10) ; /* call su V2 */
... }
-
incVettore è una procedura con due parametri formali: vett (di
ingresso-uscita) e NMAX di ingresso.
-
la procedura incVettore viene invocata due volte: la prima su V1 e la
seconda su V2.
Docente G. Armano
- 70 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni
[III]
Una funzione può essere vista come una procedura che restituisce un valore
(tipicamente il risultato della computazione effettuata).
Formalmente, in C non ci sono procedure, poiché esistono soltanto funzioni.
Rovesciando l'interpretazione usuale, una procedura può quindi essere vista
come una funzione che non restituisce alcun valore.
Dichiarazione di funzione: sintassi semplificata
<function-dcl> ::= <type> <function-id> ( <parList> ) <function-body>
<type> ::= <type-id> | <type-dcl>
<parList> ::= <parList1> | ε
<parList1> ::= <par> , <parList1> | <par>
<par> ::= <type> <par-id>
<function-body> ::= { <varList> <instrList> }
Dove <varList> e <instrList> rappresentano, rispettivamente, una o più
dichiarazione di variabili e una o più istruzioni.
Per evidenziare il fatto che una funzione è in realtà una procedura, è
consigliato utilizzare la parola chiave void. Nell'esempio precedente
incVettore è una procedura poiché “restituisce” un tipo void.
Per omogeneità, riteniamo opportuno inglobare void all'interno dei tipi
predefiniti, con il significato di "nessun tipo".
La scelta di default per il tipo restituito da una funzione C è int.
Ciononostante suggeriamo di specificare sempre il tipo restituito, anche
quando è int.
Docente G. Armano
- 71 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: prototipi
Quando l'uso di una funzione precede la sua definizione (o quando la
definizione è riportata in un altro file), occorre specificarne il prototipo.
Sostanzialmente, il prototipo di una funzione ne indica l'interfaccia, ovvero il
tipo dei parametri e il tipo del valore restituito.
Esempio:
/* incrementa tutti gli elementi di un vettore */
void incVettore ( int vett[], int NMAX) ;
void main ( void )
{ int V1[100], V2[10] ;
...
incVettore(V1,100) ; /* call su V1 */
incVettore(V2,10) ; /* call su V2 */
... }
void incVettore ( int vett[], int NMAX)
{ while ( NMAX-- > 0 ) vett[NMAX]++ ; }
Docente G. Armano
- 72 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: prototipi
[II]
Poiché il prototipo riguarda soltanto l'interfaccia di funzione, il nome dei
parametri può anche essere omesso. Ad esempio, il prototipo di incVettore
può essere definito come segue:
void incVettore ( int [], int ) ;
Per compatibilità con il K&R-C è stata mantenuta la possibilità di dichiarare
l'esistenza di una funzione senza specificare la lista dei suoi parametri. Ad
esempio:
void incVettore () ;
specifica che incVettore è una funzione che non restituisce nulla (ovvero è
una procedura) e di cui − al momento− non si specifica l'elenco dei parametri
(cosa che andrà comunque fatta successivamente).
Si noti che, invece, la dichiarazione:
void incVettore ( void ) ;
specifica che la funzione incVettore non ha parametri. Contrariamente alla
precedente, questa definizione sarebbe incompatibile con la dichiarazione di
funzione fornita in precedenza.
Docente G. Armano
- 73 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: passaggio di parametri
Il passaggio di parametri avviene in C secondo una scelta di default, ovvero:
-
i vettori vengono passati "per indirizzo"
ciò significa che alla funzione viene passato l'indirizzo del primo
elemento del vettore. Tale indirizzo viene caricato − come valore
iniziale− nella variabile locale equivalente che ha il nome del parametro
formale;
-
tutti gli altri parametri vengono passati “per valore”
ciò significa che alla funzione viene passato il valore dell'espressione
specificata nell'invocazione della funzione. Tale valore viene caricato
− come valore iniziale− nella variabile locale equivalente che ha il nome
del parametro formale.
Docente G. Armano
- 74 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: passaggio di parametri
[II]
Esempio:
/* copia una stringa sino ad NMAX caratteri*/
char * stringCopyLim ( char DST[], const char SRC[], int NMAX)
{ char * ptr = DST ;
while ( NMAX-- > 0 )
{ if ( *ptr++ = *SRC++ ) continue ;
return DST ; } /* stringa SRC completata */
*ptr = '\0' ;
return DST ; /* copiati NMAX caratteri */
void main ( void )
{ char S1[128], S2[] = "pippo e pluto" ;
stringCopyLim(S1,S2,5) ;
printf("Stringa copiata = %s\n",S1) ; }
Output:
Stringa copiata = pippo
Docente G. Armano
- 75 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: passaggio di parametri
[III]
-
la funzione stringCopyLim prende in ingresso due stringhe, SRC e
DST passate per indirizzo (sono due vettori di caratteri) e la lunghezza
massima della stringa destinazione, NMAX, passata per valore.
-
durante l'attivazione di stringCopyLim vengono create le variabili
automatiche: ptr (dichiarata esplicitamente), DST, SRC, e NMAX
(parametri).
-
ptr, DST, SRC sono tutte variabili di tipo puntatore a carattere, mentre
NMAX è una variabile intera.
-
i valori iniziali di DST, SRC, e NMAX dipendono dai parametri attuali
specificati nell'invocazione della funzione. In questo caso, le
inizializzazioni sono, rispettivamente: E A(S1), E A(S2), E V(5)=5
-
per sottolineare che un parametro non cambierà nel corso
dell'esecuzione della funzione si può usare il qualificatore const. Per
questo motivo, nell'esempio precedente SRC è stato dichiarato const.
Docente G. Armano
- 76 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: passaggio di parametri
[IV]
Per modificare la scelta di default relativa alle modalità di passaggio dei
parametri, occorre ricorrere agli operatori “&” (indirizzo) e “*”
(indirezione).
Esempio:
/* scambia il contenuto di due variabili */
void floatExchange ( float *F1, float *F2)
{ float tmp = *F1; *F1=*F2; *F2 = tmp ; }
void main ( void )
{
float V1[] = { 1.0, 2.0, 3.0, 4.0 } ;
float X = 1.0, Y = -1.0 ; int i ;
...
floatExchange(&X,&Y) ;
floatExchange(&V1[1],&V1[2]) ;
printf("X = %4.1f, Y = %4.1f\n",X,Y) ;
for ( i=0; i < 4; i++)
printf("V1[%d] = %3.1f\n", i, V1[i]) ;
}
Output:
X = -1.0, Y =
V[0] = 1.0
V[1] = 3.0
V[2] = 2.0
V[3] = 4.0
Docente G. Armano
1.0
- 77 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: passaggio di parametri
[V]
L'uso degli operatori “&” e “*” è necessario per evitare di lavorare su copie
dei parametri attuali che verrebbero distrutte all'uscita dalla funzione.
Ecco come NON si deve fare per scambiare due variabili:
/* NON scambia il contenuto di due variabili */
void wrongFloatExchange ( float F1, float F2)
{ float tmp = F1; F1=F2; F2 = tmp ; }
void main ( void )
{
float V1[] = { 1.0, 2.0, 3.0, 4.0 } ;
float X = 1.0, Y = -1.0 ; int i ;
...
wrongFloatExchange(X,Y) ;
wrongFloatExchange(V1[1],V1[2]) ;
printf("X = %4.1f, Y = %4.1f\n",X,Y) ;
for ( i=0; i < 4; i++)
printf("V1[%d] = %3.1f\n", i, V1[i]) ;
}
Output:
X =
V[0]
V[1]
V[2]
V[3]
1.0, Y = -1.0
= 1.0
= 2.0
= 3.0
= 4.0
NB non è cambiato assolutamente nulla nei parametri attuali ! Tutte le
modifiche sono state infatti effettuate su copie dei parametri attuali (che
vengono distrutte all'uscita della funzione).
Docente G. Armano
- 78 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Procedure e Funzioni: passaggio di parametri
[VI]
Un esame più attento della corretta procedura di scambio dei valori di due
variabili.
void floatExchange ( float *F1, float *F2)
{ float tmp = *F1; *F1=*F2; *F2 = tmp ; }
-
la funzione prende in ingresso due puntatori a float.
-
durante l'attivazione di floatExchange vengono create le variabili
automatiche: tmp (dichiarata esplicitamente), F1, F2 (parametri).
-
F1 ed F2 sono variabili di tipo puntatore a float.
-
i valori iniziali di F1 ed F2 dipendono dai parametri attuali specificati
nell'invocazione della funzione.
-
ad esempio, nella prima chiamata (scambio dei valori di X e di Y), le
inizializzazioni di F1 ed F2 sono, rispettivamente: E V(&X)=E
E A(X),
E V(&Y)=E
E A(Y). In altri termini, tramite F1 ed F2, alla procedura
vengono passati gli indirizzi delle variabili di cui si vuole scambiare il
valore.
Docente G. Armano
- 79 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Parametri di tipo puntatore a funzione
Le procedure e funzioni possono anche essere passate come parametri. In tal
caso, la specificazione del parametro di tipo funzione dovrà comprendere
anche il tipo dei parametri da passare alla funzione puntata.
Esempio:
Supponiamo di dover realizzare una funzione di sort che ordini un vettore, e
di voler passare come parametri le funzioni che eseguono il confronto e lo
swap.
int compInt ( int *k, int *w ) ;
/* comparaz. di interi */
int compDouble ( double *k, double *w ) ;
/* comparaz. di double */
void swapInt ( void *ptr1, void *ptr2 ) ;
/* swap di interi */
void swapDouble ( void *ptr1, void *ptr2 ) ; /* swap di double */
void sort ( void * array, int dim,
int (*comp) (void *, void *),
void (*swap) ( void *, void * ) )
{
int i, j;
for ( i=0; i < dim-1 ; i++ )
for ( j=i+1; j < dim; j++ )
if ( (*comp)(&array[i],&array[j]) )
(*swap)(&array[i],&array[j]) ;
}
void main ( void )
{
int V1[100] ; /* array di interi */
double F1[200] ; /* array di double */
... /* inizializzazione vettori */
sort ( V1, 100, compInt, swapInt ) ; /* sort di interi */
sort ( F1, 200, compDouble, swapDouble ) ; /* sort di double */
...
}
Docente G. Armano
- 80 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output
Le primitive di I/O del C non fanno parte del linguaggio, ma vengono
definite come funzioni di libreria.
In C, come in Unix, l'I/O viene realizzato da / su file. La tastiera e il monitor
sono infatti trattati come file speciali (standard input e standard output).
Le funzioni C che realizzano l'I/O hanno, tipicamente, una versione
generalizzata per i file e una versione specializzata per lo standard input /
output.
Il tipo FILE, insieme alle funzioni che realizzano l'I/O, viene definito nel
file di include <stdio.h>.
Docente G. Armano
- 81 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output
[II]
L'I/O viene solitamente suddiviso in tre categorie:
-
caratteri
stringhe
generalizzato
bufferizzato
Docente G. Armano
- 82 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output di caratteri
[II]
Esiste una particolarità del C che riguarda l'I/O di caratteri: quando si vuole
realizzare l'I/O di caratteri occorre trattare i caratteri singoli (char) come se
fossero interi (int).
La motivazione di questa scelta risiede nel fatto che (storicamente) il
carattere di EOF non era un carattere del set ASCII originario.
Si noti che sulle macchine in cui sono stati implementati i primi compilatori
C la lunghezza degli interi era tipicamente 16 bit, di cui la parte alta veniva
lasciata a zero per tutti i caratteri eccetto che per il carattere di EOF
(tipicamente codificato con -1 su 16 bit, ovvero 0xFFFF).
int getchar(void) ;
int putchar(int ch) ;
/* Legge un char da stdin */
/* Scrive un char su stdout */
int getc(FILE *F) ;
int putc(int ch, FILE *F) ;
/* Legge un char da file */
/* Scrive un char su file */
int fgetc(FILE *F) ;
int fputc(int ch, FILE *F) ;
/* Legge un char da file */
/* Scrive un char su file */
Si noti che getchar e putchar possono essere definiti in termini di getc e
putc come segue:
#define getchar() getc(stdin)
#define putchar(ch) putc(ch,stdout)
NB getc e putc sono quasi equivalenti a fgetc e fputc. Si consulti un
manuale C per ulteriori informazioni. In particolare, si veda la funzione
ungetc che “restituisce” un carattere al file da cui si sta prelevando l'input.
Docente G. Armano
- 83 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output di stringhe
/* Legge una stringa da stdin */
char * gets ( char * ) ;
/* Scrive una stringa su stdout */
char * puts ( char * );
/* Legge sino a num char da file */
char * fgets ( char *str, int num, FILE *stream ) ;
/* Scrive una stringa su file */
char * fputs ( char *str, FILE *stream );
Docente G. Armano
- 84 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output generalizzato
La specificazione dei prototipi delle primitive che realizzano l'I/O
generalizzato viene fatta utilizzando la notazione ANSI-C "..." che indica un
numero variabile di parametri.
La notazione K&R-C corrispondente utilizza il parametro speciale arglist.
/* Legge da stdin un numero variabile di param. */
int scanf ( char *format, ... ) ;
/* Scrive su stdout un numero variabile di param. */
int printf ( char *format, ... ) ;
/* Legge da file un numero variabile di param. */
int fscanf ( FILE *stream, char *format, ... ) ;
/* Scrive su file un numero variabile di param. */
int fprintf ( FILE *stream, char *format, ... ) ;
Esiste anche la possibilità di effettuare un I/O da / su stringa, con le stesse
modalità viste per i file:
/* Legge da file un numero variabile di parametri */
int sscanf ( char *str, char *format, ... ) ;
/* Scrive su file un numero variabile di parametri */
int sprintf ( char *str, char *format, ... ) ;
Docente G. Armano
- 85 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output generalizzato: formattazione
Il parametro format specifica il formato per eseguire l'operazione di I/O.
All'interno della stringa di formattazione, le specificazioni di formato
vengono fornite facendole precedere dal carattere “%”.
format %
d,
o
x,
u
c
s
f
e,
g,
p
n
i
X
E
G
%
Docente G. Armano
conversione
intero, notazione decimale con segno
intero, notazione ottale priva segno
intero, notazione esadecimale priva segno
intero, notazione decimale priva segno
char, dopo la conversione a unsigned char
stringa, stampa sino al char speciale '\0'
floating / double (default 6 cifre decimali)
float / double notazione esponenziale
float / double usa alternativamente %e o %f
scrive un indirizzo di memoria
memorizza nell'argomento corrispondente il
numero di char scritti dalla printf (sino al
momento corrente)
stampa un %
- 86 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Input/Output bufferizzato
Viene utilizzato per realizzare I/O (tipicamente in formato binario) da / su
file.
/* Legge da file count elem.,ognuno di dimensione size */
int fread( void *buffer, int size, int count, FILE *stream );
/* Scrive su file count elem.,ognuno di dimensione size */
int fwrite( const void *buffer, int size, int count, FILE *stream );
/* Seek */
int fseek ( FILE * stream, long offset, int org );
NB la funzione fseek si sposta lungo il file a partire dall'origine, dalla
posizione corrente o dalla fine del file. La codifica dei valori che può
assumere il parametro org è la seguente:
SEEK_SET Inizio file
SEEK_CUR Posizione corrente
SEEK_END Fine file
Docente G. Armano
- 87 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Gestione file
Per accedere ad un file, occorre innanzitutto aprirlo, poi usarlo e poi
chiuderlo.
Per l'utilizzazione possono essere utilizzate le funzioni viste in precedenza.
L'apertura e la chiusura vengono realizzate tramite le seguenti funzioni:
/* Apre un file */
FILE * fopen ( char *name, char *mode ) ;
/* Chiude un file */
int fclose ( FILE *stream ) ;
Modalità di apertura di un file:
mode
"r", "w", "a"
"rb", "wb", "ab"
"r+", "w+", "a+"
"r+b", "w+b", "a+b"
Docente G. Armano
commento
lettura, scrittura, append (text file)
lettura, scrittura, append (binary file)
lettura / scrittura text file (open,
create, append)
lettura / scrittura binary file (open,
create, append)
- 88 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
LINGUAGGIO C: PARTE II
Docente G. Armano
- 89 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Gestione della memoria dinamica
Oltre alle zone di memoria riservate ai dati statici e allo stack, esiste un'altra
zona di memoria, chiamata heap, destinata a contenere strutture dati create
dinamicamente durante l'esecuzione di un programma.
La memoria dinamica viene gestita in C come un dato astratto; in altri
termini, l'implementazione della struttura dati dove vengono allocate le
variabili dinamiche viene nascosta (ovvero resa “trasparente”)
disciplinandone l'accesso tramite opportune primitive.
Sostanzialmente il programmatore può allocare strutture
successivamente disallocarle (quando non servono più).
dati
e
I prototipi delle primitive messe a disposizione dal linguaggio C per gestire
la memoria dinamica sono definiti nel file di include stdlib.h.
Tra esse ricordiamo le principali (size_t è un
tipicamente coincide con il byte):
void
void
void
void
tipo predefinito che
* malloc ( size_t size ) ;
* calloc ( size_t num, size_t size ) ;
* realloc ( void * ptr, size_t size ) ;
free ( void * ptr ) ;
Docente G. Armano
- 90 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
1
Gestione della memoria dinamica
[II]
-
malloc alloca uno spazio di memoria di dimensione size nello heap e
ne restituisce il puntatore. Se l'operazione non va a buon fine (ovvero
se non c'è memoria sufficiente per soddisfare la richiesta) viene
restituito NULL. Lo spazio di memoria non viene inizializzato.
-
calloc è una versione specializzata di malloc per allocare vettori.
Infatti, occorre specificare il numero di elementi (num) da allocare e la
dimensione (size) del singolo elemento. 1
-
realloc modifica, portandola a size, l'ampiezza della struttura dati
puntata da ptr. I contenuti restano invariati per uno spazio pari al
minimo tra la vecchia e la nuova ampiezza. L'eventuale nuovo spazio
non viene inizializzato. La funzione ritorna il puntatore alla nuova area,
oppure NULL (in caso di insuccesso).
-
free libera la memoria precedentemente allocata, rendendola di nuovo
disponibile per eventuali successive richieste. La funzione prende in
ingresso un puntatore che deve essere rigorosamente quello restituito
da una delle primitive di allocazione. In caso contrario è molto
probabile che − prima o poi− seguirà un crash del programma
(terminazione anormale).
Ovviamente: calloc(num,size) ≡ malloc(num*size), ma anche: malloc(size) ≡ calloc(1,size).
Docente G. Armano
- 91 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Gestione della memoria dinamica: qualche esempio
Supponiamo di voler allocare lo spazio per un dato di tipo Persona e di
inizializzare la struttura dati con il contenuto di una variabile automatica
dello stesso tipo. Successivamente, quando il dato non serve più vogliamo
disallocarlo.
#include <stdlib.h>
typedef struct persona
{ char cognome[25] ; char nome [20] ; ... } Persona ;
Persona * allocaPersona ( char * cognome, char * nome )
{ /* alloca una struttura di tipo Persona nello heap */
Persona * ptr = ( Persona * ) malloc ( sizeof(Persona) ) ;
/* inizializza la struttura dati - non ci interessa come */
...
return ptr ; }
void main ( void )
{
Persona * ptr1 ; /* definisce un puntatore a Persona */
...
ptr1 = allocaPersona("Salis","Carlo") ; /* crea il dato */
... /* usa il dato */
free(ptr1) ; /* disalloca il dato che non serve più */
...
}
Supponiamo ora di voler allocare lo spazio per un vettore di 100 elementi
tipo Persona. In tal caso, è consigliabile usare la calloc:
#include <stdlib.h>
typedef struct persona
{ char cognome[25] ; char nome [20] ; ... } Persona ;
void main ( void )
{
Persona * ptr1 ; /* definisce un puntatore a Persona */
...
/* crea un vettore di 100 elementi di tipo Persona */
ptr1 = (Persona *) calloc(100,sizeof(Persona)) ;
...
}
Docente G. Armano
- 92 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Gestione della memoria dinamica: qualche esempio
[II]
Supponiamo ora che, dopo aver allocato un vettore di 100 elementi, ci si
accorga che è invece necessario uno spazio superiore (ad es. 150). In tal
caso, dopo aver usato calloc o malloc, si può usare realloc.
#include <stdlib.h>
typedef struct persona
{ char cognome[25] ; char nome [20] ; ... } Persona ;
void main ( void )
{
Persona * ptr1 ; /* definisce un puntatore a Persona */
...
/* crea un vettore di 100 elementi di tipo Persona */
ptr1 = (Persona *) calloc(100,sizeof(Persona)) ;
...
/* alloca spazio per altre 50 persone */
ptr1 = ( Persona * ) realloc ( ptr1, 150 * sizeof(Persona) ) ;
...
}
Docente G. Armano
- 93 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Altre funzioni di libreria
Oltre alle funzioni per la gestione della memoria dinamica e a quelle per
l'I/O, il linguaggio C mette a disposizione altre librerie di funzioni. In
particolare, ricordiamo le seguenti:
- libreria per la gestione delle stringhe e dei caratteri
- libreria matematica
- libreria per la gestione del tempo
- libreria delle chiamate di sistema
- miscellanea
Docente G. Armano
- 94 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Libreria per la gestione delle stringhe e dei caratteri
Tra le funzioni messe a disposizione per gestire caratteri (prototipi in
ctype.h), ricordiamo le seguenti:
int
int
int
int
int
int
int
isalpha ( int ch ) ;
isdigit ( int ch ) ;
isxdigit ( int ch ) ;
islower ( int ch ) ;
isupper ( int ch ) ;
tolower ( int ch ) ;
toupper ( int ch ) ;
/*
/*
/*
/*
/*
/*
/*
verifica
verifica
verifica
verifica
verifica
converte
converte
char alfabetico */
cifra decimale */
cifra esadecimale */
char minuscolo */
char maiuscolo */
in maiuscolo */
in minuscolo */
Tra le funzioni messe a disposizione per gestire stringhe (prototipi in
string.h), ricordiamo le seguenti:
int strlen ( const char *str1 ) ;
char * strcat
char * strcmp
char * strcpy
int strcspn (
( char *str1, const char *str2 ) ;
( const char *str1, const char *str2 ) ;
( char *str1, const char *str2 ) ;
const char *str1, const char *str2 ) ;
char * strncat ( char *str1, const char *str2, size_t size ) ;
char * strncmp ( const char *str1, const char *str2, size_t size ) ;
char * strncpy ( char *str1, const char *str2, size_t size ) ;
int strnspn ( const char *str1, const char *str2 ) ;
char * strchr ( const char *str1, int ch ) ;
char * strrchr ( const char *str1, int ch ) ;
char * strstr ( const char *str1, const char *str2 ) ;
Docente G. Armano
- 95 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Libreria per la gestione delle stringhe e dei caratteri
[II]
-
strlen calcola la lunghezza di una stringa.
-
strcat concatena str2 a str1;
strcmp confronta str1 e str2 secondo l'ordinamento alfabetico e ritorna
-1 se str1 < str2, +1 se str2 < str1, 0 se le due stringhe sono uguali;
strcpy copia str2 in str1;
strcspn ritorna l'indice del primo carattere di str1 che appartiene a str2
(che viene usata come bag, ovvero come "contenitore" di caratteri). In
caso di fallimento ritorna NULL.
-
-
strncat, strncmp, strncpy, strnspn sono le versioni di strcat, strcmp,
strcpy, strspn limitate ai primi size caratteri (ad es.: strncat concatena
al più size caratteri di str2 a str1).
-
strchr (strrchr) ritorna il puntatore alla prima (all'ultima) occorrenza
del carattere ch nella stringa str. In caso di fallimento ritorna NULL.
-
strstr ritorna il puntatore alla prima occorrenza della sottostringa str2
trovata in str1. In caso di fallimento ritorna NULL.
Docente G. Armano
- 96 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Libreria matematica
Tra le funzioni messe a disposizione dalla libreria matematica (prototipi in
math.h), ricordiamo le seguenti:
double cos ( double x ) ;
double sin ( double x ) ;
double tan ( double x ) ;
/* coseno, seno, tangente */
double acos ( double x ) ; /* arcocoseno, arcoseno, arcotangente */
double asin ( double x ) ;
double atan ( double x ) ;
double atan2 ( double x, double y ) ; /* arcotangente di x/y */
double cosh ( double x ) ; /* coseno, seno, tangente iperbolici */
double sinh ( double x ) ;
double tanh ( double x ) ;
double ceil ( double x ) ; /* ritorna il più piccolo intero >= x */
double floor ( double x ) ; /* ritorna il più grande intero <= x */
double exp ( double x ) ; /* esponenziale */
double pow ( double x, double y ) ; /* x elevato ad y */
double sqrt ( double x ) ; /* radice quadrata */
double fabs ( double x ) ; /* valore assoluto (double) */
double fmod ( double x, double y ) ; /* modulo (double) */
double log ( double x ) ; /* logaritmo naturale */
double log10 ( double x ) ; /* logaritmo base 10 */
Docente G. Armano
- 97 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Libreria per la gestione del tempo
Le strutture dati coinvolte nelle operazioni che riguardano il tempo sono
(i) time_t, tipicamente espresso tramite un unsigned long / long, e
(ii) struct tm, che riporta il tempo sotto forma di struttura (con anno, mese,
giorno, ore, minuti, secondi, ecc.). Il tempo viene misurato a partire dal
1900.
Vediamo meglio la forma della struct tm:
struct tm
{
int tm_sec ;
int tm_min ;
int tm_hour ;
int tm_mday ;
int tm_mon ;
int tm_year ;
int tm_wday ;
int tm_yday ;
int tm_isdst ;
}
/*
/*
/*
/*
/*
/*
/*
/*
/*
secondi dopo il minuto [0-59] */
minuti dopo l'ora [0-59] */
ore dopo la mezzanotte [0-23] */
giorno del mese [1-31] */
mese a partire da Gennaio [0-11] */
anno dopo il 1900 */
giorno dopo la Domenica [0-6] */
giorno a partire da Gennaio [0-365] */
ora legale 1=si, 0=no, -1=info non disp. */
Tra le funzioni messe a disposizione per gestire il tempo (prototipi in
time.h), ricordiamo le seguenti:
time_t time ( time_t * ptr ) ;
struct tm * localtime ( const time_t * ptr ) ;
struct tm * gmltime ( const time_t * ptr ) ;
char * asctime ( struct tm * ptr ) ;
char * ctime ( const time_t * ptr ) ;
Docente G. Armano
- 98 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Libreria per la gestione del tempo
[II]
-
time ritorna l'ora corrente, oppure -1 se l’ora non è disponibile. Se
l'argomento ptr è diverso da NULL il valore di ritorno viene anche
assegnato alla locazione puntata da ptr.
-
localtime converte in ora locale (in forma di struct tm) l'ora contenuta
in ptr (in forma di time_t).
-
gmltime converte in ora assoluta (in forma di struct tm) l'ora
contenuta in ptr (in forma di time_t).
-
asctime converte in ASCII un tempo espresso in forma di struct tm.
-
ctime converte in ASCII un tempo espresso in forma di time_t.
Si noti che: ctime(ptr) ≡ asctime(localtime(ptr))
Docente G. Armano
- 99 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Libreria delle chiamate di sistema
Tra le funzioni messe a disposizione per effettuare chiamate al sistema
operativo (prototipi in stdlib.h e in process.h), ricordiamo le seguenti:
void abort ( void ) ; /* termina il programma in modo anormale */
void exit ( int status ) ; /* termina il programma normalmente */
NB exit può anche essere usata per terminare il programma in modo
“anormale”. Infatti, per convenzione, status = 0 indica una terminazione
normale, mentre altri valori indicano una terminazione anormale.
int execl ( char *fname, char * arg0, ..., char * argN, NULL ) ;
Il gruppo di funzioni exec (ce ne sono altre oltre a quella indicata) viene
usato per far partire un altro processo (child process) durante l'esecuzione
del programma. Il nome del file che contiene il nuovo programma da
eseguire è puntato da fname e gli eventuali argomenti sono specificati di
seguito (la lista termina con NULL).
Docente G. Armano
- 100 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Librerie: miscellanea
Tra le altre funzioni di libreria ricordiamo le seguenti:
int atoi ( const char * anInt ) ;
/* conv. stringa in int */
long atol ( const char * aLong ) ;
/* conv. stringa in long */
double atof ( const char * aFloat ) ; /* conv. stringa in double */
int rand ( void ) ;
int random ( int num ) ;
void randomize ( void ) ;
/* genera num. casuale tra 0 e RAND_MAX */
/* genera num. casuale tra 0 e num-1 */
/* iniz. il generatore di numeri random */
Per gestire funzioni con un numero di parametri variabile occorre ricorrere
alle seguenti macro (= macro istruzioni), definite in stdarg.h:
void va_start ( va_list argPtr, lastParam ) ;
void va_arg ( va_list argPtr, type ) ;
void va_end ( va_list argPtr ) ;
Le macro utilizzano il tipo va_list definito anch'esso in stdarg.h. Non
occorre e non si deve conoscere l'effettiva implementazione di va_list. Le
funzioni con numero variabile di parametri devono avere almeno un
parametro che precede la lista dei parametri di cui non si conosce nome e
numero.
Un tipico esempio di funzione con numero variabile di parametri è la
funzione di libreria di I/O printf. Il suo prototipo è il seguente:
/* K&R-C */
int printf ( const char * format, arg_list ) ;
/* ANSI-C */
int printf ( const char * format, ... ) ;
Si noti che QUI la notazione "...", che fa parte del linguaggio C, indica un
numero variabile di parametri. Nel resto di questi appunti è invece stata
spesso usata per indicare qualcosa di non interessante ai fini dell'esempio
considerato.
Docente G. Armano
- 101 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Librerie: miscellanea
[II]
Una versione semplificata di printf (per illustrare l'uso di va_start, va_arg,
va_end) che stampa soltanto interi, double e stringhe di caratteri (si fa
l’ipotesi di avere a disposizione funzioni specializzate per la stampa di
interi, double e caratteri):
#include <stdarg.h>
extern void printInt(int value) ;
/* stampa un int */
extern void printDouble(double value) ; /* stampa un float */
extern void printChar(int value) ;
/* stampa un char */
void simplePrintf ( char * format, ... )
{ va_list argPtr ; char *p, *sval ; int ival; double dval;
va_start ( argPtr, format ) ;
for ( p = format ; *p ; p++ )
{
if ( *p != '%' )
{ putchar(*p) ; continue ; }
switch ( *++p )
{
case 'd' : /* specifica di stampa di numero intero */
ival = va_arg ( argPtr, int ) ;
printInt ( ival ) ; break ;
case 'f' : /* specifica di stampa di numero float */
fval = va_arg ( argPtr, double ) ;
printFloat ( fval ) ; break ;
case 's' : /* specifica di stampa di una stringa */
sval = va_arg ( argPtr, char * ) ;
while ( *sval ) printChar ( *sval++ ) ; break ;
default :
putchar(*p) ; break ; /* per stampare il % */
}
}
va_end(argPtr) ; /* altrimenti probabile crash del programma */
}
Docente G. Armano
- 102 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Altre caratteristiche del linguaggio (macro e direttive)
Il linguaggio C consente di definire macro di utilizzo generale, ovvero
costanti o sequenze di caratteri che prima della compilazione vera e propria
vengono “espanse”.
Esempi:
#define
#define
#define
#define
#define
pigreca 3.1415
currentPath "pippo/source/"
max(x,y) ( ( (x) > (y) ) ? (x) : (y) )
New(type) (type *) malloc ( sizeof(type) )
NewVector(dim,type) (type *) calloc ( dim, sizeof(type) )
Si noti che max, New e NewVector hanno anche parametri. La loro
“espansione” comporta la sostituzione della parte sinistra con la parte destra
prima che venga effettuata la compilazione vera e propria.
Docente G. Armano
- 103 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
u
Altre caratteristiche del linguaggio (macro e direttive)
[II]
Al compilatore possono essere anche fornite direttive condizionali, che
possono fare cambiare una compilazione senza che il testo sorgente venga
modificato.
Esempio:
void foo ( int value )
{
#ifdef DEBUG
printf("pluto: value = %d\n",value) ;
#endif
... /* codice della funzione */
}
In questo caso, l'effetto della direttiva #ifdef è quello di valutare se esiste
una definizione di DEBUG oppure no. Se esiste, allora viene compilata
anche la parte tra #ifdef e #endif, altrimenti si compila soltanto il (vero e
proprio) codice della funzione.
La DEBUG dell'esempio può essere definita una volta per tutte nel main,
oppure specificata all'atto della compilazione. Facendo l'ipotesi che il nome
del file contenente la funzione foo sia pippo.c, la sua compilazione potrebbe
essere effettuata nel modo seguente (usando l'opzione -D del compilatore):
$ cc -DDEBUG -c pippo.c
Docente G. Armano
- 104 -
Appunti sul linguaggio C
Univ. degli Studi di Cagliari
♦
Bibliografia sul Linguaggio C
•
B. W. Kernighan, D.M. Ritchie, Linguaggio C, Jackson
•
Davies, The Indispensable Guide to C, Addison Wesley
•
B. Gottfried, Programmare in C, McGraw Hill (Collana Schaum)
•
H. Schildt, Linguaggio C, McGraw Hill
Docente G. Armano
- 105 -
Appunti sul linguaggio C