Programmazione in linguaggio C Laboratorio di Algoritmi e Strutture Dati Programmazione in linguaggio C Prof. Luigi Lamberti 2009
Come NON programmare #define Y return #define R for #define e while #define I printf #define l int #define W if #define C y=v+111;H(x,v)*y++= *x #define H(a,b) R(a=b+11;a<b+89;a++) #define s(a) t=scanf("%d",&a) #define U Z I #define Z I("123\ 45678\n");H(x,V){putchar(".XO"[*x]); W((x-V)%10==8){x+=2;I("%d\n",(x-V)/10-1);}} l V[1600],u,r[]={-1,-11,-10,-9,1,11,10,9},h[]={11,18,81,88},ih[]={22,27,72,77}, bz,lv=60,*x,*y,m,t;S(d,v,f,_,a,b)l*v;{l c=0,*n=v+100,j=d<u-1?a:-9000,w,z,i,g,q= 3-f;W(d>u){R(w=i=0;i<4;i++)w+=(m=v[h[i]])==f?300:m==q?-300:(t=v[ih[i]])==f?-50: t==q?50:0;Y w;}H(z,0){W(E(v,z,f,100)){c++;w= -S(d+1,n,q,0,-b,-j);W(w>j){g=bz=z; j=w;W(w>=b||w>=8003)Y w;}}}W(!c){g=0;W(_){H(x,v)c+= *x==f?1:*x==3-f?-1:0;Y c>0? 8000+c:c-8000;}C;j= -S(d+1,n,q,1,-b,-j);}bz=g;Y d>=u-1?j+(c<<3):j;} main(){R(;t<1600;t+=100)R(m=0;m<100;m++)V[t+m]=m<11||m>88||(m+1)%10<2?3:0; I("Level:");V[44]=V[55]=1;V[45]=V[54]=2;s(u);e(lv>0){Z do{I("You:");s(m);}e(!E(V,m,2,0)&&m!=99); W(m!=99)lv--;W(lv<15&&u<10)u+=2;U("Wait\n");I("Value:%d\n",S(0,V,1,0,-9000,9000 ));I("move: %d\n",(lv-=E(V,bz,1,0),bz));}}E(v,z,f,o)l*v;{l*j,q=3-f,g=0,i,w,*k=v +z;W(*k==0)R(i=7;i>=0;i--){j=k+(w=r[i]);e(*j==q)j+=w;W(*j==f&&j-w!=k){W(!g){g=1 ;C;}e(j!=k)*((j-=w)+o)=f;}}Y g;}
/* per i commenti di più linee */ // per i commenti fino al termine della riga. La quantità di commenti deve superare abbondantemente il codice C. Un altro programmatore deve capire come funziona una routine senza chiedervi nulla. I commenti devono essere scritti PRIMA, in modo da formare l’ossatura su cui verrà scritto il programma, non DOPO! Inseriremo perciò alcuni righi di spiegazione all’inizio di una funzione o di un segmento di programma, una descrizione accanto ad ogni definizione di variabili, qualche chiarimento accanto ad istruzioni significative. a = a + 2; // aggiungo 2 alla variabile a non è un commento ma una scemenza: meglio chiarire il motivo di questo incremento.
Naming int NumLati; // descrizione variabile Il Nome deve richiamare l’uso della variabile e/o della funzione. Per le variabili è preferibile un sostantivo, per le funzioni (metodi) un verbo. C è case-sensitive; l’iniziale maiuscola per i nostri nomi, li distinguerà a colpo d’occhio dalle parole del C; nomi composti avranno le singole iniziali maiuscole ( CamelCase ): int NumLati; // descrizione variabile float AreaCerchio, // descrizione variabile MaxValore; // descrizione variabile
Definizione di tipi e costanti Utilizzare il #define per i valori costanti; i nomi vanno scritti in MAIUSCOLO. #define PIGRECO 3.1415927 #define ESC 27 Sempre in MAIUSCOLO andranno i nomi delle strutture typedef struct scheda { char Campo[16]; int Valore; } SCHEDA;
Nomi di File Usare nomi corti, senza spazi e senza caratteri speciali o accentati, per garantisce la compatibilità con altri sistemi operativi e protocolli di trasferimento (es. Joliet consente al massimo 31 caratteri per i nomi dei file). Evitare perciò nomi come: Media ponderata con il secondo metodo matematico di Eulero.c lino&mariù.c prova %4.c dque.c ed usare invece: Media_Aritmetica.c Primitive_Grafo.c Gestione_RS232.c Dynamic_Queue.c Se il file contiene una sola funzione, usare lo stesso nome per il file.
Spaziatura Inserire qualche rigo vuoto tra le diverse funzioni e tra le varie sezioni del programma. Inserire gli spazi tra i nomi e gli operatori for(i=0,j=N;i<j&&j>=10;i++,j-=3)if(A[i][j])A[i][j]*=i; diventa for (i = 0, j = N; i < j && j >= 10; i ++, j -= 3) if ( A[i][j] ) A[i][j] *= i;
Variabili Globali L’uso delle variabili globali è fortemente sconsigliato perché: consente a tutte le procedure di accedere ai dati, con conseguente difficoltà di debug di eventuali errori; rende difficoltoso il riutilizzo del codice; quasi sempre, impedisce l’uso della ricorsione: ogni istanza di una funzione ricorsiva deve agire su un’immagine diversa dei dati.
Allineamento e Indentazione Allineare le istruzioni, disponendo opportunamente quelle che sono subordinate ad una condizione o ad un ciclo, qualche colonna più a destra. I simboli di BEGIN e END, ovvero { e } devono essere sempre allineati con l’istruzione cui fa riferimento il blocco (if, else, while, do, switch). Tutte le istruzioni del blocco devono rientrare: 2-3 colonne dovrebbe essere una misura corretta, purché venga usata sempre la stessa quantità. Un'indentazione esagerata, lascia poco spazio agli indispensabili commenti.
Operatori aritmetici + - * / ++ -- = += -= *= /= addizione sottrazione moltiplicazione * divisione (intera o float) / incremento ++ decremento -- assegnazione = aggiunta += detrazione -= assegnazione prodotto *= assegnazione divisione /=
Operatori per i valori Interi modulo della divisione % Complemento a uno ~ AND bit a bit & OR bit a bit | EXOR bit a bit ^ shift a sinistra << shift a destra >> assegnazione modulo %= assegnazione AND &= assegnazione OR |= assegnazione EXOR ^= assegnazione shift a sinistra <<= assegnazione shift a destra >>=
Operatori Relazionali uguale == diverso != minore < minore o uguale <= maggiore > maggiore o uguale >= Operatori Logici NOT ! AND && OR ||
Operatore Ternario ? : if (condizione) var = espressione1 else L'unico operatore del C che opera su tre parametri, è una forma sintetica di alcuni casi particolari di if-else: if (condizione) var = espressione1 else var = espressione1 diventa var = condizione ? espressione1 : espressione2; Se condizione è un valore non nullo, si esegue l'espressione1, viceversa si esegue l'espressione2. L’operatore ternario ?: può essere inserito in una altra espressione. Ad esempio, per stampare il massimo tra due valori: printf (“Il massimo è %f \n”, a > b ? a : b );
Precedenze tra gli Operatori ( ) [ ] -> . ! ~ ++ -- + - * & (cast) sizeof() * / % + - << >> < <= >= > == != & (AND bit a bit) ^ | && || ? : = += -= *= /= %= &= ^= |= <<= >>= ,
Procedure Scrivere molte subroutine che svolgono poche funzioni, meglio una sola per volta. Ogni procedura (livello inferiore) fornisce un servizio al programma chiamante (livello superiore): quest’ultimo non deve sapere come questa funzione viene svolta. Ad esempio un programma che deve scambiare dati tra due PC, utilizzerà un gruppo di funzioni del tipo Connetti / Scrivi / Leggi / Disconnetti, senza sapere se queste si appoggiano alla Porta Seriale, alla Parallela o alla Ethernet. In C è usuale inserire le funzioni DOPO il main, utilizzando il prototype per la visibilità.
Uscita da una funzione Nella programmazione strutturata un blocco di istruzioni deve avere UN punto di inizio e UN punto di uscita. Questo, ovviamente, vale anche per i sottoprogrammi e il programma principale. Non usare break per interrompere un ciclo (salvo al termine di un case). L’unico return di una funzione deve essere l’ultima istruzione.
Main.c #include <stdio.h> #define PIGRECO 3.14159 int Sub1 ( float ); /*================================================= ...specifiche del Software, autori, data relase... ---------------------------------------------------*/ int main ( void ) { float Area, // ... uso della variabile Raggio; // ... int i, // ... j; // ... istruzione; do { istruzione; if ( condizione ) // motivo della selezione for ( i = 0, j = 100; i < j ; i ++, j -= 13) } else // alternativa Area = Raggio > 0 ? Raggio * Raggio * PIGRECO : 0; while (condizione); Main.c
Sub1.c #include <stdio.h> /*======================================================= ...specifiche della Subroutine, data ultima modifica... INPUT: parametri di ingresso OUTPUT: valore di uscita ------------------------------------------------------*/ int Sub1 ( float x // ..... ) { int k; // ..... istruzione; switch ( k ) { case 1 : istruzione; break; case 23: istruzione; case 4: istruzione; default: istruzione; } return valore; Sub1.c
Controllo dell’Input utente /*================================================================= Funzione per la lettura di una stringa in input da tastiera. Il chiamante deve aver allocato spazio non inferiore a LenMax; accettati al massimo LenMax-1 caratteri, con il NULL finale; il LineFeed di chiusura (10) viene eliminato; i caratteri eccedenti verranno persi. -----------------------------------------------------------------*/ void LeggiStringa ( char *Stringa, // puntatore alla stringa int LenMax // lunghezza massima spazio allocato ) { int i; fgets ( Stringa, LenMax, stdin ); i = strlen(Stringa); // posizione del NULL i -= 1; // ultimo carattere // elimina il LineFeed di chiusura if (Stringa[i] < 32) Stringa[i] = 0; fflush ( stdin ); // svuota il buffer }
Costruzione di un progetto Se il lavoro è complesso, conviene suddividere tutte le funzioni in più files .c, ciascuno con il corrispondente file header .h: Ogni file C contiene una o più funzioni, legate tra loro da un comune settore del progetto; all’inizio del file occorre citare l’header delle sotto-funzioni chiamate, con #include. Il corrispondente file H contiene tutti i prototype delle funzioni scritte, le dichiarazioni typedef, struct e define. Non contiene alcuna istruzione eseguibile. L’ambiente di sviluppo, tramite il file di progetto, provvede a collegare tutti i file tra loro. NON bisogna usare gli #include per i file .c
Metacomandi In alcuni casi, richiamare più volte lo stesso file header può creare problemi. In tal caso, incapsulare il file come segue: #ifndef NOME #define NOME . . . . . . . . . #endif
Ambiente di lavoro Per sviluppare un programma in C: Editor Compiler Linker Le tre funzioni sono solitamente riunite in un unico Ambiente di Sviluppo. Useremo il wxDev C/C++ scaricabile gratuitamente
C:\Programmi\Dev-Cpp\lib Installazione Installare wxdevcpp_6.10.2_setup.exe nella cartella C:\Programmi\Dev-Cpp. Solo sotto Windows, per emulare alcune funzioni per la gestione della finestra di testo, copiare il file libconio.a nella cartella C:\Programmi\Dev-Cpp\lib
Le barre degli strumenti possono essere posizionate in modo diverso a seconda delle esigenze del programmatore
Ad ogni programma da sviluppare deve corrispondere un Progetto : in questo modo è più facile tenere assieme le varie parti di un Job.
Assegnare la modalità Console (ovvero l’utilizzo della finestra di testo) Scrivere il Nome del progetto Indicare il linguaggio C
Salvare il progetto in una cartella di lavoro. Usare una cartella differente per ogni progetto.
Viene proposto un generico file main Viene proposto un generico file main.c da utilizzare come base di lavoro. Possiamo anche ignorarlo e aggiungere tutti i files del nostro lavoro già pronti
Per utilizzare le funzioni di gestione della finestra di testo, occorre aggiungere l’opzione relativa alla libreria libconio.a
Nelle opzioni del progetto: selezionare la scheda Opzioni Addizionali e cliccare su Aggiungi libreria
Cercare ed assegnare il file C:\Programmi\Dev-Cpp\lib\libconio.a
Aggiungere agli header standard i prototype della libreria conio #include <conio.h>
Questo programma permette di leggere i codici dei tasti ASCII e SPECIALI della tastiera.