Revisione: 2 Corso C. Lezione 1. Introduzione all'ambiente di programmazione. In questa lezione vogliamo dare le basi per iniziare a programmare in C e per poter compilare i primi programmi scritti in questo linguaggio. I vari argomenti introdotti saranno ripresi uno per uno, a compartimenti stagni, nelle lezioni successive. Per adesso e' sufficiente sapere cosa fa un certo costrutto o una certa istruzione senza conoscere i dettagli del linguaggio. In questo modo cerchiamo di dare un approccio piu' pratico al corso (specialmente per le prossime lezioni che saranno prettamente teoriche...). Iniziamo vedendo il primo programma che viene illustrato in qualunque corso di programmazione, il fatidico "Hello World!". Questo programmino una volta lanciato non fa altro che stampare sullo schermo la scritta "Hello World!". Prendete il vostro editor preferito e create un nuovo file di testo, chiamandolo ex1-hello.c (e' importante che usiate l'estensione ".c", anche se non e' obbligatoria, in questo modo tutto il mondo riuscira' a capire che il file contiene un sorgente in linguaggio C) e ricopiate il seguente programma: --------- File: ex1-hello.c -------- 1: #include 2: 3: int main() 4: { 5: printf( "Hello World!\n" ); 6: return(0); 7: } --------- File: ex1-hello.c -------- NOTA: tutti i programmi presenti in questo testo possono essere scaricati dal sito del corso. Questo e' il vostro primo "sorgente" in C. Esaminiamolo riga per riga. Riga 1: Questa riga contiene una direttiva per quello che viene chiamato "preprocessore" del compilatore C. Praticamente diciamo al compilatore che prima di iniziare la compilazione vera e propria deve includere tutte le definizioni dei comandi contenute nel file stdio.h. Queste definizioni sono generalmente dei comandi di libreria (come per esempio printf) che verranno poi usati nel nostro programma. Riga 3: Qui definiamo la funzione main(). Senza entrare troppo nel dettaglio delle funzioni, per ora vi basta sapere che tutti i programmi C hanno la funzione main(). Questa delimita l'inizio e la fine del blocco principale del programma. Quando eseguiamo un programma in C l'esecuzione inizia SEMPRE dal main. Il blocco principale e' quello racchiuso fra parentesi graffe "{" "}". Riga 5: Qui richiamiamo (eseguiamo) il comando printf. Questo comando serve per stampare una stringa (una sequenza di caratteri) sullo schermo. Subito dopo il comando printf ci sono le parentesi tonde "(" ")" che racchiudono gli argomenti del comando stesso. In particolare tutte le funzioni del C prendono degli argomenti che vanno scritti all'interno delle parentesi tonde. La printf si aspetta come primo argomento una stringa di caratteri. Le stringhe di caratteri devono essere racchiuse tra virgolette "come in quest'esempio". Dopo ogni comando dobbiamo mettere il punto e virgola ";". Questo carattere separa un comando dal successivo. Non e' necessario andare a capo dopo ogni comando, basta che questi siano separati dal punto e virgola. Riga 6: Questo comando serve per uscire da una funzione restituendo il valore inserito come argomento (in questo caso 0). Siete un po' confusi da questa frase? Non preoccupatevi... la riprendiamo piu' avanti in questa lezione e soprattutto nelle successive. Per adesso e' sufficiente sapere che questo comando esce dalla funzione main facendo terminare l'esecuzione del programma. Bene. Adesso che avete tutto passiamo alla compilazione del sorgente. Quest'operazione e' necessaria in quanto si occupa di trasformare il vostro sorgente scritto in C in un eseguibile in codice macchina, quindi direttamente interpretabile dal processore del vostro computer. Per fare questo dovete lanciare il vostro compilatore preferito. Nel nostro corso utilizziamo il gcc, un compilatore free che gira in ambiente unix. Per lanciare la compilazione dovete dare il comando: 1: megabug@pcvg:~$ gcc ex1-hello.c -o ex1-hello il primo argomento del comando e' il nome del file sorgente. Subito dopo abbiamo l'opzione "-o" che dice al compilatore di chiamare l'eseguibile con il nome che gli indichiamo subito dopo, cioe' "ex1-hello". Se tutto va bene il compilatore non dovrebbe generare nessun messaggio e dovrebbe aver prodotto il file eseguibile finale. Finalmente possiamo eseguire il nostro programma: 1: megabug@pcvg:~$ ./ex1-hello 2: Hello World! 3: megabug@pcvg:~$ Notare che dopo la scritta "Hello World!" il programma e' andato a capo. Questa cosa che a prima vista puo' sembrare scontata non e' affatto naturale per una macchina. Dobbiamo dirgli noi quindi quando andare a capo e questo e' possibile grazie ai cosiddetti "caratteri di escape". Avrete sicuramente notato lo "\n" presente in fondo alla printf. Questa sequenza di due caratteri viene convertita automanticamente dal compilatore in un CR (Carriage Return = accapo). E se io volessi stampare sullo schermo proprio la stringa "\n"? In questo caso dovrei utilizzare i caratteri di escape per generare il backslash (\) cioe' dovrei scrivere "\\n". Il doppio backslash viene convertito automaticamente in un backslash singolo e la n viene attaccata cosi' com'e'. Altra cosa notevole e' lo stile con cui viene scritto un sorgente, cioe' la cosiddetta "indentazione". Per esempio il fatto che le righe 5 e 6 siano piu' rientrate a destra, oppure che dopo ogni comando si vada a capo e anche il fatto che la riga 2 separi i comandi del preprocessore dal sorgente vero e proprio. Tutte queste sono delle note stilistiche che vengono notate quando si scrive un programma, non solo perche' del codice scritto cosi' e' piu' "bello" da vedere, ma SOPRATTUTTO perche' e' PIU' CHIARO da interpretare. Nessuno ci vieta di scrivere lo stesso programma in questo modo: 1: #include 2: int main(){printf( 3: "Hello World!\n") 4: ;return(0);} il compilatore lo compila lo stesso (anzi... occupa anche meno byte su disco!), pero' obbiettivamente un programma cosi' semplice sembra gia' arabo. Passiamo ora al secondo esempio. Come prima create il file ex2-triangolo.c e ricopiate il sorgente (oppure scaricatelo dal sito). Questo programma calcola l'area di un triangolo con base e altezza fissati a priori. --------- File: ex2-triangolo.c -------- 1: #include 2: 3: int main() 4: { 5: int a,b,c; 6: 7: a = 10; 8: b = 20; 9: c = a*b/2; 10: 11: printf( "Base:%d Altezza:%d\n", a, b ); 12: printf( "Area:%d\n", c ); 13: return(1); 14: } --------- File: ex2-triangolo.c -------- La struttura del programma e' sostanzialmente la stessa: c'e' la direttiva di inclusione la funzione main() e il corpo della funzione stessa. Nella riga 5 viene introdotto un nuovo concetto, cioe' le "variabili". Le variabili possono essere pensate come dei "contenitori" di dati (o valori). Per esempio con l'istruzione "int a,b,c;" diciamo al compilatore che vogliamo utilizzare, nel corpo della funzione main, tre variabili che chiameremo rispettivamente "a", "b", "c" e che conterranto dei numeri interi (int). Piu' brevemente si dice che con la riga 5 "dichiariamo" 3 varibili di "tipo int". Ad ogni variabile possiamo assegnare un valore con l'operatore di assegnamento "=" come succede nelle righe 7, 8 e 9. Nella riga 7 assegniamo alla variabile a il valore 10. Nella 8 assegniamo a b il valore 20. Piu' interessante e' la riga 9, dove assegniamo a c il valore attuale di a moltiplicato per il valore attuale di b diviso 2. In questo caso non cambiamo il valore di a e di b ma "leggiamo" il loro contenuto per fare delle operazioni aritmetiche e "scrivere" il risultato nella variabile c. Adesso che abbiamo l'area del triangolo nella variabile c, vogliamo stampare il risultato sullo schermo. Per fare questo usiamo sempre la printf ma con una piccola variante. Inseriamo nella stringa da stampare la sequenza di escape "%d". Questa sequenza di escape indica alla printf che DOPO la stringa ci sara' un parametro in piu', per l'esattezza ci sara' un valore intero (o un espressione il cui risultato e' un valore intero) che verra' convertito dalla printf in una stringa di caratteri e sostituito al posto del "%d". Per esempio: printf( "Prova %d Prova\n", 1234 ); Stampera': Prova 1234 Prova printf( "a contiene %d\n", a ); Se a e' di tipo int e contiene il valore 10 stampera': a contiene 10 Possiamo anche mettere piu' sequenze di formattazione seguite da piu' parametri, per es: printf( "Questa e' %d Prova. %d.\n", 12, a ); Stampera': Questa e' 12 Prova. 10. Nel caso volessi stampare proprio la stringa "%d" devo utilizzare la sequenza di escape "%%d" dato che il doppio % viene convertito in un % singolo. Se volessi stampare un valore di tipo diverso da un intero dovro' utilizzare altre sequenze di escape che verranno esaminate piu' avanti nel corso. Passiamo adesso ad un esempio che introduce meglio il concetto di "funzione". --------- File: ex3-triangolo.c -------- 1: #include 2: 3: int calcola( int a, int b ) 4: { 5: int c; 6: c = a*b/2; 7: return(c); 8: } 9: 10: int main() 11: { 12: int area; 13: 14: area = calcola( 10, 20 ); 15: printf( "Area:%d\n", area ); 16: 17: area = calcola( 20, 30 ); 18: printf( "Area:%d\n", area ); 19: 20: printf( "Area:%d\n", calcola( 5, 6 ) ); 21: return(1); 22: } --------- File: ex3-triangolo.c -------- In questo esempio la struttura del sorgente risulta leggermente ampliata dato che oltre alla direttive di compilazione e la funzione main() abbiamo anche la dichiarazione di una nuova funzione calcola. Le funzioni in C sono qualcosa che si avvicina abbastanza alle funzioni definite in Matematica. E cosa sono le funzioni definite in Matematica? Possiamo pensarle come "scatole nere" (mi perdonino i puristi dell'Analisi Matematica) che prendono in Input dei valori (chiamati PARAMETRI) e restituiscono in Output un RISULTATO: +--------------+ Parametro ---->| | | | Parametro ---->| Funzione |-----> Risultato : | | : ---->| | : +--------------+ A differenza delle funzioni Matematiche che hanno SEMPRE dei parametri e un risultato, in C una funzione puo' non restituire un risultato (in questo caso abbiamo una PROCEDURA) oppure puo' non avere parametri (per esempio una funzione per leggere lo stato di qualcosa) o addirittura puo' non avere entrambe le cose (in questo caso parliamo di COMANDI). Per dichiarare una funzione in C dobbiamo attenerci alla seguente sintassi: TIPO nomefunzione( TIPO nome, TIPO nome, .... ) { CORPO DELLA FUNZIONE } al posto di TIPO dobbiamo mettere un tipo di variabile del C (per esempio int). Il primo TIPO indica di che tipo sara' il risultato della funzione. Poi viene il nome della funzione stessa e tra parentesi tonde c'e' l'elenco dei parametri accettati dalla funzione. Per ogni parametro dobbiamo indicare il tipo e il nome con cui ci riferiremo a questo all'interno della funzione. Se vogliamo che la funzione non accetti parametri, semplicemente non dobbiamo inserire nulla fra le parentesi tonde. Se vogliamo definire una procedura dobbiamo mettere come tipo restituito dalla funzione "void" (che significa vuoto). Per es: 3: int calcola( int a, int b ) 4: { 5: int c; 6: c = a*b/2; 7: return(c); 8: } Alla riga 3 dichiariamo una funzione che si chiama calcola, che riceve in ingresso due valori interi e che restituisce (forse sarebbe piu' intuitivo "ritorna") un valore intero. Alla riga 5 dichiariamo una variabile intera c e subito dopo gli assegniamo il risultato dell'operazione a*b/2. a e b sono due variabili che contengono una "copia" del valore dei parametri passati dal "chiamante" della funzione. Vedremo tra poco come si fa a "chiamare" una funzione. Alla riga 7 diciamo di restituire il valore di c come risultato della funzione. Notare che la funzione restituisce un int (per come l'abbiamo definita alla riga 3) e la variabile c e' proprio di tipo int (riga 5). Nel main() dichiariamo la variabile area di tipo int. Subito dopo assegniamo ad area il risultato della funzione calcola(10,20). Cosa accade quando facciamo una cosa del genere? La prima cosa che accade e' che i parametri (10 e 20) vengono "copiati" nelle variabili interne alla funzione (in questo caso a e b), poi viene eseguito il corpo della funzione e infine il risultato viene assegnato alla variabile area. Dopodiche' l'esecuzione riprende dalla riga successiva alla chiamata (in questo caso la riga 15). Notare ancora una volta (lo so che sono pedissequo, ma e' importante comprendere bene questi meccanismi) che area e' di tipo int e il valore restituito da calcola e' anche lui di tipo int. +------------+ (int) 10 --------->|a | | calcola |--------> (int) 100 (int) 20 --------->|b | +------------+ La funzione calcola puo' essere richiamata piu' volte da qualunque punto del main o di altre eventuali funzioni. Perche' devo utilizzare le funzioni? Supponiamo che la funzione calcola non si limiti a calcolare l'area del triangolo, ma per es. faccia dei calcoli molto complessi (che richiedono molte righe di codice per essere svolti) e supponiamo anche che io abbia bisogno di effettuare questi calcoli piu' volte all'interno del mio programma. Se non avessi a disposizione lo strumento "funzione" dovrei riscrivere il codice che svolge i miei calcoli in piu' punti diversi del programma. Con le funzioni invece lo scrivo una volta sola e lo richiamo tutte le volte che mi serve. Un bel risparmio! Inoltre se ad un certo punto mi accorgo di un errore mi basta correggerlo SOLO all'interno della funzione, senza toccare le altre parti del programma. Questo rende il mio codice piu' "mantenibile" e soprattutto piu' "modulare" perche' posso spezzettare il programma in parti distinte in cui ognuna svolge un compito particolare. Abbiamo detto che anche main() e' una funzione. Una funzione un po' particolare se vogliamo, dato che e' quella che viene richiamata dal sistema operativo appena lanciamo il nostro programma. E' possibile passare dei parametri alla funzione main quando lanciamo il programma da linea di comando quindi da una shell, o dal prompt di MS-DOS per gli amanti di Microsoft (che sfigati..). Per es: 1: megabug@pcvg:~$ ./mioprogramma par1 par2 dalla funzione main e' possibile "catturare" le stringhe "par1" e "par2" per poi elaborarle. Come? Vediamo il seguente esempio: --------- File: ex4-commandline.c -------- 1: #include 2: 3: int main( int argc, char **argv ) 4: { 5: printf( "Argomento 1: %s\n", argv[1] ); 6: printf( "argc=%d\n", argc ); 7: return(1); 8: } --------- File: ex4-commandline.c -------- noterete che la definizione della funzione main e' cambiata. Ora accetta due parametri: un intero chiamato argc e un insieme di stringhe chiamato argv (lo so che la definizione di argv puo' sembrare un po' ostica... e in effetti lo e'! ma riprenderemo quest'argomento nelle prossime lezioni non vi preoccupate). argc ci dice quanti parametri sono stati passati da linea di comando. argv contiene i valori di questi parametri. Il main e' definito in questo modo nello standard, quindi ci sono sostanzialmente due modi per scrivere la sua intestazione: int main( ) { oppure int main( int argc, char **argv ) { per motivi storici i nomi dei parametri sono argc (ARGument Count) e argv (ARGument Value), pero' nessuno ci vieta di utilizzare altri nomi. Il programma e' molto semplice e non credo che richieda ulteriori commenti eccetto per la riga 5. Questa riga stampa il contenuto del primo argomento passato per linea di comando. Per farlo usa la sequenza di escape "%s" che, similmente alla %d, dice alla printf che il prossimo argomento passato sara' una stringa (e non un int come per %d). Un'altra cosa "strana" e' la variabile argv[1]. Senza entrare troppo nel dettaglio (per ora) vi basta sapere che argv[n] vi da l'n-mo (ennesimo) argomento passato in linea di comando. Vediamo che output da' il programma: 1: megabug@pcvg:~$ ./ex4-commandline prova 2: Argomento 1: prova 3: argc=2 4: megabug@pcvg:~$ ./ex4-commandline scemo 5: Argomento 1: scemo 6: argc=2 7: megabug@pcvg:~$ ./ex4-commandline prova prova 8: Argomento 1: prova 9: argc=3 10: megabug@pcvg:~$ ./ex4-commandline prova prova prova 11: Argomento 1: prova 12: argc=4 13: megabug@pcvg:~$ effettivamente argv[1] contiene il primo parametro passato al programma, mentre argc non contiene esattamente il numero di parametri passati, bensi' il numero di parametri passati piu' uno. Questo perche' in realta' esiste anche argv[0] che contiene il nome del programma eseguito ("./ex4-commandline" in questo caso). Nelle prove precedenti avevamo quindi la seguente situazione: Nella prova alla riga 1: argc -> 2 argv[0] -> "./ex4-commandline" argv[1] -> "prova" Nella prova alla riga 4: argc -> 2 argv[0] -> "./ex4-commandline" argv[1] -> "scemo" Nella prova alla riga 7: argc -> 3 argv[0] -> "./ex4-commandline" argv[1] -> "prova" argv[2] -> "prova" Nella prova alla riga 10: argc -> 4 argv[0] -> "./ex4-commandline" argv[1] -> "prova" argv[2] -> "prova" argv[3] -> "prova" Bene! Adesso che sappiamo usare la command line possiamo sfruttare questo strumento per rendere i nostri programmi un po' piu' interattivi. Avrete notato infatti che finora abbiamo fatto dei programmi che davano sempre lo stesso risultato. Ora che riusciamo a gestire la linea di comando possiamo dare una marcia in piu' al software che scriviamo. Per esempio potremmo modificare il programma che calcola l'area del triangolo in modo che accetti da linea di comando due numeri (base e altezza) e ci dia come risultato l'area sullo schermo, in questo modo diventerebbe un programma decisamente piu' utile rispetto ad uno che ti scrive SEMPRE la stessa cosa. Vediamo come: --------- File: ex5-triangolo.c -------- 1: #include 2: #include 3: 4: int main( int argc, char **argv ) 5: { 6: int a,b,c; 7: 8: a = atoi( argv[1] ); 9: b = atoi( argv[2] ); 10: 11: c = a*b/2; 12: 13: printf( "Area=%d\n", c ); 14: return(1); 15: } --------- File: ex5-triangolo.c -------- In questo programma ci sono sostanzialmente due novita'. La prima riguarda la riga 2 dove c'e' un #include in piu'. La seconda riguarda le righe 8 e 9 dove c'e' la nuova funzione atoi. La funzione atoi converte una stringa in un intero. Questo e' necessario in quanto dobbiamo fare dei calcoli sui numeri passati da commandline e per farli dobbiamo avere degli interi (tipo int) e non delle stringhe (tipo char *). Vediamo sulle "man pages" (pagine di manuale) la descrizione della funzione atoi (cosi' iniziamo ad abituarci a vedere quello che per il programmatore e' il pane quotidiano...): 1: megabug@pcvg:~$ man atoi 2: Reformatting atoi(3), please wait... 3: 4: ATOI(3) Linux Programmer's Manual ATOI(3) 5: 6: NAME 7: atoi - convert a string to an integer. 8: 9: SYNOPSIS 10: #include 11: 12: int atoi(const char *nptr); 13: 14: DESCRIPTION 15: The atoi() function converts the initial portion of the 16: string pointed to by nptr to int. The behaviour is the 17: same as 18: 19: strtol(nptr, (char **)NULL, 10); 20: 21: except that atoi() does not detect errors. 22: 23: RETURN VALUE 24: The converted value. Riga 7: Indica il nome della funzione e cosa fa. Riga 10: Ci fa vedere quale libreria dobbiamo includere per poter utilizzare la funzione atoi. Ecco spiegato perche' abbiamo aggiunto la seconda riga di include nel sorgente. Riga 12: Da la definizione della funzione. Notare che non ci fa vedere il corpo della funzione (anche perche' non ci interessa) ma solo i parametri e i valori che restituisce. Il primo parametro e' una stringa (char *), mentre il valore restituito e' un int. Riga 24: Ci dice che l'intero restituito e' il valore rappresentato dalla stringa. Notare che le man pages sono la sintesi di tutto quello che serve. Per questo chi non e' abituato a leggerle le ritiene molto "ermetiche". Pero' questo non toglie che sono il metodo piu' rapido per raggiungere le informazioni che si usano nella pratica quotidiana. Proviamo ora il programma: 1: megabug@pcvg:~$ ./ex5-triangolo 10 20 2: Area=100 3: megabug@pcvg:~$ ./ex5-triangolo 15 25 4: Area=187 5: megabug@pcvg:~$ ./ex5-triangolo 15 54 6: Area=405 7: megabug@pcvg:~$ ./ex5-triangolo 15 54 234 423 8: Area=405 9: megabug@pcvg:~$ ./ex5-triangolo 15 10: Segmentation fault 11: megabug@pcvg:~$ ./ex5-triangolo 12: Segmentation fault 13: megabug@pcvg:~$ Fino alla riga 8 va tutto bene (i parametri in piu' passati nella prova della riga 7 vengono semplicemente ignorati). Ma cosa e' successo nelle prove delle righe 9 e 11? Quando esce il messaggio "Segmentation fault" significa che il nostro programma ha tentato di accedere ad un'area di memoria a cui non poteva accedere (generalmente questo messaggio dovrebbe far scattare un campanello d'allarme a qualche amministratore di sistema... e anche a eventuali hacker ;). Nel nostro caso il programma ha tentato di usare argv[2] che chiaramente non esiste in quanto non e' stato fornito attraverso la commandline. Questo mostra una debolezza del programma, in quanto se un utente involontariamente (o volontariamente...) NON inserisce uno dei due parametri il programma stesso ha un crash (malfunzionamento). Il grado di resistenza di un programma a input sbagliati o a situazioni non previste si chiama "robustezza". Ci sono parecchi software (commerciali e non) che crashano senza dei motivi apparenti. Questi crash sono dovuti al fatto che il programmatore (o il team) che ha scritto il software non ha previsto delle particolari situazioni che si sono verificate durante l'uso. Come facciamo a sistemare questo "bug" (errore)? Dobbiamo solo controllare che il numero di parametri sia corretto prima di procedere al calcolo dell'area: --------- File: ex6-triangolo.c -------- 1: #include 2: #include 3: 4: int main( int argc, char **argv ) 5: { 6: int a,b,c; 7: 8: if ( argc != 3 ) 9: { 10: printf( "Devi mettere due argomenti: base e altezza!\n" ); 11: return(1); 12: } 13: else 14: { 15: printf( "OK!\n" ); 16: } 17: 18: a = atoi( argv[1] ); 19: b = atoi( argv[2] ); 20: 21: c = a*b/2; 22: 23: printf( "Area=%d\n", c ); 24: return(1); 25: } --------- File: ex6-triangolo.c -------- si nota subito il nuovo costrutto if. Questo comando introduce quello che in programmazione strutturata si chiama "alternativa". Praticamente con questo comando possiamo decidere di eseguire un blocco di istruzioni SOLO SE e' verificata una certa "condizione". Una condizione e' un'espressione che puo' essere VERA o FALSA. Per es. sono condizioni in C le seguenti: a == 10 e' VERA se a e' uguale a 10 (fate caso al DOPPIO UGUALE, non e' un errore, bisogna proprio scrivere ==... so gia' che questo sara' la causa di tanti errori...) b != 15 e' VERA se b e' diverso da 15 c > d e' VERA se c e' maggiore di d d >= 9 e' VERA se d e' maggiore o uguale a 9 ce ne sono chiaramente altre, ma verranno spiegate piu' avanti. Una possibile formulazione del comando if e' la seguente: if ( CONDIZIONE ) { BLOCCO DI ISTRUZIONI } in questo caso se la condizione e' vera verra' eseguito il blocco di istruzioni racchiuso nelle parentesi graffe, altrimenti l'esecuzione continuera' dopo l'if. Un altro modo di scrivere un blocco if e' questo: if ( CONDIZIONE ) { BLOCCO DI ISTRUZIONI 1 } else { BLOCCO DI ISTRUZIONI 2 } la differenza rispetto a prima e' che se la condizione e' vera verra' eseguito il blocco di istruzioni 1, ALTRIMENTI (else) verra' eseguito il blocco di istruzioni 2. Ed e' esattamente quello che succede nel nostro programma. Se il numero di argomenti e' diverso da 2, quindi se argc e' diverso da 3 (vi ricordate?), stampa un messaggio di errore ed esce con l'istruzione return, altrimenti, stampa "OK!" e prosegue nell'esecuzione. Vediamo qualche prova: 1: megabug@pcvg:~$ ./ex6-triangolo 10 20 2: OK! 3: Area=100 4: megabug@pcvg:~$ ./ex6-triangolo 15 25 5: OK! 6: Area=187 7: megabug@pcvg:~$ ./ex6-triangolo 15 54 8: OK! 9: Area=405 10: megabug@pcvg:~$ ./ex6-triangolo 15 54 234 423 11: Devi mettere due argomenti: base e altezza! 12: megabug@pcvg:~$ ./ex6-triangolo 15 13: Devi mettere due argomenti: base e altezza! 14: megabug@pcvg:~$ ./ex6-triangolo 15: Devi mettere due argomenti: base e altezza! 16: megabug@pcvg:~$ Adesso il comportamento e' piu' corretto. Anche la prova della riga 10 funziona meglio, dato che prima ci dava comunque un risultato (cioe' l'area del triangolo 15x54/2) anche se qualcuno passava erroneamente 2 parametri in piu'.