------------------------------------------------------------------------------ Riepilogo sul linguaggio C/C++ ------------------------------------------------------------------------------ Struttura di un programma C ------------------------------------------------------------------------------ Un programma C ha in linea di principio la seguente forma: Comandi per il preprocessore (#include/#define) Definizione di tipi (typedef) Prototipi di funzioni (dichiarazione dei tipi delle funzioni e delle variabili passate alle funzioni) Variabili Main Definizioni delle funzioni ------------------------------------------------------------------------------ Variabili ------------------------------------------------------------------------------ Il C ha i seguenti tipi di dati: Tipo Size (byte) char 1 unsigned char 1 short int 2 unsigned short int 2 (long) int 4 float 4 double 8 In C++ si puo' usare anche il tipo bool. Per dichiarare una varibile si scrive: var_tipo elenco-variabili-separate-da-virgole ; Le variabili globali si definiscono al di sopra della funzione main(), nel seguente modo: short number,sum; int bignumber,bigsum; char letter; main() { ... } E' possibile preinizializzare una variabile utilizzando = (operatore di assegnazione). Esempio 1: int i,j,k=1; float x=2.6,y; char a; Esempio 2: float sum=0.0; int bigsum=0; char letter='A'; main() { ... } Esempio 3: float sum; int bigsum; char letter; main() { sum=0.0; bigsum=0; letter='A'; ... } E' possibile effettuare assegnazioni multiple purche' le variabili siano dello stesso tipo. ` int somma; char letter="A"; main() { int a,b,c=3; a=b=c; } dove l'istruzione a=b=c (con c=3) corrisponde ad a=3, b=3 e c=3. Si possono definire nuovi propri tipi di variabili utilizzando "typedef" (questo risulta utile quando si creano strutture complesse di dati). Come esempio di utilizzo semplice consideriamo come sia possibile creare i due nuovi tipi di variabile "real" e "letter", che potranno successiva- mente essere utilizzati alla stessa maniera dei tipi predefiniti del C. typedef float real; typedef char letter; variabili dichiarate: real sum=0.0; letter nextletter; ------------------------------------------------------------------------------ Operatori aritmetici ------------------------------------------------------------------------------ Come gia' accennato, le assegnazioni in C vengono effettuate utilizzando "=". Oltre agli operatori arimetici standard +,-,*,/ e all'operatore % (modulo) per gli interi, in C si hanno anche gli operatori incremento ++ e decremento --, che possono essere preposti o posposti all'argomento. Se sono preposti il valore e' calcolato prima che l'espessione sia valutata, mentre se sono posposti il valore viene calcolato dopo la valutazione della espressione. Esempi: int x,z=2; x=(++z)-1; A questo punto x=2 e z=3 int x,z=2; x=(z++)-1; A questo punto x=1 e z=3 int x,y,w; x=((++z)-(w--))%100; che equivale alle seguenti istruzioni: int x,y,w; z++; x=(z-w)%100; w--; E' importante sottolineare che un'istruzione del tipo x++ viene eseguita in maniera piu efficiente rispetto alla corrispondente x=x+1 L'operatore "%" (modulo) puo' essere utilizzato solamente con le variabili di tipo integer; la divisione "/" e' utilizzata sia per gli integer che per i float. A proposito della divisione riportiamo un altro esempio: z=3/2 dove z avra' valore 1, anche se e' stato dichiarato come float (di regola, se entrambi gli argomenti della divisione sono integer, allora verra' effettuata una divisione integer); per avere un risultato corretto sara' necessario scrivere: z=3.0/2 oppure z=3/2.0 o, ancora meglio, z=3.0/2.0 Inoltre esiste una forma contratta per espressioni del tipo expr1 = expr1 op expr2 (ad esempio: i=i+2 oppure x=x*(y+3)) che diventano: expr1 op = expr2 Per cui i=i+2 puo' essere scritta nel modo contratto come i+=2 e x=x*(y+3) diventare x*=y+3. Nota: l'espressione x*=y+3 corrisponde a x=x*(y+3) e non a x=x*y+3. ------------------------------------------------------------------------------ Operatori di confronto ------------------------------------------------------------------------------ Per testare l'uguaglianza si usa "==" mentre per la disugualianza "!=". Ci sono poi gli operatori "<" (minore), ">" (maggiore), "<=" (minore o uguale), ">=" (maggiore o uguale). NB. if (i==j) ... esegue il contenuto dell'if se i e' uguale a j, ma if (i=j) ... e' ancora sintatticamente esatto ma effettua l'assegnazione del valore di j ad i e procede se j e' diverso da zero ------------------------------------------------------------------------------ Operatori logici ------------------------------------------------------------------------------ Gli operatori logici, solitamente utilizzati con le istruzioni condizionali sono "&&" per AND logico e "||" per OR logico. Nota: "&" e "|" hanno un significato diverso, poiche' sono bitwise AND e OR. ------------------------------------------------------------------------------ 07.04.01. Operatori di bitwise ------------------------------------------------------------------------------ Gli operatori di bitwise (che operano sui singoli bit) sono i seguenti: "&" AND "|" OR "^" XOR "~" Complemento a 1 (0=>1, 1=>0) "<<" shift a sinistra ">>" shift a destra Nota: fare attenzione, come gia' detto in precedenza, a non confondere & con && (& e' "bitwise and", mentre && e' "logical and"); la stessa cosa vale per | e ||. "~" e' un operatore unario, cioe' opera su un solo argomento indicato a destra dell'operatore. Gli operatori di shift eseguono un appropriato shift dall'operatore indicato a destra a quello indicato a sinistra. L'operatore destro deve essere positivo. I bit liberati vengono riempiti con zero. z<<2 shifta i bit in z di due posti verso sinistra cosi', se z=00000010 (binario) o 2 (decimale) allora, z>>=2 => z=00000000 (binario) o 0 (decimale) inoltre, z<<=2 => z=00001000 (binario) o 8 (decimale) Quindi, uno shift a sinistra e' equivalente ad una moltiplicazione per 2; similmente, uno shift a destra equivale ad una divisione per 2. Nota: l'operazione di shift e' molto piu' veloce della reale moltiplicazione (*) o divisione (/); ------------------------------------------------------------------------------ Ordine di precedenza degli operatori ------------------------------------------------------------------------------ Tutti gli operatori hanno una propria priorita', e gli operatori ad alta priorita' sono valutati prima di quelli a bassa priorita'. Gli operatori con la stessa priorita' sono valutati da sinistra a destra; Cosi' a - b - c e' valutato (a - b) - c come ci si puo' aspettare. L'ordine di priorita', dalla piu' alta alla piu' bassa, di alcuni degli operatori piu comuni del C e': ()[] ! ~ ++ -- * / % +- < <= >= > == != & ^ | && || = += -= , Quindi: a < 10 && 2 * b < c ---> (a < 10) && ((2 * b) < c) a=b=s/s1+t; ---> a=(b=(s/s1)+t); ------------------------------------------------------------------------------ Strutture di controllo ------------------------------------------------------------------------------ Uno statement puo' essere una singola istruzione seguita da ; oppure un blocco di istruzioni tra parentesi graffe { ... } ------------------------------------------------------------------------------ If ------------------------------------------------------------------------------ L'istruzione "if" ha le stesse funzioni degli altri linguaggi. Puo' avere tre forme di base: if (expression) statement if (expression) statement1 else statement2 if (expression1) statement1 else if (expression2) statement2 else statement3 Esempi: if (x<0) z=w; if (x<0) { z=w; a=x; } if (x<0) { z=w; a=x; } else { z=y; a=x; } if (x<0) { z=w; a=x; } else if (y=0 && z>0) { z=y; a=x; } else z=5; ------------------------------------------------------------------------------ Operatore "?" ------------------------------------------------------------------------------ L' operatore ? (ternary condition) e' la forma piu' efficente per esprimere semplici if statements. Ha la seguente forma: expression1 ? expression2 : expression3 che equivale a: if expression1 then expression2 else expression3 Ad esempio: z = (a>b) ? a : b equivale a if (a>b) z=a; else z=b; assegna a z il massimo tra a e b. ------------------------------------------------------------------------------ Switch ------------------------------------------------------------------------------ Permette scelte multiple tra un insieme di items. La sua forma generale e': switch (expression) { case item1: statement1; break; case item2: statement2; break; . . . case itemn: statementn; break; case default: statement; break; } Il valore degli item deve essere una costante (le variabili non sono permesse). Il break serve per terminare lo switch dopo l'esecuzione di una scelta, altrimenti verra' valutato anche il caso successivo. E' possibile anche avere un'istruzione nulla, includendo solamente un ";" oppure lasciando fallire l'istruzione di switch omettendo qualsiasi frase (come nell'esempio di seguito). Il caso "default" e' facoltativo e raggruppa tutti gli altri casi. Ad esempio: switch (letter) { case 'A': case 'E': case 'I': case 'O': case 'U': numerovocali++; break; case " ": numerospazi++; break; default: numerocostanti++; break; } In questo caso se letter e' una vocale ('A','E','I','O','U') viene incrementato il valore della varibile numerovocali, se e' uno spazio (" ") si incrementa numerospazi e altrimenti (se nessuno dei casi precedenti e' vero) viene eseguita la condizione di default e quindi viene incrementato numerocostanti. ------------------------------------------------------------------------------ For ------------------------------------------------------------------------------ L'istruzione C "for" ha la seguente forma: for (expression1; expression2; expression3) statement dove expression1 inizializza, expression2 e' il test di termine e expression3 e' il modificatore (che puo' anche essere piu' di un semplice incremento). Ad esempio: for (i=0;i<3;i++) cout << "x=" << i << endl; che genera come output sullo schermo: x=0 x=1 x=2 Gli esempi che seguono sono tre forme valide delle istruzioni "for" in C: for (x=0; ((x<3) && (x>9)); x++) ... for (x=0,y=4; ((x<3)&&(y>9)); x++,y+=2) ... in cui si puo' notare che le espressioni multiple possono essere separate da una "," (y+=2 equivale a y=y+2) for (x=0,y=4,z=4000; z; z/=10) ... in cui si puo' notare che il loop continua l'iterazione fino a quanto z diventa 0 (test e' direttamente z; z/=10 equivale a z=z/10) ------------------------------------------------------------------------------ While ------------------------------------------------------------------------------ L'istruzione "while" ha la seguente forma: while (expression) statement; Ad esempio: int x=3; main() { while (x>0) { cout << "x=" << x << endl; x--; } } che genera come output sullo schermo: x=3 x=2 x=1 While puo' accettare non solo condizioni ma anche espressioni, per cui risultano corrette le seguenti istruzioni: while (x--); while (x=x+1); while (x+=5); Utilizzando questo tipo di espressioni, solo quando il risultato di x--, x=x+1 oppure x+=5 ha valore 0 la condizione di while fallisce e si esce dal loop. E' possibile avere anche complete operazioni di esecuzione nelle espressioni "while": while (i++<10) che incrementa i fino a raggiungere il valore 10; ch=getchar(); while (ch != 'q') { putchar(ch); ch=getchar(); } while ((ch=getchar()) != 'q') putchar(ch); che usa le funzioni getchar() e putchar() delle librerie standard, che rispettivamente leggono un carattere dalla tastiera e scrivono un determinato carattere sullo schermo. Il loop while continua a leggere dalla tastiera e a visualizzare sullo schermo il carattere digitato, fino a quando non venga battuto il carattere "q". ------------------------------------------------------------------------------ Do-While ------------------------------------------------------------------------------ L'istruzione C "do-while" ha la seguente forma: do statement while (expression); int x=3; main() { do cout << "x=" << x-- << endl; while (x>0); } Il cui output e': x=3 x=2 x=1 Nota: l'operatore finale "x--" indica che viene usato il valore corrente di x mentre stampa, e poi viene decrementato x. ------------------------------------------------------------------------------ Break e Continue ------------------------------------------------------------------------------ Il C fornisce due comandi per controllare i loop: break - esce da un loop o da uno switch continue - salta una iterazione del loop Esempio value=1; while (value !=0) { cin >> value; if (value<0) { cout << "Valore non ammesso" << endl; break; /* Abbandona il loop */ } if (value>100) { cout << "Passo al prossimo valore" << endl; continue; /Torna nuovamente all'inizio del loop */ } /*Elabora il valore letto*/ /*che e' sicuramente tra 0 e 100 */ . . . } ------------------------------------------------------------------------------ Array singoli e multidimensionali ------------------------------------------------------------------------------ Un esempio di definizione di un array in C e' : int elenco_numeri[50]; e si accede agli elementi dell'array nel seguente modo: terzo_numero= elenco_numeri[2]; elenco_numeri[5]=100; NB. In C gli Array subscripts iniziano da 0 e finiscono alla dimensione dell'array meno uno. Nell'esempio precedente il range e' 0-49, cioe' elenco_numeri e' un array di 50 elementi e si ha: elenco_numeri[0],elenco_numeri[1],....elenco_numeri[49]. Questa e' una grossa differenza fra il C e gli altri linguaggi e richiede un po' di pratica per raggiungere "la giusta disposizione d'animo". Array multidimensionali sono cosi definiti: int tabella_numeri[50][50] => per due dimensioni int big_D[20] [30][10][40] => per piu' di due dimensioni e si accede agli elementi nel seguente modo: numero=tabella_numeri[5][32]; tabella_numeri[1][23]=100; ------------------------------------------------------------------------------ Funzioni ------------------------------------------------------------------------------ Il C fornisce delle funzioni anch'esse simili alla maggior parte degli altri linguaggi. Una differenza e' che il C considera "main()" come una funzione. A differenza di alcuni linguaggi, come il Pascal, il C non ha procedure poiche' usa le funzioni per soddisfare entrambe le esigenze. La forma generale di una funzione e': returntype function_name (parameterdef1, parameterdef2, ...) { local variables function code (C statements) } Se manca la definizione del tipo della funzione ("returntype", tipo della variabile di ritorno della funzione), il C assume che il ritorno della funzione e' di tipo integer; questo puo' essere una delle cause di problemi nei programmi. Esempio di una funzione che calcola la media tra due valori: float calcolamedia(float a, float b) { float media; media=(a+b)/2; return(media); } Per richiamare tale funzione si procede nel seguente modo: int main() { float a=10, b=25, risultato; risultato=calcolamedia(a,b); cout << "Valore medio" << risultato << endl; ... } Nota: l'istruzione "return" ritorna il risultato della funzione al programma principale. ------------------------------------------------------------------------------ Procedure = Funzioni "void" ------------------------------------------------------------------------------ Se non si vuole ritornare alcun valore da una funzione e' sufficiente dichiararla di tipo void ed omettere il return. Ad esempio: void quadrati() { int loop; for (loop = 1; loop < 10; loop++); cout << loop*loop << endl; } void main() { quadrati(); } Nota: e' obbligatorio mettere le parentesi () dopo il nome della funzione anche se non ci sono parametri ------------------------------------------------------------------------------ Passaggio dei parametri per riferimento ------------------------------------------------------------------------------ Si possono passare come parametri nomi di variabili da modificare nel corpo della funzione (cioe' l'esecuzione ha come side-effect la modifica della variabile passata per riferimento) Esempio: void Swap (int& a, int& b) /* & indica il passaggio per riferimento */ { int tmp; tmp = a; a = b; b = tmp; } Nel main: int x = 1; int y = 2; Swap (x, y); /* invocazione di una procedura */ Ora x ha valore 2 e y ha valore 1 Notate la differenza con la seguente funzione: void proc(int a, int b) { int tmp; tmp = a; a = b; /* a e b vengono utilizzate come variabili locali */ b = tmp; } Nel main: int x = 1; int y = 2; proc(x, y); proc(x,y) equivale a proc(1,2) e quind non ha nessun effetto su x e y ------------------------------------------------------------------------------ Funzioni ed array ------------------------------------------------------------------------------ Possono essere passati alle funzioni come parametri anche array singoli o multidimensionali. Gli array monodimensionali possono essere passati nel seguente modo: float trovamedia(int size,float list[]) { int i; float sum=0.0; for (i = 0; i < size; i++) sum+=list[i]; return(sum/size); } In questo esempio la dichiarazione "float list[]" dichiara al C che "list" e' un array di float. Non viene specificata la dimensione di un array quando e' un parametro di una funzione. Array multidimensionali possono essere passati alle funzioni nel seguente modo: void stampatabella(int xsize, int ysize,float tabella[][5]) {int x,y; for (x = 0; x < xsize; x++) { for (y = 0; y < ysize; y++) cout << "\t" << tabella[x][y]; cout << endl; } } In questo esempio "float tabella[][5]" dichiara al C che tabella e' un array di float di dimensioni Nx5. E' importante notare che dobbiamo specificare la seconda dimensione (e le successive) del vettore, ma non la prima dimensione. Quindi, riepilogando, nel caso di array singoli non e' necessario specificare la dimensione dell'array nella definizione come parametro della funzione, mentre nel caso di array multidimensionali si puo' non specificare solo la prima dimensione. ------------------------------------------------------------------------------ Prototipi di funzioni ------------------------------------------------------------------------------ Prima di usare una funzione, il C deve riconoscere il tipo di ritorno e il tipo dei parametri che la funzione si aspetta. L'importanza della dichiarazione e' doppia: - viene fatta per avere un codice sorgente piu' strutturato e percio' facile da leggere ed interpretare; - permette al compilatore C di controllare la sintassi delle chiamate di funzioni. Il modo in cui questo viene fatto dipende dallo scopo della funzione. Fondamentalmente, se una funzione e' stata definita prima di essere usata (call) allora e' possibile semplicemente usare la funzione. Nel caso contrario, e' obbligatorio dichiarare la funzione; la dichiarazione stabilisce in modo semplice il ritorno della funzione ed il tipo dei parametri utilizzati da questa. E' buona norma (e solitamente viene fatto) dichiarare tutte le funzioni all'inizio del programma, sebbene non sia strettamente necessario. Per dichiarare un prototipo di funzione bisogna semplicemente stabilire il tipo di ritorno della funzione, il nome della funzione e tra le parentesi elencare il tipo dei parametri nell'ordine in cui compaiono nella definizione di funzione. Ad esempio: int strlen(char[]); Questo dichiara che una funzione di nome "strlen" ritorna un valore integer ed accetta una singola stringa come parametro. ------------------------------------------------------------------------------ Type-casting ------------------------------------------------------------------------------ Il C e' uno dei pochi linguaggi che permette la coercizione, e cioe' permette di forzare una variabile di un tipo ad essere una variabile di un'altro tipo utilizzando l'operatore "( )". Ad esempio: int numerointero; int numerointero2=10; float numerofloat=6.34; float numerofloat2; char lettera='A'; numerointero=(int)numerofloat; /* assegna il valore 6 (parte intera) */ numerointero=(int)lettera; /* assegna il valore 65 (codice ASCII)*/ numerofloat2=(float)numerointero2 /* assegna 10.0 (valore float) */ Alcuni type-casting vengono fatti automaticamente, principalmente in relazione alle capacita' dei numeri integer. E' buona regola eseguire il type-casting tutte le volte che si e' in dubbio sulla corrispondenza degli operatori nelle assegnazioni. Altro uso che ne viene fatto e' all'interno delle divisioni, per assicurarsi che dia il risultato voluto; se abbiamo due numeri integer come operatori e vogliamo che il risultato sia un float, allora dovremo agire come segue: int intnumber,anotherint; float floatnumber; floatnumber=(float)intnumber/(float)anotherint Questa operazione assicura una divisione in floating-point. ------------------------------------------------------------------------------ Il preprocessore C ------------------------------------------------------------------------------ La chiamata al preprocessore e' il primo passo da compiere fra i passi per la compilazione di un programma C (si tratta di una caratteristica presente solo nei compilatori C). Il preprocessore fornisce un proprio linguaggio, il quale puo' costituire un potente strumento per i programmatori. Ricordiamo che tutte le istruzioni e i comandi del preprocessore cominciano con un #. L'utilizzo del preprocessore e' vantaggioso, poiche' rende: - i programmi piu' facili da sviluppare, - piu' facili da leggere, - piu' facili da modificare, - il codice C piu' trasportabile tra le diverse architetture macchina. Il preprocessore permette anche di "customizzare" il linguaggio. Ad esempio, per sostituire {...} blocchi di istruzioni delimitati con la notazione Pascal (come begin ... end), e' sufficiente dichiarare: #define begin { #define end } Durante la compilazione tutte le occorrenze di begin/end vengono sostituite con i corrispondenti { o }; cosi' la successiva fase di compilazione C non riconoscera' alcuna differenza di linguaggio. ------------------------------------------------------------------------------ #define ------------------------------------------------------------------------------ Viene utilizzato per definire costanti, oppure qualsiasi sostituzione macro. Va utilizzata come segue: #define Ad esempio: #define FALSE 0 #define TRUE !FALSE E' possibile anche definire delle piccole funzioni utilizzando l'istruzione #define. Se, ad esempio, vogliamo trovare il massimo tra due variabili: #define mymax(A,B) ((A)>(B) ? (A):(B)) (ricordiamo che "?" in C corrisponde all'operatore ternario). Questa istruzione, pero', non definisce propriamente una funzione "mymax"; significa invece che in qualsiasi posto noi richiamiamo mymax(var1,var2), il testo viene sostituito dalla definizione appropriata (var1 e var2 non devono necessariamente essere i nomi delle variabili). Cosi' se nel nostro codice C scriviamo ad esempio: x=mymax(q+r,s+t); dopo la chiamata al preprocessore, se fossimo in grado di vedere il codice, questo apparirebbe nel seguente modo: x=( (q+r) > (r+s) ? (q+r) : (s+t) ); ------------------------------------------------------------------------------ Formattazione Output ------------------------------------------------------------------------------ La libreria standard di I/O del C++ si include con la direttiva #include using namespace std; Vengono utilizzati due stream di I/O: cin : standard input (tastiera) cout : standard output (schermo) Esempio x=10 cout << "x = " << x << "\n"; Stampa x = 10 Mentre cin >> x >> y >> z; Legge tre valori e li memorizza in x, y e z Per stampare colonne allineate si puo usare il "\t" (tab) Esempio for (j=1; j<=3; j++) { for (i=1; i<=3; i++) cout << '(' << i << ',' << j << ')' << '\t'; cout << endl; } Stampa (1,1) (2,1) (3,1) (1,2) (2,2) (3,2) (1,3) (2,3) (3,3) ------------------------------------------------------------------------------ Modificatori ------------------------------------------------------------------------------ Per formattare l'output si possono usare i "modificatori" Ad esempio se vogliamo stampare un numero intero in notazione esadecimale usiamo il modificatore "hex" prima del numero come segue: int myint=11; cout << hex << my_int << endl; Stampa b Altri modificatori: oct :ottale) dec : decimale) endl : aggiunge una fine linea "\n" ed esegue il flush del buffer di output, per vedere subito l'output) Nella libreria iomanip ne troviamo altri piu' complicati come setprecision: cout << setprecision(4) << my_float; /* Stampa un floating point con precisione fino a 4 cifre */ setw e setfill: setw(N) definisce il numero minimo di caratteri della prossima stampa (campo) setfill(C) definisce il carattere C da usare per "riempire" un campo #include #include using namespace std; int main () { int x=78; cout << setw (10) << setfill('-') << x << endl; cout << x << endl; return 0; } Stampa 8 '-' e poi il numero 78 e poi sulla nuova linea soltanto 78: showpos: mostra segno + per i numeri positivi fixed: usa notazione decimale scientific: usa notazione scientifica per i numeri es. 123 --> 1.23e02 boolalpha: stampa true e false invece di 1 e 0 uppercase: usa lettere maiuscole ... I modificatori precedenti si possono usare al posto di opz in cout.setf(ios::opz) : aggiunge opzione opz per la stampa con cout cout.unsetf(ios::opz) : toglie opzione opz #include using namespace std; int main() { cout << fixed; /* oppure cout.setf(ios::fixed); */ cout << setprecision(3) << 123.456 << endl; cout << setprecision(4) << 123.456 << endl; cout << setprecision(5) << 123.456 << endl; cout << setprecision(6) << 123.456 << endl; cout << setprecision(7) << 123.456 << endl; cout << scientific; /* oppure cout.setf(ios::scientific); */ cout << setprecision(3) << 123.456 << endl; cout << setprecision(4) << 123.456 << endl; cout << setprecision(5) << 123.456 << endl; cout << setprecision(6) << 123.456 << endl; cout << setprecision(7) << 123.456 << endl; return 0; } Otteniamo 123.456 123.4560 123.45600 123.456000 123.4560000 1.235e+02 1.2346e+02 1.23456e+02 1.234560e+02 1.2345600e+02