380 likes | 542 Views
Introduzione al C. Davide Gadia. I linguaggi di programmazione. Linguaggi Compilati - La scrittura del codice può avvenire tramite un editor di testo, oppure attraverso dei tool IDE. - Una volta creato il codice viene controllato e compilato generando un file in codice macchina.
E N D
Introduzione al C Davide Gadia
I linguaggi di programmazione Linguaggi Compilati - La scrittura del codice può avvenire tramite un editor di testo, oppure attraverso dei tool IDE. - Una volta creato il codice viene controllato e compilato generando un file in codice macchina. - Il file in codice macchina cosi realizzato può essere eseguito. Vantaggi :: prestazioni ottime, estendibile Svantaggi :: comprensione non immediata Linguaggi Interpretati - La scrittura del codice può avvenire tramite un editor di testo, oppure attraverso dei tool IDE. - Una volta creato il codice esso viene interpretato ed eseguito senza essere tradotto in linguaggio macchina. Vantaggi :: alta portabilità, facilità di programmazione Svantaggi :: lentezza di esecuzione, poco espandibile/versatile Programmazione Grafica aa2006/2007
Il linguaggio C Caratteristiche - Linguaggio Compilato - Dimensioni codice/eseguibile ridotte - Efficienza di esecuzione dei programmi - Multi-platform - E’ un linguaggio di alto/basso livello - Allocazione dinamica della memoria - Estensione ad oggetti -> C++ Perchè lo abbiamo scelto? - Gestione a tutti i livelli di ogni periferica della macchina - Presenza di librerie per quasi ogni tipo di problema - SDK sviluppati in C/C++ - In PG, la programmazione più spinta e a basso livello viene eseguita generando codice C/C++ - Documentazione molto ampia per ogni tipo di problematica - Il core del codice se ben programmato può essere portato su qualsiasi piattaforma Programmazione Grafica aa2006/2007
Workflow di un programma C - Stesura del codice - Compilazione - Linking - Debugging - Esecuzione Workflow Codice Sorgente Compilazione Linking Esecuzione Debug Programmazione Grafica aa2006/2007
Compilazione e Linking Compilazione Statica - elaborazione del file sorgente dal parte del preprocessore, al quale si possono dare delle direttive - generazione del codice assembler - generazione del file oggetto - linking dei vari file oggetto per ottenere il file eseguibile Linking Dinamico - verifica che i simboli/funzioni usati nel codice siano definiti all'interno delle librerie - viene solo trascritta la dipendenza (nome e versione) all'interno dell'eseguibile I vantaggi sono - minori dimensioni effettive dell'eseguibile su disco e in memoria - superiore o pari velocità di caricamento ed esecuzione - aggiornando con versioni più ottimizzate delle lib dinamiche si ottimizzano automaticamente tutti gli eseguibili Programmazione Grafica aa2006/2007
Struttura di un programma C Struttura - Comandi di prepocessore Serie di comandi che permettono di comandare il compilatore secondo le proprie esigenze. - Definizione dei tipi Ad ogni variabile deve essere associato un tipo, è una regola per ogni LDP. - Prototipi (Dichiarazione) Dichiarazione dei tipi delle funzioni e delle variabili passate alle funzioni - Variabili (Globali) Variabili disponibili e allocate durante l’intera esecuzione del programma. - Funzioni (Implementazione) Implementazione del core delle funzioni - Main Function Il Main la prima funzione che fa da start (init) ad un programma C. Programmazione Grafica aa2006/2007
Primo programma Step 1 :: Stesura del codice int main(void) { return 0; } Il vostro codice C dovrà contenere una e una sola funzione main(). 1 int main(void) { printf(“Corso di PG\n”); return(0); } Evolviamo il nostro codice sopra scritto in una forma poco più complessa. 2 Programmazione Grafica aa2006/2007
Primo programma Step 2 :: Compilazione A seconda su quale SO volete creare il vostro codice sorgente, esistono una serie di compilatori per ogni tipo di piattaforma. Noi useremo Visual C++. Ma in prima istanza impareremo come utilizzare il compilatore da linea di comando in modo da capire la corretta sequenza delle operazioni che spesso nei grossi tool di programmazione è trasparente all’utente. Nota: prima di iniziare dobbiamo settare le variabili di ambiente in modo tale da essere in grado di eseguire il nostro compilatore in qualunque posizione si trovi il nostro sorgente. - settare la variabile PATH con la dir C:\Programmi\Microsoft Visual Studio\VC98\Bin\ di Visual C++ Salviamo in un file “sorgente02.c” il nostro sorgente (codice 2) che abbiamo scritto in precedenza. Apriamo un prompt di MS-Dos e andiamo nella directory in cui è contenuto il file appena salvato. Eseguiamo il seguente comando in dos c:\>...\ cl /c sorgente02.c Dove cl.exe è il nostro compilatore visual c. Il comando appena eseguito ha generato il sorgente assembler. Il passaggio successivo è la fase di linking. Programmazione Grafica aa2006/2007
Primo programma Step 3 :: Linking Dal nostro file sorgente02.obj ora dobbiamo linkarlo. Useremo sempre il nostro compilatiore ma questa volta con il flag “/l” e l’elenco delle librerie che ci servono. Eseguiamo il seguente comando in dos c:\>...\ cl sorgente02.c /l Dopo il comando viene generato il file sorgente02.exe Nel nostro caso nessuna libreria è stata usata quindi viene generato il sorgente senza nessun problema. Per eseguire il programma basta scrivere nel prompt di MS-Dos il nome del file. Ottenete come output nella vostra console: Corso di PG Con questi semplici passaggi abbiamo voluto far capire come deve essere la procedura di compilazione per un programma C. La cosa importante da ricordare è che qualunque applicazione voi usiate per compilare i vostri progetti essa eseguirà sempre queste procedure nella sequenza illustrata. Programmazione Grafica aa2006/2007
Esempio Parte 01 :: Codice sorgente // Direttive per il preprocessore // #include <stdio.h> // Pre-processore #define COSTANTE_01 30 // Pre-processore #define COSTANTE_02 50 // Pre-processore // .............................................. // Definizione dei tipi // typedef int intVector[3]; // ...................... // dichiarazione funzioni // void printValues (intVector vec); int sumVectorValues (intVector vec, int times); // ............................................ Programmazione Grafica aa2006/2007
Esempio Parte 02 :: typedef // Definizione dei tipi // typedef int intVector[3]; // ...................... La parola chiave typedef viene utilizzata per assegnare un alias ad un qualsiasi tipo fondamentale oppure derivato (introdotto dall'utente e derivato dai tipi fondamentali). Con typedef non si definisce un nuovo tipo, ma si introduce un nome che corrisponde a un tipo definito. La sintassi è la seguente: typedef nome_tipo nuovo_nome_tipo; Programmazione Grafica aa2006/2007
Esempio Parte 03 :: Codice sorgente // implementazione funzioni // void printValues(intVector vec) { ... Implementazione ... } int sumVectorValues(intVector vec, int times) { ... Implementazione ... } // Main function int main(int argc, char *argv[]) { intVector myVector; // variabile di tipo intVector ... Implementazione ... return 0; // valore di ritorno } Programmazione Grafica aa2006/2007
Esempio Parte 04 :: printValues • // implementazione funzioni • // • void printValues(intVector vec) • { • // Funzione per la gestione dell'output su console • printf("Vec :: [%d,%d,%d]",vec[0],vec[1],vec[2]); • } La funzione riceve in ingresso come parametro una variabile di tipo intVector. L’unica cosa che succede all’interno della funzione è che i valori della funzione vengono stampati su standard output tramite la funzione printf. Programmazione Grafica aa2006/2007
Esempio Parte 05 :: printf La funzione stampa una serie di argomenti secondo uno schema di formattazione. Format: Stringa che contiene del testo da essere stampato Ha il seguente prototipo: %[flags][width][.precision][modifiers]type argument(s): Parametri opzionali che contengono i valori/dati da essere visualizzati secondo la formattazione prescelta. Return Value: in caso di errore ritorna un numero negativo altrimenti ritorna il numero di caratteri stampati. Es: printf ("Some different radixes: %d %x %o %#x %#o \n", 100, 100, 100, 100, 100); printf ("floats: %4.2f %+.0e %E \n", 3.1416, 3.1416, 3.1416); Some different radixes: 100 64 144 0x64 0144 floats: 3.14 +3e+000 3.141600E+000 int printf ( const char * format [ , argument , ...] ); Programmazione Grafica aa2006/2007
Esempio Parte 06 :: sumVectorValues int sumVectorValues(intVector vec, int times) { int i; // local var for(i=0;i<times;i++) // ciclo for { vec[0] += vec[0]; // a = a + a vec[1] += vec[1]; // a = a + a vec[2] += vec[2]; // a = a + a } printValues(vec); // richiamo la funzione printValues return vec[0]+vec[1]+vec[2]; // valore di ritorno } La funzione prende in ingresso 2 valori, un intVector e un intero. Viene eseguito un ciclo per i numero di volte (times). Nel ciclo sommo il valore delle componenti del vettore x , y, z a se stesso. Una volta uscito dal ciclo chiamo la funzione printValues a cui passo il mio vettore per essere stampato. In ultima istanza passo come valore di ritorno la somma delle tre componenti del vettore risultante dalle operazioni precedenti. Programmazione Grafica aa2006/2007
Esempio Parte 07 :: main // Main function int main(int argc, char *argv[]) { intVector myVector; // variabile di tipo intVector int res; // variabile int myVector[0] = 1; // primo elemento dell'array myVector[1] = 1; // secondo elemento dell'array myVector[2] = 1; // terzo elemento dell'array res = sumVectorValues (myVector, 4); // richiamo la funzione sumVectorValues printf("\nRes :: %d",res); // gestione dell'output su console return 0; // valore di ritorno } La funzione main, è la prima funzione invocata in esecuzione. Istanzia una variabile “myVector”di tipo intVector. La variabie myVector viene inizializzati ai valori 1,1,1. Eseguo la funzione “sumVectorValues” e il suo valori di ritorno lo salvo nell variabile res. Stampo il valore della variabile res. Programmazione Grafica aa2006/2007
Funzioni Passaggio di parametri #include <stdio.h> void function (value) { value++; // value = 4 } void main (void) { int value = 3; function (value); // value = 3 } Il linguaggio C utilizza il passaggio dei parametri alle funzioni per valore. Per ottenere il passaggio per riferimento occorre utilizzare i puntatori. Programmazione Grafica aa2006/2007
Struct Struct struct box { char name[36]; int numeroBiro; float profondita; }; struct box cassetto; Viene cosi definita una nuova struttura box e definita cassetto di tipo struct box. Una struttura puo' essere pre-inizializzata al momento della dichiarazione: struct box cassetto={“sofa", 5, 40.50}; Per accedere ai membri (o campi) di una struttura il C fornisce l'operatore ".". Ad esempio: cassetto.numeroBiro = 35; Programmazione Grafica aa2006/2007
Allineamento byte nelle struct Occupazione di memoria di una struct: • non è data dalla somma delle occupazioni dei singoli campi • dipende dall’ordine in cui vengono definiti • si deve prestare attenzione all’allineamento dei byte dei campi rispetto alla word (4 byte – 32 bit nel nostro caso). Esempio: struct con un intero e due char • occupazione memoria del tipo char: 1 byte • occupazione memoria del tipo int: 4 byte • Sarebbe char + char + int = 6 byte ma…… • …………cosa succede in questi due casi? typedef struct _mia_struct1 { char c1; int n; char c2; } mia_struct1; typedef struct _mia_struct2 { int n; char c1; char c2; } mia_struct2; Programmazione Grafica aa2006/2007
4 4 8 8 12 12 Allineamento byte nelle struct Una word viene riempita con uno o più dati. Se un dato non può essere contenuto esattamente in una word si salta alla word successiva. typedef struct _mia_struct1 { char c1; int n; char c2; } mia_struct1; c1 Offset n c2 Offset Occupazione totale : 12 byte (!) typedef struct _mia_struct2 { int n; char c1; char c2; } mia_struct2; n c1 c2 Offset Occupazione totale : 8 byte (!) Programmazione Grafica aa2006/2007
Strutture di controllo Condition Statement for (expression1; expression2; expression3) statement; while (expression) statement; if (expression) statement1 else statement2 o expression1 ? statement1 : statement2 switch (expression) { case item1: statement1; break; case item2: statement2; break; casedefault: statement; break; } Cicli Programmazione Grafica aa2006/2007
sprintf sprintf La funzione scrive una serie di argomenti secondo uno schema di formattazione, in un buffer. Buffer: Buffer dove vengono scritti i valori Format: Stringa che contiene del testo da essere stampato Ha il seguente prototipo: %[flags][width][.precision][modifiers]type argument(s): Parametri opzionali che contengono i valori/dati da essere visualizzati secondo la formattazione prescelta. Return Value: in caso di errore ritorna un numero negativo altrimenti ritorna il numero di caratteri stampati. int sprintf ( char * buffer, const char * format [ , argument , ...] ); Programmazione Grafica aa2006/2007
sprintf sprintf Output: [10 + 3 è 13] is a 11 chars string #include <stdio.h> int main (void) { char buffer [100]; int n, a=10, b=3; n = sprintf (buffer, "%d + %d è %d", a, b, a+b); printf ("[%s] is a %d chars string\n",buffer,n); return 0; } Programmazione Grafica aa2006/2007
Rand Rand La function "rand" consente di estrarre un numero pseudo-casuale. n=rand(); La sequenza di numeri ottenuta con la "rand" è però sempre la stessa, per avere una sequenza che sia imprevedibile è necessario fornire alla "rand" un seme di avvio diverso ogni volta che essa viene richiamata. Per tale scopo è necessario usare la "srand". Il parametro che deve essere passato alla "srand" è un "unsigned int". srand(437U); Ogni valore diverso del seme dà inizio ad una diversa sequenza di numeri. Affinchè la sequenza possa essere imprevedibile è necessario passare alla "srand" un valore imprevedibile per il seme. Un modo di fare ciò è quello di usare il clock di macchina. srand ((unsigned int) time((NULL)); La funzione "time(NULL)" ritorna l'ora corrente; la costante "NULL" è definita nello header "<stdio.h>". Il valore restituito da tale function viene convertito tramite un "cast" al tipo "unsigned int". int rand (void) Programmazione Grafica aa2006/2007
File fopen FILE *stream; stream = fopen ("myfile.dat","rb"); if ((stream = fopen ("myfile.dat","rb"))==NULL) { printf("Can't open %s \n", "myfile.dat"); exit(1); } fclose(stream); FILE *fopen(char *name, char *mode) I files sono l'esempio piu' comune di stream. Per aprire un puntatore al file si utilizza la funzione fopen(). Tale funzione ritorna un puntatore a FILE. La stringa "name" e' il nome del file su disco a cui vogliamo accedere; la stringa "mode" definisce il tipo di accesso. Se per una qualsiasi ragione il file risulta non accessibile, viene ritornato un puntatore nullo. Le possibili modalita' di accesso ai files sono: * "r" (read), * "w" (write), * "a" (append). e: *”t” (modalità ASCII - default), *”b” (modalità binaria). Per aprire un file dobbiamo avere una stream (puntatore al file) che punta ad una struttura FILE. Ogni volta che apriamo un file dobbiamo SEMPRE chiuderlo. Programmazione Grafica aa2006/2007
Lettura e scrittura da file ASCII fprintf - fscanf int fprintf(FILE *stream, char *format, args ...) int fscanf(FILE *stream, char *format, args ...) Le funzioni fprintf ed fscanf sono comunemente utilizzate per l'accesso ai files di testo. Sono simili a printf e scanf, tranne per il fatto che i dati sono letti dalla stream, che deve essere aperta con fopen(). Il puntatore alla stream viene incrementato automaticamente con tutte le funzioni di lettura/scrittura su file, quindi non e' necessario preoccuparsi di farlo manualmente. char *string[80]: FILE *stream, *fopen(); if ((stream=fopen(...)) != NULL) fscanf(stream,"%s",string); fclose(stream); char *string[80] FILE *fp; if ((fp=fopen("file.dat","r")) != NULL) fscanf(fp,"%s",string); fclose(fp); Programmazione Grafica aa2006/2007
Lettura e scrittura da file binari I comandi fread e fwrite ci permettono di leggere/scrivere con una sola riga di codice una grande quantità di dati da file binari. int fread(void *buffer, int size, int num, FILE *fp); • Il comando: • legge dal file puntato da fp • un numero num di dati • di grandezza size byte • e li mette nel buffer buffer • ritorna il num di elementi letti int fwrite(void *buffer, int size, int num, FILE *fp); • Il comando: • scrive nel file puntato da fp • un numero num di dati • di grandezza size byte • Pigliandoli dal buffer buffer • ritorna il num di elementi scritti Programmazione Grafica aa2006/2007
Organizzazione del progetto Divisione del codice • Ci potrà essere molto utile ed anzi è caldamente consigliato organizzare su più file il progetto che si ha intenzione di affrontare. • Le ragioni sono tante tra le più importanti ci sono sicuramente: • - maggiore comprensione del codice • un code reuse molto alto, spesso molte funzioni che scriviamo possono essere riutilizzate per altri scopi. • Al nostro programma aggiungiamo ora altre 2 semplicissime funzioni; vediamo la dichiarazione delle funzioni: // dichiarazione funzioni // void printVector (intVector vec); void printInteger (int intVal); int sumVectorValues (intVector vec, int times); int sumIntValues (int intVal, int times); // ............................................ Programmazione Grafica aa2006/2007
Organizzazione del progetto Parte 02 Tutto il codice di “sorgente04” è contenuto in un unico file. Come possiamo vedere mano a mano che il codice cresce e si iniziano ad introdurre nuove funzioni la gestione del codice diviene più difficoltosa. L’ unica possibilità per rendere il tutto più chiaro è cercare di dividere il tutto su più files. Come primo approccio bisogna individuare le funzioni che fanno delle cose comuni, nel nostro caso abbiamo due funzioni che stampano su standard output, e altre due che gestiscono dati e li elaborano, ed infine abbiamo il main. Bene a questo punto dobbiamo cercare di avere non più 1 file solo ma almeno 3 file. Il primo deve contenere la main function. Il secondo set di files conterrà le funzioni di print. il terzo set di files conterrà le funzioni di gestione dei dati. Con “set di files” indico una coppia di file, header (*.h) e la sua implementazione (*.c). Vediamo nel nostro caso come risulteranno i files e come li compileremo. Programmazione Grafica aa2006/2007
Organizzazione del progetto Parte 03 – sorgente05.c // Direttive per il preprocessore // #include <stdio.h> // Pre-processore #include "OperationsFunctions.h" #include "PrintFunctions.h" #define COSTANTE_01 30 // Pre-processore #define COSTANTE_02 50 // Pre-processore int main(int argc, char *argv[]) { //implementazione } La modifica evidente è la scomparsa delle funzioni e invece la comparsa di nuovi include i quali includono le dichiarazioni delle funzioni precedentemente implementate nel codice unico (sorgente04.c). Programmazione Grafica aa2006/2007
Organizzazione del progetto Parte 04 – OperationsFunctions • typedef int intVector[3]; • int sumVectorValues (intVector vec, int times); • int sumIntValues (int intVal, int times); OperationsFunctions.h • #include "OperationsFunctions.h" • int sumVectorValues(intVector vec, int times) • { • //implementazione • } • int sumIntValues (int intVal, int times) • { • //implementazione • } OperationsFunctions.c Programmazione Grafica aa2006/2007
Organizzazione del progetto Parte 05 – PrintFunctions • typedef int intVector[3]; • int sumVectorValues (intVector vec, int times); • int sumIntValues (int intVal, int times); PrintFunctions.h • #include “PrintFunctions.h" • int printValues(intVector vec, int times) • { • //implementazione • } • int printInteger (int intVal, int times) • { • //implementazione • } PrintFunctions.c Programmazione Grafica aa2006/2007
Organizzazione del progetto Parte 06 - Compilazione Prima di tutto devo compilare i file OperationsFunctions.c e PrintFunctions.c. cl /c OperationsFunctions.c cl /c PrintFunctions.c cl /c sorgente05.c E genero i file obj dove sono contenute le dipendenze per le funzioni. Successivamente compilo il file sorgente05.c con le dipendenze di libreria (*.obj) generate prima. Adesso genero il sorgente con il seguente comando nel quale dovrò indicare le dipendenze dei 2 file esterni appena creati. cl Sorgente05.c /l OperationsFunctions.obj PrintFunctions.obj Il compilato è il medesimo della versione precedente. Programmazione Grafica aa2006/2007
Puntatori Utilizzati per: • Allocazione dinamica della memoria • Passaggio di parametri per riferimento nelle funzioni • Puntatori a funzione per gestione callback • Una variabile dichiarata come puntatore ad un tipo conterrà l’indirizzo di una locazione di memoria int i, j, k; int *pi; i = 5; j = 10; k = 15; pi = &i; 5 i 0x12ff70 10 j 0x12ff74 15 k 0x12ff78 0x12ff70 0x12ff7C pi … 0x12ff80 Programmazione Grafica aa2006/2007
Puntatori Allocazione dinamica int *a = NULL; int dim = 6; a = (int*) malloc( dim * sizeof(int) ); for(i=0; i<dim; i++) a[i] = 1000 * ( i+1); free(a); Programmazione Grafica aa2006/2007
Puntatori Allocazione dinamica - caso bidimensionale #define DIMX 8 #define DIMY 6 … int i, j; int **m; m = (int **)malloc( DIMX * sizeof(int*) ); for(i=0; i<DIMX; i++) { m [i] = (int*) malloc( DIMY * sizeof(int) ); } m Programmazione Grafica aa2006/2007