Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Versione 0.9 preparata dalling. E. Mancini sulla base dei lucidi del prof. Ghini, Univ. Di Bologna
Il linguaggio C 1/243
Riferimenti
Brian W. Kernighan, Dennis M. Ritchie, "Linguaggio C", ed. Pearson Education Italia Webpage del corso: web.ing.unisannio.it/villano/ps0607
Il linguaggio C
2/243
Il linguaggio C
4/243
Il linguaggio C
6/243
Il linguaggio C
7/243
Il linguaggio C
8/243
Esempi
Ad esempio: /* Esempio di programma con problemi nei commenti */ main() { /* Un commento */ ESATTO /* Commento /* Ancora un commento */ QUI ERRORE */ }
Il linguaggio C
9/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); }
Il linguaggio C
10/243
L'istruzione printf(...) chiama la funzione printf che visualizza ci che gli viene passato come argomento (\n indica 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.
Il linguaggio C
11/243
Il linguaggio C
12/243
Il linguaggio C
13/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.
Il linguaggio C
14/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
15/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.
Il linguaggio C 16/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 lopzione -o, nel file citato dopo il -o.
Il linguaggio C
17/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 largomento -o) invece di metterlo nel file a.out .
Il linguaggio C
18/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
19/243
Il linguaggio C
20/243
10
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
21/243
Il Preprocessore
Per 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
Il linguaggio C
22/243
11
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.E */ extern int i;
Preprocessore
/* file prova.c */ #include "header.h" /* size vettore */ #define SIZE 10 int vettore[SIZE];
Il linguaggio C
23/243
Il compilatore e lassemblatore
Il compilatore C traduce codice sorgente in codice assembly. Il codice sorgente quello prodotto dal preprocessore. Lassembler 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.
Il linguaggio C
24/243
12
Il linguaggio C
25/243
-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
Il linguaggio C
26/243
13
Il linguaggio C
27/243
14
Il linguaggio C
30/243
15
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 31/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 essere esplicitamente 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.
Il linguaggio C
32/243
16
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 loccasione. Questo riduce anche i tempi di debugging del programma.
Il linguaggio C 33/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.
Il linguaggio C 34/243
17
Le librerie
Su ogni pagina del manuale, la sezione SYNOPSIS include informazioni sulluso 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 35/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
Il linguaggio C 36/243
#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; }
18
Il preprocessore C
Il preprocessore C modifica un codice sorgente prima di passarlo al compilatore. Viene prevalentemente adoperato per includere files direttamente 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
37/243
Il linguaggio C
38/243
19
Il linguaggio C
39/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.
Il linguaggio C 40/243
20
Costanti
Se dovete definire una costante in termini di una espressione matematica, saggio includere lintero valore in parentesi: #define PI_PLUS_ONE (3.14 + 1) Cos facendo, si evita che lordine 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 delladdizione, e non dopo!!
Il linguaggio C 41/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.
Il linguaggio C
42/243
21
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 43/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
Il linguaggio C
44/243
22
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 functions appaiono definite pi volte (una per ogni volta che lheader file viene incluso). Usando la direttiva #ifndef, si pu includere un blocco di testo solo se una particolare espressione non definita; poi, nellheader file, si pu definire lespressione #ifndef _FILE_NAME_H_ #define _FILE_NAME_H_ /* code */ #endif // #ifndef _FILE_NAME_H_
Il linguaggio C
45/243
Macro
Laltro 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++
Il linguaggio C
46/243
23
Macro e funzioni
Le 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 47/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 allargomento, 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 lintera macro in parentesi #define ADD_FIVE(a) ((a) + 5) int x = ADD_FIVE(3) * 3;
Il linguaggio C 48/243
24
Macro e funzioni
Daltra 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 49/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 inserirli nel 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)
Il linguaggio C 50/243
25
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 dellultima 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
51/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: Identificatori NON ammessi:
Num num$! _Num num n_um 1Num
num;pippo
Il linguaggio C
52/243
26
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
53/243
Dim.
1 1 2 2 4 4 4 8 0
Min.
-127 0 -32768 0 -231 0 -3.2 * 10 38 -1.7 * 10 308
Max.
128 255 32768 216 1 231-1 232 1 3.2 * 1038 1.7 * 10308 -
Pascal
char integer longint real extended -
Java
byte short int float double void
Il linguaggio C 54/243
27
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
55/243
28
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
57/243
Variabili
/* i una variabile di tipo int. */ int i;
29
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
59/243
Variabili locali
Definiamo 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 for for (i=0; i<10; i++) { int j; printf("ciclo") } func(j); /* ERRORE qui j NON e' visibile */
Il linguaggio C
60/243
30
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
61/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.
Il linguaggio C
62/243
31
Il linguaggio C
63/243
Il linguaggio C
64/243
32
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.
Il linguaggio C
66/243
33
Il linguaggio C
68/243
34
Il linguaggio C
69/243
Il linguaggio C
70/243
35
f(B); /*error C2065: 'B' : undeclared identifier*/ f(C); /*error LNK2001:unresolved external symbol _C*/ }
Il linguaggio C 71/243
36
Il linguaggio C
73/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.
Il linguaggio C
74/243
37
Lo specificatore static
Vediamo un esempio di utilizzo, per contare il numero delle volte che una data funzione viene eseguita.
#include <stdio.h> void f(void) { /* file contaf.c */
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.
Il linguaggio C
76/243
38
Istruzioni di assegnamento
Esempi: int func( float f); /* prototipo */ int i , j;
Il linguaggio C
77/243
Assegnamento
L'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.
Il linguaggio C 78/243
39
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
79/243
Il linguaggio C
80/243
40
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
81/243
Il linguaggio C
82/243
41
Il linguaggio C
84/243
42
Tipo Espressione
char short int long int (4 byte) long int
Informazione Conservata
7 bit meno significativi byte - significativo byte - significativo byte - significativo la parte intera se minore del valore massimo per short int, un valore senza senso altrimenti risultato arrotondato alla precisione del float
short int
float, double
float
double
Il linguaggio C
85/243
Conversioni
Vediamo un esempio, conversione di uno short int in un char: short int i;
byte pi significativo
char ch;
byte meno significativo
ch = i ;
short int i
char ch
Il linguaggio C
86/243
43
Il linguaggio C
87/243
Il linguaggio C
88/243
44
double f = 13.7;
#define NUM 10 int n=NUM; int p=0xFF; int q=011; n inizializzato a 10 p inizializzato a 255 (costante esadecimale) q inizializzato a 9 (costante ottale)
Il linguaggio C
89/243
Costanti
Le 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.
Il linguaggio C 90/243
45
Costanti
La dichiarazione di un dato di tipo costante deve essere cos: const oppure Tipo Ad es. const NomeVariabile = costante; Tipo NomeVariabile = costante;
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 91/243
46
Il linguaggio C
93/243
Esempi:
Valore 0 1 8 -8 17 -30 100000 -100000 Tipo int int int int int int long int long int DECIMALE 0 1 8 -8 17 -30 100000L 100000l -100000L -100000l ESADEC. 0x0 0x1 0x8 -0x8 0x11 -0xFD 186A0L 186A0l -186A0L -186A0l OTTALE 00 01 010 -010 021 036 -303240L -3032040l -303240L -3032040l
Il linguaggio C 94/243
47
oppure in formato esponenziale, cio nella forma XeM con il significato di X*10M, 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
95/243
Il linguaggio C
96/243
48
Il linguaggio C
97/243
Il linguaggio C
98/243
49
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 '.
Il linguaggio C 100/243
50
Sequenze di escape
Infine 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 rappr. esadecimale
char ch; ch='A'; ch=65; ch='\x41'; ch='\101';
'\OOO '\xhh
fino a tre caratteri ottali (0:7), (max \377) fino a due (hh) caratteri esadec. (0:9,A:F)
Il linguaggio C
101/243
\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.
Il linguaggio C 102/243
51
'' e' un caratt. non ASCII ' ' ' delimitatore di carattere, non di stringa, quindi non c'e' confusione
"quest\x41 stringa e\47 giusta" "quest\101 stringa e\047 giusta" "questA stringa " sbagliata" "questA stringa \" stata corretta
' ' ' scritto in ottale con 2 cifre(occhio) ' ' ' scritto in ottale a 3 cifre ERRORE, '"' delimitatore di stringa
"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 103/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.
Il linguaggio C
104/243
52
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 105/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.
Il linguaggio C 106/243
53
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 107/243
Un cast quindi equivale ad assegnare l"espressione" ad una variabile del nuovo tipo specificato, che viene poi utilizzata al posto dell'intera espressione.
Il linguaggio C 108/243
54
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
109/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.
Il linguaggio C
110/243
55
Type casting
Quindi, dati due operandi di tipo diverso, il cast automatico si ha secondo queste regole:
Tipo 1 operando long double double float double int long int Tipo 2 operando double float (long) int (long) int char , short (short) int Tipo risultato long double double float double int long int
Il linguaggio C
111/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
Il linguaggio C
112/243
56
Operatori aritmetici
Gli operatori aritmetici + - * / % sono operatori presenti in tutti i linguaggi di programmazione. Sono operatori binari (servono due operandi):
+ * / %
valore del tipo pi grande valore del tipo pi grande stesso tipo valore del tipo pi grande valore float tra float o valore int tra int resto modulo m solo tra interi
Il linguaggio C
113/243
Attenzione, sono previste due modalit di esecuzione, qualora l'incremento sia parte di un'istruzione pi complessa. x++; ++x; prima usa x nell'istruzione, poi lo incrementa prima incrementa x, poi lo usa nell'istruzione
Il linguaggio C
114/243
57
Priorit:
++ -*/% +-
minima
Il linguaggio C 115/243
(doppio simbolo =)
58
Il linguaggio C
117/243
59
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
119/243
60
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
121/243
61
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. 2. il codice va modificato nel tempo, ed il codice scritto in forma compatta pi difficile da capire, anche per chi l'ha scritto. l'aggiunta di parentesi tonde e spazi per rendere pi visibile e comprensibile il codice non diminuisce le prestazioni.
Il linguaggio C
124/243
62
Istruzioni C del seguente tipo (dove op un'operazione aritmetica) espressione_1 equivalgono all'istruzione espressione_1 = espressione_1 op espressione_2 op= espressione_2
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.
Il linguaggio C
126/243
63
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
127/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
Il linguaggio C 128/243
64
Il linguaggio C
129/243
Istruzione if-else
L'istruzione if puo' avere due forme di base: if (espressione) istruzione 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. if (espressione) istruzione1
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).
Il linguaggio C 130/243
65
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.
Il linguaggio C
131/243
Istruzione if-else
Il 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.
Il linguaggio C 132/243
66
Istruzione if-else
Ecco 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 133/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.
Il linguaggio C 134/243
67
Istruzione switch
Un esempio di switch semplice 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; } #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
135/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.
Il linguaggio C
136/243
68
Istruzione switch
L'esecuzione procede fino a che non si incontra la keyword break, 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
137/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.
Il linguaggio C
138/243
69
Ciclo while
Poich 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++]);
while(1) { .... }
Il linguaggio C 139/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);
Il linguaggio C
140/243
70
Ciclo for
Il 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 141/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.
Il linguaggio C
142/243
71
Ciclo for
Ciascuna 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
143/243
/* con decremento */ void main(void) { int x; for ( x=100; x>0; x-- ) printf ("%d ", x); }
Il linguaggio C
144/243
72
Il linguaggio C
145/243
73
Il linguaggio C
147/243
74
void main(void) { int x=13; goto dentro; for ( x=1; ; y++ ) { dentro: printf("atterraggio: x=%d\n", x ); } }
Il linguaggio C
150/243
75
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 151/243
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]; char stringa[100]; un vettore di 10 interi un vettore di 100 caratteri, cio potenzialmente una stringa per 99 caratteri pi il carattere terminatore '\0
Il linguaggio C 152/243
76
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 153/243
Il linguaggio C
154/243
77
Il linguaggio C
155/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 */
78
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 157/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;
Il linguaggio C 158/243
79
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
159/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.
Il linguaggio C
160/243
80
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
161/243
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 */ }
Il linguaggio C 162/243
81
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 163/243
Unioni: esempio
struct A {int idA; char field1[10]; char field2[20]; char field3[10] }; char campo2[10]; }; char val2[25]; char val3[20] }; struct B { int idB; char campo1[8]; struct C { int idC; char val1[12];
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);
Il linguaggio C
164/243
82
Puntatori
I 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 165/243
Puntatori
Un 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; p = &c ; dichiaro una var c di tipo tipo ed un puntatore p a tipo assegno a p l'indirizzo di c
Il linguaggio C
166/243
83
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.
dichiaro una variabile c di tipo int ed un puntatore p a int. assegno a p lindirizzo di c assegno a c il valore 5
printf("%d\n", *p);
Il linguaggio C
167/243
Puntatori
Consideriamo gli effetti delle seguenti istruzioni:
int *pointer; int x=1,y=2; (1) pointer = &x; (2) y = *pointer; (3) x = pointer; /* assegna a pointer l'indirizzo di x */ /* assegna a y il contenuto dell'int puntato da pointer, x */ /* 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 */ /* dichiara pointer come un puntatore a int */
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).
Il linguaggio C 168/243
84
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 169/243
Puntatori
L'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 allistruzione 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.
Il linguaggio C 170/243
85
Il linguaggio C
172/243
86
oppure p +=9;
Il linguaggio C
174/243
87
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 175/243
Il linguaggio C
176/243
88
Il linguaggio C
177/243
Il linguaggio C
178/243
89
Il linguaggio C
179/243
90
91
Il linguaggio C
183/243
Il linguaggio C
184/243
92
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; strutturata */ struct PUNTO punto; strutturato */ /* dichiaraz. puntatore a var. /* dichiaraz. variabile di tipo
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;
Il linguaggio C
186/243
93
Puntatori in strutture
Ora 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 187/243
Array multidimensionali
Un 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
Il linguaggio C
188/243
94
Array multidimensionali
Indicizzando 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 189/243
Array multidimensionali
Analogamente 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.
Il linguaggio C
190/243
95
Vettori di puntatori
Vogliamo 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
191/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.
Il linguaggio C
192/243
96
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
193/243
Il linguaggio C
194/243
97
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':
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;).
Il linguaggio C
196/243
98
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
197/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); }
Il linguaggio C
198/243
99
Il linguaggio C
199/243
La chiamata della funzione viene fatta cos: void main(void) { int A=23; azzera_variabile ( &A ); /* viene passato l'indirizzo */ }
Il linguaggio C
200/243
100
Il linguaggio C
201/243
Il linguaggio C
202/243
101
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 203/243
Matrici
Analogamente 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.
Il linguaggio C 204/243
102
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
205/243
Il linguaggio C
206/243
103
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 loutput 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
207/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; }
104
105
Il linguaggio C
211/243
Esempio corretto: vet_globale una variabile globale, quindi sopravvive alla terminazione della funzione
int vet_globale[10000]; int *spazio_pre_allocato( void ) { return vet_globale ; } }
Il linguaggio C 212/243
106
chiamata
definizione
Il linguaggio C
213/243
Il linguaggio C
214/243
107
Il linguaggio C
215/243
} 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.
Il linguaggio C 216/243
108
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, ...)
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 217/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;
Il linguaggio C
218/243
109
110
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
221/243
Il linguaggio C
222/243
111
Il linguaggio C
223/243
112
Il linguaggio C
225/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.
Il linguaggio C
226/243
113
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 227/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.
Il linguaggio C
228/243
114
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 229/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); } }
Il linguaggio C 230/243
115
Il linguaggio C
231/243
Il linguaggio C
232/243
116
Output formattato
L'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" "%10d" stampa un intero 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" "%lf" "%10f" stampa un float stampa un double stampa un float e un minimo di 10 caratteri almeno in cui il float e' a destra
Il linguaggio C 233/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) TABULAZIONE BACKSPACE \n \t \b torna indietro di un carattere ma non lo cancella se per scrivo qualcosa gli passo sopra
Il linguaggio C
234/243
117
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
235/243
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.
Il linguaggio C
236/243
118
%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 %f %lf viene letto un intero viene letto un float viene letto un double
Il linguaggio C
237/243
119
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
239/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 -13.1 .... 29.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
Il linguaggio C
240/243
120
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
241/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); }
Il linguaggio C
242/243
121
Accesso ai files
NB. 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 243/243
Accesso ai files
La 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);
Il linguaggio C
244/243
122
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
245/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 nchar1 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.
Il linguaggio C 246/243
123
La gestione dellerrore
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
247/243
La gestione dellerrore
Nel 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); } }
Il linguaggio C 248/243
124
La gestione dellerrore
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
249/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.
Il linguaggio C
250/243
125
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
251/243
I/O in blocchi
INPUT / 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.
Il linguaggio C
252/243
126
Riga di comando
LANSI 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
253/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 calvin hobbes 3.0 40 | argc=5 { 0:param 1:cal 2:hobbes 3:3.0 4:40} param param calvin | argc=1 { 0:param } | argc=2 { 0:param 1:calvin }
Il linguaggio C
254/243
127
Il linguaggio C
255/243
Il linguaggio C
256/243
128
I/O
#include <stdio.h> #include <fcntl.h> Per aprire un file si usa la funzione:
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.
Il linguaggio C
258/243
129
Il linguaggio C
260/243
130
OR ESCLUSIVO (XOR) NOT (complemento ad uno) SHIFT (SCORRIMENTO) a DESTRA SHIFT (SCORRIMENTO) a SINISTRA
Il linguaggio C
261/243
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; op2=240; op3 = op1 | op2; op3 assume valore 252 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 0
Il linguaggio C
262/243
131
0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 0 0 1 1 0 0
op2 0 0 1 1
op1^op2 0 1 1 0
00111100 11000011
Il linguaggio C
264/243
132
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
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; op3 assume valore 232 entranti) 1 1 1 0 1 0 0 0 (in neretto i bit
Il linguaggio C 266/243
133
Il linguaggio C
267/243
134
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 269/243
Il linguaggio C
270/243
135
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.
Il linguaggio C
272/243
136
Funzioni su stringhe
#include <string.h>
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) stringhe. - Confronta i primi "n caratteri di due
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
273/243
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.
Il linguaggio C
274/243
137
Il linguaggio C
275/243
Changelog
14/10/2003 Sostituiti exit(0) con exit(1) nelle slide. 196, 197, 203, 233. Aggiunta la slide Changelog. Eliminate le slides con esempi finali. Stile bold su alcuni pezzi di codice. Aggiunta la slide 204 sulla gestione degli errori. Modificata la agina sulle librerie matematiche. Dopo una serie innumerevole di aggiusti grafici, aggiunti i riferimenti al C99 e le macro con parametri (con tutta lambaradan sul preprocessore) (UV) Aggiusti grafici e di testo. (UV) 29/9/2005 10/2005
Il linguaggio C
276/243
138