il linguaggio c - people.ding.unisannio.it
TRANSCRIPT
1
Il linguaggio C 1/243
Il linguaggio C
Corso di Programmazione di Sistema
a.a. 2005/06
Versione 0.8 – preparata dall’ing. E. Mancini sulla base dei lucidi del prof. Ghini, Univ. Di Bologna
Il linguaggio C 2/243
Riferimenti
• Brian W. Kernighan, Dennis M. Ritchie, "Linguaggio C",
ed. Pearson Education Italia
• Webpage del corso: web.ing.unisannio.it/villano/ps0506
2
Il linguaggio C 3/243
Caratteristiche del linguaggio C
• Utilizzo frequente di chiamate a funzioni.
• Debole controllo sui tipi di dato: a differenza del Pascal, il C permette di operare con assegnamenti e confronti su dati di tipo diverso, in qualche caso solo mediante un type cast(conversione di tipo) esplicito.
• Linguaggio strutturato. Il C prevede costrutti per il controllo di flusso, quali raggruppamenti di istruzioni, blocchi decisionali (if-else), selezione di alternative (switch), cicli con condizione di terminazione posta all'inizio (while, for) o posta alla fine (do) e uscita anticipata dal ciclo (break).
Il linguaggio C 4/243
Caratteristiche del linguaggio C
• programmazione a basso livello facilmente disponibile
• implementazione dei puntatori: l’uso di puntatori per memoria, vettori, strutture e funzioni rende facile commettere errori.
• portabilità sulla maggior parte delle architetture.
• disponibilità di librerie standard.
3
Il linguaggio C 5/243
Il primo programma in linguaggio C
Un programma minimo in C e':
main()
{
}
che corrisponde a un programma in Java:
class PrimoProg {
public static void main(String[] args) {
}
}
Il linguaggio C 6/243
Caratteristiche del linguaggio C
• Lo standard per i programmi C in origine era basato sulla
prima edizione del testo di Kernighan e Ritchie (K&R).
• Al fine di rendere il linguaggio più accettabile a livello
internazionale, venne messo a punto uno standard
internazionale chiamato ANSI C (American National
Standards Institute). Questo standard è quello descritto nella
seconda edizione del libro (ANSI/ISO/IEC 9899:1990)
• Standard internazionale corrente (non Americano!) per il C:
ISO/IEC 9899:1999 (noto come C99)
4
Il linguaggio C 7/243
Caratteristiche di ogni programma C
• Ogni programma C deve contenere una e una sola funzione main(), che rappresenta il programma principale ed il
punto di inizio dell'esecuzione del programma.
• La parentesi graffa aperta { indica l’inizio di un blocco di
istruzioni mentre la parentesi graffa chiusa } ne indica la
fine esattamente come in Java, e corrispondono al begin -
end del Pascal.
• Per ogni parentesi graffa aperta { deve essercene una
chiusa }.
Il linguaggio C 8/243
Caratteristiche di ogni programma C
• I commenti possono essere posti ovunque. L'inizio del commento è dato dalla coppia /*
• La fine del commento è indicata da */
• Non si può inserire un commento in un altro (vietato
l’annidamento dei commenti).
• Molti compilatori ammettono anche l’uso dei commenti in stile C++ con la doppia barra //, presenti anche in Java.
5
Il linguaggio C 9/243
Esempi
Ad esempio:
/* Esempio di programma con problemi nei commenti */
main()
{
/* Un commento */ ESATTO
/* Commento /* Ancora un commento */ QUI ERRORE */
}
Il linguaggio C 10/243
Esempi
Il seguente esempio e' un programma che produce l'output sullo schermo della frase "Hello World":
#include <stdio.h>
int main()
{
printf("Hello World \n");
exit(0);
}
6
Il linguaggio C 11/243
In C, occorre un punto e virgola ; dopo ogni istruzione.
L'istruzione printf(...) chiama la funzione printf che
visualizza ciò che gli viene passato come argomento (\nindica l'andata a capo), e ricorda la System.out.println() di Java e la write del Pascal.
L'istruzione exit(0) chiama la funzione exit che termina il
programma e restituisce al sistema operativo il codice 0.
Caratteristiche di ogni programma C
Il linguaggio C 12/243
In C non esiste la distinzione che esiste in Pascal tra funzionie procedure, e neppure esistono metodi come in Java. In C sono tutte funzioni, che possono restituire un qualche risultatooppure no.
Le chiamate ad ogni funzione in C si effettuano chiamando il nome della funzione seguita dalle parentesi tonde, aperta e chiusa, all'interno delle quali vengono passati i parametri necessari.
Anche se la funzione non richiede nessun parametro, il suo nome deve essere seguito dalle parentesi tonde aperta e chiusa.
Caratteristiche di ogni programma C
7
Il linguaggio C 13/243
Sviluppo di un Programma in linguaggio C
Il linguaggio C 14/243
Creare il programma
Create un file contenete il programma completo. Per creare il file
potete usare ogni editor ordinario con il quale abbiate familiarità
(vi, kedit, nedit).
Il nome di file deve per convenzione terminare con “.c”, (es.
myprog.c o progtest.c).
I contenuti devono essere conformi alla sintassi C.
8
Il linguaggio C 15/243
Compilazione
• Ci sono molti compilatori C disponibili. cc è il compilatore di default nei sistemi Unix. Il compilatore GNU C gcc è popolare e disponibile per molte piattaforme. Gli utenti di PC potrebbero avere familiarità col compilatore Borland bcc. Per i più audaci: Intel C compiler per Linux icc (http://developer.intel.com)
• Ci sono anche compilatori equivalenti C++ che di solito si chiamano CC (notate il maiuscolo). Per esempio, CC e GNU GCC. Il compilatore GNU si chiama anche g++ .
• Esistono altri compilatori C/C++ meno comuni.
• Tutti i compilatori citati sopra operano essenzialmente nello stesso modo e hanno molte opzioni command line in comune.
Il linguaggio C 16/243
Compilazione
La migliore fonte di informazioni su ogni compilatore è il suo manuale in linea: e.g. man cc.
Per compilare in vostro programma semplicemente invocate il comando cc seguito dal nome del programma (C) che volete
compilare.
E’ possibile specificare anche alcune opzioni di compilazione.
Quindi il comando di compilazione di base è: cc program.c
dove program.c è il nome del file.
9
Il linguaggio C 17/243
Compilazione
Se ci sono errori ovvi nel programma (di battitura, scrittura
sbagliata di una delle keywords o omissione di un punto e virgola),
il compilatore li troverà e li riporterà.
Ci possono essere ovviamente errori logici che il compilatore non
può scoprire. Potreste stare chiedendo al computer di fare
operazioni sbagliate.
Quando il compilatore ha “digerito” con successo il vostro
programma, la versione compilata (eseguibile) è lasciata in un file
chiamato a.out o, se viene usata l’opzione -o, nel file citato dopo il
-o.
Il linguaggio C 18/243
Compilazione
E’ più conveniente usare un -o e il nome del file nella
compilazione come in
cc -o program program.c
che mette il programma compilato nel file program (o in un
qualsiasi file citato nel nome che segue l’argomento “-o”) invece
di metterlo nel file a.out .
10
Il linguaggio C 19/243
Esecuzione
Il passo successivo è di eseguire il programma compilato. Per
lanciare un eseguibile in UNIX, basta semplicemente digitare il
nome del file che lo contiene, in questo caso program (o a.out),
preceduto dalla working directory corrente (./program, ./a.out).
Questo manda in esecuzione il programma, stampando i risultati
sullo schermo. A questo punto ci possono essere errori run-time,
come divisioni per zero, o può divenire evidente che il programma
ha prodotto un output non corretto.
In tal caso, occorre editare il sorgente del programma, ricompilarlo
e eseguirlo di nuovo.
Il linguaggio C 20/243
Il modello di Compilazione per il C
Gli step essenziali della
compilazione per un
programma sviluppato in C
sono rappresentati nel
seguente schema:
11
Il linguaggio C 21/243
Il Preprocessore
Il Preprocessore accetta codice sorgente in ingresso e ha la
responsabilità di
rimuovere i commenti,
interpretare preprocessor directives, indicate dal simbolo #.
Il linguaggio C 22/243
Il PreprocessorePer esempio
#include – include i contenuti del file nominato.
I file inclusi sono tipicamente header files.
#include <math.h> -- standard library maths file.
#include <stdio.h> -- standard library I/O file.
#define – definisce un nome simbolico o costante.
Macro substitution. #define MAX_ARRAY_SIZE 100
12
Il linguaggio C 23/243
Il Preprocessore
2) elimina i commenti contenuti nel sorgente.
3) genera così un nuovo file senza commenti e senza più necessità di
effettuare sostituzioni, pronto per essere processato dal compilatore.
/* file header h*/
extern mt, ni;
extern double f;
/* file prova.c */
#include "header.h"
/* size vettore */
#define SIZE 10
int vettore[SIZE];
/* file prova.E */
extern int i;
extern double f;
int vettore[10];
Preprocessore
Il linguaggio C 24/243
Il compilatore e l’assemblatore
Il compilatore C traduce codice sorgente in codice assembly. Il
codice sorgente è quello prodotto dal preprocessore.
L’assembler crea codice oggetto. In un sistema UNIX si possono
vedere file con un suffisso .o (.OBJ in MSDOS/WIN) che indica
file di codice oggetto.
13
Il linguaggio C 25/243
Il linkage editor (linker)
Se un file sorgente fa riferimento a funzioni di libreria o a
funzioni definite in altri file sorgente il linkage editor combina
queste funzioni (col main()) per creare un file eseguibile.
Anche i riferimenti a variabili esterne sono risolti in questa fase.
Il linguaggio C 26/243
Alcune opzioni del compilatore
-c
Sopprime il processo di linking e produce un file .o per
ogni file sorgente listato. Questi possono essere
successivamente linkati sempre col comando cc:
cc file1.o file2.o ...... -o executable
14
Il linguaggio C 27/243
Alcune opzioni del compilatore
-llibrary
Linkare con librerie oggetto. Questa opzione deve seguire
gli argomenti di tipo file sorgente. Le librerie oggetto sono
archiviate e possono essere standard, di terze parti o create
dall’utente. Probabilmente la libreria più usata è la math
library (libm.a). Occorre linkare questa libreria esplicitamente
se si vogliono usare le funzioni matematiche (non dimenticare
anche di #include l’header file <math.h> ), ad esempio:
cc calc.c -o calc -lm
Il linguaggio C 28/243
Alcune opzioni del compilatore
-Ldirectory
Aggiunge directory alla lista di directory contenente
object-library routines. Il linker cerca sempre le librerie
standard e di sistema in /lib and /usr/lib. Se si vogliono linkare
librerie che sono state create o installate dall’utente (a meno
che questi non abbia i privilegi necessari ad installare le
librerie in /usr/lib) è necessario specificare la directory dove i
file sono memorizzati, ad esempio:
cc prog.c -L/home/myname/mylibs mylib.a
15
Il linguaggio C 29/243
Alcune opzioni del compilatore-Ipathname
Aggiunge pathname alla lista di directory in cui cercare
#include files con filename relativi (che non cominciano con
slash /). Per default, il preprocessore prima cerca #include files
nella directory contenente il file sorgente, poi nelle in
directories indicate con le opzioni -I options (se presenti), e
finalmente, in /usr/include. Quindi per includere header files
memorizzati in /home/myname/myheaders occorre:
cc prog.c -I/home/myname/myheaders
Nota: I system library header files sono memorizzati in una
posizione speciale (/usr/include) e non sono influenzati dall’uso
dell’opzione -I. System header files e user header files vengono trattati in maniera lievemente differente.
Il linguaggio C 30/243
Alcune opzioni del compilatore
-g
invoca l’opzione di debugging. Questa dice al compilatore di
produrre informazioni addizionali sulla tabella dei simboli che
vengono usate da una varietà di tool di debugging.
-D
definisce simboli o come identificatori (-Didentifer) o come
valori (-Dsymbol=value) in una maniera del tutto simile al
comando del preprocessore #define .
16
Il linguaggio C 31/243
Le librerie
Il C è un linguaggio estremamente piccolo: molte delle funzioni
disponibili in altri linguaggi non sono presenti in C (e.g. niente
I/O, funzioni per gestione stringhe o matematiche).
A che serve il C allora?
Il C fornisce funzionalità mediante un ricco insieme di librerie di
funzioni.
Come risultato la maggior parte delle implementazioni C
includono librerie standard di funzioni per molte funzionalità ( I/O
etc.). Dal punto di vista pratico queste possono essere considerate
come facenti parte del C. Ma possono variare da macchina a
macchina (Borland C vs. UNIX C).
Il linguaggio C 32/243
Le librerie
Il programmatore può anche sviluppare le sue librerie di funzioni o
anche usare librerie di terze parti special purpose (e.g. NAG,
PHIGS).
Tutte le librerie (eccetto quelle di standard I/O) devono essereesplicitamente linkate con le opzioni del compilatore -l e,
possibilmente, -L, descritte prima.
Il sistema UNIX dispone di un ampio numero di funzioni di
libreria C. Alcune di queste implementano operazioni usate di
frequente, mentre altre sono di utilizzo specialistico.
17
Il linguaggio C 33/243
Le librerie
Non Reinventare la Ruota: E’ saggio per il
programmatore vedere se esiste una funzione di libreria
che esegua un certo compito prima di scriverne una
propria versione. Questo riduce il tempo di sviluppo del
programma. Le funzioni di libreria sono state testate,
quindi è più probabile che siano corrette rispetto a
qualsiasi funzione che possa essere scritta per
l’occasione. Questo riduce anche i tempi di debugging
del programma.
Il linguaggio C 34/243
Le librerie
Il manuale UNIX ha un entry per ciascuna funzione disponibile.
La documentazione delle funzioni è memorizzata nella sezione 3
del manuale, e molte utili system calls sono contenute nella
sezione 2. Se conoscete già il nome della funzione cercata, si può
leggere la pagina digitando (ad es., per sqrt):
man 3 sqrt
Se non conoscete il nome della funzione, una lista completa e’
inclusa nella pagina introduttiva della sezione 3 del manuale. Per
leggerla, digitate
man 3 intro
Ci sono circa 700 funzioni descritte qui. Questo numero tende a crescere ad ogni upgrade del sistema.
18
Il linguaggio C 35/243
Le librerie
Su ogni pagina del manuale, la sezione SYNOPSIS include
informazioni sull’uso della funzione. E.g.: #include <time.h>
char *ctime(time_t *clock)
Questo significa che occorre includere #include <time.h>
nel file sorgente prima di chiamare ctime. E che la funzione ctime
ha un puntatore al tipo time_t come argomento, e ritorna una
stringa (char *). time_t probabilmente viene definita nella stessa
pagina del manuale.
La sezione DESCRIPTION dà poi una breve descrizione di quello
che fa la funzione. Per esempio:
ctime() converte un long integer, puntato da clock, in una stringa di
26 caratteri del tipo prodotto da asctime().
Il linguaggio C 36/243
Struttura di un programma C
Un programma C ha in linea di principio la
seguente forma:
•Direttive per il preprocessore
•Definizione di tipi
•Prototipi di funzioni, con dichiarazione
dei tipi delle funzioni e delle variabili
passate alle funzioni)
•Dichiarazione delle Variabili Globali
•Dichiarazione Funzioni, dove ogni
dichiarazione di una funzione ha la forma:
tipo NomeFunzione(Parametri) { … }
•Dichiarazione variabili locali
•Istruzioni C
#include <stdio.h>
typedef struct point {
int x; int y;
};
int f1(void);
void f2(int i, double g);
int sum;
int main(void) {
int j;
double g=0.0;
for(j=0;j<2;j++)
f2(j,g);
return(2);
}
void f2(int i, double g)
{
sum = sum + g*i;
}
19
Il linguaggio C 37/243
Il preprocessore C
• Il preprocessore C modifica un codice sorgente prima
di passarlo al compilatore.
• Viene prevalentemente adoperato per includere filesdirettamente dentro altri files (#include), o per
definire costanti (#define).
• Può anche essere usato per creare inlined code usando
macro espanse al compile time e per prevenire che del
codice sia compilato più volte.
Il linguaggio C 38/243
Direttive del preprocessore
• Ci sono essenzialmente tre utilizzi del preprocessore: direttive, constanti e macro.
• Le direttive sono comandi che dicono al preprocessore di tralasciare parte di un file, di includere un altro file, o di definire una constante o una macro. Le direttive cominciano sempre con uno sharp sign (#) e, per leggibilità, dovrebbero essere
collocate alla sinistra della pagina.
• Tipicamente, constanti e macro sono scritte in ALL CAPS per indicare chiaramente cosa sono.
20
Il linguaggio C 39/243
Direttive del preprocessore
• Header Files
La direttiva #include dice al preprocessore
di prendere il testo di un file e di piazzarlo
all’interno del file corrente.
Tipicamente, questi statements sono collocati
in testa al programma - da cui il nome di
“header file” per i file inclusi in tal modo.
Il linguaggio C 40/243
Costanti#define [identifier name] [value]
Ogni volta che [identifier name] compare nel file, sarà rimpiazzato da [value]. Si noti che tutto quello che segue [identifier name] sarà
parte del rimpiazzo. Questo può portare a strani risultati: per es., è una cattiva idea usare commenti in C++ style in linee #define:
#define PI 3.14 // This is pi
x = PI + 1; // oops!
Nella linea precedente, PI sarà rimpiazzata da “3.14 // This is pi”, che commenterà il “ + 1;”,
causando un syntax error difficile da trovare.
21
Il linguaggio C 41/243
Costanti
Se dovete definire una costante in termini di una espressione matematica, è saggio includere l’intero valore in parentesi:
#define PI_PLUS_ONE (3.14 + 1)
Così facendo, si evita che l’ordine di valutazione delle espressioni distrugga il significato della costante:
x = PI_PLUS_ONE * 5;
Senza parentesi, la precedente sarebbe convertita in
x = 3.14 + 1 * 5;
che avrebbe portato al valutare 1 * 5 prima dell’addizione, e non dopo!!
Il linguaggio C 42/243
Definizioni vuote
E’ anche possibile scrivere semplicemente #define [indentifier name]
che definisce [identifier name] senza
assegnargli un valore.
Questo può servire insieme con un altro set di
direttive che consentono la compilazione
condizionale.
22
Il linguaggio C 43/243
Compilazione condizionale
• C’è un insieme di opzioni che può esser usato per
determinare se il preprocessore rimuoverà linee di
codice prima di passare il file al compilatore:
#if, #elif, #ifdef, e #ifndef.
• Un blocco #if o #if/#elif/#else o un blocco
#ifdef o #ifndef deve essere terminato da un
#endif.
• La direttiva #if prende un argomento numerico che
viene valutato true se è non-zero. Se il suo argomento
è false, allora il codice fino all’ #else, #elif, o
#endif di chiusura sarà escluso.
Il linguaggio C 44/243
Compilazione condizionale
Commenting out Code
• La compilazione condizione è particolarmente utile per
“comment out” un blocco di codice che contiene commenti
multi-line (che non possono essere innestati).
#if 0
/* comment ...
*/
code
/* comment */
#endif
23
Il linguaggio C 45/243
Compilazione condizionale
Evitare di includere file più volte (idempotenza)
• Un altro problema comune è che un header file è richiesto in più altri header files che poi sono inclusi in un source code file, con il risultato che variables, structs, classes e functionsappaiono definite più volte (una per ogni volta che l’headerfile viene incluso).
• Usando la direttiva #ifndef, si può includere un blocco di
testo solo se una particolare espressione è non definita; poi, nell’header file, si può definire l’espressione
#ifndef _FILE_NAME_H_
#define _FILE_NAME_H_
/* code */
#endif // #ifndef _FILE_NAME_H_
Il linguaggio C 46/243
Macro
• L’altro utilizzo prevalente del preprocessore è per definire macro. Il vantaggio di una macro è che può essere type-neutral (ma questo a volte è uno svantaggio!), e che viene inlined direttamente nel codice, cosicché non c’è nessun overhead per la chiamata di funzioni. (In C++, è possibile fare entrambe le cose usando templated functions e la keyword inline.)
• Una definizione di macro ha di solito la forma:
#define MACRO_NAME(arg1, arg2, ...) [code to expand to]
• Per es.:
#define INCREMENT(x) x++
24
Il linguaggio C 47/243
Macro e funzioniLe macro somigliano a chiamate di funzioni, ma ci sono almeno
un paio di trucchi da tenere presente:
Il testo esatto di una macro viene inserito nella macro
#define MULT(x, y) x * y
usando int z = MULT(3 + 2, 4 + 2)
z non assumerà il valore 30!!!
Espandendo la macro MULT:int z = 3 + 2 * 4 + 2;
2 * 4 viene valutato prima, e z assume il valore 13!
Per evitare tutto questo, bisogna forzare la valutazione degli argomenti prima del resto del corpo della macro. Questo può essere ottenuto usando le parentesi nella definizione di macro:#define MULT(x, y) (x) * (y)
ora MULT(3+2, 4+2) viene espanso in (3+2) * (4+2)
Il linguaggio C 48/243
Macro e funzioni
E’ anche di solito una buona idea includere il codice della macro
in parentesi se ci si aspetti che ritorni un valore. Altrimenti, ci
sono problemi simili a quelli per la def. di costanti.
Per es., la seguente macro, che aggiunge 5 all’argomento, ha
problemi quando viene inserita in uno statement più grande:
#define ADD_FIVE(a) (a) + 5
int x = ADD_FIVE(3) * 3; // this expands to
(3) + 5 * 3, so 5 * 3 is evaluated first //
Ora x è 18, non 24!
La soluzione è racchiudere l’intera macro in parentesi
#define ADD_FIVE(a) ((a) + 5) int x =
ADD_FIVE(3) * 3;
25
Il linguaggio C 49/243
Macro e funzioniD’altra parte, una macro multilinea usata per i suoi side effects e non per calcolare un valore, va racchiusa tra parentesi graffe in maniera tale da poterla utilizzare anche dopo uno statement if
#define SWAP(a, b) a ^= b; b ^= a; a ^= b;
int x = 10; int y = 5;
SWAP(x, y); // works OK
// What happens now?
if(x < 0) SWAP(x, y);
Nel secondo esempio, solo il primo statement, a ^= b, è gestito dal condizionale: gli altri due statements vengono eseguiti sempre!
Usando le parentesi graffe, la cosa funziona:
#define SWAP(a, b) {a ^= b; b ^= a; a ^= b;}
Per inciso, gli argomenti non sono in parentesi perché non saranno mai espressioni!!!
Il linguaggio C 50/243
Macro e funzioni
Altri problemi
Il problema più irritante con le macro è evitare di passare alle macro argomenti con "side effects".
Side effect: ogni espressione che fa qualcosa oltre a valutare un valore. (es.: ++x valuta x+1, ma incrementa anche x).Le macro non valutano gli argomenti, ma si limitano ad inserirlinel testo.
#define MAX(a, b) ((a) < (b) ? (b) : (a))
int x = 5, y = 10;
int z = MAX(x++, y++);
diventa:
int x = (x++ < y++ ? y++ : x++)
E y++ viene valutato due volte (y assumerà il valore 12 anziché11)
26
Il linguaggio C 51/243
Macro multilinea
Usando "\" Per indicare la continuazione di una linea, è possibile scrivere le macro su più righi per renderle più leggibili.
#define SWAP(a, b) { \
a ^= b; \
b ^= a; \
a ^= b; \
}
Non serve uno slash alla fine dell’ultima riga (lo slash dice al preprocessore che la macro continua alla linea successiva, non che la linea è la continuazione della linea precedente).
Il linguaggio C 52/243
Identificatori
Gli identificatori per il C possono essere usati per indicare variabili,
funzioni, etichette e dati di tipo definito dall'utente.
Un identificatore deve essere costituito da uno o più caratteri. Il
primo carattere di un identificatore (quello più a sinistra) deve essere
una lettera o una sottolineatura (underscore _ ). I caratteri
successivi al primo possono essere numeri o lettere o sottolineature.
Non sono ammessi caratteri di punteggiatura o altro, che hanno
significati speciali.
Identificatori ammessi: Num _Num num n_um
Identificatori NON ammessi: num$! num;pippo 1Num
27
Il linguaggio C 53/243
Identificatori
Il C, come in Java ma a differenza del Pascal, è case-sensitive, ovvero
considera caratteri minuscoli e caratteri maiuscoli come differenti.
Quindi l'identificatore "NUMERO" è diverso dall'identificatore
"numero" e da "Numero".
Non sono ammessi caratteri di punteggiatura o altro, che hanno
significati speciali.
Ogni identificatore, usato sia per identificare variabili sia per indicare
funzioni, deve essere diverso dalle parole riservate utilizzate per il
linguaggio C, e deve essere diverso anche dai nomi di variabili o
funzioni delle librerie utilizzate durante la fase di linking.
Il linguaggio C 54/243
Tipi di dati semplici
Il C mette a disposizione i seguenti dati di tipo semplice, la dimensioni di
alcuni di essi è dipendente dall’architettura:
void---0void
doubleextended1.7 * 10±308-1.7 * 10 ± 3088double
floatreal3.2 * 10±38-3.2 * 10 ± 384float
--232 – 104unsigned
long int
intlongint231-1-2314long int
--216 – 102unsigned
short int
shortinteger32768-327682short int
--25501unsigned
char
bytechar128-1271char
JavaPascalMax.Min.Dim.Tipo
28
Il linguaggio C 55/243
Tipi di dati semplici
Sui sistemi UNIX tutte le variabili dichiarate "int" sono
considerate “long int", mentre "short int" deve essere
dichiarato esplicitamente. In alcune architetture, inoltre, il dato
di tipo int è costituito da un numero maggiore di byte.
E' importante notare che in C non esiste un tipo di variabile
booleano. Come variabili di tipo booleano si possono utilizzare
variabili "char", "int", "long int" sia signed che unsigned.
Il linguaggio C 56/243
Tipi di dati sempliciCiascuno di questi dati, quando viene valutato dal punto di vista
booleano, è valutato FALSO quando assume valore 0 (ZERO),
se invece assume un valore diverso da zero è valuato come
VERO.
Per esempio, la seguente istruzione condizionale
if(-1)
i=1;
esegue l'assegnamento perchè il valore -1 è considerato VERO.
Il tipo void rappresenta un tipo di dato indefinito, e ha due
funzioni: serve ad indicare che una funzione non restituisce
nessun valore, e serve per definire un puntatore che punta ad un
dato generico.
29
Il linguaggio C 57/243
Variabili
Tutte le variabili devono essere dichiarate prima di essere usate. La
dichiarazione delle variabili è così fatta:
tipo ElencoVariabili;
dove “tipo” è uno dei tipi di dati ammessi dal C, e ElencoVariabili
è composto da uno o più identificatori validi separati da una virgola.
In questo modo ogni identificatore che compare in ElencoVariabili diventa una variabile di tipo “tipo”.
Il linguaggio C 58/243
Variabili
/* i è una variabile di tipo int. */
int i;
/* l1 ed l2 sono long int */
long int l1, l2;
/* f, g, x, y sono variabili in virgola
mobile */
float f, g, x, y;
30
Il linguaggio C 59/243
Variabili
Le variabili assumono caratteristiche diverse, in particolare
caratteristiche di visibilità (scope) da parte delle funzioni, in
dipendenza della posizione in cui avviene la dichiarazione.
A seconda della posizione in cui avviene la dichiarazione, si
distinguono tre tipi di variabili:
•Variabili Locali.
•Parametri Formali.
•Variabili Globali.
Il linguaggio C 60/243
Variabili localiDefiniamo “Blocco di Istruzioni” una sequenza di istruzioni C
racchiusa tra una parentesi graffa aperta ed una parentesi graffa
chiusa.
Il corpo di una funzione (il codice C che implementa una funzione) è
un caso particolare di Blocco.
Esempi di Blocchi:
Corpo di Funzione
int funcA (double f) {
int j; /* corretto */
printf("corpo funz.")
int K; /* ERRORE */
}
Interno di un ciclo forfor (i=0; i<10; i++)
{
int j;
printf("ciclo")
}
func(j);
/* ERRORE qui j NON
e' visibile */
Ovunque, usando il trucco
aperta-chiusa { ... }printf("codice C");
{
int j;
printf(“ciao")
}
31
Il linguaggio C 61/243
Variabili locali
Una variabile locale può essere dichiarata dentro un qualunque
blocco, ma in questo caso sempre e solo all'inizio del blocco, cioè
mai dopo che nel blocco sia stata scritta un'istruzione diversa da una
dichiarazione), ed in tal caso:
• la variabile verrà detta locale al blocco,
• potrà essere acceduta solo dall'interno del blocco stesso, cioè
non è visibile fuori dal blocco,
• avrà un ciclo di vita che inizierà nel momento in cui il controllo
entra nel blocco, e terminerà nel momento in cui il controllo esce
dal blocco.
Il linguaggio C 62/243
Variabili locali
Le variabili locali sono caricate sullo stack quando il controllo entra
nel blocco considerato, e vengono eliminate quando il controllo esce
dal blocco in cui sono state dichiarate.
Quando una variabile è dichiarata nel corpo di una funzione, è locale
alla funzione, e assomiglia alle variabili locali del Pascal o di Java.
32
Il linguaggio C 63/243
Variabili come parametri formali
Sono le variabili che definiscono, nell'implementazione di una
funzione, i parametri passati come argomenti alla funzione.
Sono esattamente equivalenti ai parametri formali delle funzioni
o procedure del Pascal o dei metodi Java.
Per default i dati di tipo semplice sono passati per valore, come in
Pascal. Invece i dati di tipo matrice sono passati per puntatore (la
modalità var del pascal).
int func( float f , int i ) {
printf ("param: f=%f i=%d\n,f,i);
}
Il linguaggio C 64/243
Variabili come parametri formali
Come per le variabili locali, anche i parametri formali potranno
essere acceduti solo dall'interno della funzione in cui sono stati
dichiarati, avranno un ciclo di vita che inizierà nel momento in
cui il controllo entra nella funzione stessa, e terminerà nel
momento in cui il controllo esce dal blocco.
I parametri formali vengono caricati sullo stack quando il
controllo entra nel blocco considerato, e vengono eliminati
quando il controllo esce dal blocco in cui sono state dichiarate. Se
il Parametro è passato per puntatore, è il puntatore ad essere
caricato sullo stack.
33
Il linguaggio C 65/243
Variabili globali e specificatore extern
Le variabili Globali sono quelle variabili che sono dichiarate
fuori da tutte le funzioni, in una posizione qualsiasi del file.
Una tale variabile allora verrà detta globale, perchè
• potrà essere acceduta da tutte le funzioni che stanno nello
stesso file ma sempre dopo la dichiarazione della variabile
stessa,
• potrà essere acceduta da tutte le funzioni che stanno in altri
file in cui esiste una dichiarazione extern per la stessa
variabile, ma sempre dopo la dichiarazione extern della
variabile stessa,
• avrà durata pari alla durata in esecuzione del programma.
Il linguaggio C 66/243
Variabili globali e specificatore extern
Per default, una variabile globale NomeVariabile è visibile da
tutti i moduli in cui esiste una dichiarazione di variabile extern
di NomeVariabile, ovvero una dichiarazione siffatta:
extern tipo NomeVariabile;
che è la solita dichiarazione di variabile preceduta però dalla parola extern.
34
Il linguaggio C 67/243
Variabili globali e specificatore extern
extern tipo NomeVariabile;
Una tale dichiarazione dice al compilatore che:
1.nel modulo in cui la dichiarazione extern è presente, la variabile NomeVariabile non esiste, ma esiste in
qualche altro modulo,
2. il modulo con la dichiarazione extern è autorizzato ad usare
la variabile, e quindi il compilatore non si deve preoccupare
se non la trova in questo file, perchè la variabile esiste da
qualche altra parte.
3. sarà il Linker a cercare in tutti i moduli fino a trovare il
modulo in cui esiste la dichiarazione senza extern per la
variabile NomeVariabile.
Il linguaggio C 68/243
Variabili globali e specificatore extern
La variabile NomeVariabile viene fisicamente collocata
solo nel modulo in cui compare la dichiarazione senza
extern, (che deve essere uno solo altrimenti il Linker non sa
quale scegliere) e precisamente nel punto in cui compare la
dichiarazione. Nei moduli con la dichiarazione extern invece
rimane solo un riferimento per il linker.
35
Il linguaggio C 69/243
Variabili globali e specificatore static
Se vogliamo che una certa variabile globale NomeVariabile,
collocata in un certo file, non sia accedibile da nessun altro
modulo, dobbiamo modificare la sua dichiarazione in quel modulo, facendola precedere dalla keyword static ottenendo
una dichiarazione di questo tipo.
static tipo NomeVariabile;
In tal modo, quella variabile potrà ancora essere acceduta dalle
funzioni nel suo modulo, ma da nessun altro modulo.
Il linguaggio C 70/243
Problemi con variabili globali
Esempio, Problemi con variabili globali, all'interno dello stesso
file in cui le variabili globali sono definite
#include <stdio.h>
int K=2; /* variabile globale visibile da tutte le funzioni */
int main(void) {
int i=34;
printf("i = %d \n", i ); /* stampa i cioè 34, corretto */
int J=0; /* ERRORE, dichiarazione dopo istr. */
printf("K = %d \n", K );
printf("g = %f \n", g );
funzione1();
exit(0);
}
double g=13;
void funzione1(void) {
printf("g = %f \n", g ); /* stampa g, cioè 13, corretto */
printf("i = %d \n", i ); /* NON VEDE i, ERRORE */
}
36
Il linguaggio C 71/243
Problemi tipici in programmi con più moduli
Il nostro programma è costituito da due moduli, var.c e main.c.
main.c contiene il main del programma, ed alcune funzioni, tra cui
la funzione f , che accetta come parametro formale un intero e lo
stampa. var.c contiene alcune variabili intere, alcune (A) globali,
altre (C) globali ma statiche e quindi visibili solo dentro il modulo
var.c.
/* file var.c */
int A=1;
static int C;
#include <stdio.h> /* file main.c */
extern int A; extern int C;
void f(int c){ printf("c=%d\n",c); }
void main(void) {
f(A); /*corretto */
f(B); /*error C2065: 'B' : undeclared identifier*/
f(C); /*error LNK2001:unresolved external symbol _C*/
}
Il linguaggio C 72/243
Problemi tipici in programmi con più
moduli
Il modulo main.c contiene due errori, perchè:
1) con l'istruzione f(C) main tenta di accedere alla variabile C
che non può vedere perchè è protetta dallo specificatore static
che la rende visibile solo dentro var.c.
Il compilatore non si accorge dell'errore perchè main.c ha
una dichiarazione extern per C, e il compilatore si fida e fa
finta che C esista e sia accedibile in un qualche altro modulo.
Il linker invece, che deve far tornare i conti, non riesce a
rintracciare una variabile C accedibile, e segnala l'errore
indicando un "error LNK2001: unresolved external symbol"
perchè non trova C.
37
Il linguaggio C 73/243
Problemi tipici in programmi con più
moduli
2) con l'istruzione f(B) main tenta di accedere alla variabile B
che non è definita nel modulo main.c, nemmeno da una
dichiarazione extern.
il compilatore si accorge dell'errore e lo segnala con il
messaggio "error C2065: 'B' : undeclared identifier".
Il linguaggio C 74/243
Lo specificatore static
Lo specificatore static, applicato ad una variabile locale ordina al
compilatore di collocare la variabile non più nello stack all'atto della chiamata
alla funzione, ma in una locazione di memoria permanente (per tutta la durata
del programma), come se fosse una variabile globale.
A differenza delle variabile globali, la variabile locale static sarà visibile solo
all'interno del blocco in cui è stata dichiarata.
L'effetto è che la variabile locale static:
• viene inizializzata una sola volta, la prima volta che la funzione viene
chiamata.
• mantiene il valore assunto anche dopo che il controllo è uscito dalla
funzione, e fino a che non viene di nuovo chiamata la stessa funzione.
38
Il linguaggio C 75/243
Lo specificatore static
Vediamo un esempio di utilizzo, per contare il numero delle volte
che una data funzione viene eseguita.
#include <stdio.h> /* file contaf.c */
void f(void) {
/* viene inizializzato solo una volta */static int contatore=0;
contatore = contatore + 1;
printf("contatore =%d\n", contatore);
}
void main() { /* per vedere cosa succede in f*/
int i;
for( i=0; i<100; i++ )
f();
}
Il linguaggio C 76/243
Istruzioni di assegnamento
L'operatore di assegnamento in C, come in Java, è =, mentre in
Pascal è := .
Quindi l'istruzione di assegnamento diventa:
NomeVariabile = espressione;
dove espressione può essere semplice come una singola
costante, o essere una combinazione di variabili, operatori, funzioni e costanti. NomeVariabile deve essere una variabile, e mai una
funzione.
39
Il linguaggio C 77/243
Istruzioni di assegnamento
Esempi:
int func( float f); /* prototipo */
int i , j;
i = 0; corretta
i = func(3.8); corretta
j = i ; corretta
func( 3.8 ) = 100; NON AMMESSA, errore
in compilazione
Il linguaggio C 78/243
AssegnamentoL'assegnamento in C produce un risultato, che è il valore
assegnato alla variabile a sinistra dell'uguale, ed è del tipo della
variabile. Tale risultato può essere utilizzato:
•per un ulteriore assegnamento, tenendolo alla destra di un uguale,
valgono perciò istruzioni composite del tipo:
int i, j , k ;
k = j = i = 10;
che assegnano alla variabile a sinistra il valore assegnato alla
variabile subito a destra, cioè si assegna prima il valore 10 ad i,
poi dato che i vale 10 si assegna il valore 10 a j, poi visto che j
vale 10 si assegna 10 a k. Il vantaggio è un'esecuzione più veloce
delle istruzioni di assegnamento separate, perchè il dato da
assegnare è già caricato sui registri.
40
Il linguaggio C 79/243
Assegnamento
•come espressione ad esempio come condizione in un if,
eventualmente tenendolo dentro parentesi per evitare confusioni
if ( i=10 )
func(9);
i=10 vale 10 che è diverso da zero e vale VERO
Il linguaggio C 80/243
Conversioni di tipo negli assegnamenti
void main(void) {
int i,j; double g,x;
x = i = g = 10.3;
printf("x=%f i=%d g=%f\n",x, i, g );
}
Tale programma stampa la stringa: x=10.0 i=10 g=10.3
41
Il linguaggio C 81/243
Conversioni di tipo negli assegnamenti
void main(void) {
int i,j; double g,x;
i = x = g = 10.3;
printf("x=%f i=%d g=%f\n",x, i, g);
}
Tale programma stampa la stringa: x=10.3 i=10 g=10.3
Il linguaggio C 82/243
Conversioni di tipo negli assegnamenti
Valutiamo cosa succede nell'assegnamento multiplo del primo
programma.
L'istruzione di assegnamento g=10.3; assegna un valore floating
point 10.3 alla variabile floating point g, e 10.3 è il risultato
dell'assegnamento più a destra.
Tale risultato (10.3) viene assegnato ad i che però è una variabile
intera, e quindi viene persa la parte decimale e ad i viene assegnato
il valore 10, che è il risultato dell'assegnamento ad i.
42
Il linguaggio C 83/243
Conversioni di tipo negli assegnamenti
Questo risultato (10) viene quindi assegnato a x che lo memorizza
come floating point 10.0 , che è il risultato dell'assegnamento ad x,
ed è anche il risultato finale di tutto l'assegnamento multiplo.
Nell'assegnamento multiplo del programma a destra, invece,
l'assegnamento ad x viene fatto dandogli il valore ottenuto
dall'assegnamento di 10.3 a g che è floating point, e quindi
memorizza 10.3.
N.B. In entrambi i casi il compilatore si accorge che c'e' una
perdita di dati nel passaggio da double a int e avverte il
programmatore con un warning di questo tipo:
warning: '=' : conversion from 'double ' to 'int ', possible loss of
data
Il linguaggio C 84/243
Conversioni di tipo nelle istruzioni di
assegnamento
Il problema delle conversioni di tipo esiste quando l'assegnamento
coinvolge variabili (o una variabile ed una costante) di tipo
diverso. La regola di conversione che vale in C è la seguente:
Il C converte il valore alla destra del segno di uguale (il risultato
della valutazione dell'espressione) nel tipo del lato sinistro (la
variabile destinazione dell'assegnamento).
43
Il linguaggio C 85/243
Conversioni di tipo nelle istruzioni di
assegnamento
Tipo
Destinazione
Tipo
Espressione
Possibile Perdita
di Informazione
Informazione
Conservata
unsigned char char se valore è > 127 7 bit meno significativi
char short int byte + significativo byte - significativo
char long int (4 byte) 3 byte + significativi byte - significativo
short int long int 2 byte + significativi byte - significativo
short int float, double almeno la parte frazionaria
la parte intera se minore del valore
massimo per short int, un valore
senza senso altrimenti
float double arrotondamento del risultatorisultato arrotondato alla
precisione del float
Il linguaggio C 86/243
Conversioni
Vediamo un esempio, conversione di uno short int in un char:
short int i; char ch; ch = i ;
byte più significativo byte meno significativo
short int i
char ch
44
Il linguaggio C 87/243
Inizializzazione delle variabili
All'atto della dichiarazione di una variabile, è possibile assegnare un
valore alla variabile stessa, mediante un'estensione della
dichiarazione così fatta:
Tipo NomeVariabile = costante;
Questa inizializzazione si traduce in due fatti diversi a seconda della
collocazione della variabile che stiamo dichiarando:
• Se la variabile è una variabile globale, il valore viene assegnato
alla variabile prima di cominciare l'esecuzione del programma,
Il linguaggio C 88/243
Inizializzazione delle variabili
Se la variabile è una variabile locale l'inizializzazione viene
effettuata nella posizione dello stack in cui viene posizionata la
variabile locale quando si entra nel blocco a cui appartiene, e
questa inizializzazione viene ripetuta ogni volta che si entra in
quel blocco. Fa eccezione il caso in cui la variabile locale sia
preceduta dallo specificatore static, nel qual caso la variabile non
viene messa sullo stack ma in una zona dati, e l'inizializzazione
viene effettuata una ed una sola volta, nel momento in cui il
controllo entra per la prima volta nella funzione in cui la variabile
è stata dichiarata.
• Se la variabile è un parametro formale di una funzione, non
può essere inizializzata.
45
Il linguaggio C 89/243
Inizializzazione delle variabili
Esempi:
int i = 0; i inizializzato a zero
double f = 13.7; f inizializzato a 13.7
int j=2, k, m=3; j inizializzato a zero, k non inizializzato,
m inizializzato a 3
#define NUM 10
int n=NUM; n inizializzato a 10
int p=0xFF; p inizializzato a 255 (costante esadecimale)
int q=011; q inizializzato a 9 (costante ottale)
Il linguaggio C 90/243
CostantiLe costanti sono entità che non possono essere modificate durante l'esecuzione del
programma, ma mantengono invariato il loro valore per tutta la durata del programma.
Le costanti possono essere:
1. scritte direttamente nel programma scrivendo il valore che interessa. Ad es.
i = 1; f = 137.12; k = 0xFF;
2. indicate mediante un simbolo tramite una direttiva al preprocessore #define nel qual
caso il preprocessore, sostituendo al simbolo il valore, ci riporta al caso precedente,
Ad es. #define SIZE 10
int i = SIZE;
viene tradotto dal preprocessore in
int i = 10;
3. indicate mediante un simbolo (tramite la keyword const) che viene inizializzato al
valore che interessa e mantenuto in una locazione di memoria come una variabile, ma
che non può essere modificato.
46
Il linguaggio C 91/243
Costanti
La dichiarazione di un dato di tipo costante deve essere così:
const Tipo NomeVariabile = costante;
oppure
Tipo const NomeVariabile = costante;
Ad es. const int i = 10;
Dichiarare una costante mediante la const impone al compilatore di associare il simbolo
ad una certa locazione di memoria separata dal resto dei dati, e di mantenerne in questa
locazione il valore assegnato in via di inizializzazione.
Comunque, in tutti i 3 casi, resta sempre il problema di come scrivere un certo valore
costante (numerico intero, numerico in virgola mobile, stringa) all'interno in una certa
istruzione. Il formato varia al variare del tipo di dato.
Esiste inoltre un caso particolare di uso delle costanti che sarà descritto a proposito delle
funzioni e dei loro parametri formali, e che serve a dichiarare che un parametro formale
non è modificabile.
Il linguaggio C 92/243
Costanti numeriche intere
Le costanti numeriche intere: sono espresse da numeri senza
componente frazionaria, possono essere preceduti
eventualmente da un segno (+ o -), e possono essere espresse in
notazione decimale (base 10), esadecimale (base 16) o ottale
(base 8).
• In notazione decimale il numero è espresso nel modo consueto,
in notazione esadecimale il numero deve essere preceduta dal
segno 0x, in notazione ottale il numero deve essere preceduta da
uno 0.
• Per indicare il tipo di dato con cui rappresentare la costante, si
utilizzano dei caratteri da porre immediatamente dopo i caratteri
numerici.
47
Il linguaggio C 93/243
Costanti numeriche intere
• Per default le costanti intere sono considerate int. Se la costante
intera è troppo grande per essere contenuto in un int, viene
considerato long.
• Una costante unsigned int deve essere seguita dal carattere u o U.
es: 41234U
• Una costante long deve essere seguita dal carattere L o l.
es: 49761234L
• Una costante unsigned long deve essere seguita dai caratteri UL o
ul.
Il linguaggio C 94/243
Esempi:
Valore Tipo DECIMALE ESADEC. OTTALE
0 int 0 0x0 00
1 int 1 0x1 01
8 int 8 0x8 010
-8 int -8 -0x8 -010
17 int 17 0x11 021
-30 int -30 -0xFD 036
100000 long int100000L
100000l
186A0L
186A0l
-303240L
-3032040l
-100000 long int-100000L
-100000l
-186A0L
-186A0l
-303240L
-3032040l
48
Il linguaggio C 95/243
Costanti numeriche in virgola mobile
Le costanti numeriche in virgola mobile sono scritte in rappresentazione decimale nel
modo consueto, ovvero: un eventuale segno, la componente intera, il punto decimale
ed eventualmente la componente decimale.
es: -10.4 37.235f 7. .001
oppure in formato esponenziale, cioè nella forma Xe±M con il significato di
X*10±M, dove X è una componente floating point rappresentata come prima
indicato, M è un intero
es: -1.04e+1 0.37235e+2L 0.7e+1 1.e-2
Inoltre, il tipo di dato usato per rappresentare la costante sarà:
•il tipo double se non viene specificato un suffisso alla costante.
•il tipo float se invece viene aggiunto un suffisso f o F ,
•il tipo long double a 16 byte se viene aggiunto un suffisso l o L.
Il linguaggio C 96/243
Costanti di tipo char
I caratteri, cioè i tipi char (signed e unsigned) servono a rappresentare un
insieme di simboli quali i simboli della tastiera a A b B 1 2 ed alcuni caratteri
cosiddetti "di controllo" quali Enter, Tab, BackSpace, NewLine.
Lo standard ASCII associa un carattere ai valori da 0 a 127, mentre per i
caratteri da 128 a 255 l'associazione varia a seconda del computer.
I caratteri di controllo sono i primi 32 della tabella ASCII, cui seguono i
caratteri stampabili, cioè visualizzabili da un comune editor. Qui di seguito un
estratto dalla tabella ASCII.
Il tipo char del C è più simile al tipo byte di Java che non al tipo char.
Carattere: ‘0’ ‘1’ ‘A’ ‘B’ ‘a’ ‘b’
Codice ASCII: 48 49 65 66 97 98
49
Il linguaggio C 97/243
Variabili di tipo carattere
Una variabile di tipo carattere quindi memorizza un certo valore, da
0 a 255, che corrisponde ad un certo carattere. In un assegnamento
ad una variabile di tipo carattere, potremo assegnare o il codice
numerico o il carattere rappresentato da quel codice numerico.
Una costante di tipo carattere quindi potrà essere rappresentata o
come valore numerico da 0 a 255, oppure come simbolo
corrispondente a quel codice, ad es. ad un certo char potremo
assegnare o il carattere '0' o il valore numerico 48.
Il linguaggio C 98/243
Variabili di tipo carattere
La rappresentazione numerica delle costanti di tipo carattere è
fatta mediante la rappresentazione decimale del codice numerico.
Ad es., assegnando ad un char il valore 97 gli assegniamo il
carattere 'a'.
La rappresentazione simbolica delle costanti di tipo carattere non
soffre dei problemi dovuti alla differenze tra le codifiche dei
caratteri, perchè scrive nel codice direttamente il simbolo che si
vuole ottenere, e non una sua rappresentazione numerica.
50
Il linguaggio C 99/243
Variabili di tipo carattere
D'altro canto è necessario delimitare la rappresentazione simbolica di un
certo carattere separandola dagli altri elementi del linguaggio C. A questo
scopo la rappresentazione delle costanti di tipo carattere mediante i
simboli viene fatta scrivendo il simbolo all'interno di due apici singoli.
ad es. 'L' indica il carattere che rappresenta il simbolo L.
Però, poiché non tutti i caratteri sono visualizzabili da un editor (ad es. i
caratteri di controllo) deve essere previsto un modo simbolico per indicare
un carattere che non può essere visualizzato. A tal scopo sono definite le
cosiddette "sequenze di escape", ovvero dei simboli stampabili che quando
sono precedute dal carattere \ vengono interpretate dal C come un carattere
diverso.
Ad es. la sequenza di escape '\n' indica il carattere new line cioè l'andata a
capo di una riga di testo. Le sequenze di escape vanno racchiuse tra apici
singoli come i simboli dei caratteri.
Il linguaggio C 100/243
Sequenze di escape
Le sequenze di escape vengono introdotte anche per poter indicare
simbolicamente alcuni caratteri particolari ( ' " ) che hanno un significato speciale
nel linguaggio C
Le principali sequenze di escape disponibili in C sono le seguenti:
\a suono
\b BackSpace
\n New Line, andata a capo
\r carriage return
\t il Tab orizzontale
\\ il BackSlash, che è il delimitatore delle sequenze di escape
\' il carattere apice singolo ' che in C è delimitatore di carattere
\” il carattere doppio apice " che in C è delimitatore di stringa
Notare l'analogia tra l'uso delle sequenze di escape \\ \' \" in C e l'uso della coppia
di apici singoli '' dentro la write del Pascal o la println di Java per stampare un
solo apice singolo '.
51
Il linguaggio C 101/243
Sequenze di escapeInfine è possibile rappresentare i caratteri ancora in forma numerica mediante rappresentazione ottale
o esadecimale, scrivendo all'interno della coppia di apici singoli:
- un BackSlash seguito dalla rappresentazione ottale del numero, oppure
- un BackSlash seguito da una x seguita dalla rappresentazione esadecimale.
rappr. ottale '\OOO‘ fino a tre caratteri ottali (0:7), (max \377)
rappr. esadecimale '\xhh‘ fino a due (hh) caratteri esadec. (0:9,A:F)
es, sono equivalenti i seguenti assegnamenti, nella stessa colonna:char ch;
ch='A'; ch=' " '; ch=' \' '; ch='\\';
ch=65; ch=34; ch=39; ch=92;
ch='\x41'; ch='\x22'; ch='\x27'; ch='\x5C';
ch='\101'; ch='\42'; ch='\47'; ch='\134';
ch='\042'; ch='\047'; ch='\'; /*errore*/
ch='\"'; ch='\047';
non ammesso ch='\0042'; ch=' ' '; /*errore*/
ch='\400‘ errore,4 ottali
Il linguaggio C 102/243
Costanti di tipo stringa
In C la stringa vera e propria non esiste, si usa come stringa un vettore di n+1 char , in
cui al carattere in ultima posizione è assegnato il valore 0, ovvero il carattere nullo '\0'.
Questo carattere particolare rappresenta il delimitatore finale della stringa, ovvero
l'indicazione che la stringa è finita.
‘s’ ‘t’ ‘r’ ‘i’ ‘n’ ‘g’ ‘\0’
Quindi, una stringa in C internamente è rappresentata da un vettore di caratteri terminati
da un carattere nullo. La lunghezza è nota solo dopo avere trovato il carattere 0 in fondo.
Una costante di tipo stringa, ovvero una costante di tipo vettore di caratteri viene scritta
come una sequenza di caratteri racchiusa tra 2 doppi apici, che non fanno parte della
stringa.
Ciascun carattere nella stringa può essere rappresentato simbolicamente o per mezzo di
una sequenza di escape o in forma esadecimale o ottale.
52
Il linguaggio C 103/243
Costanti di tipo stringa
Esempi di costanti stringa sono:
"questA stringa è giusta" 'è' e' un caratt. non ASCII
"quest\x41 stringa e' giusta" ' ' ' è delimitatore di carattere, non di
stringa, quindi non c'e' confusione
"quest\x41 stringa e\47 giusta" ' ' ' scritto in ottale con 2 cifre(occhio)
"quest\101 stringa e\047 giusta" ' ' ' scritto in ottale a 3 cifre
"questA stringa " è sbagliata" ERRORE, '"' è delimitatore di stringa
"questA stringa \" è stata corretta“
"aa12bb" è uguale a "aa\0612bb" ma è diverso da "aa\612bb"
NB: la const a destra dà errore in compilazione, "numeric constant too
large"
Il linguaggio C 104/243
Operatori in C
Il linguaggio C mette a disposizione degli operatori, ovvero dei simboli speciali che
indicano al compilatore di generare codice per far eseguire delle manipolazioni logiche,
matematiche o di indirizzamento sulle variabili che costituiscono gli operandi
dell'operatore.
Le funzionalità degli operatori in C sono in linea di massima le stesse di tutti i linguaggi
di programmazione evoluti, ad es. il Pascal.
Il C si distingue perchè mette a disposizione alcuni operatori particolari che servono ad
effettuare velocemente alcune operazioni semplici.
Le operazioni realizzate da questi operatori sono molto veloci perchè la semplicità delle
funzionalità rende possibile implementare l'operazione in una maniera che sfrutta appieno
la potenzialità dei registri del calcolatore, limitando il numero degli accessi alla memoria,
e lavorando il più possibile coi registri.
Prima di vedere alcuni operatori, è bene introdurre una caratteristica molto particolare del
C, il Type Casting, ovvero la modifica del tipo di dato.
53
Il linguaggio C 105/243
Type Casting
Molto spesso il risultato di un'operazione dipende dal tipo di dato
che è coinvolto nell'operazione. Ad es. la divisione tra interi ha un
risultato diverso dalla divisione tra floating point, anche partendo
dagli stessi dati iniziali. 5.0/2.0=2.5 5/2=2
Questo perchè a seconda del tipo di dati coinvolti nelle operazioni,
le operazioni sono svolte in modo diverso, o utilizzando registri o
porzioni di registri diversi.
Una caratteristica peculiare del C è che permette durante
l'esecuzione di un'operazione, o durante il passaggio di
parametri ad una funzione all'atto della chiamata, di modificare
il tipo del o dei dati coinvolti nell'operazione.
Il linguaggio C 106/243
Type Casting
Ciò non significa affatto che la variabile (o la costante, o il
risultato di un'espressione) interessata cambia le sue
caratteristiche (dimensioni, proprietà, ecc..) di tipo.
Significa invece che se la variabile deve essere utilizzata in dei
calcoli, viene copiata in un registro sufficentemente grande per
contenere il nuovo tipo di dato e per svolgere le operazioni di
conversione, ed il valore del registro caricato viene convertito
secondo le caratteristiche del nuovo tipo di dato.
Solo dopo questa conversione, l'operazione viene effettuata,
secondo le regole proprie del nuovo tipo di dato, ottenendo
quindi come risultato un valore coerente con il nuovo tipo.
54
Il linguaggio C 107/243
Type Casting
La conversione di tipo type-casting in qualche caso viene effettuata
implicitamente dal compilatore C, ma può anche essere forzata dal
programmatore mediante un operatore unario detto cast, che opera in questa
maniera.
( nome_nuovo_tipo ) espressione
dove "nome_nuovo_tipo" è un tipo di dato primitivo o definito dall'utente, ed
"espressione" può essere una variabile, una costante o un'espressione complessa.
es. (double) i;
"Espressione" viene risolta fino ad arrivare ad un risultato (di un suo certo
tipo), poi il risultato viene convertito nel tipo "nome_nuovo_tipo" mediante
opportune operazioni più o meno complesse a seconda della conversione.
A questo punto abbiamo come risultato della conversione un dato di tipo
"nome_nuovo_tipo" con un certo valore, che può essere utilizzato secondo
le regole definite per "nome_nuovo_tipo".
Il linguaggio C 108/243
Type casting: esempio
Vediamo subito un esempio di cosa succede:
Un cast quindi equivale ad assegnare l"espressione" ad una
variabile del nuovo tipo specificato, che viene poi utilizzata al
posto dell'intera espressione.
int i=5, j=2;
double f;
f=i/j;
A questo punto f vale 2
int i=5, j=2;
double f;
f=(double)i/(double)j;
A questo punto f vale 2.5
55
Il linguaggio C 109/243
Type casting
L'esempio riportato mostra un caso in cui il type cast modifica fortemente
il risultato di un'operazione, che comunque avrebbe potuto essere
effettuata, pur con risultati diversi, senza type cast.
Il casting viene però utilizzato spesso anche nel passaggio di parametri a
funzioni, qualora il programmatore abbia necessità di passare alla
funzione chiamata un parametro formalmente diverso da quello richiesto
dalla funzione. Ad esempio qualora si debba passare alla funzione un
puntatore ad una struttura, di tipo diverso da quella che la funzione si
aspetta. Il compilatore dovrebbe segnalare l'errore. Mediante un type cast
del parametro passato all'atto della chiamata (di cui il programmatore si
deve assumere la responsabilità relativamente alla correttezza) si può
“convincere” il compilatore della correttezza formale dell'operazione.
Il linguaggio C 110/243
Type casting
Abbiamo finora parlato di type-casting forzato, ovvero imposto
dall'utente.
In realtà il C effettua automaticamente delle conversioni
implicite di tipo, in particolare quando effettua operazioni
matematiche tra operandi di tipo diverso.
Il casting viene effettuato automaticamente dal
compilatore C quando due operandi di un'operazione
binaria sono tra loro diversi. In tal caso l'operando di
tipo più piccolo viene convertito nel tipo più grande,
senza perdita di informazioni.
56
Il linguaggio C 111/243
Type casting
Quindi, dati due operandi di tipo diverso, il cast automatico si ha
secondo queste regole:
Tipo 1° operando Tipo 2° operando Tipo risultato
long double double long double
double float double
float (long) int float
double (long) int double
int char , short int
long int (short) int long int
Il linguaggio C 112/243
Type casting
Attenzione, il casting automatico può dare delle false sicurezze.
Nell'esempio seguente, prima viene eseguita la divisione (tra due interi, quindi
nessuna conversione) e c'è perdita di informazione, e solo in un secondo tempo c‘è
l'assegnamento al float.
int i=5, j=2;
double f;
f = (double) (i /j); /* f diventa 2.0 */
N.B. come abbiamo già visto, anche nell'assegnamento il C effettua conversioni di
tipo automatiche, convertendo il valore del lato destro nel tipo del lato sinistro,
eventualmente perdendo informazioni quando si passa da un tipo ad un tipo più
piccolo
57
Il linguaggio C 113/243
Operatori aritmetici
Gli operatori aritmetici + - * / % sono operatori presenti in tutti i linguaggi di
programmazione. Sono operatori binari (servono due operandi):
Somma + x+y valore del tipo più grande
Differenza - x-y valore del tipo più grande
Cambio segno - -x stesso tipo
Prodotto * x*y valore del tipo più grande
Divisione / x/y valore float tra float o valore int tra int
Resto % n%m resto modulo m solo tra interi
Il linguaggio C 114/243
Operatori di incremento e decremento
Gli operatori incremento ++ e decremento -- sono operatori unari (serve un
unico operando): aggiungono (tolgono) l'unità (1) all'operando.
Equivalgono ad assegnare alla stessa variabile il proprio valore incrementato
(decrementato) di uno, ma lavorano velocemente perchè usano (bene) registri.
x++ x=x+1
x-- x=x-1
Attenzione, sono previste due modalità di esecuzione, qualora l'incremento sia
parte di un'istruzione più complessa.
x++; prima usa x nell'istruzione, poi lo incrementa
++x; prima incrementa x, poi lo usa nell'istruzione
58
Il linguaggio C 115/243
Operatori di incremento e decremento
Le due istruzioni di incremento (decremento) isolate hanno lo stesso effetto. Il risultato
cambia quando vengono usate in istruzioni meno semplici.
es. int n, m=0; int n, m=0;
n = m++; n = ++m;
n vale 0, m vale 1 n vale 1, m vale 1
Priorità: ++ -- massima
- (cambio segno)
* / %
+ - minima
A pari priorità si valuta a partire da sinistra, a meno dell’uso di parentesi.
Il linguaggio C 116/243
Operatori relazionali e logici
Ricordiamo che in C il valore booleano Falso equivale a 0, e che
Vero equivale a diverso da 0. Gli operatori logici (equivalenti ad
AND OR NOT del Pascal) e gli operatori relazionali (minore
maggiore uguale diverso ecc..) restituiscono 0 quando il risultato
è falso e 1 quando il risultato è vero.
Operatori Logici Operatori Relazionali
AND && Maggiore >
OR || Maggiore o uguale >=
NOT ! Minore <
Minore o uguale <=
Uguale == (doppio simbolo =)
Diverso !=
59
Il linguaggio C 117/243
Operatori relazionali e logici
La priorità degli operatori relazionali e logici è minore della priorità degli
operatori aritmetici, quindi in un'espressione complessa prima vengono valutate
le operazioni aritmetiche, e solo in un secondo momento gli operatori relazionali.
Ad es., l'espressione
x <= 5+y
viene valutata prima effettuando la somma tra 5 ed y, ed il risultato viene
confrontato con x. Ciò equivale alla valutazione di questa espressione
(x <= (5+y))
Il linguaggio C 118/243
Operatori relazionali e logici
E' buona norma abituarsi ad usare parentesi tonde e spaziature in
abbondanza per delimitare visivamente almeno le espressioni più semplici
che entreranno poi in relazione tramite operatori logici.
Si commettono di solito meno errori di programmazione guidando la
valutazione delle espressioni tramite alcune parentesi tonde poste ad hoc,
piuttosto che non affidandosi esclusivamente alla precedenza degli
operatori.
- Le espressioni sono valutate da sinistra a destra.
- Le priorità all'interno del gruppo degli operatori logici e relazionali sono le
seguenti:
! priorità massima (valutato per primo)
> >= < <=
== !=
&&
||
60
Il linguaggio C 119/243
Operatori relazionali e logici
Ad es. valutiamo l'espressione qui di seguito:
10>5 &&!(10<9) || 3<=4 10>5 vale 1
!(10<9) vale 1
otteniamo: 1 && 1 || 3<=4 1 && 1 vale 1
otteniamo: 1 || 3<=4 3<=4 vale 1
otteniamo: 1 || 1 1 || 1 vale 1
Il risultato finale dell'espressione è 1.
Attenzione ad evitare di usare espressioni scritte così. Utilizzate le parentesi tonde. La stessa
espressione, scritta in forma più leggibile, è riportata qui sotto:
( (10>5) && (!(10<9)) ) || (3<=4)
La sequenza delle parentesi impone l'ordine di valutazione degli operatori, ed evidenzia il criterio
logico con cui si deve essere formata l'espressione.
Il linguaggio C 120/243
Valutazione abbreviata di espressioni
connesse da operatori logici
Consideriamo una espressione formata da espressioni connesse da operatori logici && e || come ad esempio ESPR1 && ESPR2 || ESPR3
Tali espressioni sono valutate da sinistra verso destra, quindi, ricordando che l'operatore
AND logico && ha priorità maggiore dell'operatore OR logico, l'espressione nel suo
complesso può essere interpretato come segue:
( ESPR1 && ESPR2 ) || ESPR3
nel senso che dovrebbe essere valutata prima l'espressione ( ESPR1 && ESPR2 ) ed in un
secondo tempo l'espressione ESPR3, per poi mettere in OR logico i due risultati.
Ciò è abbastanza vero, ma in realtà il compilatore C è sufficientemente furbo da
generare il codice corrispondente alla valutazione di questa espressione, in modo tale
da interrompere la valutazione dell'espressione non appena determina la VERITA' o
la FALSITA' dell'INTERA espressione.
Lo stesso concetto vale per porzioni di espressioni, la cui valutazione viene interrotta
quando viene determinata la Verità o Falsità della porzione di espressione.
61
Il linguaggio C 121/243
Valutazione abbreviata di espressioni
connesse da operatori logici
Vediamo come il compilatore C organizza la valutazione di:
( ESPR1 && ESPR2 ) || ESPR3
viene valutata ESPR1
se ESPR1 è falsa (0)
• NON VIENE VALUTATA ESPR2 perchè in ogni caso l'espressione ESPR1 &&
ESPR2 sarà falsa
• viene valutata ESPR3
• se ESPR3 è falsa (0) l'espressione totale vale falso (0)
• se invece ESPR3 è vera (!=0) l'espressione totale vale vero.
• in ogni caso non abbiamo valutato ESPR2 perchè era inutile
Il linguaggio C 122/243
Valutazione abbreviata di espressioni
connesse da operatori logicise ESPR1 è vera
•VIENE VALUTATA ESPR2
•se ESPR2 è vera allora l'espressione ESPR1 && ESPR2 sarà vera, ed anche
l'espressione intera ESPR1 && ESPR2 || ESPR3 sarà vera, indipendentemente dal
valore di ESPR3, quindi ESPR3 non viene valutata, ed il risultato finale è vero.
•se ESPR2 è falsa allora l'espressione ESPR1 && ESPR2 sarà falsa, e deve essere
valutata ESPR3 per conoscere il risultato finale.
•viene valutata ESPR3
•se ESPR3 è vera il risultato finale è vero
•se ESPR3 è falsa il risultato finale è falso
Questo modo di valutare le espressioni, velocizza le operazioni, perchè non esegue
operazioni inutili, ma deve essere attentamente valutato, se all'interno delle espressioni
sono presenti istruzioni o funzioni che generano qualche effetto secondario, perchè tali
funzioni potrebbero essere eseguite oppure no a seconda del risultato booleano delle varie
porzioni dell'espressione.
62
Il linguaggio C 123/243
Valutazione abbreviata di espressioni
connesse da operatori logiciSupponiamo di avere un vettore di interi, con SIZE elementi, vogliamo stampare tutti gli elementi del
vettore o fino all'ultimo o fino a che non incontriamo un elemento che vale 17, nel qual caso non
vogliamo stampare 17 e vogliamo interrompere la stampa.
Possiamo sfruttare la valutazione abbreviata per scrivere un codice cosi fatto:
#define SIZE 10;
int Vet[SIZE];
int i;
.... scrittura degli n elementi del vettore Vet
i=0;
while ( (i<SIZE) && (Vet[i]!=17) )
printf( "Vet[%d]=%d\n", i , Vet[i++] );
l'espressione Vet[i]!=17 viene valutata solo espressione (i<SIZE).
Ciò consente di evitare di accedere alla se è stata valutata vera la prima variabile Vet in una posizione
maggiore o uguale della posizione numero SIZE, perchè si cadrebbe fuori dal vettore e si potrebbe
causare un eccezione di sistema del tipo segmentation fault che causa l'interruzione traumatica
del programma.
Il linguaggio C 124/243
Valutazione di espressioni
Uno degli errori più comuni per chi comincia a programmare in C, consiste
nell'ostinarsi a voler scrivere codice "stretto" e poco commentato.
E' sempre un errore, perchè:
1. il codice va modificato nel tempo, ed il codice scritto in forma compatta
è più difficile da capire, anche per chi l'ha scritto.
2. l'aggiunta di parentesi tonde e spazi per rendere più visibile e
comprensibile il codice non diminuisce le prestazioni.
63
Il linguaggio C 125/243
Operatori di assegnamento ed
espressioni aritmetiche
Il C mette a disposizione alcuni operatori particolari, che coniugano un'operazione aritmetica
all'operazione di assegnamento.
Somma e Assegnamento x += y equivale a x = x+y
Differenza e Assegnamento x -= y equivale a x = x-y
Prodotto e Assegnamento x *= y equivale a x = x*y
Divisione e Assegnamento x /= y equivale a x = x/y
Resto e Assegnamento x %= y equivale a x = x%y
Istruzioni C del seguente tipo (dove op è un'operazione aritmetica)
espressione_1 op= espressione_2
equivalgono all'istruzione
espressione_1 = espressione_1 op espressione_2
in cui l'espressione espressione_1 viene valutata due volte.
Il linguaggio C 126/243
Operatori di assegnamento ed
espressioni aritmetiche
Questi operatori composti servono a velocizzare il codice, perchè evitano di valutare
ripetutamente una stessa espressione, e in qualche caso riescono a mantenere i valori
valutati nei registri, quindi minimizzano l'accesso alla memoria.
Ad es. se noi avessimo un vettore di interi Vet, e volessimo aggiungere un 3 all'elemento
in posizione i+7*k, dovremmo scrivere (l'accesso alle posizioni di un vettore si effettua
mediante l'uso di parentesi quadre) un'istruzione del tipo:
Vet[i+7*k] = Vet[i+7*k] +2;
dovendo così valutare due volte l'espressione i+7*k
Invece con l'istruzione
Vet[i+7*k] += 2;
l'espressione i+7*k viene valutata una volta sola.
64
Il linguaggio C 127/243
Operatore sizeof()
Il C mette a disposizione un operatore unario, che restituisce la
dimensione della variabile o dello specificatore di tipo passato in
input. Serve a conoscere le dimensioni di alcuni tipi di dati, che
potrebbero cambiare al variare dell'architettura su cui il programma
deve girare, come ad esempio cambiano le dimensioni degli interi
int.
L'operatore sizeof prende in input o una variabile o un identificatore
di tipo, e restituisce la dimensione in byte del dato. Il dato può
anche essere un dato definito dall'utente, non solo un tipo di dato
primitivo.
Il linguaggio C 128/243
Operatore sizeof()
Es:
int I;
printf("dimensione di I: %d \n", sizeof(I) );
/* stampa 2 in Windows */ /* stampa 4 in Linux */
printf("dimensione del float: %d \n",
sizeof(float) ); /* stampa 4 */
L'operatore sizeof è molto importante per la portabilità del codice, da
un'architettura ad un'altra.
Particolarità dell'operatore unario sizeof è che viene valutato non
durante l'esecuzione del programma, ma al momento della
compilazione
65
Il linguaggio C 129/243
Strutture di controllo del flussoIn un linguaggio, le strutture di controllo del flusso specificano l'ordine secondo il quale le
operazioni devono essere effettuate. Abbiamo già visto informalmente alcune strutture, ora
completeremo e preciseremo la descrizione.
Istruzioni e Blocchi di Istruzioni
Ogni istruzione in C deve essere terminata da un punto e virgola ; Un'espressione qualsiasi
(come x=0, i++, 1&&23<13==7>>5) oppure una chiamata ad una funzione (come ad
es. printf(...)) diventa un'istruzione quando seguita da un punto e virgola. Il valore restituito
da queste espressioni è il risultato della valutazione dell'espressione considerata.
Un'istruzione costituita dal solo punto e virgola è un'istruzione nulla, che non ha effetto.
Un Blocco di Istruzioni è una sequenza di istruzioni C racchiusa tra una parentesi graffa
aperta ed una parentesi graffa chiusa. Un blocco dal punto di vista dei costrutti linguistici
(else while ecc..) va considerato come un'unica istruzione. Il corpo stesso di una funzione è
un blocco di istruzioni. All'interno di un blocco è possibile dichiarare delle variabili locali.
L'unica altra differenza tra un'istruzione singola ed un blocco è che dopo un blocco non deve
essere inserito il punto e virgola. Se viene messo viene considerato un'altra istruzione, nulla.
Il linguaggio C 130/243
Istruzione if-elseL'istruzione if puo' avere due forme di base:
if (espressione) if (espressione)
istruzione istruzione1
else
istruzione2
Viene valutata l'espressione "espressione", e se risulta vera (cioè diversa da zero)
viene eseguita l'istruzione "istruzione". In caso contrario, se esiste la keyword else
viene eseguita l'istruzione "istruzione2".
N.B. 1: con "istruzione" deve intendersi o singola istruzione o blocco di istruzioni.
N.B. 2: quando viene valutata l'espressione dentro parentesi tonde nel costrutto if, viene
effettuato il controllo se il risultato della valutazione è uguale o diverso da zero, cioè se è
falso o vero. Quindi scrivere if(espressione) oppure if(espressione!=0) è la
stessa cosa, ma il primo tipo di scrittura viene codificato dal compilatore in modo più
veloce, perchè non è richiesto di caricare un registro con la costante inserita nel
programma (lo zero) per il confronto, ma si ricorre ad una istruzione assembler di jump
condizionato dall'essere il risultato della valutazione (già contenuto in un registro) diverso
o uguale a zero. (Alcuni compilatori ottimizzano situazioni di questo tipo).
66
Il linguaggio C 131/243
Istruzione if-else
N.B. 3: Nel caso di costrutti if-else annidati, una keyword else è relativa al più vicino
degli if che lo precedono che manchi dell'else, ovviamente sempre che sia rispettata la
sintassi dell'if-else.
questo codice: significa: che è diverso da:
if (x) if (x){ if (x){
if(y) if(y) if(y)
istr1 istr1 istr1
else else }
istr2 istr2 else
} istr2
Il linguaggio C 132/243
Istruzione if-elseIl costrutto if-else-if è fatto nel modo seguente
if (espressione1)
istruzione1
else if (espressione2)
istruzione2
else if (espressione3)
istruzione3
else
istruzione
e consente di effettuare una scelta multipla. Le espressioni 1, 2, ..ecc. vengono valutate
nell'ordine in cui si presentano. La prima espressione che si rivela vera fa eseguire la
corrispondente istruzione, e fa uscire il controllo dalla struttura di controllo if-else-if.
L'ultimo else (che può anche non esserci) serve nel caso in cui nessuna delle precedenti
condizioni si realizza.
67
Il linguaggio C 133/243
Istruzione if-elseEcco un esempio, in cui si discrimina secondo il valore di una variabile x,
considerando 4 intervalli (-infinito,0) , [0,10) , [10,100) , [100 , +infinito)
double x=
if (x<0.)
istruzione1
else if (x<10.)
istruzione2
else if (x<100.)
istruzione3
else
istruzione
La struttura annidata if-else-if, per quanto molto potente, ha lo svantaggio di non
essere particolarmente veloce, perchè se la prima espressione che risulta vera è molto
annidata, prima di poterla verificare è necessario valutare tutte le espressioni
precedenti.
Il linguaggio C 134/243
Istruzione switch
L'istruzione switch è un'altra struttura di scelta plurima, presente
anche in Java ed analoga al case del Pascal, che controlla se
un'espressione assume un valore intero in un insieme di
COSTANTI intere, e fa eseguire una serie di istruzioni in
corrispondenza del valore intero verificato.
Una limitazione dello switch è che i valori ammessi come possibili
scelte (possibili casi previsti) devono essere delle costanti, e non
possono essere delle espressioni. Questo perchè il costrutto switch
vuole implementare una scelta multipla molto più veloce del
costrutto if-else-if, ma per fare questo deve limitare le possibili
scelte, definendole non in modo run-time mediante un'espressione,
ma mediante delle costanti, che il compilatore utilizzerà per scrivere
in codice macchina una sequenza di jump condizionati.
68
Il linguaggio C 135/243
Istruzione switch
La struttura dello switch è :
switch (espressione) {
case espressione_costante1:
sequenza di
istruzioni1;
break;
case espressione_costante2:
sequenza di
istruzioni2;
break;
.... altri case ...
default:
sequenza di istruzioni;
break;
}
Un esempio di switch semplice
#define NUM 3
int j;
switch( j ) {
case 1:
case 2+NUM:
sequenza_di_istruzioniA
break;
case -34:
case -34<<2:
sequenza_di_istruzioniB
break;
default
sequenza_di_istruzioniC
}
Il linguaggio C 136/243
Istruzione switch
Ogni possibile caso deve essere etichettato mediante uno (o più) costrutti case "Espressione_costante": dove
Espressione_costante è un'espressione formata solo da
costanti e operatori, ma non da variabili o funzioni.
Possono esserci più etichette per una stessa sequenza di
istruzioni, come nel precedente esempio. Le diverse case
rappresentano delle entry point nel costrutto switch.
Se l'espressione valutata nello switch assume uno dei valori
indicati nelle espressioni costanti, l'esecuzione passa alla
sequenza di istruzioni che seguono la keyword case per
quell'espressione costante.
69
Il linguaggio C 137/243
Istruzione switch
L'esecuzione procede fino a che non si incontra la keywordbreak, o si oltrepassa la parentesi graffa aperta che termina la
struttura switch, ed allora il controllo esce dalla struttura di
controllo di flusso switch.
Il caso etichettato come default può essere presente o no. Se è
presente assume il significato di "in tutti gli altri casi" cioè viene
eseguito se l'espressione valutata nello switch non ha assunto
nessuno dei valori indicati da una delle espressioni costanti di un
qualche case.
N.B. la keyword break non è strettamente indispensabile. Se non
è presente viene eseguita sequenzialmente ogni istruzione a
partire dal case che è stato raggiunto.
Il linguaggio C 138/243
Ciclo while
Il loop di tipo while è analogo al while del pascal, e consente di
eseguire un loop condizionale. La struttura del while è la
seguente:
while (espressione)
istruzione
dove "espressione" è una espressione che deve essere valutata
ogni volta prima di eseguire "istruzione". Se la valutazione di
"espressione" da' come risultato vero allora l'istruzione viene
eseguita, altrimenti no, e si esce dal ciclo.
70
Il linguaggio C 139/243
Ciclo whilePoichè la valutazione della condizione è effettuata prima delle
istruzioni che costituiscono il loop, il loop stesso può essere eseguito
zero volte oppure un numero finito di volte oppure ancora può
realizzare un loop infinito.
Esempio, stampa dei caratteri di una stringa
char str[]="stringa";
int j=0;
while( str[j] )
printf("%c", str[j++]);
Esempio, loop infinito: while(1) { .... }
Il linguaggio C 140/243
Ciclo do-while
Il loop di tipo while è analogo al repeat-until del pascal, e consente di eseguire un loop
condizionale da 1 ad infinite volte. La struttura del do-while è la seguente:do {
istruzioni
} while (espressione);
dove "espressione" è una espressione che deve essere valutata ogni volta dopo avere
eseguito "istruzioni". Se la valutazione di "espressione" da' come risultato vero il loop
viene ripetuto, cioè si riesegue "istruzioni", altrimenti si esce dal ciclo.
Poichè la valutazione della condizione è effettuata dopo le istruzioni che costituiscono
il loop, il loop stesso può essere eseguito da una volta a infinite volte.
es:int num;
do {
scanf("%d", &num);
} while(num>100);
71
Il linguaggio C 141/243
Ciclo forIl loop di tipo for è analogo al for del pascal, ma è più potente. La forma generale
del ciclo for è fatta così:
for ( inizializzazione ; condizione ; incremento)
istruzione
le tre componenti di un ciclo for sono delle espressioni, ovvero possono essere
degli assegnamenti o delle chiamate a funzione.
L'espressione "inizializzazione" viene eseguita una sola volta, nel
momento in cui il controllo viene affidato al costrutto for. Serve per impostare le
variabili e quanto necessario per cominciare il loop. Questa "espressione"
può anche non essere presente, ed allora dopo la parentesi tonda aperta viene
subito il punto e virgola.
L'espressione "condizione" viene valutata (eseguita) ogni volta prima di
eseguire le istruzioni del loop, ed è quella che stabilisce se il ciclo deve
continuare ad essere eseguito ancora una volta, oppure si deve uscire dal costrutto
for. Se "condizione" è vera si esegue ancora "istruzione", se invece è falsa
(0) si esce dal ciclo for passando alla istruzione successiva del programma.
Il linguaggio C 142/243
Ciclo for
Anche l'espressione "condizione" può non essere presente, nel qual caso lo
spazio tra i due punti e virgola dentro il costrutto for rimane vuoto, ed il
compilatore assume vero come risultato della valutazione della (assente)
condizione di proseguimento del ciclo for, e quindi continua ad eseguire le
istruzioni dentro al loop.
L'espressione "incremento" viene eseguita alla fine di ogni ciclo, per
modificare ad es. le variabili che stabiliscono l'incremento o decremento delle
variabili contatore che stabiliscono il numero di cicli del loop. Questa
"espressione" può anche non essere presente, ed allora dopo il secondo punto e
virgola del for viene subito la parentesi tonda chiusa.
Quindi un ciclo fatto così: for( ; ; ) { .. } realizza un loop infinito.
N.B. poichè la condizione di continuazione del loop viene valutata (eseguita)
prima di ogni ciclo, il loop for permette anche di non eseguire nemmeno una volta
il ciclo.
72
Il linguaggio C 143/243
Ciclo forCiascuna delle tre espressioni del for, cioè inizializzazione, condizione e
incremento può contenere più istruzioni, che dovranno essere separate da
virgole. Vediamone un esempio nel caso in cui si vogliano utilizzare due
variabili di controllo./* con due variabili di controllo */
void main(void) {
int x ,y;
for ( x=0, y=0; x+y<100; x++ , y+=3 )
printf ("%d ", x+y);
}
Il ciclo for può anche non contenere un corpo di istruzioni vero e proprio, che
può essere limitato ad un'istruzione vuota, il punto e virgola./* lunghezza di una stringa null-terminata*/
int len;
char stringa[] = "mia stringa";
for( len=0; stringa[len]; len++) ;
Il linguaggio C 144/243
Ciclo for: esempi/* con incremento */
void main(void) {
int x;
for ( x=1; x<100; x++ )
printf ("%d ",
x);
}
/* loop infinito */
int x;
for ( ;; )
printf ("?");
/* con decremento */
void main(void) {
int x;
for ( x=100; x>0; x-- )
printf ("%d ",
x);
}
/* con espressioni costituite
da funzioni */
for ( funzA();funzB();funzC())
73
Il linguaggio C 145/243
Istruzione break: uscita da un ciclo
L'istruzione break (che è stata vista già a proposito della struttura switch) serve
ad imporre l'uscita da un loop di tipo for, while, do-while, oppure l'uscita dallo
switch, e a far riprendere il controllo di flusso immediatamente dopo la fine del
loop (o switch) da cui si esce.
void main(void) {
int x;
for ( x=1; ; x++ ) {
if ( x==5)
break; /* esce dal ciclo for */
}
printf ("dopo break ricomincia qui ");
}
Il linguaggio C 146/243
Istruzione break: uscita da un cicloNel caso di cicli annidati, con l'istruzione break si esce solo dal ciclo piu interno, entro il
quale si trova la chiamata alla istruzione break. es.:
void main(void) {
int x;
for ( y=1; ; y++ ) {
for ( x=1; ; x++ ) {
if ( x==5)
break; /* esce dal ciclo for */
}
printf ("dopo break ricomincia qui%d ", x);
}
}
Ovviamente, in caso di switch dentro un ciclo, un break dentro lo switch fa uscire solo
dallo switch.
74
Il linguaggio C 147/243
Istruzione continue:
ricominciare il ciclo
L'istruzione continue, a differenza della break si applica ai cicli ma non
allo switch. Come la break interrompe l'esecuzione di un ciclo for while o do-
while, ma anzichè uscire dal ciclo definitivamente fa eseguire la successiva
iterazione.
Nel caso di ciclo while o do-while una continue posta nel corpo del ciclo fa
saltare alla espressione di controllo, che viene valutata, e se risulta vera viene
eseguito ancora il loop, altrimenti si esce dal loop.
Nel caso del ciclo for invece, un'istruzione continue fa eseguire l'espressione che
effettua l'incremento, poi viene effettuata la valutazione della condizione di
continuazione del ciclo for, e se risulta vera viene eseguito il ciclo, altrimenti si
esce dal for.
N.B. L'istruzione continue è pochissimo usata.
Il linguaggio C 148/243
Istruzione continue:
ricominciare il cicloAnche per la continue come per la break, in caso di cicli annidati, si interrompe solo il ciclo più
interno in cui è avvenuta la chiamata alla continue.
void main(void) {
int x;
for ( y=1; ; y++ )
for ( x=1; ; x++ ) {
if ( x==5)
continue;
/* si effettua l'incremento di x, x diventa 6,
si verifica la condizione, risulta vera
perchè assente si riprende dal for interno
(quello della x) con x=6 */
}
}
75
Il linguaggio C 149/243
Istruzione goto: salto
L'istruzione goto, consente di
passare il controllo dal punto in
cui si effettua la goto ad una
locazione del programma
individuata da una label
(un'etichetta) seguita dai due
punti, collocata in una qualche
posizione del codice. Può essere
utile per uscire da più livelli di
annidamento di cicli, cosa che la
break e la continue non riescono a
fare. Va usato con attenzione.
void main(void) {
int x;
for ( y=1; ; y++ )
for ( x=1; ; x++ ) {
for ( z=10; z<1000; z++ ) {
...
if ( x==5)
goto uscita;
...
}
}
uscita:;
...
}
Il linguaggio C 150/243
Istruzione goto: salto
Altro esempio di stranezza consentita dal goto:
void main(void) {
int x=13;
goto dentro;
for ( x=1; ; y++ ) {
dentro:
printf("atterraggio: x=%d\n", x );
}
}
76
Il linguaggio C 151/243
Istruzione goto
Il goto non permette di passare da una funzione ad un'altra. Ecco
un' esempio di cosa non è permesso.
int funzione1(void){
arrivo: ;
...
}
void funzione2(int i){
goto arrivo; /* errore in
compilazione */
...
}
Il linguaggio C 152/243
Array a una dimensioneUn array è un insieme di variabili dello stesso tipo cui è possibile accedere mediante un
nome comune e referenziare uno specifico elemento tramite un indice.
Il C alloca gli elementi di un array in posizioni adiacenti, associando l'indirizzo più basso al
primo elemento, e il più alto all'ultimo. Gli array ad una dimensione vengono dichiarati come
segue:
Tipo NomeVariabile[ dimensione_costante ] ;
dove Tipo è il tipo degli elementi del vettore, dimensione è una costante che indica quanti
elementi deve contenere l'array.
La dichiarazione serve al compilatore per riservare in memoria spazio sufficente al vettore.
Lo spazio occupato dal vettore sarà:
numero_byte = sizeof(Tipo) * dimensione_costante ;
esempi:
int vettore[10]; un vettore di 10 interi
char stringa[100]; un vettore di 100 caratteri, cioè potenzialmente una
stringa per 99 caratteri più il carattere terminatore '\0
77
Il linguaggio C 153/243
Array a una dimensione
Il C indicizza gli elementi a partire da 0, quindi possiamo accedere agli elementi con gli
indici da 0 a dimensione_costante-1.
L'accesso si effettua mediante il nome dell'array seguito dalle parentesi quadre in cui viene
racchiuso l'indice dell'elemento cercato.
vettore[3] accede al quarto elemento del vettore "vettore"
stringa[0] accede al primo carattere del vettore "stringa"
Non si puo' assegnare qualcosa al vettore intero, ma solo alle diverse posizioni del vettore
(non ha senso scrivere vettore=19; o vettore=stringa).
Inizializzazione di un array: int vet [3] = { 1,2,3 } ;
Un esempio di programma con un semplice vettore.
void main(void) {
char str[100]; int i;
for ( i=0; i<100; i++)
str[i] = (char) i;
}
Il linguaggio C 154/243
Il problema dello sconfinamento
Il linguaggio C non effettua controlli per verificare che non si acceda a posizioni
fuori dal vettore.
Cioè è possibile (ma è un errore gravissimo anche se comune) accedere ad una
locazione di memoria fuori dal vettore, con vari effetti possibili:
•Se si accede in lettura al vettore fuori da esso può accadere, a seconda del
sistema operativo:
•nessun problema apparante, solo errore logico
•segmentation fault.
•se invece si accede in scrittura al vettore ci saranno danni maggiori, perchè oltre
ai due problemi già citati si sovrascriverà una locazione di memoria che potrebbe
contenere dati essenziali al programma o peggio ancora al sistema operativo.
Oppure si modificherà il valore delle variabili dichiarate immediatamente sopra o
sotto al vettore.
78
Il linguaggio C 155/243
Esempio di errore di sconfinamento
void main(void) {
int prima=0;
int vet[10];
int dopo=0;
printf("INIZIO: prima=%d dopo=%d\n",
prima, dopo );
for ( i=-1; i<=10; i++) {
vet[i] = 99;
printf("i=%d prima=%d dopo=%d\n",
i, prima, dopo );
}
}
Il linguaggio C 156/243
Istruzione typedef
Il C permette di definire esplicitamente nomi nuovi per i tipi di dati, tramite la parola
chiave typedef. L'uso di typedef consente di rendere il codice più leggibile.
Il formato dell'istruzione typedef è il seguente:
typedef tipo nuovo_nome_tipo;
in questo modo assegnamo al tipo tipo il nuovo nome nuovo_nome_tipo.
Da questo momento in avanti potremo riferirci al tipo di dato tipo sia con il nome tipo sia
con il nome nuovo_nome_tipo.
Es
typedef int intero;
/* ora posso usare intero come tipo invece di int */
intero i;
/* definisce una variabile i di tipo int. */
79
Il linguaggio C 157/243
Tipi di dati complessi
Il C mette a disposizione i seguenti dati di tipo complesso:
• array monodimensionali, in parte già visti,
• strutture, dette struct,
• unioni, dette union,
• puntatori,
• array multidimensionali,
Ci sarebbe da discutere sul fatto che i puntatori siano un dato di tipo complesso,
in quanto in realtà gli indirizzi rappresentano uno dei dati di base per la
programmazione a basso livello (assembler) per tutte le moderne architetture dei
calcolatori. Col termine "dato complesso" intendiamo perciò indicare dati il cui
uso richiede attenzione.
Il linguaggio C 158/243
Strutture (struct)
Vediamo come è definita un tipo di dato struct chiamato luogo, che vuole rappresentare
un punto geografico individuato da tre coordinate intere x, y, z ed un nome rappresentato
da una stringa di 30 caratteri:
Vediamo come si definisce il tipo di dato "luogo":
struct luogo {
char nome[30];
int x;
int y;
int z;
};
Una volta definito il tipo di dato, possiamo dichiarare una variabile di quel tipo,
premettendo però la keyword struct:
struct luogo monte_bianco;
80
Il linguaggio C 159/243
Strutture (struct)
Una sintassi diversa permette di dichiarare in una sola istruzione il tipo di dato struct
luogo ed alcune variabili di quel tipo.
struct luogo {
char nome[30]; int x;
int y; int z;
} monte_bianco , monte_rosa;
In questo caso, qualora solo queste variabili debbano essere dichiarate, ovvero qualora
non ci sia altrove necessità di conoscere il tipo di dato "luogo", l'identificatore "luogo "
può essere omesso, e si avrà:
struct {
char nome[30]; int x;
int y; int z;
} monte_bianco , monte_rosa;
Il linguaggio C 160/243
Strutture (struct)Anche con i dati di tipo struct possiamo ricorrere alla typedef per definire nuovi nomi per i
tipi di dato. Ad es:
typedef struct {
char nome[30];
int x;
int y;
int z;
} LUOGO;
In questo modo abbiamo definito un nome che può essere utilizzato per dichiarare delle
variabili di tipo LUOGO in tutto e per tutto uguale al precedente tipo di dato luogo, che però
in questa dichiarazione non compare. Ora potremo dichiarare variabili omettendo la
keyword struct.
LUOGO milano.
81
Il linguaggio C 161/243
Strutture (struct)E' possibile comunque mantenere i due identificatori:
typedef struct luogo {
char nome[30];
int x; int y; int z;
} LUOGO;
e definire due variabili di tipo uguale pure se con nomi diversi, ed il compilatore li tratta
come dati di un solo tipo, permettendo ad es. gli assegnamenti.
struct luogo Cesena;
LUOGO Cesenatico.
Cesena = Cesenatico;
Per accedere ai membri (o campi) di una struttura struct, il C fornisce l'operatore punto "." .
Ad esempio: Cesena.nome accede alla stringa nome del dato Cesena di tipo luogo.
Il linguaggio C 162/243
Inizializzazione delle struct
Una struttura struct puo' essere pre-inizializzata al momento della dichiarazione, mettendo
le costanti che inizializzano uno per uno tutti i campi della struct tra parentesi graffe.
struct luogo rimini = { "RIMINI" , 100, 139 , 10};
E’ possibile anche annidare nell'esempio qui di seguito le struct ed inizializzarle assieme,
come:#include <stdio.h>
void main() {
struct luogo {
char nome[30];
int x, y, z;
};
struct coppia_di_luoghi {
struct luogo luogo1;
struct luogo luogo2;
};
struct coppia_di_luoghi Rimini_Milano = {
{ "RIMINI" , 100, 139 , 10} , { "MILANO" , 80, 170 , 50 }
};
printf("luogo1=%s\n" , Rimini_Milano.luogo1.nome); /* stampa
"RIMINI“ */
}
82
Il linguaggio C 163/243
Unioni: union
Un'union e' una variabile che può tenere (in momenti diversi)
oggetti di diversa dimensione e tipo, che sono quindi posizionati
sulla stessa area di memoria, cioè sovrapposti.
Spesso i diversi oggetti sono delle struct, ed hanno tutte un campo,
eventualmente con un nome diverso da struct a struct, ma collocato
nella stessa posizione in memoria, in cui viene mantenuta una
costante che identifica quale struttura è contenuta nell'union in quel
momento.
In questo modo, ad es, si può passare dati diversi ad una stessa
funzione, e la funzione leggendo questo campo di identificazione
della struttura, capisce che genere di struttura è contenuta in quel
momento nella union, e può accedere correttamente ai dati della
union.
Il linguaggio C 164/243
Unioni: esempiostruct A {int idA; char field1[10]; char field2[20]; char field3[10] };
struct B { int idB; char campo1[8]; char campo2[10]; };
struct C { int idC; char val1[12]; char val2[25]; char val3[20] };
union ABC {
struct A a;
struct B b;
struct C c;
} abc = {IS_A, “field1”, “field2”, “field3”}
Il compilatore alloca area sufficente a contenere la più grande delle strutture contenute nella union. I
campi sono acceduti col nome della struttura contenuta, seguita dal punto come nelle struct e dal
nome del campo.
es: if( abc.a.idA == IS_B )
printf("%s", abc.b.campo2);
83
Il linguaggio C 165/243
PuntatoriI puntatori sono il segreto della potenza e la flessibilità del C, perchè:
- sono l'unico modo per effettuare alcune operazioni;
- servono a produrre codici sorgenti compatti ed efficienti, anche se a volte difficili da
leggere.
In compenso, la maggior parte degli errori che i programmatori commettono in linguaggio
C sono legati ai puntatori.
Il C utilizza molto i puntatori in maniera esplicita con:
- vettori;
- strutture;
- funzioni.
In C ogni variabile è caratterizzata da due valori:
- un indirizzo della locazione di memoria in cui sta la variabile,
- ed il valore contenuto in quella locazione di memoria, che è il valore della variabile.
Il linguaggio C 166/243
PuntatoriUn puntatore e' un tipo di dato, è una variabile che contiene l'indirizzo in memoria di
un'altra variabile, cioè un numero che indica in quale cella di memoria comincia la variabile
puntata.
Si possono avere puntatori a qualsiasi tipo di variabile.
La dichiarazione di un puntatore include il tipo dell'oggetto a cui il puntatore punta; questo
serve al compilatore che deve accedere alla locazione di memoria puntata dal puntatore per
sapere cosa troverà, in particolare per sapere le dimensioni della variabile puntata dal
puntatore.
Per dichiarare un puntatore p ad una variabile di tipo tipo, l'istruzione e':
tipo *p ;
L' operatore & fornisce l'indirizzo di una variabile, perciò l'istruzione p = & c scrive
nella variabile p l'indirizzo della variabile c, ovvero:
tipo c, *p; dichiaro una var c di tipo tipo ed un puntatore p a tipo
p = &c ; assegno a p l'indirizzo di c
84
Il linguaggio C 167/243
Puntatori
L'operatore * viene detto operatore di indirezione o deriferimento. Quando
una variabile di tipo puntatore è preceduta dall'operatore *, indica che stiamo
accedendo all'oggetto puntato dal puntatore.
Quindi con *p indichiamo la variabile puntata dal puntatore.
int c, *p; dichiaro una variabile c di tipo int ed un puntatore p a int.
p = &c; assegno a p l’indirizzo di c
c= 5; assegno a c il valore 5
printf("%d\n", *p); stampo il valore puntato dal puntatore p
viene stampato 5
Il linguaggio C 168/243
PuntatoriConsideriamo gli effetti delle seguenti istruzioni:
int *pointer; /* dichiara pointer come un puntatore a int */
int x=1,y=2;
(1) pointer = &x; /* assegna a pointer l'indirizzo di x */
(2) y = *pointer; /* assegna a y il contenuto dell'int puntato da pointer, x */
(3) x = pointer; /* assegna ad x l'indirizzo contenuto in pointer, serve cast
perchè pointer a int è diverso da int */
(4) *pointer=3; /* assegna alla variabile puntata da pointer il valore 3 */
Vediamo cosa succede in memoria. Supponiamo che la variabile x si trovi nella
locazione di memoria 100, y nella 200 e pointer nella 1000.
•L'istruzione (1) fa sì che pointer punti alla locazione di memoria 100 (quella di x), cioè
che pointer contenga il valore 100.
•La (2) fa sì che y assuma valore 1 (il valore di x).
•La (3) fa sì che x assuma valore 100 (cioe' il valore di pointer).
•La (4) fa sì che il valore del contenuto di pointer sia 3 (quindi x=3).
85
Il linguaggio C 169/243
Puntatori
Quindi con i puntatori possiamo considerare tre possibili valori:
• pointer: contenuto o valore della variabile pointer (indirizzo della
locazione di memoria a cui punta)
• &pointer: indirizzo fisico della locazione di memoria del puntatore
• *pointer: contenuto della locazione di memoria a cui punta
NB. Quando un puntatore viene dichiarato non punta a nulla, o meglio poichè
il contenuto di una cella di memoria è casuale fino a che non viene
inizializzata ad un valore noto, il puntatore punta ad una locazione di
memoria casuale, che potrebbe non essere accessibile dal processo. Cosi':
int *ip;
*ip=100;
scrive il valore 100 in una locazione qualsiasi: grave errore.
Il linguaggio C 170/243
PuntatoriL'utilizzo corretto e' il seguente; prima di scrivere un valore nella locazione di memoria
puntata dal puntatore ci assicuriamo che tale locazione di memoria appartenga al nostro
programma. Ciò e possibile in due modi:
1) il primo modo consiste nell'assegnare al puntatore l'indirizzo di una variabile del
nostro programma, quindi scriveremo il valore nella variabile puntata.
int *ip; int x; ip=&x;
*ip=100; scrivo 100 in x
2) il secondo modo consiste nel farci riservare dal sistema operativo una porzione di
memoria, salvare l'indirizzo di questa porzione di memoria nel nostro puntatore (ovvero far
puntare il puntatore a quell'area di memoria) in modo che i successivi riferimenti alla
locazione di memoria puntata dal puntatore lavorino su questa area di memoria che ci è
stata riservata.
Esiste una funzione di libreria standard malloc(), equivalente all’istruzione new di Java
e C++, che permette l'allocazione dinamica della memoria; e' definita come:
void *malloc ( int number_of_bytes).
Ad es.: int *p;
p= (int *) malloc(sizeof(int)); assegna a p spazio per un int.
86
Il linguaggio C 171/243
Aritmetica degli indirizzi
Si possono fare operazioni aritmetiche intere con i puntatori,
ottenendo come risultato di far avanzare o riportare indietro il
puntatore nella memoria, cioè di farlo puntare ad una locazione
di memoria diversa. Ovvero con i puntatori è possibile
utilizzare due operatori aritmetici + e - , ed ovviamente anche
++ e --.
Il risultato numerico di un'operazione aritmetica su un
puntatore è diverso a seconda del tipo di puntatore, o meglio a
seconda delle dimensioni del tipo di dato a cui il puntatore
punta. Questo perchè il compilatore interpreta diversamente
la stessa istruzione p++ a seconda del tipo di dato, in modo
da ottenere il comportamento seguente:
Il linguaggio C 172/243
Aritmetica degli indirizzi• Sommare un'unità ad un puntatore significa spostare in avanti in
memoria il puntatore di un numero di byte corrispondenti alle dimensioni del dato puntato dal puntatore.
• Ovvero se p è un puntatore di tipo puntatore a char, char *p; poichè il char ha dimensione 1, l'istruzione p++ aumenta effettivamente di un'unita il valore del puntatore p, che punterà al successivo byte.
• Invece se p è un puntatore di tipo puntatore a short int, short int *p; poichè lo short int ha dimensione 2 byte, l'istruzione p++ aumenteràeffettivamente di 2 il valore del puntatore p, che punterà allo short intsuccessivo a quello attuale.
87
Il linguaggio C 173/243
Aritmetica degli indirizzi
In definitiva, ogni volta che un puntatore viene
incrementato passa a puntare alla variabile successiva che
appartiene al suo tipo base, mentre un decremento lo fa
puntare alla variabile precedente. Quindi incrementi e
decrementi di puntatori a char fanno avanzare o
indietreggiare i puntatori a passi di un byte, mentre
incrementi e decrementi di puntatori a dati di dimensione
K fanno avanzare o indietreggiare i puntatori a passi di K
bytes. Il caso dei puntatori a void void *p viene trattato
come il caso dei puntatori a char, cioè incrementato o
decrementato a passi di un byte.
Il linguaggio C 174/243
Aritmetica degli indirizzi
Sono possibili non solo operazioni di incremento e decremento
(++ e --) ma anche somma e sottrazione di dati interi (char, int,
long int) che comunque vengono effettuati sempre secondo le
modalità di incremento decremento a passi di dimensioni pari alla
dimensione del dato puntato dal puntatore.
es: long int *p;
p = p + 9; oppure p +=9;
queste istruzioni fanno avanzare il puntatore p di 9*sizeof(long
int)=9*4=36 byte.
88
Il linguaggio C 175/243
Aritmetica degli indirizzi
long int *p;
char i;
i = 9;
p = p +i; oppure p +=i;
Anche queste istruzioni fanno avanzare il puntatore p di i*sizeof(long
int)=9*4=36 byte.
Non sono consentite altre operazioni oltre all'addizione e sottrazione di interi.
Non è possibile sommare sottrarre moltiplicare o dividere tra loro dei
puntatori.
Ad es,
int *ptr, *ptr1;
ptr = ptr + ptr1;
da' errore in compilazione di tipo "invalid operands to binary".
Il linguaggio C 176/243
Confronto tra puntatori
Si possono effettuare confronti tra puntatori, per verificare ad es. quale tra due
puntatori punta ad una locazione di memoria precedente, oppure se due
puntatori puntano ad una stessa locazione di memoria.
Ad es. sono valide istruzioni del tipo:
int x, y;
int *p, *q;
p = &x;
q = &y;
if(p<q)
printf("minore");
89
Il linguaggio C 177/243
Allocazione dinamica della memoria
I programmi C suddividono la memoria in 4 aree distinte: codice,
dati globali, stack e heap. Lo heap è un'area di memoria libera che
viene gestita da funzioni di allocazione dinamica del C quali lamalloc() e la free().
malloc() alloca la memoria (chiede al s.o. di riservare una area
di memoria) e restituisce un puntatore a void che punta all'inizio
di quest'area di memoria allocata. Se non è disponibile sufficiente
memoria restituisce NULL, (l'equivalente a null di Java) ad
indicare che l'allocazione non è stata effettuata. NULL equivale al
valore zero, quindi è possibile testare il risultato della malloc() in
questo modo:
Il linguaggio C 178/243
Allocazione dinamica della memoria
int *p;
p = (int*) malloc( 100*sizeof(int) ); /* alloca 100 interi */
if( !p ) {
printf("errore: allocazione impossibile");
exit(1);
} else {
.... uso la memoria
free(p); /* rilascia la memoria allocata v*/
}
90
Il linguaggio C 179/243
Puntatori e array monodimensionali
Riassumendo, i puntatori sono delle variabili che contengono un
indirizzo in memoria, e si dice che il puntatore "punta a
quell'indirizzo". Il puntatore può essere di tipo "puntatore ad un
tipo" oppure di tipo generico "puntatore a void". Il puntatore
consente di accedere alla memoria a cui punta mediante
l'operatore [].
Se p è un puntatore ad un certo tipo (tipo *p;) e contiene un certo
valore addr, ovvero punta ad un certo indirizzo addr, l'espressione
p[k] accede all'area di memoria che parte dal byte
addr+k*sizeof(tipo) ed ha dimensione sizeof(tipo), trattandola
come se fosse una variabile di tipo tipo.
Il linguaggio C 180/243
Puntatori e array monodimensionali
• Nel caso di un puntatore a void, viene considerata 1 la dimensione del dato puntato, cioè il puntatore punta ad un byte.
• Anche per i vettori (gli array monodimensionali di dati di tipo tipo) l'accesso ai dati avviene secondo queste modalità vet[k], perchè in C, il nome di unarray è TRATTATO dal compilatore come un puntatore COSTANTE alla prima locazione di memoria dell'array stesso . (costante significa che non posso assegnare qualcosa al nome del vettore ma solo alle sue posizioni)
91
Il linguaggio C 181/243
Puntatori e array monodimensionali
A differenza dei vettori però, l'area di memoria a cui il puntatore punta non
viene allocata staticamente come per i vettori a partire dalla loro definizione
(che contiene le dimensioni del vettore stesso), ma può essere allocata
dinamicamente mediante alcune funzioni che richiedono al sistema operativo
di riservare memoria e restituire un indirizzo a quell'area di memoria allocata,
oppure può non essere allocata affatto.
Comunque la differenza principale tra puntatori e vettori e' che i puntatori
sono variabili che contengono un indirizzo, e questo contenuto (indirizzo)
può essere cambiato per puntare ad un'area di memoria diversa.
Invece per i vettori, anche se il loro nome è trattato come un puntatore (ma
costante) all'inizio del vettore stesso, non esiste una variabile (una cella di
memoria) in cui è mantenuto l'indirizzo della prima locazione del vettore,
e quindi questo indirizzo non può essere modificato, è costante. Ad es: il
compilatore da' errore qui:
int vet[100]; vet++; vet =10;
Il linguaggio C 182/243
Puntatori e array monodimensionali
A partire da queste considerazioni sottolineamo che definite due
variabili, un vettore int vet[100] ed un puntatore int *p mentre è
possibile fare riferimento all'indirizzo di un puntatore &p
ottenendo un indirizzo che punta alla locazione di p, cioè che indica
a che indirizzo sta la variabile p, nel caso dei vettori l'indirizzo del
vettore &vet è l'indirizzo che punta all'inizio del vettore stesso,
ovvero è l'indirizzo del primo elemento del vettore stesso. Ovvero le
seguenti espressioni indicano tutte la stessa cosa, l'inizio del vettore: vet, &(vet[0]), &vet
char str[100];
char *p;
p = str;
92
Il linguaggio C 183/243
Puntatori e array monodimensionali
con questo assegnamento viene assegnato al puntatore p l'indirizzo della prima locazione di memoria del vettore. Da questo momento in avanti potremo accedere ai dati del vettore sia tramite str, sia tramite p esattamente negli stessi modi, o tramite l'indicizzazione tra parentesi quadre, o tramite l'aritmetica dei puntatori.
str[10], *(str+10), p[10], *(p+10) sono tutti modi uguali per accedere alla 11-esima posizione del vettore str puntato anche da p.
es: str[10] = 'A';
printf( "str[10]=%c\n", str[10] );
printf( "str[10]=%c\n", p[10] );
printf( " str[10]=%c\n", *(p+10) );
Il linguaggio C 184/243
Un esempio di uso di puntatori: copia di una
stringa in un'altra.
Vediamo un esempio di applicazione dei puntatori, la copia di una stringa,
confrontandola con un'implementazione che non usa puntatori.
char sorgente[]="STRINGA";
char destinazione[100];
char *src, *dest;
src=sorgente;
dest=destinazione;
while( *(dest++) = *(src++) );
char sorgente[]="STRINGA";
char destinazione[100];
int i=0;
for (i=0; ; i++){
destinazione[i]=sorgente[i];
if( ! destinazione[i] )
break;
}
93
Il linguaggio C 185/243
Puntatori a strutture
E' possibile utilizzare puntatori che puntano a dati di tipo strutturato
come le struct. La definizione di questo tipo di puntatore ricalca
esattamente la definizione generale, cioè prevede di definire il tipo
di dato, e poi di definire il puntatore a quel tipo di dato.
Es.
struct PUNTO {char nome[50]; int x; int y; int z; }; /*
def. tipo */
struct PUNTO *ptrpunto; /* dichiaraz. puntatore a var.
strutturata */
struct PUNTO punto; /* dichiaraz. variabile di tipo
strutturato */
ptrpunto = &punto; /* assegno l'indirizzo della var.
punto */
Il linguaggio C 186/243
Puntatori a strutture
E' necessario per semplicita' definire un operatore che permetta di accedere ai campi della struttura direttamente dal puntatore.In C esiste l'operatore -> che permette l'accesso ad un campo della struttura puntata.
Nell'esempio, ptrpunto->x = 102; accede in scrittura al campo x della struttura di tipo PUNTO puntata da ptrpunto. Quindi l'operatore -> equivale ad usare in maniera combinata l'operatore * facendolo precedere al nome del puntatore per accedere al dato strutturato, seguito dall'operatore . per accedere al campo di interesse. Nell'esempio, l'istruzione ptrpunto->x = 102;
equivale all'istruzione:
(*ptrpunto).x = 102;
94
Il linguaggio C 187/243
Puntatori in struttureOra che abbiamo introdotto i puntatori, vediamo perchè è stato reso possibile utilizzare
per le struct quella sintassi particolare, con il nome della struct prima della sua
descrizione.
Vogliamo definire una struttura dati che serva come nodo di una lista semplice, una
struttura che contenga cioe' un intero (il dato della lista) ed un puntatore all'elemento
successivo della lista.
Il problema è come definire un puntatore ad un tipo di dato mentre ancora il tipo
di dato non è stato definito. Vediamo due esempi alternativi di come procedere, in un
caso utilizzando la typedef, nell'altro no.
struct nodolista {
int id;
struct nodolista *next;
};
struct nodolista nodo;
typedef struct nodolista {
int id;
struct nodolista *next;
} NODOLISTA;
NODOLISTA nodo;
Il linguaggio C 188/243
Array multidimensionaliUn array multidimensionale è un array, i cui elementi sono a loro volta degli array, i cui
elementi ecc.. fino ad esaurire le dimensioni volute.
Facciamo riferimento per semplicità al caso degli array bidimensionali, le matrici.
Le matrici in C sono locazioni di memoria contigue che contengono i dati
memorizzati per righe (come in pascal, mentre il fortran memorizza per colonne).
Quindi la matrice, anche se viene pensata come un oggetto a due dimensioni del tipo ad
es. (3 righe per 4 colonne), cioè come un vettore di righe, ciascuna delle quali è un
vettore,
è in realtà un oggetto disposto linearmente in memoria, riga dopo riga, come un vettore
95
Il linguaggio C 189/243
Array multidimensionaliIndicizzando righe e colonne a partire da 0, se NR è il numero di righe, ed NC è il
numero di colonne, allora l'elemento in posizione (n,c) della matrice sta fisicamente
nella posizione r*NC+c del vettore.
La matrice (es. di int) viene quindi dichiarata come un vettore di NR elementi di tipo
vettore di NC interi, ovvero come segue:
#define NR 3
#define NC 4
int matrice[NR][NC];
Nella dichiarazione la dimensione delle matrici deve essere una costante, perchè il
compilatore per effettuare l'accesso all'elemento (r,c) della matrice ovvero all'elemento
r*NC+c (a partire da dove inizia la matrice) deve conoscere la dimensione delle righe
cioè il numero NC di colonne.
L'accesso al dato in posizione r,c della matrice viene effettuato mediante l'operatore [ ]
già usato per i vettori.
Ad es. matrice[1][3]=7;
scrive il valore 7 nell'elemento che sta nella seconda riga in quarta colonna.
Il linguaggio C 190/243
Array multidimensionaliAnalogamente ai vettori, il nome della matrice rappresenta il puntatore al primo
elemento della matrice, cioè alla prima riga.
All'atto della dichiarazione è possibile inizializzare le matrici come di seguito
indicato:
int matrice [2][3] = {
{ 1,2,3 } ,
{ 4,5,6 }
};
L'inizializzazione viene fatta scrivendo tra parentesi graffe i valore di ciascun
elemento, separati da virgole. Come si vede, essendo la matrice un vettore di righe,
ciascuna riga viene inizializzata in modo analogo, scrivendo tra parentesi graffe i
valori di ciascun elemento separati da virgole.
96
Il linguaggio C 191/243
Vettori di puntatoriVogliamo costruire dinamicamente un dato M che sia un array di n puntatori ad int,
per poi passare ad allocare, per ciascuno dei puntatori ad int dell'array, spazio sufficiente
ad m int, ottenendo in definitiva un array di vettori di interi.
Il linguaggio C 192/243
Vettori di puntatori
Se gli elementi dell'array dinamico M sono puntatori ad interi, il nostro
puntatore M sarà un puntatore di puntatore ad int, cioè sarà un int* *M;
L'elemento intero che sta nella posizione c-esima dell'r-esimo vettore di
interi verrà acceduta mediante un'espressione: M[r][c]
i = M[r][c]
In effetti questa struttura dati è spesso usata come matrice in C, quando
non sono note a priori le dimensioni della matrice da realizzare, e si
preferisce non sovradimensionarla con un'allocazione statica.
97
Il linguaggio C 193/243
Vettori di puntatori/* allocazione della struttura dati */
int n,m r,c;
int* *M;
/* valori ad n ed m assegnati run-time */
n = ...;
m = ...;
if ( ! (M = malloc( n * sizeof(int*) )) )
exit(1);
for ( r=0; r<n; r++)
if ( ! (M[r] = malloc( m * sizeof(int) )) )
exit(1);
else
for ( c=0; c<m; c++)
M[r][c] = valore
Il vettore di puntatori qui visto viene utilizzato in un caso particolare, in cui tutti i vettori
allocati hanno stessa dimensione. In generale invece è possibile per ogni puntatore
allocare un vettore di dimensioni diverse.
Il linguaggio C 194/243
Inizializzazione statica di un vettore di
puntatori
E' inoltre possibile costruire un dato M che sia un array di n puntatori, non
solo dinamicamente, ma anche staticamente al momento della dichiarazione.
La seguente istruzione crea ed inizializza un array di puntatori a char, cioè un
array di stringhe di lunghezza diversa (nomi è un vettore di puntatori a char).
char *nomi [] = {
"paola",
"marco",
"giovanna"
};
98
Il linguaggio C 195/243
Funzioni
In C non esiste la distinzione che esiste in Pascal tra funzioni e procedure, non esistono le classi e
i metodi di Java, in C sono tutte funzioni, che possono restituire un qualche risultato oppure no,
nel qual caso restituiscono void.
Le chiamate ad ogni funzione in C si effettuano chiamando il nome della funzione seguita dalle
parentesi tonde, aperta e chiusa, all'interno delle quali vengono passati i parametri necessari.
Anche se la funzione non richiede nessun argomento, nella chiamata il suo nome deve essere
seguito dalle parentesi tonde aperta e chiusa. Il main stesso è una funzione.
La forma generale della definizione di una funzione e':
tipo_restituito nome_funzione (paramdef1, paramdef2,
...) {
dichiarazione variabili locali ii
istruzioni
}
Dove
•paramdef1, paramdef2, ecc. sono la definizione degli argomenti da passare alla
funzione all'atto della chiamata (ad es. int i).
•tipo_restituito è il tipo del dato che viene restituito come risultato dalla funzione.
Il linguaggio C 196/243
Funzioni
Se manca la definizione del tipo di dato restituito dalla funzione
("tipo_restituito"), il C assume che il risultato della funzione e' di tipo int.
La funzione restituisce il risultato mediante un'istruzione detta return(), che,
analogamente a quanto avviene in Java ha la forma:
return espressione;
o
return (espressione);.
La return termina la funzione e restituisce il controllo al chiamante. Se la
funzione non deve restituire nessun valore, si dichiara il tipo_restituito
come void, e non si esegue la return o la si esegue senza passare alcun argomento
( es.: return;).
99
Il linguaggio C 197/243
Funzioni: esempio
Es. funzione che somma due valori di tipo int e restituisce un int:int somma(int a, int b) {
int sum;
sum = a+b;
return(sum);
}
La chiamata della funzione viene fatta così:
void main(void) {
int A=23;
int B=-31;
int risultato;
risultato = somma(A,B);
printf("somma= %d\n", risultato);
}
Il linguaggio C 198/243
Funzioni: esempio
Es. funzione che non restituisce alcun valore: void somma(int a, int b) {
int sum;
sum = a+b;
printf("somma= %d\n", sum); /* non serve la return */
}
La chiamata della funzione viene fatta così: void main(void) {
int A = 23;
int B = -31;
somma(A,B);
}
100
Il linguaggio C 199/243
Passaggio degli argomenti: dati semplici
I dati di tipo semplice (char, int, long float, double e puntatori ), che
vengono passati come argomenti ad una funzione, vengono passati per valore
e non per indirizzo.
Ciò significa che nel momento della esecuzione della funzione, il valore
degli argomenti viene copiato sullo stack, e la funzione usa (ed
eventualmente modifica) questa copia degli argomenti. In tal modo il dato
originale rimane invariato dopo che la funzione ha restituito il controllo al
chiamante.
Se invece si vuole che un argomento di tipo semplice passato alla funzione
conservi le modifiche apportate dalla funzione durante l'esecuzione della
funzione, l'argomento deve essere passato per indirizzo, cioè alla funzione
deve essere passato l'indirizzo della variabile, in modo che la funzione (tramite
l'indirizzo) modifica la variabile originale.
Il linguaggio C 200/243
Passaggio degli argomenti: dati semplici
Es. funzione che somma due valori di tipo int e restituisce un int:
void azzera_variabile( int *ptr_a ) {
*ptr_a = 0;
}
La chiamata della funzione viene fatta così:void main(void) {
int A=23;
azzera_variabile ( &A );
/* viene passato l'indirizzo */
}
101
Il linguaggio C 201/243
Passaggio degli argomenti: array
I dati di tipo array ( vettori, matrici, array di dimensioni maggiori), che vengono
passati come argomenti ad una funzione, in C vengono passati per indirizzo, nel
senso che "passando l'array per nome si passa l'indirizzo in cui comincia
l'array".
Ciò significa che quando, all'atto della chiamata, passiamo ad una funzione il
nome di un vettore, passiamo l'indirizzo del primo elemento del vettore, e non
tutti i dati del vettore ( i 100 interi dell' esempio)
void main(void) {
int vet[100];
modifica_vet (vet , 100 );
}
di conseguenza la definizione della funzione dovrà contenere come argomento
formale il puntatore al tipo di dati del vettore (un puntatore ad int nell'esempio)
Il linguaggio C 202/243
Passaggio degli argomenti: array
void modifica_vet( int *v , int size ) {
v[0] = 137; /* modifica conservata fuori
dalla funzione */
v = NULL; /* annullo l'indirizzo in v, ma
questa
modifica NON viene mantenuta fuori
dalla funzione */
}
In tal modo se il vettore passato come argomento viene modificato dalla funzione,
cioè se la funzione modifica il contenuto degli elementi del vettore, queste
modifiche sono permanenti, cioè restano nel vettore anche dopo che la
funzione è terminata.
102
Il linguaggio C 203/243
Vettori
Quando l'argomento passato è un vettore, nella definizione della funzione l'argomento
può essere indicato o come un puntatore oppure in un modo alternativo, come segue:
void modifica_vet( int v[] , int size ) {
v[0] = 137; /* modifica conservata fuori
dalla funzione */
v = NULL; /* annullo l'indirizzo in v, ma questa
modifica NON viene mantenuta fuori
dalla funzione */
}
Questa notazione int v[] vuole indicare che v è un vettore di interi di dimensione
sconosciuta (non si sa di quanti interi è composto il vettore).
Comunque le due notazioni int *v e int v[] sono assolutamente equivalenti,
entrambi i parametri vengono trattati come puntatori.
Il linguaggio C 204/243
MatriciAnalogamente ai vettori, quando, all'atto della chiamata, passiamo ad una
funzione il nome di una matrice, passiamo l'indirizzo del primo elemento
della matrice, quindi le modifiche sui dati della matrice vengono conservate
dopo l'uscita dalla funzione.
void main(void) {
int mat[10][20];
modifica_mat ( mat );
}
void modifica_mat( int m[][20] ) {
m[1][0] = 137;/* modifica conservata
fuori dalla funz. */
m = NULL; /* questa modifica NON viene mantenuta */
}
Analogamente ai vettori, la definizione della funzione dovrà contenere un puntatore
ad array di 20 (= num. colonne) interi. Notare: si passa la dimensione delle righe, per
calcolare l'indice r*NC+c.
103
Il linguaggio C 205/243
Passaggio degli argomenti: strutture
I dati di tipo struct possono venire passati alle funzioni sia per valore sia per puntatore,
esplicitamente.struct point {
int x;
}
/* copia i valori di p1 nella struttura puntata da pp2 */
void copia_e_modifica_struct( struct point p1, struct point *pp2 ) {
pp2-> x = p1.x + 10; /* modifiche permanenti */
pp2-> y = p1.y + 10;
}
void main(void) {
struct point p1 = { 14 , 27 };
struct point p2;
copia_e_modifica_struct( p1, & p2 );
}
Se la struttura da passare è molto grande, è bene passarla per puntatore, per non
sovraccaricare lo stack, soprattutto quando le chiamate sono molto annidate.
Il linguaggio C 206/243
Passaggio di parametri: confronto C-Java
Java manipola gli oggetti per riferimento, e tutte le variabili che si riferiscono a
oggetti sono riferimenti (handle).
Java, però, effettua il passaggio di parametri esclusivamente per valore.
public void badSwap(int var1, int var2) {
int temp = var1;
var1 = var2;
var2 = temp;
}
Quando la funzione termina, il valore dei parametri var1 e var2 non sono
cambiati, dal punto di vista del chiamante. Ciò resta valido anche cambiando il
tipo da int a Object.
104
Il linguaggio C 207/243
Scambio di parametri in C
void swap(int x, int y){int temp;temp = x;x = y;y = temp;
}
main() {int a = 3;int b = 5;swap(a,b);printf(“%d
%d\n”,a,b);}
� Qual è l’output del programma ?
� 3 5
� Ciò accade perché la funzione swap agisce solo su una copia delle variabili a e b
� Per far sì che la funzione agisca sulle variabili stesse e non su delle copie occorre passare gli indirizzi delle variabili
Il linguaggio C 208/243
Scambio di parametri in C
void swap(int *x_ptr, int *y_ptr) {int temp;temp = *x_ptr;*x_ptr = *y_ptr;*y_ptr = temp;
}
main() {int a = 3;int b = 5;swap(&a,&b);printf(“%d %d\n”,a,b);
}
105
Il linguaggio C 209/243
Tipi di dato restituiti dalle funzioni
Le funzioni possono restituire:
•dati di tipo semplice, come char, int, long, float, double, puntatori a void, o
puntatori a qualche tipo di dato,
•ma anche strutture (struct) o puntatori a struct.
All'atto della chiamata, il valore restituito da una funzione:
- può essere utilizzato come espressione booleana:
double somma( double f, double g);
if ( somma(a,b) > 100.3 )
può essere utilizzato come membro di destra in un'istruzione di assegnamento,
f = somma(a,b);
oppure può non essere considerato affatto.
somma(a,b);
Il linguaggio C 210/243
Tipi di dato restituiti dalle funzioni
Vediamo un esempio di restituzione di una struct.
struct point { int x; int y; };
struct point crea_point( int x, int y ) {
struct point p;
p.x = x;
p.y = y;
return p ;
}
void main(void) {
struct point p1;
int x=21 , y = -10987;
p1 = crea_point( x, y );
printf ( "p1.x=%d p1.y=%d \n" , p1.x, p1.y );
}
106
Il linguaggio C 211/243
Considerazioni sui puntatori restituiti
dalle funzioni
Una funzione può restituire un puntatore ad un qualche tipo di dato, ma la
correttezza nell'uso di questo puntatore si può fare, dipende da come lo spazio in
memoria è allocato. Cioè:
Esempio corretto: p è allocato dinamicamente quindi, anche se p è una
variabile locale, lo spazio allocato sopravvive alla terminazione della funzione
int *alloca_vettore( int size ) {
int *p;
p = malloc(size*sizeof(int));
return p ;
}
void main(void) {
int *v;
v = alloca_vettore ( 10 ) ;
... usa v ....
}
Il linguaggio C 212/243
Considerazioni sui puntatori restituiti
dalle funzioni
Esempio sbagliato: p è una variabile locale, lo spazio allocato non sopravvive alla
terminazione della funzione
int *alloca_vettore( int size ) {
int p[1000];
return p ;
}
Esempio corretto: vet_globale è una variabile globale, quindi sopravvive alla
terminazione della funzione
void main(void) {
int *v;
v = alloca_vettore ( 10 ) ;
}
int vet_globale[10000];
int *spazio_pre_allocato( void ) {
return vet_globale ;
}
void main(void) {
int *v;
v = spazio_pre_allocato();
... usa v ....
}
107
Il linguaggio C 213/243
Il Prototipo delle funzioniIl prototipo o dichiarazione della funzione è un'istruzione che ripete l'intestazione della
funzione, senza il codice.
es:
double somma( double a, double b) ; dichiarazione
void main( void) {
double A=10 , B=29, C;
C = somma(A,B); chiamata
}
double somma( double a, double b) { definizione
return a+b ;
}
Il linguaggio C 214/243
Il Prototipo delle funzioni
Il prototipo serve a due scopi:
1) dare visibilità alla funzione quando il luogo in cui è chiamata precede il
luogo in cui è definita (se stiamo sullo stesso file), altrimenti il compilatore
non sa cos'è il simbolo nome della funzione. Se il compilatore trova un
simbolo nuovo seguito da parentesi tonde, lo tratta come una funzione che
restituisce un valore intero.
2) informare il compilatore su come deve trattare il dato restituito dalla
funzione, e su come passare i dati agli argomenti della funzione. Quindi il
prototipo e la definizione della funzione devono essere coerenti, altrimenti il
main (nell'esempio) tratterebbe i dati scambiati in modo diverso da come la
funzione somma si aspetta, generando errori. Il compilatore si accorge di
un'inconsistenza tra prototipo e definizione della funzione solo se entrambe
stanno su uno stesso modulo. Se stanno su due moduli diversi il compilatore
non se ne accorge e non ci avvisa.
108
Il linguaggio C 215/243
Ordine di valutazione degli argomenti
passati alle funzioni.
Il compilatore C opera in modo che le espressioni passate come argomenti alle
funzioni sono prima valutate, ed il risultato della valutazione viene scritto sullo
stack per essere disponibile al codice che implementa la funzione stessa.
Sorgono due problemi:
- in che ordine vengono valutate le espressioni?
-in che ordine vengono scritti sullo stack i risultati delle valutazioni ?
La risposta alle due domande è la stessa:
vengono prima valutate (e il risultato scritto sullo stack) le espressioni
passate come ultimo argomento della funzione (le più a destra), e poi via via
quelle più a sinistra.
Il linguaggio C 216/243
void stampa3( int a, int b, int c){
printf("a=%d b=%d c=%d\n", a, b, c);
printf("&a=%p &b=%p &c=%p\n”,&a,&b,&c);
}
void main( void) {
int x=0;
stampa3 (x++, x++, x++);
}
L'output del programma sarà del tipo:
a=2 b=1 c=0 (c valutato per primo)
&a=9003:0FF8 &b=9003:0FFA &c=9003:0FFC (c primo su stack) che indica come il terzo
argomento (ultimo, cioè più a destra) venga valutato (e scritto sullo stack) per primo, e poi gli
argomenti via via più a sinistra.
Ordine di valutazione degli argomenti
passati alle funzioni.
109
Il linguaggio C 217/243
Puntatori a funzione
In C è possibile utilizzare dei puntatori a funzioni, ovvero delle variabili a cui possono
essere assegnati gli indirizzi in cui risiedono le funzioni, e tramite questi puntatori a
funzione, le funzioni puntate possono essere chiamate all'esecuzione.
Confrontiamo la dichiarazione di una funzione:
tipo_restituito nome_funzione (paramdef1, paramdef2, ...)
con la dichiarazione di un puntatore a funzione:
tipo_restituito ( * nome_ptr_a_funz ) (paramdef1, paramdef2, ...)
dove:
paramdef1, paramdef2, ecc. sono la definizione degli argomenti da passare alla
funzione all'atto della chiamata (ad es.: int i).
tipo_restituito è il tipo del dato che viene restituito come risultato dalla funzione.
Ad esempio la seguente dichiarazione definisce un puntatore a funzione che punta a
funzioni le quali prendono come argomenti due double, e restituiscono un double (void).
double (*ptrf) ( double g, double f);
Il linguaggio C 218/243
Puntatori a funzione
Il C tratta i nomi delle funzioni come se fossero dei puntatori alle funzioni
stesse.
Quindi, quando vogliamo assegnare ad un puntatore a funzione l'indirizzo di una
certa funzione dobbiamo effettuare un operazione di assegnamento del nome
della funzione al nome del puntatore a funzione
Se ad es. consideriamo la funzione dell'esempio precedente:
double somma( double a, double b);
allora potremo assegnare la funzione somma al puntatore ptrf così:
ptrf = somma;
110
Il linguaggio C 219/243
Puntatori a funzione: esecuzioneAnalogamente, l'esecuzione di una funzione mediante un puntatore che la punta,viene
effettuata con una chiamata in cui compare il nome del puntatore come se fosse il
nome della funzione, seguito ovviamente dai necessari parametri. Per es. riprendiamo
l'esempio della somma:
double somma( double a, double b); /* dichiarazione
*/
void main( void) {
double A=10 , B=29, C;
double (*ptrf) ( double g, double f);
ptrf = somma;
C = ptrf (A,B); /* chiamata alla funz. somma */
}
double somma( double a, double b) { /* definizione */
return a+b ;
}
Spesso è complicato definire il tipo di dato puntatori a funzione, ed ancora di più
definire funzioni che prendono in input argomenti di tipo puntatori a funzione. In queste
situazioni è sempre bene ricorrere alla typedef per creare un tipo di dato
puntatore a funzione per funzioni che ci servono, ed utilizzare questo tipo di dato nelle
altre definizioni.
Il linguaggio C 220/243
Usare la typedef per rendere leggibile il
codice C
Esempio:
esiste in ambiente unix una funzione detta signal che puo' servire ad associare una
funzione al verificarsi di un evento. Ad esempio può servire a far eseguire una funzione
allo scadere di un timer.
Il prototipo della funzione, contenuto in signal.h, e' il seguente:
#include <signal.h>
void (*signal(int signum, void (*handler)(int) )
)(int);
NON E' SUBITO CHIARISSIMO COSA SIA QUESTA ROBA !!!
Il significato è che
1) la funzione signal vuole come parametri un intero signum, ed un puntatore handler
ad una funzione che restituisce un void e che vuole come parametro un intero.
2) la funzione signal restituisce un puntatore ad una funzione che restituisce un void e
che vuole come parametro un intero.
111
Il linguaggio C 221/243
Usare la typedef per rendere leggibile il
codice C
Converrebbe definire un tipo di dato come il puntatore a funzione richiesta:
typedef void (*tipo_funzione) (int);
ed utilizzarlo per definire la signal così:
tipo_funzione signal(int signum, tipo_funzione handler);
Nel man della signal e' presente questo commento:
If you're confused by the prototype at the top of this manpage, it may help to see it
separated out thus:
typedef void (*handler_type)(int);
handler_type signal(int signum, handler_type handler);
Il linguaggio C 222/243
Funzioni con numero di argomenti variabile
In C e' possibile definire funzioni aventi un numero di argomenti
variabile, cioe' funzioni che in chiamate diverse possono avere un
numero di argomenti diverso, ma ne debbono avere sempre
almeno uno. Ne è un esempio la printf();
Si utilizza a questo scopo una struttura va_list definita nel file
stdarg.h. Vediamo un esempio di funzione che riceve in input
n argomenti, di cui il primo e' un intero che contiene il numero di
argomenti seguenti, e gli altri sono delle stringhe, cioe' dei
puntatori a char. La funzione deve solo stampare tutti gli
argomenti passati.
112
Il linguaggio C 223/243
Funzioni con numero di argomenti variabile
void print_lista_argomenti(int narg, ...) {
va_list va;
int i; char *ptrchar;
/* va_start inizializza va_list all'argomento, tra passati a
print_lista_argomenti, che segue l'argomento narg indicato come
secondo argomento nella va_start, cioè inizializza la lista al primo
degli argomenti variabili */
va_start(va,narg);
for(i=0;i<narg;i++) {
ptrchar=va_arg(va,char*); /*ptrchar punta alla stringa
passata*/
printf("%d %s\n", i, ptrchar);
}
va_end(va);
}
la funzione potra' essere chiamata in una di questi modi:
print_lista_argomenti(0);
print_lista_argomenti(4,"primo","secondo","terzo","quarto");
Il linguaggio C 224/243
Funzioni con numero di argomenti variabile
Vediamo un esempio complicato ma utile di uso della lista di argomenti variabili. Stampa gli
argomenti passati, anche se sono di tipo diverso. Usa un primo parametro come formato per
sapere cosa viene passato nei successivi argomenti, indicando con s una stringa, con d un
intero, con c un carattere.
#include <stdio.h>
#include <stdarg.h>
void stampa_argomenti(char *fmt, ...) {
va_list ap;
int d;
char c, *p, *s;
va_start(ap, fmt);
while (*fmt)
switch(*fmt++) {
case 's': /* string */
s = va_arg(ap, char *);
printf("string %s\n", s);
break;
113
Il linguaggio C 225/243
Funzioni con numero di argomenti variabile
case 'd': /* int */
d = va_arg(ap, int);
printf("int %d\n", d); break;
case 'c': /* char */
c = va_arg(ap, char);
printf("char %c\n", c); break;
}
va_end(ap);
}
void main (void) {
stampa_argomenti ( "sdc" , "stringa" , (int) 10 ,
(char)'h' ); stampa_argomenti ("s" , "stringa2");
}
Il linguaggio C 226/243
INPUT ed OUTPUT
Le funzioni della libreria standard per il C per l'I/O sono definite nell'header
file stdio.h . (input / output standard)
Quando un programma C entra in esecuzione l'ambiente del sistema operativo
si incarica di aprire 3 files di I/O, ovvero 3 flussi di dati, e di fornire i rispettivi
puntatori di tipo FILE * globali.
La struttura chiamata FILE contiene le informazioni fondamentali sul file,
quali il modo di apertura del file, il nome del file, l'indirizzo di un buffer in cui
sono conservati i dati letti dal file e la posizione corrente nel buffer. L'utente
non deve conoscere questi dettagli, ma deve memorizzare in una variabile di
tipo puntatore a FILE (FILE*) l'indirizzo di questa struttura per utilizzarla
nelle letture o scritture su file.
114
Il linguaggio C 227/243
INPUT ed OUTPUT
I flussi di dati standard sono:
STANDARD INPUT serve per l'input normale (per default da tastiera), e ha come
puntatore a FILE la variabile globale stdin. Dallo standard input prendono i dati le
funzioni getchar, gets e scanf.
STANDARD OUTPUT serve per l'output normale (per default su schermo), e ha
come puntatore a FILE la variabile globale stdout. Sullo standard output scrivono i loro
dati le funzioni putc, printf e puts
STANDARD ERROR serve per l'output che serve a comunicare messaggi di errore
all'utente (per default anche questo su schermo), e ha come puntatore a FILE la
variabile globale stderr.
stdin, stdout e stderr sono delle costanti globali contenute in stdio.h,. e non
possono essere modificate.
Questi flussi possono pero' essere ridirezionati in modo da scrivere su file su disco,
oppure di leggere da file su disco.
Il linguaggio C 228/243
INPUT
- Il sistema prevede che l'input sia organizzato a linee, ciascuna terminata
da un carattere new line (\n), solitamente fornite dall'utente tramite la
tastiera, ma a volte anche fornite via file.
- L'utente da tastiera può digitare i caratteri e cancellarli, ma tali caratteri non
vengono passati all'applicazione fino a che l'utente non preme
<RETURN> nel qual caso i dati presenti sul buffer di tastiera vengono inseriti
in un vettore di caratteri, e in fondo viene aggiunto un carattere NEW LINE
(individuato da \n ). - NOTARE che l'utente non ha a disposizione i
caratteri fino a che non viene premuto RETURN, dopodiché i dati, in forma
di linea di testo terminante con un NEW_LINE sono pronti per essere letti
dall'applicazione.
La lettura dei dati può essere effettuata dallo standard input un carattere
alla volta oppure una linea alla volta.
115
Il linguaggio C 229/243
INPUT
La lettura carattere per carattere viene effettuata mediante la funzione:
int getchar(void);
che restituisce il successivo carattere in input in forma di codice ASCII, cioe' restituisce
ad es. 65 per 'A', 66 per 'B', ecc. 97 per 'a'. 98 per 'b', oppure restituisce EOF quando
incontra la fine del flusso di input, che viene rappresentata come la fine del file di
input.
Tale funzione e' bloccante nel senso che quando una linea di caratteri
precedentemente data in input e' finita, rimane in attesa che dallo standard input arrivi
una nuova linea di dati.
La lettura di una intera linea viene effettuata mediante la funzione:
char *gets( char *dest);
che scrive in dest la linea e restituisce un puntatore a dest se tutto va bene, restituisce
NULL altrimenti.
Il linguaggio C 230/243
Fine dell'INPUT
Se l'utente preme contemporaneamente la coppia di tasti (CTRL+d in UNIX, CTRL+z
in DOS seguito prima o poi da RETURN), in un certo senso segnala all'applicazione
che il flusso di dati e' terminato. Nella linea di testo viene aggiunto un carattere EOF
(che di solito e' rappresentato dal valore -1) ma che e' bene sempre confrontare con la
costante EOF (definita in stdio.h), ed il flusso di input viene chiuso, cioè da quel
momento in avanti ogni successiva chiamata a getchar restituirà sempre EOF.
ESEMPIO di lettura dallo standard input
#include <stdio.h>
void main(void) {
int ch;
while( (ch=getchar()) != EOF ) {
/* uso il carattere, ad es lo incremento di 1 e lo
stampo*/
ch ++;
putchar(ch);
}
}
116
Il linguaggio C 231/243
Ridirezionamento di input e output
L'utente puo' utilizzare lo standard input dare input non solo da tastiera ma anche da
file, ridirezionando un file sullo standard input nel seguente modo, al momento
dell'esecuzione del programma:
program < file_input
Analogamente l'output di un programma puo' essere ridirezionato su un file, su cui sara'
scritto tutto l'output del programma invece che su video
program > file_output
e le due cose possono essere fatte assieme
program < file_input > file_output
In UNIX si può ridirezionare assieme standard output e standard error:
program >& file_error_and_output
Il linguaggio C 232/243
Output non formattato
La scrittura dei dati sullo standard output (video) puo' essere effettuata un
carattere alla volta mediante la funzione:
int putchar(int ch);
che restituisce il carattere scritto ch se va tutto bene, altrimenti restituisce EOF
in caso di errore.
117
Il linguaggio C 233/243
Output formattatoL'output può essere effettuato anche in maniera formattata, e non solo un carattere alla volta.
La funzione printf serve proprio a stampare un output a blocchi sullo standard output secondo un
formato specificato, traducendo le variabili in caratteri, cioè printf stampa sullo standard output
gli argomenti arg1, arg2, ... secondo le specifiche contenute nel format.
int printf(char *format, ... );
meno formalmente
int printf(char *format, arg1, arg2, arg 3, ...);
il format e' una stringa di caratteri null-terminata, in cui compaiono o dei semplici caratteri che
verranno stampati così come sono oppure degli specificatori di formato nella forma %qualcosa.
"%d" stampa un intero
"%10d" stampa un intero e un minimo di 10 caratteri almeno in cui l'intero e' a destra
"%-10d" stampa un intero e un minimo di 10 caratteri almeno in cui l'intero e' a sinistra
"%f" stampa un float
"%lf" stampa un double
"%10f" stampa un float e un minimo di 10 caratteri almeno in cui il float e' a destra
Il linguaggio C 234/243
Output formattato"%-10f" stampa un float e un minimo di 10 caratteri almeno in cui il float e' a sinistra
"%10.5f" stampa un float e un minimo di 10 caratteri con al massimo 5 cifre dopo il punto
decimale
"%s" stampa tutti i caratteri di una stringa (cioe' un vettore di caratteri con un \0 in fondo)
fino a che incontra lo 0 finale "%10s" stampa almeno 10 caratteri di una stringa,
con la stringa a destra
"%15.10s" stampa almeno 15 caratteri, prendendone al massimo 10 dalla stringa, con la stringa a
destra
"%c" stampa un singolo carattere NB. per stampare un % devo scrivere %%
NB alcuni caratteri di controllo possono essere scritti mediante la loro sequenza di escape
NEW LINE (a capo linea) \n
TABULAZIONE \t
BACKSPACE \b torna indietro di un carattere ma non lo cancella
se però scrivo qualcosa gli passo sopra
118
Il linguaggio C 235/243
Output
NB poiché le stringhe vengono delimitate da due doppi apici " per stampare un doppio
apice devo scriverlo preceduto da un BACKSLASH cioè devo scrivere \"
Analogamente, poiché la \ serve a indicare una sequenza di escape, per stampare una \
devo scriverne due. (vedi nomi file nella fopen).
esempio:
int i=16;
double g=107.13987626;
char *str="pippo";
printf("%d) %10.3lf \"%s\"\n",i,g,str);
da' come risultato
16) 107.139 "pippo"
Il linguaggio C 236/243
Input formattato da stdin
Per estrarre dal flusso dello standard input i dati e' spesso utilizzata la funzione scanf.
La scanf cioe' prende l'input dallo standard input (aspetta un RETURN) e cerca di
estrarre i dati che sono specificati nel suo primo agomento
int scanf(char *format, ....);
int scanf(char *format, &arg1, &arg2, &arg 3, ... );
I dati estratti vengono scritti nelle variabili i cui indirizzi sono passati come argomenti
successivi al primo nella scanf.
NB. L'errore classico e' passare come argomento la variabile e non il suo indirizzo.
119
Il linguaggio C 237/243
Input formattato da stdin
La stringa di formato può contenere sia dei semplici caratteri, che allora devono
corrispondere ai caratteri che vengono digitati in input, sia degli specificatori di
conversione nella forma “%qualcosa” che indicano come devono essere interpretati i
caratteri che costituiscono il flusso di input.
%c viene letto un singolo carattere e copiato singolarmente nel primo byte
indicato dal puntatore
%10c vengono letti 10 caratteri e copiati nei primi 10 byte indicati dal puntatore
%s viene letta la stringa e scritta interamente a partire dal puntatore passato, e in
fondo viene aggiunto un carattere '\0' di terminazione
%d viene letto un intero
%f viene letto un float
%lf viene letto un double
Il linguaggio C 238/243
Input formattato da stdinLa scanf termina quando esaurisce la sua stringa di formato o quando verifica una
inconsistenza tra l'input e le specifiche del formato.
La scanf restituisce il numero di elementi trovati. Se non è stato scritto nessun elemento
possono esserci due motivi diversi: o lo standard input era stato chiuso e allora la scanf
restituisce EOF, oppure lo standard input era ancora aperto ma l'input non era consistente con
le richieste del formato e c'è stato errore nella conversione ed allora viene restituito 0.
Es. se in input l'utente scrive: "punti: 14 9.1 7 21" mediante la sequenza di istruzioni
int i,j,k,result;
double g;
result=scanf("punti: %d %lf %d %d", &i , &g , &j , &k);
if(result==EOF)
printf("FINE FILE INPUT\n");
else if(result<4)
printf("ERRORE, solo %d dati in input\n",result);
else
printf("OK: i=%d g=%lf j=%d k=%d\n",i,g,j,k);
120
Il linguaggio C 239/243
Input formattato da stdin
ottiene a video:
OK: i==14 g==9.1 j==7 k==21
NB la stringa di formato viene scandita dalla scanf, e se nel formato c'è un
blank (uno spazio bianco) l'input può contenere più caratteri di spaziatura
blank tab newline ecc. che non vengono considerati (cioè vengono considerati
come un unico carattere blank).
Il linguaggio C 240/243
Accesso ai files
Problema: Leggere i dati da un file, e scrivere dei dati su un altro file.
Supponiamo di avere un file c:\users\input.txt di testo formato da linee
costituite da due double x e y (ovvio in forma di caratteri ascii).
139.2 29.1
-13.1 1009.0
.... ....
Vogliamo leggere tutte le coppie e scrivere su un file di testo
c:\users\output.txt le sole coppie in cui x>0
121
Il linguaggio C 241/243
Accesso ai files
int main(void) {
FILE *finput, *foutput; double x,y;
int result;
finput=fopen("c:\\input.txt","rt");
if ( finput==NULL ) {
printf("errore: impossibile aprire file di
input\n");
exit(1);
}
foutput=fopen("c:\\output.txt","wt");
if ( foutput==NULL ) {
printf("errore: impossibile aprire file di
output\n");
exit(1);
}
Il linguaggio C 242/243
Accesso ai files/* fino a che non sono alla fine del file di input
*/
while( (result=fscanf(finput,"%lf %lf\n", &x, &y))
!= EOF ) {
if(result != 2) {
/* ho letto meno campi di quelli
richiesti */
printf("errore in lettura\n");
exit(1);
}
if( x > 0.0 )
fprintf(foutput,"%f %f\n",x,y);
}
fclose(finput);
fclose(foutput);
return(0);
}
122
Il linguaggio C 243/243
Accesso ai filesNB. prima di essere letto o scritto un file deve essere aperto mediante la funzione fopen
FILE *fopen( char *nomefile, char *modo);
il primo parametro specifica il nome del file ed eventualmente il percorso per raggiungerlo come
ad es: c:\\pippo.txt. NOTARE la necessità d'usare la doppia BACKSLASH \\ posto di una
sola.
il secondo parametro specifica il modo di apertura del file, che sara' diverso a seconda dell'uso
che si vuole fare del file. Il modo puo' essere uno dei seguenti
"r" apertura in sola lettura, per leggere, posiziona all'inizio del file. "w" apertura in sola scrittura,
crea il file se non esiste, lo cancella e si posiziona all'inizio se gia' esiste.
"a" apertura in scrittura per aggiungere dati alla fine del file, se il file esiste gia' si posiziona alla
fine del file per poter aggiungere dati senza cancellare quelli presenti
A questi modi si puo' aggiungere un "+" ottenendo "r+" "w+" "a+" per permettere
l'aggiornamento in lettura e scrittura. La posizione in cui ci si colloca e' quella dovuta a "r" o "w"
o "a"
A questi modi si puo'' aggiungere un "t" per indicare che si apre il file come un file di testo, ed
allora saranno utilizzabili le primitive di lettura e scrittura di testo fgets, fputs, oppure un "b" ad
indicare che il file verra' aperto come un file binario. Per default il modo di apertura é di tipo testo
"t".
Il linguaggio C 244/243
Accesso ai filesLa funzione fopen restituisce un PUNTATORE a FILE detto file pointer che punta ad una
struttura chiamata FILE che contiene le informazioni fondamentali sul file, quali il modo di
apertura del file, il nome del file, l'indirizzo di un buffer in cui sono conservati i dati letti dal file e
la posizione corrente nel buffer.
L'utente non deve conoscere questi dettagli, ma deve memorizzare in una variabile questo valore
restituito ed utilizzarlo in tutte le altre letture o scritture su file.
Quando il file e' stato aperto, possiamo leggerlo e/o scriverlo a seconda del modo di apertura
utilizzando le seguenti funzioni di libreria.
int getc(FILE *f);
int putc(int c, FILE *f);
int fscanf(FILE *finput, char *format, ... );
int fprintf(FILE *foutput, char *format, ... );
char *fgets(char *dest, int nchar, FILE *finput);
123
Il linguaggio C 245/243
I/O da file
Quando il file non serve più, deve essere chiuso con la chiamata alla funzione di
libreria
int fclose( FILE *f);
che restituisce 0 in caso vada tutto bene, 1 in caso di errore.
int getc(FILE *f);
legge il prossimo carattere dal file f, restituisce il carattere oppure EOF in caso di errore
o di fine file.
int putc(int c, FILE *f);
scrive il carattere c sulla posizione corrente del file. restituisce il carattere scritto
oppure EOF in caso di errore.
Il linguaggio C 246/243
I/O da file
int fscanf(FILE *finput, char *format, ... );
e' analoga alla scanf ma prende input dal file finput invece che dallo standard input restituisce il
numero di campi letti oppure EOF se la posizione corrente nel file e' a fine file
int fprintf(FILE *foutput, char *format, ... );
e' analoga alla printf ma scrive sul file invece che sullo standard output restituisce il numero di
byre scritti
La lettura scrittura da un file puo' essere effettuata anche linea per linea usando le seguenti
funzioni:
char *fgets(char *dest, int nchar, FILE *finput);
legge dal file finput una linea di input (compreso il NEWLINE \n) fino ad un massimo di nchar-
1 caratteri e la scrive nel vettore dest. Aggiunge dopo l'ultimo carattere un '\0' per terminare la
stringa. Restituisce un puntatore alla stringa in cui ha scritto oppure NULL in caso di errore
int *fput(char *string, FILE *foutput);
scrive sul file foutput una stringa ( con o senza il NEWLINE \n ) restituisce 0 se va tutto bene,
EOF in caso di errore.
124
Il linguaggio C 247/243
La gestione dell’errore
Esiste una variabile globale intera, definita nell'header errno.h che viene
settata nel caso in cui una chiamata di sistema non possa eseguire
correttamente il suo compito. Tale variabile allora indica il tipo di errore
avvenuto.
#include <errno.h>
extern int errno;
Tale variabile può essere letta e scritta come ogni altra variabile.
In particolare il valore di questa variabile serve come indice per una variabile
di sistema che è un vettore di stringhe. Queste stringhe contengono dei
messaggi di errore caratteristici dell'errore avvenuto.
#include <stdio.h>
const char *sys_errlist[];
Il linguaggio C 248/243
La gestione dell’erroreNel caso una funzione di sistema ci avvisi di un errore possiamo farci
visualizzare a video (sullo standard error) notifica l'errore, utilizzando la
funzione
void perror(const char *str);
La stringa puntata da str viene visualizzata prima del messaggio di errore. Se
l'ultima chiamata di sistema non ha causato errno, perchè il suo valore non è
definito.#include <stdio.h>
#include <errno.h>
void apri_file(void) {
FILE *finput;
finput=fopen(“/home/user/input.txt","rt");
if ( finput==NULL ) {
perror("errore in funzione apri_file”);
exit(1);
}
}
125
Il linguaggio C 249/243
La gestione dell’errore
errno è una variabile globale definita dalla libreria.
Se la si legge senza che si sia verificato un errore il risultato è indefinito, la
stess cosa succederà per la funzione perror.
Prima di poter utilizzarle, quindi è necessario verificare che si sia verificato un
errore:finput=fopen(“/home/user/input.txt","rt");
if ( finput==NULL ) {
perror("errore in funzione apri_file”);
exit(1);
}
Il linguaggio C 250/243
I/O da file
Quando un file precedentemente aperto non serve più, deve essere chiuso con la chiamata alla
funzione di libreria
int fclose( FILE *f);
che restituisce 0 in caso vada tutto bene, 1 in caso di errore.
Poichè l'I/O con le funzioni viste è bufferizzato, potrebbero essere rimaste ancora delle operazioni
di scrittura da completare, cioè qualche byte scritto sul file in precedenza potrebbe essere ancora
in un buffer temporaneo, e non essere stato ancora fisicamente scritto sul file. Con la chiamata alla
fclose si effettuano definitivamente eventuali operazioni di scrittura rimaste in sospeso.
La funzione
int fflush( FILE *f);
effettua esplicitamente lo svuotamento dei buffer facendo completare le operazioni rimaste in
sospeso riguardanti lo stream di output indicato da f.
Serve ad assicurarci che ad un certo istante le operazioni precedenti siano state effettuate. Può
servire ad esempio dopo alcune printf per far effettivamente scrivere i caratteri sul video. In
qualche caso infatti tale operazione potrebbe essere dilazionata. fflush() restituisce 0 in caso vada
tutto bene, EOF in caso di errore.
126
Il linguaggio C 251/243
I/O da file
La funzione
int feof( FILE *f);
restituisce un valore diverso da 0 se la posizione corrente del file ha raggiunto la fine
del file. Restituisce 0 se non siamo alla fine del file.
La funzione
int ferror( FILE *f);
restituisce un valore diverso da 0 se lo stream ha verificato un qualche tipo di errore.
Il linguaggio C 252/243
I/O in blocchiINPUT / OUTPUT di BLOCCHI di byte da file
ANSI C mette a disposizione anche delle primitive che permettono di leggere/scrivere su un file un
blocco di byte di una dimensione specificata. I prototipi di queste finzioni sono anch'essi contenuti
nell'header stdio.h, e tali funzioni sono:
size_t fread(void *ptr, size_t size, size_t number, FILE *finput);
cerca di leggere dal file finput un blocco di byte formato da number blocchi ciascuno di size byte. I
dati letti sono scritti nell'area dati puntata da ptr, che deve ovviamente essere correttamente allocata,
cioè di dimensioni sufficienti. fread restituisce il numero di blocchi letti, e non il numero di byte letti.
Se all'inizio della lettura si incontra la fine del file la funzione fread restituisce 0, perché non riesce a
leggere neanche un blocco. Però anche in caso di un qualche tipo di errore fread restituisce 0. Quindi
se fread restituisce zero è necessario utilizzare le funzioni ferror o feof per capire se è accaduto un
errore o se si è raggiunta la fine del file.
size_t fwrite(const void *ptr, size_t size, size_t num, FILE*fout);
cerca di scrivere sul file foutput un blocco di byte formato da number blocchi ciascuno di size byte,
copiandoli dall'area dati puntata da ptr. fwrite restituisce il numero di blocchi scritti, e non il numero
di byte. Come per la fread in caso di un qualche tipo di errore fwrite() restituisce 0.
127
Il linguaggio C 253/243
Riga di comandoL’ANSI C mette a disposizione il modo di passare al programma dei parametri nel
momento in cui questo viene lanciato. Partendo ad esempio da una shell di comandi di
Windows NT (una finestra simil DOS per intenderci) possiamo far seguire al nome del
programma una serie di caratteri separati da spazi,
che verranno copiati e passati sotto forma di stringhe in un vettore di puntatori a char
passati come argomento al main.
Il main dovrà essere a tal scopo definito come nell'esempio seguente, dove:
- argc indica il numero di parametri passati più uno (il nome del programma).
- argv è un vettore di stringhe che contiene la riga di comando, in prima posizione il
nome del programma lanciato, nelle seguenti i parametri passati Il programma stampa il
numero dei parametri passati a riga di comando compreso il nome del programma, e
poi li stampa ad uno ad uno
Il linguaggio C 254/243
Riga di comando
#include <stdio.h>
int main(int argc, char *argv[]) {
int i;
printf("argc=%d { ",argc); for ( i=0; i<argc; i++)
printf("%d:%s ", i, argv[i]); printf("}\n");
}
Se il programma si chiama param, può essere eseguito "lanciandolo" dalla shell di comando
mediante una delle seguenti linee di comando, ottenendo come output:
linea di comando | output
|
param calv hobbes 3.0 40 | argc=5 { 0:par 1:cal 2:hobbes 3:3.0 4:40}
param | argc=1 { 0:param }
param calvin | argc=2 { 0:param 1:calvin }
128
Il linguaggio C 255/243
I/O di basso livello
Abbiamo in precedenza visto che è possibile effettuare I/O da file, utilizzando primitive
di tipo fopen, fread fwrite, fscanf, fgets. Tali operazioni sono però limitate ai file.
Quello che si vorrebbe avere a disposizione sono delle primitive che possono agire
indifferentemente su file o su dispositivi di I/O di tipo diverso, quali interfacce di
rete, interfacce seriali ecc. Si vorrebbe cioè poter utilizzare una stessa primitiva per
accedere a entità di tipo diverso. Esistono a tale scopo delle funzioni di librerie
cosiddette per l'I/O di basso livello, che permettono di operare su diversi device.
Vengono ad esempio utilizzate per scrivere applicazioni per comunicazioni via rete.
Ogni dispositivo per I/O, verrà aperto con una funzione detta open che restituisce un
intero detto "descrittore di file". Questo numero intero rappresenta un indice per una
struttura dati che descrive le modalità con cui operare sul dispositivo. Tale descrittore
verrà utilizzato in tutte le chiamate per l'effettuazione di I/O.
Poiché queste istruzioni sono di basso livello, non esiste più distinzione tra file di testo
e file binari. Tutti i file sono visti come concatenazione di byte.
Il linguaggio C 256/243
I/O di basso livelloUn esempio d'uso di queste primitive è qui di seguito riportato.
/*leggo i byte di un file binario. */
#include <stdio.h>
#include <fcntl.h>
main(int argc,char **argv) {
int fd;
int bytes_read;
char byte;
if((fd=open(argv[1],O_RDONLY))==-1)
exit(1); /*errore,file non aperto*/
while ( (bytes_read=read(fd,&byte,1))>0 ) { /* leggo un
byte */
.... uso il byte letto
}
if (bytes_read==-1)
exit(1); /* errore in lettura file */
close(fd);
}
129
Il linguaggio C 257/243
I/O#include <stdio.h>
#include <fcntl.h>
Per aprire un file si usa la funzione:
int open(char *filename, int flag, int perms)
che ritorna un file descriptor con valore >=0, oppure -1 se l'operazione fallisce.
Il parametro flag controlla l'accesso al file ed è una combinazione (un OR bit a bit) ha i seguenti
predefiniti valori definiti nel file fcntl.h: O_APPEND, O_CREAT, O_EXCL, O_RDONLY,
O_RDWR, O_WRONLY ecc., con i seguenti significati:
O_APPEND apre per aggiungere dati in fondo al file, e posiziona il puntatore alla posizione
corrente alla fine del file O CREAT crea un file nuovo se non esiste
O_EXCL viene usato con O_CREAT, se il file già esiste causa errore O_RDONLY apre e
consente solo la lettura, si posiziona all'inizio del file O RDWR apre e consente sia lettura che
scrittura si posiziona all'inizio
O_WRONLY apre e consente solo la scrittura
O_TRUNC se il file esiste viene troncato, riparte da lunghezza zero
Il parametro "perms" specifica i permessi da assegnare al file quando questo viene creato, in caso
contrario viene ignorato.
Il linguaggio C 258/243
I/O
Per creare un file si puo' anche usare la funzione:
int creat(char *filename, int perms)
Per chiudere un file si usa:
int close(int fd)
Per leggere/scrivere uno specificato numero di bytes da/su un file immagazzinati in una
locazione di memoria specificata da "buffer" si utilizzano:
int read(int fd, char *buffer, unsigned length)
int write(int fd, char *buffer, unsigned length)
Queste due funzioni ritornano il numero di byte letti/scritti o -1 se falliscono.
130
Il linguaggio C 259/243
I/O ad accesso casuale
Un file può essere acceduto in lettura o scrittura anche in una maniera diversa da
quella sequenziale, cioè è possibile spostarsi in un file aperto ovvero spostare il
cosiddetto puntatore alla posizione corrente nel file.
Questo puntatore è un intero e indica il prossimo byte da leggere o scrivere. Il
primo byte del file è indicato da 0, il secondo da 1 e così via.
E possibile spostare la posizione corrente utilizzando una primitiva C:
int fseek(FILE *stream, long offset, int ptrname);
fseek stabilisce la nuova posizione del prossimo byte da leggere o scrivere. La
nuova posizione viene indicata da due parametri: una base indicata da ptrname
che puo' essere o l'inizio del file (SEEK_SET) o la posizione corrente
(SEEK_CUR) o la fine del file (SEEK_END), ed uno scostamento cioè la
distanza dalla base espressa in numero di byte, che è il parametro offset.
La funzione restituisce -1 in caso di errore, 0 se tutto OK.
Il linguaggio C 260/243
I/O ad accesso casuale
/* lettura del byte in posizione 135 nel file */
int c;
if ( ! fp=fopen("prova.dat","rt") )
exit(1);
if ( (fseek(fp, (long)135, SEEK_SET)) <0 )
exit(2); /* errore */
c = getc(fp);
131
Il linguaggio C 261/243
Operatori sui bit
Il C mette a disposizione degli operatori che lavorano su numeri di tipo intero (char, int,
long int) manipolando il dato a livello di singolo bit. Non si applicano ad operandi
floating point o a dati di tipo strutturato.
& AND (bit a bit)
| OR ''
^ OR ESCLUSIVO (XOR)
~ NOT (complemento ad uno)
>> SHIFT (SCORRIMENTO) a DESTRA
<< SHIFT (SCORRIMENTO) a SINISTRA
Il linguaggio C 262/243
Operatori sui bit
L'operatore AND & effettua un and bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa
posizione quando entrambi i bit in quella posizione nei due operandi valgono 1, altrimenti lo setta a 0.
unsigned char op1, op2, op3;
op1=63; 0 0 1 1 1 1 1 1
op2=240; 1 1 1 1 0 0 0 0
op3 = op1 & op2; 0 0 1 1 0 0 0 0
op3 assume valore 48
L'operatore OR | effettua un or bit a bit tra due operandi, ovvero setta ad 1 un bit in una certa posizione
quando almeno uno dei bit in quella posizione dei due operandi vale 1 altrimenti lo setta a 0.
unsigned char op1, op2, op3;
op1=60; 0 0 1 1 1 1 0 0
op2=240; 1 1 1 1 0 0 0 0
op3 = op1 | op2; 1 1 1 1 1 1 0 0
op3 assume valore 252
132
Il linguaggio C 263/243
Operatori sui bit
L'operatore XOR ^ effettua un or esclusivo bit a bit tra due operandi, ovvero setta ad 1 un bit in
una certa posizione quando i bit in quella posizione nei due operandi sono diversi, altrimenti lo
setta a 0.
unsigned char op1, op2, op3;
op1=60; 0 0 1 1 1 1 0 0
op2=240; 1 1 1 1 0 0 0 0
op3 = op1 ^ op2; 1 1 0 0 1 1 0 0
op3 assume valore 204
Questo operatore presenta una tabella di verità (bit a bit) siffatta:
op1 op2 op1^op2
0 0 0
1 0 1
0 1 1
1 1 0
op3 assume valore 195
Il linguaggio C 264/243
Operatori sui bit
L'operatore NOT ~ è un operatore unario che inverte lo stato di ogni bit, cioè
setta ad 1 i bit che valgono 0, e viceversa setta a 0 i bit che valgono 1.
op1=60; 0 0 1 1 1 1 0 0
op3 = ~op1; 1 1 0 0 0 0 1 1
133
Il linguaggio C 265/243
Operatori sui bit
Gli operatori di scorrimento (SHIFT) sono operatori unari che realizzano lo
spostamento a sinistra o a destra dei bit di una variabile di tipo intero, mettendo a 0 i
bit che entrano, cioè non rientrano da sinistra i bit usciti da destra. La forma generale
dell'istruzione di SHIFT A DESTRA è del tipo:
variabile_intera >> numero_posizioni
Significa che i bit di variabile_intera vengono tutti spostati a destra di
numero_posizioni.
op1=61; 0 0 1 1 1 1 0 1
op3 = op1>>1; 0 0 0 1 1 1 1 0
op3 assume valore 30 (in neretto il bit entrante)
Si noti come l'operatore corrisponda ad una divisione intera per multipli di 2. Shiftare a
destra di 1 significa dividere per 2, shiftare di 2 è dividere per 4.
op3 = op1>>3; 0 0 0 0 0 1 1 1
op3 assume valore 7 (in neretto i bits entranti)
Il linguaggio C 266/243
Operatori sui bit
La forma generale dell'istruzione di SHIFT A SINISTRA è del tipo:
variabile_intera << numero_posizioni
Significa che i bit di variabile_intera vengono tutti spostati a sinistra di
numero_posizioni. I bits entranti da destra sono posti a 0.
op1=61; 0 0 1 1 1 1 0 1
op3 = op1<<1; 0 1 1 1 1 0 1 0
op3 assume valore 122 (in neretto il bit entrante)
Notare come questo operatore corrisponda al risultato di una moltiplicazione intera
per un multiplo di due finchè a sinistra non esce qualche bit ad 1. “Shiftare” a
sinistra di 1 significa moltiplicare per 2
op3 = op1<<3; 1 1 1 0 1 0 0 0
op3 assume valore 232 (in neretto i bit
entranti)
134
Il linguaggio C 267/243
Operatori compositi sui bit
Il C mette a disposizione anche operatori che coniugano un'operazione bit a bit
all'operazione di assegnamento.
AND bit a bit e Assegnamento x &= y equivale a x = x&y
OR bit a bit e Assegnamento x |= y equivale a x = x|y
XOR bit a bit e Assegnamento x ^= y equivale a x = x^y
Shift a Destra e Assegnamento x >>= y equivale a x = x>>y
Shift a Sinistra e Assegnamento x <<= y equivale a x = x<<y
Il linguaggio C 268/243
Alcune funzioni della libreria C standardManipolazione dei buffer
#include <memory.h>
void *memchr (void *s, int c, size_t n) - Cerca un carattere c nei primi
n caratteri di un buffer puntato da s. Restituisce un puntatore al primo carattere c trovato,
o NULL se non lo trova.
int memcmp (void *s1, void *s2, size_t n) - Paragona i primi n byte di
due buffers s1 ed s2. Restituisce un risultato minore di zero se s1<s2, uguale a zero se
s1=s2, maggiore di zero se s1>s2.
void *memcpy (void *dest, void *src, size_t n) - Copia n byte di un
buffer in un altro. Causa problemi se le due aree sono sovrapposte.
void *memmove (void *dest, void *src, size_t n) - Copia n byte di
un buffer in un altro. Funziona correttamente anche se le due aree sono sovrapposte.
Restituisce un puntatore alla destinazione.
void *memset (void *s, int c, size_t n) - Setta tutti i bytes di un buffer
ad un dato carattere. Restituisce un puntatore al buffer s.
135
Il linguaggio C 269/243
Classificazione di caratteri#include <ctype.h>
int isalnum(int c) - Vero se "c" e' alfanumerico, '0'...'9' 'a'...'z' 'A'...'Z'. int isalpha(int c) -
Vero se "c" e' una lettera dell'alfabeto. int iscntrl(int c) - Vero se "c" e' un carattere di controllo, i
primi 31.
int isdigit(int c) - Vero se "c" e' un numero decimale.
int islower(int c) - Vero se "c" e' una lettera minuscola.
int isprint(int c) - Vero se "c" e' un carattere stampabile.
int ispunct (int c) - Vero se "c" e' un carattere di punteggiatura.
int isspace(int c) - Vero se "c" e' un carattere spazio cioè tab o blanck o newline o
carriage return..
int isupper(int c) - Vero se "c" e' una lettera maiuscola.
int isxdigit(int c) - Vero se "c" e' un numero esadecimale.
int toascii(int c) - Converte "c" in ASCII, settando a zero il bit piu‘ significativo.
tolower(int c) - Converte "c" in minuscolo.
int toupper(int c) - Converte "c" in maiuscolo.
Il linguaggio C 270/243
Conversione dei dati
#include <stdlib.h>
double atof(char *string) -Converte una stringa in un valore floating point.
int atoi(char *string) - Converte una stringa in un valore integer.
int atol(char *string) - Converte una stringa in un valore long integer.
char *itoa(int value, char *string, int radix) - Converte un valore integer in una
stringa utilizzando il "radix" come base per la conversione (es 10 per conversione di numeri in base 10).
Le seguenti funzioni effeftuano un maggiore controllo sull'errore:
char *ltoa(long value, char *string, int radix) - Converte un valore long integer
in una stringa in un dato "radix".
double strtod(char *string, char *endptr) - Converte una stringa in un valore in
floating point.
long strtol(char *string, char *endptr, int radix) - Converte una stringa in un
valore long integer utilizzando un dato "radix".
unsigned long strtoul(char *string, char *endptr, int radix) - Converte
una stringa in un valore long senza segno.
136
Il linguaggio C 271/243
Alcune funzioni matematiche#include <math.h>
nota le funzioni matematiche vengono definite nella libreria libm.a, per includerla è
necessario effettuare il link con l’opzione –lm.
int abs (int n) - valore assoluto di un intero.
double acos(double x) - arcocoseno di x.
double atan(double x) - arcotangente
double ceil(double x) - il piu piccolo intero maggiore o uguale a x.
double cos(double x) - coseno di angolo x in raduanti
double exp(double x) - esponenziale
double log(double x) - logaritmo naturale
void randomize(void) - inizializza il generatore di numeri casuali.
double fabs (double x ) - valore assoluto di un double
void srand(unsigned seed) - inizializza il generatore di numeri casuali.
int random(int max_num) - genera un numero casuale tra 0 e max_num.
double pow (double x, double y) - potenza, x elevato alla y.
Il linguaggio C 272/243
Allocazione di Memoria
#include <malloc.h>
void *calloc(size_t num elems, size_t elem_size) -
Alloca un vettore ed inizializza tutti gli elementi a zero.
void free(void *mem address) - Libera un blocco di memoria.
void *malloc(size_t num bytes) - Alloca un blocco di memoria.
137
Il linguaggio C 273/243
Funzioni su stringhe
#include <string.h>
int strlen(char *string) - Determina la lunghezza di una stringa.
int strcmp(char *string1, char *string2) - Confronta string1 e string2 per determinare
l'ordine alfabetico.
char *strcpy(char *string1, char *string2)- Copia string2 in stringl.
char *strncat(char *string1, char *string2, size_t n) - Aggiunge "n“ caratteri di string2 in string1.
int strncmp(char *string1, char *string2, size_t n) - Confronta i primi "n“ caratteri di due
stringhe.
char *strncpy(char *string1, char *string2, size_t n) - Copia i primi "n“ caratteri di string2 in
string1.
int sprintf(char *string, char *format_string, args) Scrive output formattato su una stringa,
comportamento analogo alla printf, ma scrive su una stringa.
int sscanf(char *buffer, char *format_string, args) Legge input formattato da una "string",
comportamento analogo alla scanf, ma prende l'input da una stringa
Il linguaggio C 274/243
Funzioni per la cattura del tempo#include <time.h>
time_t time(time_t *t);
restituisce il tempo trascorso a partire dal 1 gennaio del 1979, misurato in secondi. Se il puntatore
passato come argomento e' diverso da NULL, scrive lo stesso valore nella locazione di memoria
puntata dal puntatore. In caso di errore restituisce -1 e setta errno in modo appropriato.
int gettimeofday(struct timeval *tv, struct timezone *tz);
l'argomento di tipo timezone e' obsoleto, e viene istanziato a NULL. La struttura di tipo timeval e'
così definita:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
La funzione scrive nella struttura puntata da tv il valore del clock corrente, ed e' quindi piu' precisa
della time che mette a disposizione solo un valore in secondi. Restituisce 0 se tutto OK, altrimenti -
1 se si verifica qualche errore.
138
Il linguaggio C 275/243
Funzioni per la cattura del tempoclock_t clock(void);
restituisce il tempo di CPU utilizzato dal processo chiamante, a partire dalla prima volta
che è stata chiamata la clock(). Il tempo restituito è la somma del tempo utilizzato dal
processo chiamante e dai processi di sistema nell'esecuzione delle chiamate di sistema
effettuate dal processo chiamante. Il valore restituito è convenzionalmente il numero di
clock eseguiti. Per ottenere il tempo in secondi deve essere diviso per la costante
CLOCKS_PER_SEC.
clock_t start, end;
start = clock();
istruzioni del programma ...
end = clock();
printf("tempo consumato in secondi: %lf\n",
(double)(end-start) / (double)CLOCKS_PER_SEC
);