Introduzione al linguaggio C Dr. Francesco Fabozzi Corso di Informatica
Cenni storici Il linguaggio C è sviluppato da Dennis Ritchie nel 1971 nei laboratori della AT&T Bell Laboratories L’obiettivo fu quello di creare un linguaggio adatto allo sviluppo di sistemi operativi ma più facile da programmare rispetto a un assembler Nel 1973 la seconda versione del sistema operativo Unix viene scritta in C Unix era nato nel 1969 e la prima versione era stata scritto nell’assembler di un PDP-7 Il linguaggio viene standardizzato da ANSI nel 1989 (ANSI-C)
Caratteristiche del C Il C è un linguaggio general purpose Cioè è in grado di descrivere qualsiasi algoritmo che possa essere descritto con un linguaggio di programmazione Il C è un linguaggio di basso livello Gli oggetti che si manipolano sono simili a quelli che l’hardware mette a disposizione (caratteri, numeri, indirizzi di memoria)
Caratteristiche del C Il C è un linguaggio compatto Sono definite nel linguaggio solo le strutture essenziali Ad esempio molte funzioni di uso comune (funzioni matematiche o di I/O) non fanno parte del linguaggio Sono disponibili tramite la libreria standard (standard library)
Compilazione e linking Il sistema operativo Linux mette a disposizione il compilatore gcc include anche il linker $gcc [-l nomelib] [–o file.exe] file.c File sorgente Nome del file di output default: file.out Linka la libreria (non standard) dal nome libnomelib.a La libreria standard è linkata di default
Il concetto di procedura Una procedura è un’insieme di istruzioni che è stato incapsulato in un’unità L’incapsulamento permette il riutilizzo dello stesso codice evitando ripetizioni delle istruzioni Si usano dei semplici riferimenti alla procedura Una procedura può essere invocata in differenti punti di un programma
La programmazione procedurale Nella programmazione procedurale un programma complesso viene diviso in un certo numero di procedure più semplici Consente riutilizzo del codice evitando errori dovuti a ripetizione di istruzioni Consente un più agevole sviluppo e gestione di programmi complessi perchè permette di lavorare a sottoproblemi più semplici
Funzioni Una funzione è un tipo di procedura identificata da: un nome una lista di dati in ingresso (argomenti della funzione) un valore di uscita (valore restituito dalla funzione) Nella funzione sono contenute le istruzioni che specificano le operazioni da effettuare sui dati in ingresso per determinare il valore di uscita
Funzioni Il tipo degli argomenti e del valore restituito non sono arbitrari ma sono ben specificati nella definizione della funzione Una funzione può anche non restituire nessun valore in uscita Ad esempio una funzione che compie un’operazione di I/O
Un programma C Un programma C è costituito da funzioni e dati In un programma C esiste sempre una funzione principale (che si chiama main) da cui inizia l’esecuzione del programma La funzione main può chiamare altre funzioni La funzione main può restituire un valore in uscita
Un programma C Le istruzioni di una funzione sono racchiuse fra parentesi graffe Le istruzioni devono essere concluse da un punto e virgola In generale un gruppo di istruzioni racchiuse tra parentesi graffe si chiama blocco E’ possibile trovare blocchi di istruzioni anche all’interno di una funzione
Salve, mondo! Programma che scrive una frase in output /* Programma che stampa un saluto */ #include <stdio.h> main(){ printf(“Salve, mondo \n”); } Commento Direttiva al preprocessore per includere le definizioni delle funzioni della libreria standard di I/O Indica la fine dell’istruzione Stringa di caratteri Funzione della libreria standard
Regole basilari Il compilatore ignora tutto ciò che è compreso tra /* e */ Serve ad inserire commenti nel programma main( ){ … } indica la funzione principale nel programma Il nome di una funzione è sempre seguito da una coppia di parentesi tonde che servono per contenere una lista di dati (argomenti) che sono passati alla funzione da chi la chiama La funzione main non si aspetta argomenti
La funzione printf La funzione printf appartiene alla libreria standard di I/O e serve a produrre una stampa sullo standard output (tipicamente il terminale grafico) Nell’esempio l’argomento passato a printf è una costante stringa di caratteri Rappresenta ciò che dovrà essere stampato La sequenza \n indica un carattere speciale: carattere di new line, che sposta il cursore alla riga successiva
Alcuni caratteri speciali A capo \t Tabulazione orizzontale \b Backspace \f Salto pagina \\ Backslash \? Punto interrogativo \’ Apice singolo \’’ Doppio apice
Funzioni di libreria Le definizioni delle funzioni di una libreria si trovano in un header file con estensione .h Nel nostro esempio abbiamo usato la libreria standard di I/O: stdio header file: stdio.h Per poter usare una funzione di una certa libreria è necessario che il programma conosca (includa) l’header file della libreria
#include L’operazione di inclusione delle informazioni di una certa libreria viene fatta con un’istruzione detta direttiva al preprocessore: #include<nome_header_file> Il preprocessore agisce prima del compilatore ed esegue le direttive che compaiono nel programma Esistono diversi tipi di direttive al preprocessore
Programma euroconvertitore Programma che effettua la conversione da lire a euro /* Euroconvertitore */ #include <stdio.h> main(){ float cifra_lire, cifra_euro; const float fattore_conv = 1936.27; printf(“Inserisci la cifra in lire \n”); scanf(“%f”,&cifra_lire); cifra_euro = cifra_lire / fattore_conv; printf(“il controvalore in euro è %f \n”, cifra_euro); } Dichiarazione Funzione di libreria per leggere dallo standard input Istruzione di assegnazione
Dichiarazione delle variabili Tutte le variabili che sono usate nel programma devono essere dichiarate La dichiarazione può essere fatta in qualunque punto del programma La dichiarazione specifica il tipo di dato che la variabile deve contenere (tipo della variabile) Esistono tipi predefiniti in C Un tipo può essere anche definito dall’utente
Tipi predefiniti in C Variabile intera int Intero con segno, 32 bit short Intero con segno, 16 bit long Intero con segno, 64 bit unsigned int Come int, ma senza segno unsigned short Come short, ma senza segno unsigned long Come long, ma senza segno
Tipi predefiniti in C Variabile carattere In realtà è un intero a 8 bit char Carattere, 8 bit
Tipi predefiniti in C Variabile reale Preferibile float se non si vuole occupare troppa memoria (ad esempio per vettori di grande dimensione) float reale, 32 bit double reale, 64 bit long double reale, 128 bit
Inizializzazione delle variabili Inizializzare una variabile significa attribuirle un valore iniziale In caso contrario non è possibile prevedere il valore iniziale (sarà il contenuto delle locazioni assegnate alla variabile) Spesso non costituisce un problema, ma fate attenzione! Tipicamente una variabile è inizializzata quando la si dichiara int n = 5; Maggiore efficienza
Costanti del programma E’ possibile associare un nome a una costante del programma E’ molto utile nel caso di costanti con grosso numero di cifre significative o costanti che ricorrono spesso nel programma Il numero viene scritto solo una volta nel programma; in tutte gli altri posti si usa il nome simbolico
Costanti del programma Vantaggi nell’uso di nomi associati a costanti: Si aumenta la leggibilità del programma Si riduce il rischio di errori di scrittura Se occorre modificare il valore della costante si deve modificare solo una linea di programma
Il qualificatore const Un modo per dichiarare una quantità costante è usare il qualificatore const in fase di dichiarazione di una variabile La variabile va anche inizializzata col suo valore Il qualificatore const premesso al tipo indica che il valore della variabile non potrà essere cambiato nel corso del programma const int max_val = 100; const char no = ’n’ const double pi_greco = 3.141592
Costanti simboliche Un altro modo sfrutta la possibilità di definire una costante simbolica usando la direttiva del preprocessore #define #define <nome> <testo_da_sostituire> Esempi: #define MAX_VAL 100 #define PI 3.141592 Il preprocessore sostituisce il nome con il testo da sostituire in ogni occorrenza del programma Nota: le costanti somboliche non sono variabili!
Istruzioni di assegnazione Un istruzione di assegnazione permette di assegnare un valore a una variabile variabile = espressione Viene valutata l’espressione a dx e il risultato viene assegnato alla variabile a sx L’espressione è formata da variabili, costanti e operatori Il tipo dell’espressione deve essere uguale al tipo della variabile
Conversione di tipo Che succede se: Gerarchia di tipi Nell’espressione si fanno operazioni su tipi diversi? Risposta: un tipo inferiore viene promosso a un tipo superiore Gerarchia di tipi char, short ≤ int ≤ long ≤ float ≤ double ≤ long double
Conversione di tipo Esempio: float f = 100.0; int i = 1; float a; a = i + f; In questo caso l’intero i viene convertito in un float e poi sommato a f Il risultato finale è ancora un float
Forzare una conversione: cast E’ sempre possibile forzare la conversione da un tipo di variabile a un altro (cast) (nome tipo) espressione L’espressione è convertita nel tipo tra parentesi float f = 100.0; int i = 1; int a; a = i + (int)f; Il tipo di f viene forzato a un intero. Il risultato finale della somma è ancora un intero
Conversione di tipo Che succede se: Il tipo dell’espressione non è uguale al tipo della variabile? Risposta: il risultato numerico dell’espressione viene convertito nel tipo della variabile Fare attenzione: in una conversione si possono perdere informazioni (ad esempio a causa di un troncamento) Esempio: i = x con i intero e x float x viene convertito ad intero e si tronca ciò che viene dopo la virgola
Operatori aritmetici Operatori che compaiono nelle espressioni numeriche +, -, *, / % (operatore modulo) x % y = resto della divisione di x e y
Operatori di incremento e decremento Operatore di incremento ++ Aumenta di uno la variabile Esempio: ++n; Operatore di decremento – – Diminuisce di uno la variabile Esempio: --n;
Operatori di incremento e decremento Gli operatori di incremento e decremento possono essere usati prima o dopo una variabile Esempio: ++n oppure n++ Nel primo caso si parla di preincremento o predecremento La variabile viene prima incrementata o decrementata e poi viene utilizzata Se vengono usati dopo si parla di postincremento o postdecremento La variabile viene prima utilizzata e poi viene incrementata o decrementata
Operatori di incremento e decremento Esempi: n=5; ++n; n=5; n++; dopo l’incremento: n=6 dopo l’incremento: n=6 n=5; k=++n; n=5; k=n++; n=6; k=6 k=5; n=6
Operatori di assegnamento Si usano per scrivere in forma compatta le operazioni in cui una variabile deve essere modificata a partire da se stessa Esempi: x += 2 x = x+2 x -= 3 x = x-3 y *= x y = y*x y *= x+1 y = y*(x+1) z %= x z = z%x
Operatori relazionali Un’espressione relazionale è un’espressione logica in cui sono confrontate due espressioni numeriche o due espressioni con caratteri Vengono usati degli operatori di confronto detti operatori relazionali Maggiore di Maggiore o uguale a < Minore di <= Minore o uguale a == Uguale a != Diverso da
Operatori logici Gli operatori logici sono usati nelle espressioni booleane && AND logico || OR logico
Ancora sulla funzione printf In generale la funzione printf viene usata non solo per stampare una semplice stringa costante ma anche il valore attuale di un certo numero di variabili In questo caso bisogna passare come argomenti oltre alla stringa anche le variabili che bisogna stampare Nella stringa bisogna specificare il formato in cui le variabili saranno stampate
Ancora sulla funzione printf printf( formato, arg1, arg2, …); formato stringa di caratteri contiene sia i caratteri espliciti da stampare sia le informazioni sul formato in cui le variabili saranno stampate (descrittori del formato) arg1, arg2, … nomi delle variabili che bisogna stampare nell’ordine in cui compaiono
Ancora sulla funzione printf Il descrittore del formato ha la seguente forma: % [ - ] [ ampiezza ] [ .precisione ] tipo Solo il tipo deve essere obbligatoriamente specificato L’ampiezza è il numero di campi di carattere che devono essere riservati al dato L’allineamento delle cifre è a destra; se si vuole l’allineamento a sinistra occorre specificare il - La precisione ha diversi significati a seconda del tipo di dato da stampare
Ancora sulla funzione printf La precisione indica: Per una stringa: Il numero massimo di caratteri da stampare Per un intero Il minimo numero di cire da stampare Per un tipo reale Il numero di cifre decimali (cioè dopo la virgola)
Ancora sulla funzione printf Per specificare il tipo si usano opportuni simboli: d int f float lf double c char s stringa E,e,g double in notazione scientifica
Ancora sulla funzione printf Esempi printf(“il controvalore in euro è %f \n”, cifra_euro); printf(“%f, %f, %f, %f”, x, y, x*y, x/y); printf(“\n Posizione = %d Valore = %f”, i, val); printf(“Il giocatore %s ha totalizzato %d punti \n”, nome, punti);
Funzione scanf La funzione scanf è un’altra funzione della libreria stdio e serve a leggere un dato dallo standard input (tipicamente la tastiera) Stessi argomenti di printf, ma: Il primo argomento rappresenta il formato dell’input Le variabili degli argomenti successivi devono essere precedute da “&”
I/O di caratteri La libreria stdio fornisce anche funzioni per la lettura e scrittura di singoli caratteri Funzione getchar( ) Legge il successivo carattere dallo standard input e ne restituisce il valore intero Funzione putchar( int ) Prende come argomento un intero e lo stampa nello standard output come carattere int c; c = getchar(); putchar(c);
Istruzioni di controllo Si chiamano strutture di controllo quei costrutti che modificano il flusso dei dati Strutture di controllo condizionali Permettono di implementare uno schema di selezione Strutture di controllo iterative Permettono di implementare uno schema di iterazione
Strutture condizionali Struttura if – else Struttura if – else if Struttura switch
La struttura if – else La struttura condizionale if – else permette di scegliere tra due possibili blocchi di istruzioni Un blocco di istruzioni è racchiuso da parentesi graffe Le parentesi possono essere omesse nel caso in cui il blocco è costituito da una sola istruzione if( espressione ) { blocco 1 } else { blocco 2 } Se espressione è vera viene eseguito il blocco 1 altrimenti viene eseguito il blocco 2
La struttura if – else Schema corrispondente: V F condizione blocco 1
La struttura if – else L’else è opzionale Si omette quando la scelta non è tra due blocchi ma si ha un solo blocco che deve essere eseguito solo al verificarsi di una certa condizione Se espressione è vera viene eseguito il blocco 1 altrimenti il programma prosegue dalla prima istruzione dopo l’if if( espressione ) { blocco 1 }
La struttura if – else Schema corrispondente: F V condizione blocco
La struttura if – else Esempi if( a > b ) { max = a; printf(“Il massimo è a e vale %f”, a); } else { max = b; printf(“Il massimo è b e vale %f”, b); } z = b; scanf(%f, &a); if( a > b ) z = a; z++; La variabile z assume il valore massimo tra a e b e poi viene incrementata
La struttura if – else if Se le alternative sono più di due allora si può estendere il costrutto if – else mediante gli else if Le alternative sono esaminate in sequenza sono possibili più else if in sequenza Il blocco relativo all’ultimo else (opzionale) viene eseguito se non è verificata nessuna condizione if( espressione 1 ) { blocco 1 } else if ( espressione 2 ) { blocco 2 } else { blocco 3 } Se espressione 1 è vera viene eseguito il blocco 1, se espressione 2 è vera viene eseguito il blocco 2, altrimenti viene eseguito il blocco 3
La struttura else if Esempio: if( n > 0 ) printf(“n è positivo”); else if ( n == 0 ) printf(“n è nullo”); else printf(“n è negativo”);
La struttura switch Struttura condizionale per scegliere tra più alternative in base al valore di una espressione o di una variabile switch ( espressione ) { case costante_1:istruzione_1; break; case costante_2:istruzione_2; … case costante_n:istruzione_n; default:istruzione_default;} L’istruzione break serve per uscire dalla struttura condizionale dopo aver eseguito un’istruzione. Se espressione vale costante_1 viene eseguita istruzione_1: se vale costante_2 viene eseguita istruzione_2, e così via. In tutti gli altri casi viene eseguita istruzione_default.
La struttura switch Esempio: switch( nBusta ){ case 1: printf(“Hai scelto la busta n. 1”); break; case 2: printf(“Hai scelto la busta n. 2”); default: printf(“La busta non è valida”); }
La struttura switch Altro esempio: scanf(“%c”, &carattere); switch( carattere ){ case ‘a’: case ‘e’: case ‘i’: case ‘o’: case ‘u’: printf(“Hai scritto una vocale”); break; default: printf(“Hai scritto una consonante”); }
Strutture iterative Ciclo while Ciclo for Ciclo do – while
Ciclo while Nel ciclo while la condizione di controllo viene verificata prima di eseguire il corpo dell’iterazione Il ciclo potrebbe non essere mai eseguito while( espressione ) blocco_istruzioni Si esegue il blocco_istruzioni finché espressione risulta vera
Schema di iterazione Schema corrispondente: Iterazione per vero F condizione V Controllo in testa (il corpo può anche non essere mai eseguito) Corpo dell’iterazione
Ciclo while Esempi: int somma = 0; int i = 0; while( i <= 100 ){ somma += i; i++; } printf(“Somma = %d”, somma); Programma che somma i primi 100 numeri interi int i; float fact = 1 scanf(“%d”, &i); while( i > 1 ){ fact *= i; i--; } printf(“Fattoriale = %f”, fact); Programma che calcola il fattoriale di un numero i = i*(i-1)*(i-2)*…*1
Ciclo for Nel ciclo for la condizione di controllo viene verificata prima di eseguire il corpo dell’iterazione Il ciclo potrebbe non essere mai eseguito for( istr_init; espressione_test; istr_incr ) blocco_istruzioni equivale a: istr_init; while( espressione_test ){ blocco_istruzioni istr_incr; }
Ciclo for Esempi: int somma = 0; for( int i=0; i<=100; i++ ) somma += i; printf(“Somma = %d”, somma); Programma che somma i primi 100 numeri interi int init; int fact = 1 scanf(“%d”, &init); for( int i=init; i>1; i-- ) fact *= i; printf(“Fattoriale = %d”, fact); Programma che calcola il fattoriale di un numero i = i*(i-1)*(i-2)*…*1
Ciclo do – while Nel ciclo do – while la condizione di controllo viene verificata dopo aver eseguito il corpo dell’iterazione Il ciclo viene eseguito almeno la prima volta do{ blocco_istruzioni } while( espressione ) Si esegue il blocco_istruzioni finché espressione risulta vera
Schema di iterazione Schema corrispondente: Corpo dell’iterazione V Controllo in coda (il corpo è eseguito almeno una volta) condizione F
Ciclo do – while Esempi: char input; do { scanf(%c,&input); printf(“Hai digitato %c”, input); }while(input != ‘q’) int i; int fact = 1; scanf(%d, &i); do { fact *= i; i--; } while( i >= 1) printf(“Fattoriale = %d”, fact); Programma che calcola il fattoriale di un numero i = i*(i-1)*(i-2)*…*1
break, continue L’istruzione break provoca l’uscita incondizionata da un ciclo Viene eseguita la prima istruzione dopo la struttura del ciclo L’istruzione continue forza l’inizio della successiva iterazione di un ciclo Viene eseguita l’istruzione di controllo del ciclo