Programmare. La programmazione. Linguaggi. Algoritmo

Размер: px
Начинать показ со страницы:

Download "Programmare. La programmazione. Linguaggi. Algoritmo"

Транскрипт

1 Programmare 2 La programmazione Ver. 2.2 Il calcolatore sa eseguire molte operazioni di base: somma, sottrazione, AND, etc. Per risolvere un determinato problema si combinano queste operazioni in modo opportuno Programmare significa determinare quali operazioni eseguire e in quale sequenza Claudio Fornaro - Corso di programmazione in C Algoritmo 3 Linguaggi 4 Sequenza di operazioni necessarie per risolvere un problema Esempio: una ricetta di cucina Ci possono essere più algoritmi in grado di risolvere lo stesso problema Il calcolatore comprende solo sequenze di zeri e uno (es significa per esso fai la somma ): linguaggio macchina Il programmatore comprende le parole fai la somma (mentre non significa nulla per lui): linguaggio umano Problema: serve un linguaggio noto sia al calcolatore sia al programmatore

2 Linguaggio di programmazione Soluzione 1 Il programmatore impara il linguaggio macchina, ma questo: ha un basso livello di astrazione è difficile da ricordare è diverso per ogni piattaforma hardware 5 Linguaggio di programmazione Soluzione 2 Il programmatore impara un linguaggio simile al linguaggio macchina detto assembly Questo è più mnemonico (es. ADD è l istruzione per calcolare una somma) Il programma in linguaggio assembly viene tradotto in linguaggio macchina da un semplice programma (assembler). Il linguaggio: ha un basso livello di astrazione è più facile da ricordare (es. invece di scrivere scrive ADD) è diverso per ogni piattaforma hardware 6 Linguaggio di programmazione 7 Linguaggi ad alto livello 8 Soluzione 3 Il programmatore impara un linguaggio di programmazione ad alto livello e usa un traduttore complesso ed efficiente per tradurlo in linguaggio macchina o in assembly Caratteristiche del linguaggio: ha un alto livello di astrazione (esprime le operazioni da svolgere senza entrare nei dettagli di funzionamento del processore, es. A+B calcola la somma di due valori) è più simile al linguaggio umano e quindi più facile da ricordare (es. print X potrebbe essere l istruzione per visualizzare il valore di X) è (quasi) indipendente dalla piattaforma hardware e dal sistema operativo (PC, McIntosh, Windows, Linux, etc.), ci pensa il traduttore a convertirlo per il sistema in uso

3 Programma sorgente Il programmatore sviluppa un programma scrivendo in un linguaggio di programmazione (di alto o basso livello) le operazioni da far eseguire e le memorizza in un file detto: programma programma sorgente codice sorgente sorgente 9 Traduttore: interprete Le istruzioni del codice sorgente vengono ad una ad una tradotte in linguaggio macchina e subito eseguite dalla CPU Sorgente somma stampa leggi calcola Traduttore: compilatore Tutto il codice sorgente viene tradotto in linguaggio macchina e memorizzato in un file detto programma (o file o codice) eseguibile Sorgente somma stampa leggi calcola... Eseguibile Differenze Ogni volta che l interprete esegue un programma, deve attuare la traduzione delle istruzioni in linguaggio macchina: lento Il programma compilato è già tradotto e ha quindi una velocità di esecuzione molto superiore Il compilatore è in genere in grado di produrre una traduzione più efficiente dal punto di vista della velocità di esecuzione del codice prodotto 12

4 Differenze Eseguire un programma interpretato richiede l acquisto e la competenza d uso del programma interprete da parte dell utente finale Eseguire un programma compilato non richiede che un (doppio) click Il compilatore viene acquistato solo dal programmatore e solo questi ha la necessità di avere la competenza necessaria per utilizzarlo 13 Differenze L interprete richiede il codice sorgente che quindi risulta visibile a chiunque Il programma eseguibile non necessita del sorgente: protezione del copyright La procedura di compilazione permette di suddividere un programma complesso in più parti, ma è più complessa da gestire 14 Librerie 15 Compilazione e linking 16 In un linguaggio ad alto livello, molti dettagli non vengono risolti dal programmatore, ma vengono forniti pre-risolti dal compilatore: funzioni di I/O funzioni matematiche... I codici eseguibili (quindi già tradotti in linguaggio macchina) che risolvono questi dettagli (anche molto complessi) vengono raggruppati in file detti librerie Il processo di compilazione è composto da 2 fasi: compilazione vera e propria: il sorgente viene compilato, ma alcune parti (i dettagli ) sono lasciate in sospeso; viene generato un file detto file oggetto linking: il file oggetto e le librerie vengono unite (collegate) per costituire un unico file eseguibile

5 17 18 Librerie statiche Librerie statiche Nella compilazione il codice delle funzioni di libreria viene inserito nel file eseguibile Spesso le stesse identiche librerie sono usate da più programmi: spreco di memoria File eseguibile File oggetto Eseguibile A Oggetto A Eseguibile B Oggetto B Eseguibile C Oggetto C File libreria Libreria Libreria Libreria RAM Librerie dinamiche Librerie dinamiche Dynamic Link Libraries (DLL) Shared Libraries, Shared Objects Nella compilazione il codice delle funzioni di libreria NON è inserito nel file eseguibile, viene invece indicato il nome del file che lo contiene Quando viene eseguito un programma che usa quella libreria, questa viene caricata in memoria, ma è utilizzabile anche dagli altri programmi Eseguibile A Eseguibile B Eseguibile C File eseguibile Oggetto A Oggetto B Oggetto C File oggetto File libreria RAM Libreria

6 Librerie dinamiche 21 Interfacce utente 22 Quando viene eseguito un altro programma che necessita di quella libreria, questa è già caricata in memoria e pronta all uso: il programma impiega meno tempo a partire Se una delle funzioni della libreria deve essere aggiornata (es. nuova versione), è sufficiente sostituire la DLL mentre il programma in sé non viene toccato Se la DLL viene sostituita da una con codice contenente virus, il lancio del programma esegue il virus! L interazione tra utente e programma può avvenire tramite: Interfacce a carattere (console mode): vengono visualizzate e immesse solo righe di testo Interfacce grafiche (GUI Graphic User Interface): vengono visualizzati pannelli, bottoni, caselle di testo, immagini, etc. Molti linguaggi offrono entrambe le interfacce, o nativamente (Visual Basic, Java, ) o mediante librerie (C, C++, )

7 Lo scopo 2 Il primo esperimento Si vuole scrivere un programma in linguaggio C che chieda all utente di introdurre da tastiera due numeri interi e visualizzi il valore della loro somma sul video Ver Claudio Fornaro - Corso di programmazione in C Soluzione informale 3 Le variabili 4 Le operazioni da svolgere saranno le seguenti, nell ordine indicato: 1. Chiedere il primo numero 2. Chiedere il secondo numero 3. Calcolare la somma del primo numero e del secondo numero 4. Visualizzare il risultato I numeri richiesti vengono immagazzinati nella memoria del calcolatore (RAM) Ciascun numero sarà immagazzinato in una piccola porzione di memoria detta variabile costituita da un certo numero di byte A ogni variabile il programmatore dà un nome per potersi riferire ad essa

8 Le variabili Allora la sequenza delle operazioni da svolgere può essere descritta più dettagliatamente come segue: 1. Chiedere il primo numero e metterlo in una variabile di nome A 2. Chiedere il secondo numero e metterlo in una variabile di nome B 3. Calcolare la somma di A e B 4. Visualizzare il risultato 5 Le variabili Anche il risultato della somma dovrà essere immagazzinato nella memoria del calcolatore in una variabile, quindi avremo: 1. Chiedere il primo numero e metterlo in una variabile di nome A 2. Chiedere il secondo numero e metterlo in una variabile di nome B 3. Calcolare la somma di A e B e mettere il risultato in una variabile di nome S 4. Visualizzare il contenuto della variabile S 6 Riduzione all essenziale Riducendo al minimo indispensabile quanto scritto senza perdere alcuna informazione, si ha la seguente sequenza di operazioni: Chiedere A Chiedere B A + B S Visualizzare S Input Input Elaborazione Output 7 Input da tastiera Input significa mettere dentro Le operazioni di input sono quelle tramite le quali il calcolatore riceve dei dati dall esterno Per leggere un numero dalla tastiera e memorizzarlo in una variabile si può ad esempio utilizzare la funzione scanf: scanf("%d", &A) Notare che la variabile A è preceduta dal carattere & La funzione precedente assegna ad A un valore intero (i dettagli in seguito) 8

9 Output su video 9 Assegnazioni di variabili 10 Output significa mettere fuori Le operazioni di output sono quelle tramite le quali il calcolatore mostra i risultati Per visualizzare il valore di una variabile si può utilizzare ad esempio la funzione printf: printf("%d", S) Notare che la variabile S non è preceduta dal carattere & La funzione precedente visualizza un numero intero: quello contenuto nella variabile A (i dettagli in seguito) Assegnare un valore ad una variabile significa memorizzare in essa un valore Per assegnare un valore si usa il simbolo = : X = 12 Il valore può essere il risultato di un calcolo: S = A+B questo indica che in S viene memorizzato il valore risultante dal calcolo A+B Il segno = significa prende il valore di : S prende il valore del risultato di A+B Ogni volta che si assegna un valore ad una variabile, il contenuto precedente viene perso Bozza di soluzione 11 Punto e virgola 12 La sequenza di operazioni viste precedentemente diventa: scanf("%d", &A) scanf("%d", &B) S = A + B printf("%d", S) Le istruzioni devono terminare sempre con un punto e virgola: scanf("%d", &A); scanf("%d", &B); S = A + B; printf("%d", S); Mancano ancora alcune parti per essere un programma C completo

10 Definizione delle variabili Tutte le variabili utilizzate nel programma devono essere definite Le definizioni vanno collocate tutte insieme prima delle istruzioni eseguibili: int A, B, S; scanf("%d", &A); scanf("%d", &B); S = A + B; printf("%d", S); int indica che A, B ed S sono variabili adeguate a contenere valori interi 13 Il programma principale Le istruzioni che costituiscono il programma devono essere racchiuse tra parentesi graffe per formare un blocco. Se questo è l unico presente, deve essere chiamato main: main() { int A, B, S; scanf("%d", &A); scanf("%d", &B); S = A + B; printf("%d", S); } 14 Il programma principale Per far terminare il programma, si usa l istruzione return (seguita da un numero, ad es. 0) collocata nel blocco del main: main() { int A, B, S; scanf("%d", &A); scanf("%d", &B); S = A + B; printf("%d", S); return 0; } 15 I file di intestazione L utilizzo delle funzioni di I/O richiede che all inizio del file C sia indicato il nome del file di intestazione che le descrive: stdio.h #include <stdio.h> main() { int A, B, S; scanf("%d", &A); scanf("%d", &B); S = A + B; printf("%d", S); return 0; } 16

11 Compilazione Ora il programma può essere compilato ottenendo il corrispondente file eseguibile Mandando in esecuzione l eseguibile il programma chiederà due numeri e darà come risultato il valore della somma x C:\>primo.exe Attende l input (di A) 35 Attende l input (di B) Visualizza S 17 Miglioramento input Quando il programma parte, non informa l utente di essere in attesa che egli introduca due valori da sommare Bisogna far precedere ciascuna scanf da un istruzione printf che informi l utente su che cosa egli debba fare: printf("introduci 1o valore: "); scanf("%d", &A); printf("introduci 2o valore: "); scanf("%d", &B); 18 Miglioramento input Dopo i miglioramenti indicati, all esecuzione si ottiene la seguente schermata: C:\>primo.exe Introduci 1o valore: 12 Introduci 2o valore: x 19 Miglioramento output Il risultato del calcolo viene mostrato a video come semplice numero, ma è preferibile aggiungere una scritta che ne specifichi il significato Si può aggiungere tale scritta tra le virgolette della stessa printf che visualizza S: printf("la somma vale: %d", S); 20

12 Risultato finale Dopo i miglioramenti indicati, si ottiene la seguente schermata: C:\>primo.exe Primo valore: 12 Secondo valore: 23 La somma vale: 35 x 21 Soluzione finale #include <stdio.h> main() { int A, B, S; printf("primo valore? "); scanf("%d", &A); printf("secondo valore? "); scanf("%d", &B); S = A + B; printf("la somma vale: %d", S); return 0; } 22 Esercizi 1. Scrivere un programma che chieda di introdurre 3 numeri A, B e C dalla tastiera e successivamente visualizzi il risultato dei seguenti calcoli: 1) A B 2) A B+C 3) A B C 2. Scrivere un programma che chieda due numeri e li memorizzi nelle variabili A e B. Il programma deve ora scambiare il contenuto di A e di B (es. se inizialmente A contiene 12 e B 27, dopo lo scambio A contiene 27 e B 12). Suggerimento: si abbiano 2 bicchieri, uno rosso che contiene acqua e uno blu che contiene aranciata, travasando opportunamente, si vuole che quello rosso contenga aranciata e quello blu acqua. 23 Soluzione esercizio 1 1. Prima soluzione: #include <stdio.h> main() { int A, B, C, X, Y, Z; printf("primo valore? "); scanf("%d", &A); printf("secondo valore? "); scanf("%d", &B); printf("terzo valore? "); scanf("%d", &C); continua... 24

13 Soluzione esercizio 1 25 Soluzione esercizio 1 26 (Continuazione) X = A-B; printf("risultato 1 = %d\n", X); Y = A B+C; printf("risultato 2 = %d\n", Y); Z = A B-C; printf("risultato 3 = %d\n", Z); return 0; } Nota: \n serve per andare a capo La schermata risultante dall esecuzione: C:\>primo.exe Primo valore? 44 Secondo valore? 23 Terzo valore? 4 Risultato 1 = 21 Risultato 2 = 25 Risultato 3 = 17 C:\>_ x Variazioni esercizio 1 27 Variazioni esercizio 1 28 Poiché i risultati dei calcoli sono memorizzati in variabili diverse, si possono eseguire prima i 3 calcoli e poi visualizzare i 3 risultati: X = A B; Y = A B+C; Z = A B C; printf("risultato 1 = %d\n", X); printf("risultato 2 = %d\n", Y); printf("risultato 3 = %d\n", Z); In questo esempio le variabili X, Y e Z dopo essere state visualizzate non vengono più utilizzate, si può allora risparmiare memoria definendo e utilizzando solo la X (ma non si possono fare prima i 3 calcoli): X = A B; printf("risultato 1 = %d\n", X); X = A B+C; printf("risultato 2 = %d\n", X); X = A B C; printf("risultato 3 = %d\n", X);

14 Variazioni esercizio 1 29 Variazioni esercizio 1 30 Si possono utilizzare i valori intermedi: X = A B; printf("risultato 1 = %d\n", X); X = X+C; qui X vale A B printf("risultato 2 = %d\n", X); Si possono indicare i calcoli anche nella funzione printf(): printf("ris 1 = %d\n", A B); printf("ris 2 = %d\n", A B+C); printf("ris 3 = %d\n", A B C); X = X C C; qui X vale A B+C printf("risultato 3 = %d\n", X); Soluzione esercizio 2 Non si può scrivere: A = B; B = A; Eseguendo l istruzione A=B: il (vecchio) valore di A viene perso e A assume il (vecchio) valore di B Nella successiva istruzione B=A: B assume il nuovo valore di A (cioè il vecchio B); Quindi alla fine entrambe le variabili contengono il vecchio valore di B 31 Soluzione esercizio 2 Serve una variabile temporanea di scambio: Temp = A; salva il vecchio A in Temp A = B; copia in A il vecchio B B = Temp; copia in B il vecchio A salvato in Temp 1 A Temp 2 3 B 32

15 Soluzione esercizio 2 La soluzione con 2 variabili temporanee è meno efficiente (usa una variabile in più ed esegue un operazione di assegnamento in più) e non è quindi preferibile: TempA = A; TempB = B; A = TempB; B = TempA; 33

16 Storia e versioni 2 Il linguaggio C Ver Claudio Fornaro - Corso di programmazione in C Sviluppato da Dennis Ritchie ai Bell Labs nel 1972 per realizzare il sistema operativo UNIX K&R C: 1978 (prima versione, K&R dal nome degli autori del libro che lo ha divulgato: Kernighan e Ritchie) ANSI C: 1989 (alias: Standard C, C89) ISO C: 1990 (quasi identico al C89, alias: C90) C99: 1999 (Nuovo standard ISO, non ne esiste un implementazione completa) Standard ANSI Bibliografia e approfondimenti 4 In questo corso ci si attiene strettamente alla versione ANSI 1989 (ancora il più utilizzato) Queste slide coprono la maggior parte del linguaggio, salvo alcuni dettagli, per i quali si rimanda al testo di riferimento: Il linguaggio C, B. Kernighan, D. Ritchie, 2 a ed., 2004, Pearson/Prentice-Hall (2 a ed. originale: 1988) Testo sacro per generazioni di programmatori, la versione italiana è aggiornata secondo l errata corrige dagli autori per aderire allo standard ANSI 89 Il linguaggio C, B. Kernighan, D. Ritchie, 2 a ed., 2004, Pearson/Prentice-Hall ANSI C - A Lexical Guide, Mark Williams Company, 1988, Prentice Hall C-FAQ-list, S. Summit, 2004, URL: C - Corso completo di programmazione, H. Deitel, P. Deitel, 3 a ed Newsgroup: comp.lang.c

17 Caratteristiche del linguaggio Un compilatore C è disponibile su tutti sistemi Codice molto efficiente (veloce) Ha caratteristiche di alto livello: adatto per programmi anche complessi Ha caratteristiche di basso livello (accesso ai bit): permette di sfruttare le peculiarità proprie di una macchina o architettura (efficienza) Tantissime librerie per aggiungere funzionalità Tra i linguaggi più diffusi, il più usato per software di sistema Interfaccia utente testuale Non a oggetti, gestione manuale della memoria dinamica (nessun garbage collector) 5 Fasi di compilazione 1. Il preprocessore elabora le direttive #include, #define,... modificando il sorgente 2. Il compilatore traduce il codice C in linguaggio macchina: 1. con ottimizzazione (della velocità di esecuzione o della dimensione dell eseguibile) 2. senza ottimizzazione (per il debug) 3. Il linker assembla in un unico file eseguibile: i file oggetto prodotti da diversi file sorgente (anche compilati da sorgenti scritti in linguaggi diversi) le librerie (I/O, matematiche, network, etc.) 6 Errori e warning Il compilatore verifica la correttezza sintattica del codice C e produce due tipi di errori: Error: errori sintattici, impediscono la generazione del codice eseguibile Warning: errori non sintattici che non impediscono la generazione del codice eseguibile; i warning segnalano un possibile (e altamente probabile) problema che il compilatore risolve in base a regole generiche (ma attenzione: la soluzione generica potrebbe non essere quella corretta) Un codice pulito non deve produrre né errori né warning 7 La sintassi I caratteri maiuscoli sono considerati diversi dai corrispondenti minuscoli (il linguaggio C è case sensitive ) Le istruzioni sono una sequenza di caratteri terminate dal carattere ; Quando l istruzione contiene solo il carattere ; essa è detta istruzione nulla e non produce alcuna azione I commenti sono annotazioni sul codice fatte dal programmatore, iniziano con la coppia di caratteri /* e terminano con la coppia */, vengono ignorati dal compilatore che li considera come un unico carattere spazio 8

18 La sintassi Le istruzioni possono continuare su più righe Si può andare a capo in ogni punto dove si può mettere uno spazio (tranne che all interno delle stringhe) Una sequenza (anche mista) di uno o più: spazi caratteri Tab ritorni a capo commenti è considerata dal compilatore equivalente ad un unico spazio (tranne che all interno delle stringhe) 9 La sintassi Un blocco di codice è un insieme di istruzioni racchiuso tra parentesi graffe e costituito, nell ordine, da due parti: una sezione opzionale con la definizione di tutte le variabili ad uso esclusivo di quel blocco una sezione con le istruzioni eseguibili (costituita da tutte le righe di codice successive alle eventuali definizioni) Le parentesi graffe sono opzionali e normalmente omesse se il blocco di codice è costituito da una sola istruzione (salvo il blocco che costituisce il corpo di una funzione, in particolare il main) 10 La sintassi Le istruzioni di un blocco (non le eventuali parentesi graffe) si scrivono tutte indentate di un numero fisso di spazi (es. 3) { a = 12; } b = 23; c = a + b; L indentazione viene ignorata dal compilatore ma aiuta il programmatore a comprendere meglio il flusso del programma per cui va fatta mentre si programma e non dopo 11

19 Variabili 2 Tipi di dati primitivi Ver Claudio Fornaro - Corso di programmazione in C Porzioni di memoria RAM usate per mantenere dati variabili nel tempo La definizione di una variabile riserva una porzione di memoria adeguata a contenere un valore del tipo di dato indicato (intero, floating point, etc.) Definizione tipodato nomevar [, nomevar]... ; Esempi int x; float y, k, t; Variabili Tutte le definizioni delle variabili sono collocate all inizio del blocco, prima di tutte le istruzioni eseguibili Le variabili vengono create quando si entra nel blocco dove sono definite e vengono eliminate quando si esce dal blocco stesso, si dice che sono automatiche La visibilità (scope) di una variabile si estende dal punto dove viene definita fino al termine del blocco che la contiene (parentesi graffa di chiusura): scope locale Il termine dichiarazione in C ha un altro significato (e non alloca memoria) 3 Variabili I nomi delle variabili sono identificatori composti da: Lettere Cifre Il carattere underscore _ Il primo carattere deve essere una lettera Non può contenere spazi Minima lunghezza: 1 carattere Massima lunghezza: almeno 31 Meglio non usare il carattere _ come primo carattere (tipicamente usato dal compilatore) 4

20 Variabili Lettere maiuscole e minuscole sono considerate diverse (Var e var sono due variabili diverse e indipendenti) Non si possono usare nomi riservati (come int, main, include, if, for,...) Si usino nomi significativi del contenuto Sono convenzionalmente scritte in minuscolo salvo eventualmente il primo carattere o i primi caratteri di ciascuna parola somma Somma2 somma_2 SommaAlQuadrato Somma_al_quadrato 5 Tipi di dati Determinano la rappresentazione interna (complemento a 2, floating point, etc.) di un valore (variabile o costante) Tipi primitivi: Interi: char un intero su un byte, è in grado di contenere un carattere int un intero con segno, è di solito pari alla word macchina In floating point (virgola mobile): float singola precisione double doppia precisione 6 Scelta del tipo di dato Si decide sapendo che: I tipi interi sono elaborati molto più velocemente dei tipi floating point Più bit ci sono, più lento è il calcolo Più bit ci sono, maggiore è l occupazione di memoria complessiva Esempio Si vogliono memorizzare 2 milioni di valori interi compresi tra 0 e 100, soluzioni: Si usa il tipo char: si occupano 2 MB e i calcoli sono veloci Si usa il tipo double: si occupano 16 MB e i calcoli sono molto più lenti (senza alcun vantaggio rispetto al caso precedente) 7 int, short, long Il tipo int può essere preceduto da: short long Questi tipi di interi occupano rispettivamente meno e più bit rispetto al semplice int: short int x; long int y; In questi casi, int viene spesso omesso Dimensioni: short: almeno 16 bit int: almeno 16 bit e non meno di uno short long: almeno 32 bit e non meno di un int 8

21 signed, unsigned 9 signed, unsigned 10 I valori di tipo intero (char, int, short e long) possono essere memorizzati come numeri con segno (in genere in Complemento a 2) o senza segno (in binario puro) Per specificare che una variabile deve contenere un valore con o senza segno, la sua definizione deve essere preceduta dalla keyword signed o unsigned: unsigned short int z; Per i tipi int, short e long il default è signed (ossia non serve specificarlo) Per il tipo char, dipende dalla macchina e dal compilatore (e dalle sue impostazioni) se i valori sono normalmente signed o unsigned, lo standard dice solo che i caratteri stampabili hanno valori positivi I calcoli con valori interi senza segno non producono overflow (es. su 8 bit: 255+1=0) Limiti interi del compilatore I range e altre caratteristiche dei tipi interi dipendono dal compilatore L header file <limits.h> contiene la definizione di costanti numeriche relative all implementazione dei tipi di dati interi del compilatore in uso: CHAR_MAX CHAR_MIN SHRT_MAX (almeno ) SHRT_MIN (almeno 32767) INT_MAX (almeno ) INT_MIN (almeno 32767) LONG_MAX (almeno ) LONG_MIN (almeno ) 11 float, double, long double float: floating point singola precisione double: floating point in doppia precisione long double: floating point in precisione multipla Il tipo float deve avere almeno lo stesso numero di bit del tipo double Il tipo long double deve avere almeno lo stesso numero di bit del tipo double 12

22 Limiti float del compilatore 13 Limiti float tipici (IEEE P754) 14 I range e altre caratteristiche dei tipi floating point dipendono dal compilatore L header file <float.h> contiene la definizione di costanti numeriche relative all implementazione dei tipi di dati floating point del compilatore in uso: FLT_MAX (almeno 1E+37) FLT_MIN (almeno 1E 37) FLT_EPSILON (almeno 1E 5) è il minimo valore float x tale che x 1.0 DBL_MAX DBL_MIN DBL_EPSILON float occupazione: 32 bit di memoria minimo: (circa) massimo: (circa) precisione: circa 7 cifre (in base 10) (ad esempio ) double occupazione: 64 bit di memoria minimo: (circa) e massimo: (circa) precisione: circa 15 cifre (in base 10) (ad esempio ) Costanti intere decimali 15 Costanti intere ottali ed hex 16 Sequenza di cifre decimali eventualmente preceduta da + o E di tipo int se è nel range degli int E di tipo long se esce dal range degli int ma è in quello dei long E un errore se esce dal range dei long E di tipo long se è seguita dalla lettera L E unsigned se è seguita dalla lettera U 123 int long int (es. se int su 16 bit) 123L long int 123UL unsigned long int Una costante intera è espressa in base 8 se è preceduta da uno ATTENZIONE: vale (dodici!) Una costante intera è espressa in base 16 se è preceduta da 0x oppure da 0X 0X2C vale Possono essere seguite da: Il carattere L o l per renderle long Il carattere U o u per renderle unsigned 014ul 12 unsigned long int 0xbabau unsigned long int 0xblu 11 unsigned long int

23 Costanti floating point Contengono un punto (non la virgola) decimale e/o un esponente intero positivo o negativo L esponente è preceduto da una e o E che ha il significato di per 10 elevato a : 4e Sono di tipo double salvo indicazione diversa: sono di tipo float se sono seguite da F o f sono di tipo long double se seguite da L o l e5 12.3e+5 65e F 14.2e-3F 43e+4F 43e4F 56.3L e-5L 87e8L 87e+8L Attenzione! 12F (errore) 12L (è un long int) 17 Inizializzazione delle variabili La definizione di una variabile (tranne le variabili di classe static ed extern) non le assegna un valore iniziale (contenuto casuale) L assegnazione esplicita del valore iniziale alle variabili contestualmente alla definizione è detta inizializzazione I valori usati per inizializzare possono anche essere espressioni (si riveda dopo aver visto le espressioni e le funzioni) che coinvolgono variabili già inizializzate Esempi int x = 12, y = 27*3*x; double pigreco = 4.0*atan(1.0); 18 const Il modificatore const nella definizione delle variabili informa il compilatore che queste non dovranno essere modificate Il valore della variabile const deve essere specificato nell inizializzazione, anche mediante il risultato di un espressione const double Pi = ; const double Pi = 4.0*atan(1.0); const può essere messo prima o dopo il nome di tipo a cui si riferisce. Nei casi dubbi si metta dopo. Le seguenti definizioni sono equivalenti: const int x = 12; int const x = 12; 19 Costanti enumerative Lista di identificatori aventi valori costanti di tipo int Sintassi: enum [nome] {cost1, cost2,...}; I nomi delle costanti vengono in genere scritti in maiuscolo Al primo identificatore viene assegnato 0, al secondo identificatore 1, e così via enum boolean {FALSE, TRUE}; definisce le due costanti FALSE (con valore 0) e TRUE (con valore 1) 20

24 Costanti enumerative I singoli valori possono essere inizializzati, i successivi proseguono la numerazione enum mesi {GEN=1, FEB, MAR, etc.}; enum lettere {A=1, B, C=9, D, etc.}; quindi qui B vale 2 e D vale 10 I nomi delle costanti devono essere unici, anche in enum diverse, è un errore scrivere: enum unodue {UNO, DUE}; enum unotre {UNO, TRE}; I valori delle costanti possono ripetersi anche nella stessa numerazione enum a {A=1, B=1, C=2, D=2, etc.}; 21 Costanti enumerative Si possono definire variabili del tipo enumerativo, ma il compilatore non è tenuto a verificare i valori nelle assegnazioni enum bool {FALSE, TRUE} pippo; pippo = 12; Il nome dell enumerazione può essere omesso enum {FALSE, TRUE}; 22 Costanti simboliche Sono sostituzioni di simboli (identificatori) #define nome sequenza_di_caratteri #define TRUE 1 #define FALSE 0 Prima della compilazione, il preprocessore sostituisce ogni nome (per convenzione scritto in maiuscolo) con la corrispondente sequenza_di_caratteri La sostituzione inizia dove è indicata la #define e continua fino a fine file Non ci vuole il carattere = né il ; finale La sequenza_di_caratteri può contenere spazi e termina a fine riga 23 define, const ed enum I valori enum e const riservano memoria come le variabili e vengono utilizzati dal debugger, una #define no I valori enum sono solo int I valori enum sono inizializzati automaticamente La visibilità dei valori enum e const è confinata alla funzione dove sono definiti 24

25 Funzioni di I/O per numeri Per utilizzare le funzioni di Input/Output bisogna includere l header file <stdio.h> che ne contiene la descrizione (in modo che il compilatore possa controllare che l utilizzo della funzione sia corretto) <stdio.h> contiene anche la definizione di alcune costanti simboliche come quella usata da alcune funzioni per segnalare la fine del file: EOF (End Of File) 25 Stream preesistenti Quando il programma viene mandato in esecuzione, il sistema operativo gli fornisce 3 stream (flussi di dati) già aperti: stdin collegato alla tastiera stdout collegato al video per l output normale (i dati risultato dell elaborazione) stderr collegato al video per la diagnostica (informazioni relative al funzionamento del programma, ad esempio per segnalare errori, non sono i dati risultato dell elaborazione) 26 Input formattato scanf Funzione per leggere dati dalla tastiera scanf(stringa di formato, lista di variabili) La lista di variabili è un elenco di variabili da assegnare con i dati letti dalla tastiera assegnate nell ordine in cui sono elencate separate da virgole precedute (ciascuna) dal carattere & (per ricavarne il puntatore) La stringa di formato contiene un elenco di specifiche di conversione specificano il tipo di dato atteso per ciascuna delle variabili della lista di variabili sono lettere precedute da un carattere % 27 Input formattato scanf Esempi scanf("%d", &a); legge 1 valore di tipo int (come richiesto dalla specifica %d) e lo assegna alla var a (di tipo int) scanf("%d%d%d", &a, &b, &c); legge 3 valori di tipo int (come richiesto dalla specifica %d) e li assegna ad a, b e c Quest ultima equivale a: scanf("%d", &a); scanf("%d", &b); scanf("%d", &c); Negli ultimi 2 casi, i 3 valori in input possono essere immessi dall utente o di seguito o andando a capo ogni volta 28

26 Input formattato scanf La stringa di formato può contenere al suo interno combinazioni di: Specifiche di conversione: %carattere, es. %d Caratteri ordinari: indicano che a quel punto ci si aspetta l introduzione di proprio quei caratteri in quella sequenza, se non ci sono la scanf termina. I caratteri vengono semplicemente scartati (per includere il carattere %, questo deve essere raddoppiato: %% altrimenti ha il significato di specifica di conversione): "%d%%" legge un intero e scarta il simbolo % White space (ossia spazi, Tab e \n ): richiedono che tutti i white space (di qualunque tipo) consecutivi in quel punto dell input vengano saltati 29 Input formattato scanf Tipicamente la stringa di formato contiene solo specifiche di conversione, senza altri caratteri in mezzo (neppure spazi): "%d%d%d" Errore comune è pensare che indicando il carattere \n in una stringa di formato si richieda alla scanf di aspettasi in quel momento l introduzione di un semplice ritorno a capo e di scartarlo La scanf può essere fonte di problemi se l input non è conforme a quanto richiesto dalla stringa di formato, per questo molti consigliano di usare una gets seguita da una sscanf (trattate più avanti) 30 Input formattato scanf Principali specifiche di conversione per valori numerici: %d int %u unsigned int %x unsigned int in esadecimale %f, %e, %g float (equivalenti) Per gli interi, dopo il % : l indica un long es. %ld h indica uno short es. %hd Per i floating point, dopo il % : l indica un double es. %lf L indica un long double es. %Lf 31 Input formattato scanf La funzione scanf restituisce un valore di tipo int che può essere: un numero 0: indica quante variabili della lista sono assegnate con valori prelevati dalla tastiera il numero rappresentato dalla costante EOF: segnala che si è verificato un errore o che in input non c è nulla da leggere Per indicare alla scanf (e alle altre funzioni di input) che l inserimento dei dati è terminato, si può inserire da tastiera il carattere di Fine File (che non è il valore di EOF) premendo i tasti: Control-Z su sistemi DOS/Windows Control-D su sistemi Unix/Linux 32

27 Input formattato scanf Esempio n = scanf("%d%d", &a, &b); se n vale 2: la scanf ha letto 2 valori e li ha assegnati ad a e b se n vale 1: la scanf ha letto un solo valore (assegnato ad a), mentre b non è stata modificata (ha il vecchio valore) se n vale 0: nessun valore è stato letto, né a né b sono stati modificati, ma ci sono altri caratteri pronti per essere letti dalla prossima istruzione di input se n vale EOF: nessun valore è stato letto, né a né b sono stati modificati e non ci sono altri caratteri pronti per essere letti dalla prossima istruzione di input 33 Input formattato scanf La scanf legge da tastiera dei caratteri e in base alla specifica di conversione % li trasforma in valori del tipo appropriato per essere memorizzati nelle corrispond. variabili Ad esempio, con la specifica %d la scanf legge i caratteri 1 e 2 e li trasforma nel numero 12 (int) Il gruppo di caratteri letti di volta in volta dalla scanf per essere trasformati in un unico valore viene detto campo (field ) o token I campi numerici non contengono spazi La scanf salta i white space iniziali del campo che legge (è per questo che i valori possono essere immessi su una o più righe) 34 Input formattato scanf Il numero di caratteri utilizzati per costituire un campo dipende dalla specifica di conversione %: viene prelevato il massimo numero di caratteri compatibili con il tipo richiesto dalla specifica Ad esempio, con la specifica %d la scanf legge tutti i caratteri cifra che trova, fino al primo carattere non-cifra (ad esempio uno spazio) i caratteri non letti sono utilizzati dalle successive input) 35 Input formattato scanf Esempi Se in input (da tastiera) la funzione n=scanf("%d", &a) legge i caratteri: 12 questi producono per a = 12, n = a = 12, n = 1, i caratteri 23 restano a disposizione della successiva operazione di input 12abc a = 12, n = 1, i caratteri abc restano a disposizione della successiva operazione di input abc non producono nulla per a (che mantiene il vecchio valore), n = 0, i caratteri abc restano a disposizione della successiva operazione di input abc12 non producono nulla per a (mantiene il vecchio valore), n = 0, i caratteri abc12 restano a disposizione della successiva operazione di input 36

28 Input formattato scanf 37 Input formattato scanf 38 Una specifica contenente un * (tra il % e la lettera) indica che il corrispondente campo deve essere letto, ma il valore corrispondente non deve essere assegnato ad alcuna variabile (soppressione dell input) Esempio n = scanf("%d%*d%d", &a, &b); Se si dà in input: si ottiene: a=12, b=34, n=2 mentre il valore 23 viene letto e scartato Una specifica contenente un numero intero (tra il % e la lettera) specifica la quantità massima di caratteri da leggere per identificare il campo corrispondente (e trasformarli in numero): se il numero di caratteri in input che possono fare parte del campo è inferiore alla quantità massima specificata, vengono letti solo quei caratteri altrimenti si considerano solo tanti caratteri quanti sono quelli indicati dalla quantità massima specificata (gli altri restano per le letture successive) Input formattato scanf 39 Output formattato printf 40 Esempi La specifica %4d indica che il campo da leggere per essere convertito in int può essere costituito da al massimo 4 caratteri Se in input (da tastiera) la funzione scanf("%4d%d",&a,&b) legge i caratteri: a=12, b=23 (lo spazio delimita il primo campo prima di arrivare al 4 o carattere) a=1234, b=56 Funzione per scrivere dati sul terminale printf(stringa di formato, lista di espressioni) La stringa di formato contiene: caratteri ordinari, vengono visualizzati (per visualizzare il carattere %, lo si deve indicare raddoppiato: %%) specifiche di conversione La lista di espressioni è l elenco delle espressioni di cui visualizzare il risultato ciascun risultato viene visualizzato al posto della corrispondente specifica di conversione (devono corrispondere per numero e tipo) sono separate da virgole printf("somma = %d\n", a);

29 Output formattato printf 41 Output formattato printf 42 Restituisce un valore int che può essere: se 0: il numero di caratteri scritti a video se < 0: la segnalazione di un errore n = printf("somma = %d", a); Con a=5, n vale 9 Le sequenze di escape sono caratteri ordinari: \n inserisce un ritorno a capo (si mette in fondo alla stringa di formato, di solito) \t inserisce un carattere Tab \" inserisce un carattere doppie virgolette " \ inserisce un carattere apice \\ inserisce un carattere backslash \ Specifiche di conversione per valori numerici %d int %u unsigned int %x unsigned int in esadecimale %f, %e, %g double (e float) in formato decimale, esponenziale o misto Per gli interi, dopo il % : l indica un long es. %ld h indica uno short es. %hd Per i floating point, dopo il % : L indica un long double es. %Lf Output formattato printf Tra il carattere % e la specifica di conversione possono esserci, nell ordine: Opzioni di allineamento (es. un allinea a sinistra) Un numero che specifica l ampiezza minima del campo (in caratteri) Un punto se si vuole specificare la precisione Un numero che specifica la precisione: per i floating point: numero di cifre da visualizzare dopo il punto decimale (default: 6), i valori comunque hanno il numero di cifre proprio del tipo a cui appartengono, es. i double 14 per gli interi: numero minimo di caratteri da visualizzare Un modificatore di tipo (h,l,l) 43 Output formattato printf Esempi %8d int su almeno 8 caratteri n=printf("x%8dx", 12); visualizza X 12X, n vale 10 %12f float/double su almeno 12 char n=printf("x%12fx", 12.4); visualizza X X, n vale 14 %.2f float/double con 2 caratteri decimali n=printf("x%.2fx", 12.4); visualizza X12.40X, n vale 7 %8.2f float/double su almeno 8 caratteri e con 2 caratteri dopo la virgola n=printf("x%8.2fx", 12.4); visualizza X 12.40X, n vale 10 n=printf("x%8.2fx", ); visualizza X X, n vale 11 44

30 Esercizi Scrivere un programma che chieda 4 numeri double e ne calcoli la media (double) con 2 decimali. 2. Scrivere un programma che chieda un valore double e lo visualizzi con le 3 specifiche di conversione %f, %e e %g.

31 2 Espressioni numeriche Espressioni e funzioni matematiche Ver Claudio Fornaro - Corso di programmazione in C 1 Composte da operatori, variabili, costanti e funzioni, producono un valore Delta = b*b-4*a*c; Gli operatori matematici sono: + somma - sottrazione * moltiplicazione / divisione % resto della divisione intera La divisione tra interi produce risultato intero con troncamento della parte frazionaria Il resto può essere calcolato solo con operandi di tipo intero (non floating point) Precedenza degli operatori 3 Associatività degli operatori 4 Specifica in quale ordine vengono eseguiti i calcoli Raggruppati in livelli di priorità decrescente: 1. + segno 2. () 3. * / % 4. + somma e sottrazione Esempi x = a+b*c; prima la moltiplicazione x = (a+b)*c; prima la somma x = a + -b; a + (-b) Specifica in quale ordine vengono eseguiti i calcoli contenenti operatori con lo stesso livello di precedenza Per gli operatori matematici l associatività è sempre da sinistra a destra x=a+b+c; x=(a+b)+c; x=a+b-c+d; x=(((a+b)-c)+d); x=a*b/c; x=(a*b)/c; x=a+b+c*d; x=((a+b)+(c*d));

32 Espressioni numeriche Operandi dello stesso tipo 5 Espressioni numeriche Operandi dello stesso tipo 6 Le operazioni matematiche possono essere eseguite solo tra due operandi dello stesso tipo (int con int, long con long, float con float, etc.) I risultati intermedi dei calcoli vengono memorizzati in variabili temporanee (senza nome) dello stesso tipo degli operandi Le variabili temporanee vengono rimosse automaticamente dalla memoria dopo essere state utilizzate X = A + B * C α β α e β sono le variabili temporanee Espressioni numeriche Operandi di tipo diverso 7 Espressioni numeriche Operandi di tipo diverso 8 Le operazioni tra operandi di tipo diverso non possono essere calcolate direttamente Tra i due operandi, uno ha un tipo più capiente dell altro (ad es. il tipo double può contenere qualsiasi valore di tipo float, long, int, etc.) Il valore del tipo meno capiente può essere convertito nell altro tipo senza perdita di dati Questa conversione si chiama promozione e viene effettuata automaticamente sull operando del tipo meno capiente La promozione crea una variabile temporanea del tipo più capiente contenente il valore promosso (es. un 3 di tipo int viene convertito in un 3.0 di tipo double) La promozione avviene solo nel momento in cui è necessaria per proseguire il calcolo

33 Espressioni numeriche Operandi di tipo diverso 9 Espressioni numeriche Operandi di tipo diverso 10 X = A d + B d * C i Promozione X = A d + B i * C i α d α i Promozione β d β d γ d La lettera d indica il tipo double, i il tipo int γ d Il calcolo tra B e C non richiede alcuna promozione, a differenza di quello tra A ed α Espressioni numeriche Operandi di tipo diverso 11 Espressioni numeriche Operandi di tipo diverso 12 Attenzione Supponiamo gli int siano su 16 bit (max 32767) e i long su 32 bit (max 2 miliardi) Il codice seguente dà overflow nel prodotto. int a, b; long c; a = 30000; b = 2; c = a * b; Perché? Risposta: perché la variabile intermedia α è di tipo int e un int su 16 bit non può contenere Per risolvere il problema si deve far in modo di avere una variabile intermedia di tipo long: definendo almeno una delle variabili di tipo long oppure richiedendo esplicitamente la promozione a long di almeno una delle variabili La seconda soluzione è migliore: non richiede di cambiare il tipo delle variabili (è il risultato a non essere rappresentabile, non gli operandi)

34 Conversione di tipo - cast 13 Promozioni integrali 14 L operatore di cast produce una variabile temporanea del tipo richiesto e contenente il valore originale convertito E simile ad una promozione, ma mentre la promozione è automatica, il cast è una richiesta esplicita di conversione di tipo Ha priorità maggiore degli operatori matem. Sintassi: (tipo)espressione Esempio (e soluzioni dell overflow in c=a*b): c = (long)a * b; cast applicato ad a c = a * (long)b; cast applicato ad b c = (long)a * (long)b; c = (long)(a*b); INUTILE! Nelle espressioni matematiche, i valori dei tipi char e short int, i campi di bit e gli elementi delle enum vengono automaticamente convertiti: in int se questo tipo può rappresentare i valori originali in unsigned int altrimenti Conversioni nelle assegnazioni Quando un valore di un tipo viene assegnato ad una variabile di un altro tipo, vi è una conversione automatica del valore al tipo della variabile assegnata Se la variabile assegnata ha il tipo più capiente dei due, non ci sono problemi Altrimenti ci possono essere problemi (ci sono meno bit) per cui il compilatore attua la conversione ma in genere (non tutti) segnala il problema con un warning Se l assegnazione è comunque voluta, per eliminare l eventuale warning si usi un cast 15 Conversioni nelle assegnazioni La conversione di un valore floating-point ad un intero avviene con troncamento della parte frazionaria Esempi int a, b=12; long l= ; double e, f=3.0, g=9.275; a = f; warning (a vale 3) a = (int)f; No warning (a vale 3) a = (int)g; No warning (a vale 9) e = b; No warning (e vale 12.0) a = l; warning (a vale???) a = (int)l; No warning (a vale???) 16

35 Operatori ++ e 17 Operatori ++ e 18 Incrementano/decrementano di 1 una variabile: ++a; ++a incrementa a di 1 prima che a venga utilizzata nel calcolo (incremento prefisso) a++ incrementa a di 1 dopo che a è stata utilizzata nel calcolo (incremento postfisso) a e a decrementano a di 1 Priorità maggiore degli operatori matematici Utilizzabili solo con modifiable L-value (ossia un qualcosa che si può mettere a sinistra di un segno di assegnazione): non si possono applicare al risultato di un calcolo (a+1)++; ERRORE Esempi: a = 5; x = ++a; ora a vale 6 e x vale 6 a = 5; x = a++; ora a vale 6 e x vale 5 Attenzione Le variabili usate con un operatore ++ o non possono apparire più di una volta nella stessa espressione (fino al ; finale) x = i * i++; Errore Operatori di assegnamento 19 Operatori di assegnamento 20 In C l assegnazione è un espressione e dunque produce un risultato: il valore dell espressione a destra del segno = Questo e l avere associatività da destra a sinistra permette di assegnare lo stesso valore a più variabili con la scrittura seguente: a = b = c = 2; che equivale a: a = (b = (c = 2)); La forma di assegnamento: variabile op= espressione essendo op un operatore del C, equivale a: variabile = variabile op espressione x += 5; equivale a x = x + 5; Esiste per tutti gli operatori aritmetici e bitwise: += = *= /= %= &= ^= = <<= >>= Dopo aver visto i vettori: vett[y%(x+2)] += 5; in questo esempio è evidente il vantaggio (di digitazione e di computazione) di non dover scrivere due volte la quantità da incrementare

36 Funzioni matematiche Sono contenute in una libreria esterna al compilatore, collegata all eseguibile dal linker Richiedono che venga indicato il file di intestazione (header file) <math.h> che le descrive Richiedono valori double e producono risultati double Le funzioni trigonometriche usano i radianti (180 o = π rad) 21 Funzioni matematiche sin(x) seno cos(x) coseno tan(x) tangente asin(x) arcoseno acos(x) arcocoseno atan(x) arcotangente atan2(y,x) arcotangente di y/x, migliore della precedente per x piccoli exp(x) esponenziale e x log(x) logaritmo naturale log10(x) logaritmo in base Funzioni matematiche pow(x,y) x y (se y<0, x deve essere intero) sqrt(x) radice quadrata (x 0) fabs(x) valore assoluto ceil(x) minimo intero x floor(x) massimo intero x 23 Funzioni matematiche Alcune altre funzioni matematiche sono descritte nell header file <stdlib.h>: abs(x) calcola il valore assoluto di un valore int e produce un risultato di tipo int labs(x) calcola il valore assoluto di un valore long e produce un risultato di tipo long 24

37 Funzioni matematiche 25 Funzioni matematiche 26 Come arrotondare un numero all intero più vicino? Non esiste alcuna funzione della libreria standard che lo faccia, per valori positivi si può ad esempio usare una delle seguenti espressioni (non sono statisticamente corrette ma in genere sono sufficienti allo scopo): (int)(valore+0.5) per valori x.5 arrotonda sempre all intero successivo (int)(valore+(0.5-dbl_epsilon)) per valori x.5 arrotonda sempre all intero precedente Per valori negativi bisogna invece usare le seguenti variazioni: (int)(valore-0.5) (int)(valore-(0.5-dbl_epsilon)) Una soluzione statisticamente corretta richiede che i valori x.5 siano approssimati: per eccesso (all intero successivo) se x è dispari per difetto (all intero precedente) se x è pari Valori casuali 27 Valori casuali 28 La funzione rand ogni volta che viene chiamata produce un diverso valore intero compreso tra 0 e RAND_MAX (estremi inclusi) con distribuzione uniforme x=rand(); RAND_MAX vale almeno (il valore effettivo dipende dal compilatore) Le funzioni e le definizioni sono contenute in <stdlib.h> Per rendere effettivamente casuali i valori generati, all inizio del programma si aggiunga (una sola volta) l istruzione: srand(time(null)); (bisogna includere <time.h>) Per avere un valore intero tra 0 e N: x = rand() % N; In alcuni sistemi il generatore di numeri pseudocasuali produce valori la cui parte bassa ha distribuzione poco uniforme, per cui può essere preferibile la formula : x = rand() / (RAND_MAX / N + 1); Per un esempio di funzione casuale con distribuzione gaussiana si veda la citata FAQ di Steve Summit (13.20)

38 Esercizi 1. Scrivere un programma che chieda 4 numeri int, ne calcoli la media, la memorizzi in una variabile float e la visualizzi con 2 decimali. 2. Scrivere un programma che chieda un valore double di temperatura in gradi Fahrenheit e calcoli i valori delle corrispondenti temperature in gradi Celsius e Kelvin (entrambi con parte frazionaria). 5 C = ( F 32) 9 K = C Esercizi 3. Un oggetto che si muove ad una velocità v confrontabile con quella della luce c ( m/s) si accorcia (nel senso della direzione) e aumenta di massa di un fattore γ (minore di 1) pari a: v 1 c Si scriva un programma che chieda la lunghezza e la massa di un oggetto fermo e calcoli la variazione delle due grandezze a quella velocità (richiesta in input in km/sec) Esercizi 4. Si scriva un programma per calcolare la distanza in linea d aria tra due punti della superficie terrestre, note le coordinate geografiche. Il programma chiede i valori di latitudine (N-S) e di longitudine (E-O) in gradi dei due punti. Per calcolare la distanza si usi la seguente formula (le coordinate Nord e Est sono positive, Sud e Ovest negative). Si ricordi che le funzioni trigonometriche utilizzano i radianti. distanza = arccos( p1+p2+p3 ) * r 31 Esercizi (Continuazione) dove: r è il raggio medio della Terra: km p1 = cos(lat1 )*cos(lon1 )*cos(lat2 )*cos(lon2 ) p2 = cos(lat1 )*sin(lon1 )*cos(lat2 )*sin(lon2 ) p3 = sin(lat1 )*sin(lat2 ) essendo: lat1 lon1 lat2 lon2 latitudine in gradi del primo punto longitudine in gradi del primo punto latitudine in gradi del secondo punto longitudine in gradi del secondo punto N.B. La formula considera la terra sferica con raggio medio r : non essendolo, la formula dà un risultato con un errore massimo dello 0.5%. 32

39 Esercizi (Continuazione) Per la formula del arco-coseno non si utilizzi la funzione di libreria acos, ma la seguente: x π arccos( x) = arctan x 2 Calcolare le distanze tra gli aeroporti di: Torino (TRN, o N, o E) Roma (FCO, o N, o E) Los Angeles (LAX, o N, o W) Nota: π = 4 tan -1 (1) [Risposte: TRN-FCO: km, TRN-LAX: km, FCO-LAX: km] 33

40 Esecuzione condizionale Esecuzione condizionale Permette l esecuzione di un blocco di codice solo se si verifica una certa condizione SE (condizione è vera) ALLORA esegui istruzioni 2 Ver. 2.2 Falso condizione Vero istruzioni Ramo else Ramo then Claudio Fornaro - Corso di programmazione in C Costrutto if Sintassi (minimale): if (condizione) blocco istruzioni Parentesi necessarie prima e dopo condizione Non richiede il ; alla fine Il blocco istruzioni è racchiuso da parentesi graffe, queste sono opzionali se è composto da una sola istruzione (si indenti il codice del blocco) Come dopo tutte le parole chiave, dopo if generalmente si preferisce mettere uno spazio 3 Costrutto if Esempio Programma che chiede un numero e se è positivo scrive positivo altrimenti nulla. scanf("%d", &n); if (n > 0) { printf("positivo\n"); } F V printf("fine\n"); n>0 Nota: la seconda printf positivo non è dentro il blocco e quindi viene eseguita comunque 4

41 5 6 Costrutto if-else Costrutto if-else La clausola else ( altrimenti ) identifica il blocco da eseguire nel caso la condizione NON sia vera (non richiede il ; alla fine) Esempio Programma che chiede un numero, se positivo scrive positivo altrimenti non positivo scanf("%d", &n); if (n > 0) printf("positivo\n"); else printf("non positivo\n"); printf("fine\n"); Nota: l ultima printf non è dentro il blocco e quindi viene eseguita comunque Il flow-chart corrispondente è: Falso n>0 Vero non positivo FINE positivo 7 8 Selezione a più rami Selezione a più rami È richiesta quando si hanno più di due casi che necessitano di elaborazioni diverse Esempio Programma che chiede un numero, se positivo scrive positivo, se negativo negativo, se zero nullo Soluzione 1 Si possono avere più costrutti if completi in sequenza, ossia uno di seguito all altro Soluzione 1 - codice scanf("%d", &n); if (n > 0) printf("positivo\n"); if (n < 0) printf("negativo\n"); if (n == 0) printf("nullo\n"); printf("fine\n"); Inefficiente perché le condizioni vengono SEMPRE valutate TUTTE, anche quando non è necessario (in questo esempio quando n > 0) F F F V n>0 positivo V n<0 negativo V n=0 nullo FINE

42 Selezione a più rami 9 Selezione a più rami 10 Soluzione 2 Si utilizzano più costrutti if annidati, ossia uno completamente dentro un ramo dell altro (in questo esempio il blocco del ramo else contiene un altro costrutto if completo) Efficiente perché le condizioni NON vengono SEMPRE valutate TUTTE Soluzione 2 Flow-chart F n>0 F n<0 V nullo negativo V positivo FINE Selezione a più rami Soluzione 2 codice (1 a forma) scanf("%d", &n); if (n > 0) printf("positivo\n"); else if (n < 0) printf("negativo\n"); else printf("nullo\n"); printf("fine\n"); Quando n > 0 non valuta l altro controllo 11 Costrutto if interno Selezione a più rami Soluzione 2 codice (2 a forma, compatta) scanf("%d", &n); if (n > 0) printf("positivo\n"); else if (n < 0) printf("negativo\n"); else printf("nullo\n"); printf("fine\n"); Nota: le keyword sono allineate, i blocchi sono allineati. Forma PREFERIBILE 12

43 Corrispondenza dell else Ogni else si riferisce sempre all ultimo if Se è necessaria una corrispondenza diversa, si usino le graffe per isolare il blocco interno: if (condizione1) { if (condizione2) blocco_then_if2; } else blocco_else_if1; Senza le graffe precedenti, l else sarebbe associato all if interno e costituirebbe il blocco_else_if2 13 Espressioni relazionali Confrontano due valori I valori possono essere espressioni Se il risultato è vero danno risultato 1 (non un generico valore diverso da 0), se falso danno 0 Operatori: == uguale!= diverso < minore <= minore o uguale > maggiore >= maggiore o uguale 14 Espressioni relazionali 15 Valori logici 16 Hanno priorità inferiore alle operazioni aritmetiche if (a-5 >= 12*x)... prima calcola le espressioni a-5 e 12*x poi ne fa il confronto Hanno priorità maggiore degli operatori di assegnamento int x; x = 5 > 2; prima fa il confronto tra 5 e 2 poi ne assegna il risultato (1) a x Il valore 0 equivale a falso Ogni valore 0 equivale a vero Quindi nelle espressioni relazionali scrivere!=0 è opzionale: if (trovato)... equivale a if (trovato!= 0)... Conviene indicare il!=0 quando questo facilita la comprensione del programma (ci pensa eventualmente il compilatore a rimuoverlo per migliorare l efficienza)

44 Operatori logici Combinano valori logici (variabili o risultati di espressioni relazionali) Producono risultato 1 (non un generico valore diverso da 0) se il risultato è vero, 0 se falso Operatori (in ordine di priorità decrescente):! NOT, ha priorità superiore agli operatori relazionali e aritmetici if (!(a>b) )... && AND, ha priorità inferiore agli operatori relazionali e aritmetici if ( a>b && c!=0 )... OR, ha priorità inferiore agli operatori relazionali e aritmetici if ( a>b c!=0 ) Espressioni logiche Si possono usare le parentesi per cambiare l ordine di valutazione delle operazioni Esempi if ( a>b c<d && a!=0 )... equivale a: if ( a>b (c<d && a!=0) )... if (trovato == 0)... equivale a: if (!(trovato!= 0) )... e quindi a: if (!trovato)... Attenzione: è errato scrivere una condizione come a < b < c, occorre spezzarla come a<b && b<c 18 Valutazione minima di && e Gli operatori && e vengono valutati sempre da sinistra a destra La valutazione delle espressioni termina non appena è possibile stabilire se la condizione è complessivamente vera o falsa: if (cond1 && cond2 && cond3 &&...)... Se cond1 è falsa (0), non si valutano le cond x successive e complessivamente la condizione dà 0 (falso) if (cond1 cond2 cond3...)... Se cond1 è vera (!=0), non si valutano le cond x successive e complessivamente la condizione dà 1 (vero) 19 Valutazione minima di && e Gli operatori && e inseriscono un sequence poin tra le espressioni che collegano Questo significa ad esempio che gli effetti collaterali degli operatori ++ e vengono portati a termine prima della valutazione della condizione successiva 20

45 Selezione multipla Permette di eseguire blocchi diversi a seconda del risultato di un unica espressione Si può sempre utilizzare una serie di costrutti if annidati (o in cascata) Se i risultati da considerare sono valori costanti noti a priori, si può utilizzare il costrutto switch 21 Selezione multipla Sintassi switch(espressione) { case risultato 1 : blocco 1 case risultato 2 : blocco 2... default: blocco default } L ordine di valutazione dei risultato x è ininfluente e indefinito Il ramo default è opzionale e non deve necessariamente essere l ultimo dell elenco switch non deve essere terminato da ; 22 Selezione multipla Dove: espressione è un espressione che produce un risultato intero (char, short, int, long) risultato x sono valori interi costanti (numeri, #define, enum, non valori const) blocco x sono blocchi di codice (le parentesi graffe per i blocchi non sono indispensabili) Viene calcolata espressione e il valore risultante cercato tra i risultato x, quindi: se viene trovato, il blocco x corrispondente viene eseguito altrimenti, se esiste il blocco default, questo viene eseguito altrimenti nessun blocco viene eseguito 23 Selezione multipla Quando si entra ad eseguire un blocco, vengono eseguiti in cascata (fall-through) anche tutti i blocchi successivi Per uscire dal blocco e dallo switch si utilizza l istruzione break E buona norma aggiungere l istruzione break anche nell ultimo blocco, in modo che se si aggiungono successivamente altri rami questi siano già terminati correttamente Dopo aver visto i cicli: L istruzione break in un blocco switch collocato dentro un ciclo, fa uscire soltanto dallo switch e NON anche dal ciclo 24

46 Selezione multipla Esempio (corretto) switch (cifra) { case 0: Cont0++; break; case 1: Cont1++; break; case 2: Cont2++; break;... default: Altro++; break; } 25 Selezione multipla Esempio (scorretto!) switch (cifra) { case 0: Cont0++; case 1: Cont1++; case 2: Cont2++;... default: Altro++; } Se ad esempio c contiene il valore 1, vengono incrementati TUTTI i contatori, incluso Altro ed escluso Cont0 26 Selezione multipla L esecuzione in cascata permette di associare più risultato x allo stesso blocco x switch (cifra) { case 0: blocco vuoto case 1: blocco vuoto case 2: blocco vuoto case 3: Cifre03++; break; default: Cifre49++; break; } 27 Esercizi 1. Scrivere un programma che chieda due numeri da tastiera e dei due visualizzi il maggiore. 2. Scrivere un programma che chieda un numero da tastiera e stampi sul video se è pari o dispari (consiglio: calcolare il resto). 3. Scrivere un programma che chieda tre numeri da tastiera e dei tre stampi il maggiore. 4. Scrivere un programma che chiede tre numeri da tastiera e li stampi in ordine decrescente. 28

47 Esercizi 5. Si vogliono dividere gli allievi di un corso in tre squadre denominate ROSSA, VERDE e BLU secondo il loro numero di matricola. L assegnazione avviene con il seguente criterio: l allievo con matricola 1 va nella squadra ROSSA, quello con matricola 2 nella VERDE, quello con matricola 3 nella BLU, quello con matricola 4 nella ROSSA, quello con 5 nella VERDE ecc. Il programma deve chiedere il numero di matricola dell allievo e indicare a quale squadra è assegnato. Usare il costrutto if. 6. Come il precedente, utilizzare uno switch. 29 Esercizi 7. Scrivere un programma che chieda da tastiera di introdurre un numero intero corrispondente ad un voto e stampi a video Insufficiente se è inferiore a 18, Appena sufficiente (18), Basso (19-20), Medio (21-23), Buono (24-26), Alto (27-29), Massimo (30) Impossibile (tutti gli altri) Usare il costrutto if. 8. Come il precedente, utilizzare uno switch. 30 Esercizi 9. Si scriva un programma che chieda i tre coefficienti a, b e c di un equazione di secondo grado e calcoli i valori delle soluzioni se questi sono reali; nel caso non lo siano deve semplicemente scrivere a video Valori non reali. 10. Un anno secolare (divisibile per 100) è bisestile se è divisibile per 400, un anno non secolare è bisestile se è divisibile per 4. Ad esempio l anno 1900 non era bisestile, il 1996 era bisestile, il 2000 lo era, il 2002 non lo era. Si scriva un programma che chieda all utente di introdurre l anno e indichi se è bisestile. 31

48 Strutture iterative 2 Strutture iterative Ver. 2.2 Problema: Visualizzare i numeri interi da 0 a 1000 Soluzione printf("0\n"); printf("1\n"); printf("2\n"); printf("3\n"); printf("4\n");... Non è davvero una buona idea ma con le conoscenze attuali non c è alternativa Claudio Fornaro - Corso di programmazione in C Strutture iterative 3 I cicli in C 4 Vorremmo scrivere: Esegui l istruzione: printf("%d\n", i); con i che assume i valori da 0 a 1000 percorso chiuso, topologicamente detto anello, loop o ciclo V i=0 stampa i i=i+1 i<=1000 F Per tornare indietro si potrebbe utilizzare un istruzione apposita, ma per questioni di chiarezza si utilizzano strutture sintattiche che fanno tornare indietro solo se la condizione di ripetizione è vera Le strutture iterative sono comunemente dette cicli o loop In C i cicli sono controllati da una condizione di permanenza nel ciclo: fintantoché la condizione è vera, si esegue il corpo del ciclo (il blocco di codice da eseguire più volte)

49 Ciclo WHILE Fa eseguire un blocco di codice fintantoché una certa condizione è vera Valuta la condizione prima di eseguire il blocco condizione Se la condizione è inizialmente falsa, il blocco non viene eseguito neppure una volta V blocco F 5 Ciclo WHILE while (condizione) senza il ; blocco Viene valutata la condizione: se è vera esegue il blocco torna su a valutare nuovamente la condizione se è falsa passa ad eseguire le istruzioni successive al blocco La condizione può essere un espressione qualsiasi (come nel costrutto if) 6 Ciclo WHILE Esempio Il seguente codice somma i valori introdotti finché non viene dato il valore 0 somma = 0; scanf("%d", &v); while (v!= 0) { somma += v; scanf("%d", &v); } printf ("Somma: %d", somma); 7 Ciclo FOR Come il ciclo WHILE fa eseguire il blocco fintantoché la condizione è vera for (espr1;condizione;espr2) senza il ; blocco Viene calcolata espr1 (soltanto la prima volta) Viene valutata la condizione: se è vera: esegue il blocco esegue expr2 torna su a valutare nuovamente la condizione altrimenti (se è falsa): passa ad eseguire le istruzioni successive a blocco 8

50 Ciclo FOR Il flow-chart corrispondente è il seguente: expr1 condizione V blocco expr2 F 9 Ciclo FOR La condizione può essere un espressione qualsiasi, se manca viene considerata pari a 1 expr1 e/o expr2 possono mancare (ma i separatori ; devono esserci ugualmente) Esempio Stampa i numeri interi da 0 a 1000 for (i=0; i<=1000; i++) printf("%d", i); Una variabile come i nell esempio precedente che tiene conto del numero di iterazioni viene detta variabile di conteggio o indice Notare che, nell esempio, dopo che il ciclo è stato eseguito completamente, i vale Ciclo FOR Il ciclo FOR è un ciclo WHILE riscritto in modo tale da raggruppare tra le parentesi tutto ciò che gestisce l indice: inizializzazione (espr1), controllo (condizione) e aggiornamento (espr2) for (espr1;condizione;espr2) blocco Il ciclo FOR precedente equivale a: expr1; fuori dal corpo del ciclo! while (condizione) { blocco expr2; } 11 Ciclo FOR Esempio Questo ciclo WHILE: i=0; while (i<=1000) { printf("%d", i); i++; } e questo ciclo FOR: for (i=0; i<=1000; i++) printf("%d", i); sono equivalenti, ma il secondo è più compatto 12

51 La variabile di conteggio Talvolta (ma non sempre) è conveniente che la variabile di conteggio sia corta per questioni di leggibilità del codice Esempio Qui v[i] viene usata nel corpo del ciclo più volte, quindi è conveniente usare come indice i e non la variabile totvaloriletti che invece viene assegnata alla fine del ciclo: for (i=0; scanf("%d",&v[i])!=eof; i++) tot += v[i]*v[i-1]*v[i+1]; totvaloriletti = i; Se invece della variabile i ci fosse stata la variabile totvaloriletti, il codice sarebbe molto meno compatto e leggibile 13 Scelta tra ciclo FOR e WHILE Quando il numero di iterazioni è noto a priori (e quindi il ciclo è controllato da un indice), è preferibile (per chiarezza e stilisticamente) utilizzare un ciclo FOR che raggruppa in un punto solo l inizializzazione, il controllo e l aggiornamento dell indice 14 Ciclo DO-WHILE Fa eseguire un blocco di codice fintantoché una certa condizione è vera Valuta la condizione dopo aver eseguito il blocco V blocco condizione Anche se la condizione è inizialmente falsa, il blocco viene eseguito almeno una volta F 15 Ciclo DO-WHILE Nella letteratura questo ciclo viene detto ciclo Repeat-Until (dove però se la condizione è vera si esce dal ciclo: non è di permanenza) do { blocco }while (condizione); con il ; La condizione può essere un espressione qualsiasi che produce un valore Le graffe sono opzionali, ma consigliabili (proprio con la graffa di chiusura subito prima della keyword while) per distinguere facilmente il ciclo WHILE dal ciclo DO-WHILE 16

52 Ciclo DO-WHILE 17 Scelta tra ciclo WHILE e DO 18 Esempio Somma i valori dati finché non viene introdotto il valore particolare 0 somma = 0; do { scanf("%d", &v); somma += v; }while (v!= 0); Notare che il valore v viene comunque addizionato a somma (ma in questo esempio non causa problemi: somma uno 0) Si può sempre passare da un tipo di ciclo ad un altro modificando (poco) il programma La scelta tra ciclo WHILE e ciclo DO-WHILE è talvolta ovvia e talavolta frutto di preferenze personali Break 19 Break 20 Per uscire da un ciclo immediatamente, senza aspettare la valutazione della condizione, si può utilizzare l istruz. non strutturata break Dopo il break, l esecuzione continua dalla prima riga successiva al blocco while (condizione) { istruzioni... if (condizione_particolare) break; istruzioni... } Il break può essere usato per gestire condizioni particolari e infrequenti (non deve essere il metodo normale di terminazione del ciclo) Esempio Somma fino a 10 valori dati in input. Per introdurre meno valori, introdurre 0 somma = 0; for (i=0; i<10; i++) { scanf("%d", &v); if (v == 0) break; somma += v; } printf("somma = %d\n",somma);

53 Continue Per passare immediatamente all iterazione successiva, si può utilizzare l istruzione non strutturata continue Per effetto dell istruzione continue: vengono saltate tutte le istruzioni dalla continue fino alla parentesi di terminazione del corpo del ciclo se si tratta di un ciclo for, viene eseguita expr2 l esecuzione riprende dalla valutaz. della condizione 21 Continue Esempi while (condizione) { istruzioni... if (condizione_particolare) continue; istruzioni saltate se eseguito continue } do { istruzioni... if (condizione_particolare) continue; istruzioni saltate se eseguito continue }while (condizione); 22 Continue Esempio Somma i valori dati finché non viene introdotto il valore 0, ignorando i negativi. somma = 0; do { scanf("%d", &v); if (v < 0) continue; somma += v; }while(v!= 0); Talvolta è più chiaro utilizzare una continue rispetto ad una if, sebbene quest ultima produca codice strutturato 23 Lettura di valori Quando non si può sapere a priori il numero di valori che verranno introdotti dall utente si deve stabilire un modo per l utente di indicare la fine dell input: Si chiede all utente quanti valori verranno introdotti Si prevede un valore particolare che quando introdotto indica la fine dell input, tale valore è detto sentinella (es. lo 0 degli esempi precedenti) Si chiede all utente di segnalare la fine dell input mediante l immissione di un codice di controllo detto End Of File (EOF) che viene riconosciuto e segnalato dalle stesse funzioni di input (mentre la sentinella viene riconosciuta dopo l input) 24

54 Lettura di valori La costante EOF è un valore intero definito in stdio.h (in genere vale 1) Viene prodotto dall utente: Su sistemi Windows premendo (dopo essere andati a capo) Control-Z e poi INVIO Su sistemi Linux/Unix premendo Control-D Le funzioni scanf e getchar restituiscono EOF quando l utente indica la fine dell input Esempio while (scanf("%d", &a)!= EOF) somma += a; In modo analogo gets restituisce NULL 25 Cicli annidati Un ciclo può essere collocato (completamente) nel corpo di un altro ciclo In genere, nel caso di cicli FOR ogni ciclo deve avere una variabile di conteggio diversa Il ciclo esterno controlla quello interno Il ciclo interno ricomincia sempre da capo (ad esempio l inizializzazione dell indice di un ciclo FOR interno ad un altro ciclo viene eseguita ogni volta) 26 Cicli annidati Esempio for (i=1; i<=7; i+=3) ciclo esterno { for (j=2; j<5; j++) ciclo printf("%d,%d ", i, j); interno printf("\n"); } printf("%d,%d ", i, j); produce il seguente output: 1,2 1,3 1,4 4,2 4,3 4,4 7,2 7,3 7,4 10,5 notare i valori di uscita 27 Uscita da cicli annidati Nel caso di cicli annidati, break fa uscire solo da un livello; per uscire contemporaneamente da tutti i cicli annidati si può usare una goto for (i=0; i<10; i++) for (j=0; j<10; j++) { scanf("%d", &v); if (v == 0) goto fuori; somma += v; } fuori: printf("somma = %d\n",somma); 28

55 Uscita da cicli annidati 29 Etichette 30 A scapito di un po di efficienza si può comunque sempre fare a meno della goto esci = NO; for (i=0; i<10 && esci==no; i++) for (j=0; j<10 && esci==no; j++) { scanf("%d", &v); if (v == 0) esci = SI; else somma += v; } printf("somma = %d\n",somma); Una label (etichetta) viene usata per dare un nome ad una riga, viene in genere posizionata all inizio della riga stessa senza indentazione ed è terminata da un carattere :, esempio: fuori: Tutte le label devono avere nomi diversi (stesse regole sintattiche degli identificatori) Una label è visibile in ogni punto della funzione dove è definita, ma non al di fuori di essa Salti 31 Salti 32 Un salto fa continuare l esecuzione di un programma da un altro punto del codice L istruzione di salto goto ha sintassi: goto label; label senza il carattere : Quando viene eseguita, il programma salta alla riga con quella label e continua da lì Una label può essere collocata in una riga precedente o successiva quella con il goto (salto indietro o avanti), ma nell uso accettato sarà sempre avanti e in posizioni ben precise Una label può essere usata da più goto, ma nell uso accettato non si presenta mai il caso L utilizzo di goto produce sempre codice non strutturato e quindi quasi sempre più difficile da comprendere e da manutenere Se il linguaggio dispone di adeguati costrutti strutturati si può sempre trovare il modo di fare a meno di goto. Il linguaggio C li ha. La liceità di utilizzo del goto nei programmi è oggetto di diatribe, con ferventi e autorevoli sostenitori in entrambe le parti (Dijkstra vs. Knuth) Con il goto sono condannati anche break, continue e return multipli

56 Salti 33 Esercizi 34 In alcuni (pochi) casi il goto può essere utile per questioni di efficienza E utile per uscire da due o più cicli annidati, in questo caso l etichetta DEVE essere collocata alla prima riga dopo il corpo del ciclo più esterno (senza istruzioni intermedie), preferibilmente allineata con il ciclo più esterno da cui permette di uscire Lo si eviti in tutti gli altri casi Alcuni linguaggi moderni (es. Java) non hanno goto, ma dispongono di costrutti aggiuntivi (es. break con etichetta) proprio per uscire da cicli annidati 1. Scrivere un programma che calcoli la media (con parte frazionaria) di 10 valori introdotti dalla tastiera. 2. Scrivere un programma che chieda quanti siano i valori che verranno introdotti dalla tastiera, li chieda tutti e ne stampi la somma e la media. 3. Scrivere un programma che calcoli la media di tutti i valori introdotti dalla tastiera finché non ne viene introdotto uno non compreso tra 18 e 30, ad esempio 9999 (provare proprio questo valore!). La visualizzazione della media deve avvenire solo alla fine (ossia non ogni volta che un valore viene introdotto). Esercizi 35 Esercizi Scrivere un programma che richieda N numeri da tastiera e ne calcoli il valore massimo. 5. Scrivere un programma che richieda N numeri da tastiera e ne calcoli il valore massimo, il valore minimo, la somma e la media. 6. Si scriva un programma che calcoli il fattoriale di un numero intero N dato dalla tastiera. Si ricordi che il fattoriale di un numero n (simbolo n!) viene calcolato con la seguente formula: n! = n (n 1) (n 2) Scrivere un programma che calcola i primi N numeri di Fibonacci, con N introdotto dalla tastiera. I numeri di Fibonacci sono una sequenza di valori interi che inizia con i due valori fissi 1 e 1 e ogni successivo valore è la somma dei due precedenti. Ad esempio i primi 10 numeri di Fibonacci sono: Scrivere un programma che calcoli i primi numeri di Fibonacci minori o uguali a N, con N introdotto dalla tastiera. Ad esempio i primi numeri di Fibonacci minori o uguali a 10 sono:

57 Esercizi 9. Si scriva un programma per calcolare ex mediante il suo sviluppo in serie: e x x = ! 2 x 2! 3 x ! Ogni frazione aggiunge precisione al risultato, per cui conviene usare valori di n adeguatamente elevati, ad esempio compresi tra 30 e 40. Si verifichi che i risultati calcolati in questo modo siano coerenti con quelli forniti dalla funzione intrinseca exp calcolando la differenza dei valori. 37 Esercizi 10. Si scriva un programma dove il calcolatore determini casualmente un numero intero compreso tra 0 e 99 e chieda all utente di trovare il numero stesso. Ad ogni input dell utente il calcolatore risponde con troppo alto o troppo basso, finché non viene trovato il valore corretto. Per generare valori casuali si utilizza la funzione rand. 11. Si scriva un programma per calcolare la radice quadrata mediante la fomula iterativa di Newton: x i+ 1 = 1 xi A xi Esercizi (Continuazione) Dato il valore A, se ne vuole calcolare la radice quadrata x. La formula data calcola valori di x sempre più precisi. Inizialmente si considera x i=0 = A, ricavando un valore x 1 che approssima molto grossolanamente il valore della radice quadrata. Si riinserisce x 1 nella formula (al posto di x i ) ottenendo un x 2 che è un approssimazione migliore della precedente. Si continua in questo modo finché il risultato non varia più (cioè x i = x i +1 ). 39

58 Problema 2 Vettori e matrici Ver Claudio Fornaro - Corso di programmazione in C Si vuole un programma che chieda 10 numeri dalla tastiera e li visualizzi dall ultimo al primo Soluzione 1 Si possono definire 10 variabili, scrivere 10 scanf e quindi 10 printf Considerazione E se fossero 100 numeri? 1000? Soluzione 2 Serve un metodo per definire tutte insieme N variabili dello stesso tipo, inoltre queste devono poter essere identificate singolarmente (tramite un numero) Vettori 3 Vettori 4 Variabili scalari: contengono un solo valore Variabili vettoriali: contengono più valori dello stesso tipo, detti elementi Se il tipo è primitivo (come char, int, long, double, etc.) gli elementi del vettore sono variabili scalari di quel tipo Tutti gli elementi condividono lo stesso nome e sono contraddistinti da un indice: a[1] a[2] a[3] a[4] in termini matematici questo si scrive: a 1 a 2 a 3 a 4 Definizione tipo nome[num_di_elementi ]; Esempio int vett[10]; definisce un vettore di 10 elementi di tipo int num_di_elementi deve essere una costante intera positiva (no const, no enum), nota al momento della compilazione (quindi non si può chiedere all utente mentre il programma gira), in genere la si indica in una #define

59 Vettori 5 Vettori 6 Si accede ai singoli elementi indicando il nome del vettore seguito dall indice dell elemento tra parentesi quadre (costante o variabile): vett[7] L indice deve essere un valore di tipo intero Il primo elemento ha indice 0: vett[0] L ultimo elemento di un vettore di N elementi ha indice N 1: vett[9] Utilizzo (riferito all esempio precedente): x = vett[4] * 5; infatti vett[4] è un valore scalare di tipo int Il nome di un vettore viene usato dal compilatore come sinonimo dell indirizzo di memoria del primo elemento del vettore Gli elementi del vettore sono allocati in locazioni di memoria contigue e successive int vett[10]; vett: indice vett[0] vett[4] vett[9] Vettori La presenza di un indice suggerisce che sia possibile efficacemente scandire tutti i valori di un vettore mediante un ciclo for La soluzione del problema iniziale è: #define N 10 int vett[n]; for (i=0; i<n; i++) scanf("%d", &vett[i]); for (i=n-1; i>=0; i--) printf("%d\n", vett[i]); memorizzati in quest ordine i Vettori Attenzione a non sforare i limiti del vettore: il sistema operativo potrebbe fermare il programma oppure si potrebbero avere malfunzionamenti del programma stesso (lo sforamento potrebbe invadere la memoria associata ad altri suoi dati o al suo codice eseguibile) Lo standard non richiede che il compilatore faccia controlli, molti compilatori lo offrono opzionalmente, ma ciò riduce le prestazioni 8

60 Matrici 9 Matrici 10 Variabili vettoriali con due dimensioni Definizione tipo nome[numero_righe][numero_colonne]; int Mx[7][5]; definisce una matrice di 7 righe 5 colonne Utilizzo Mx[3][2] è una variabile scalare di tipo int Mx[3][2] = 12; La presenza di 2 indici suggerisce che sia possibile scandire sequenzialmente i valori di una matrice mediante due cicli for annidati Il codice seguente visualizza una matrice int mat[righe][colonne];... for (r=0; r<righe; r++) { for (c=0; c<colonne; c++) printf("%d ", mat[r][c]); printf("\n"); } Matrici 11 Matrici 12 Una matrice è in realtà un vettore di vettori: int Mx[7][5]; definisce un vettore di 7 elementi ciascuno dei quali è un vettore di 5 int (l associatività delle parentesi quadre è da sinistra a destra) Gli elementi Mx[i] sono i 7 vettori di 5 int In particolare, sintatticamente ciascuno dei termini Mx[i] viene usato dal compilatore come sinonimo dell indirizzo di memoria del primo elemento di ciascun vettore di 5 int; quindi non si possono assegnare valori a Mx[i] Gli elementi di una matrice sono memorizzati per righe, questo significa che i 7 vettori di 5 int sono collocati uno di seguito all altro in locazioni di memoria crescenti, ossia gli elementi del primo vettore di 5 int sono seguiti dagli elementi del secondo, etc. (dopo Mx[0][4]si trova Mx[1][0]) Mx: Mx[0] Mx[1] Mx[2] Mx[3] Mx[4] Mx[5] Mx[6]

61 Matrici multidimensionali 13 Inizializzazione di vettore 14 Sono vettori di vettori di vettori di vettori... Non c è limite al numero delle dimensioni Esempio float xyz[7][5][4]; xyz[5][2][3] è una variabile scalare di tipo float xyz[5][2][3] = 12.0F; I 7 elementi xyz[i] (ciascuno è una matrice 5x4 di int) e gli elementi xyz[i][j] (ciascuno è un vettore di 4 int) non possono essere assegnati La lista degli inizializzatori è indicata tra parentesi graffe dopo un = : int vett[4]={12, 5, 3, 6}; Gli inizializzatori devono essere espressioni costanti: numeri, #define, valori enum, indirizzi di memoria di variabili statiche ; non possono invece essere: variabili, valori const, il risultato di funzioni, indirizzi di memoria di variabili automatiche (le classi di memorizzazione sono trattate più avanti) Se non c è inizializzazione, i valori contenuti in un vettore sono indeterminati Inizializzazione di vettore 15 Inizializzazione di matrice 16 La dimensione può essere omessa, in questo caso viene calcolata dal compilatore contando i valori: int vett[]={12, 5, 3, 6}; ci sono 4 elementi, hanno i valori indicati Se la lista contiene meno valori di quelli indicati dalla dimensione ma almeno uno, quelli non specificati sono inizializzati a 0: int vett[4]={6, 2}; i 4 valori sono 6, 2, 0 e 0 Per inizializzare a 0 tutto un vettore: int vettore[10]={0}; Il primo valore 0 è indicato esplicitamente, i successivi sono posti a 0 automaticamente Se non c è inizializzazione, i valori contenuti in una matrice (multi-dim.) sono indeterminati Gli inizializzatori degli elementi di una matrice si elencano separando con parentesi graffe i valori delle diverse righe int Mx[2][2]={{1,2},{3,4}}; Gli inizializzatori possono non avere le graffe interne: vengono assegnati ordinatamente agli elem. della matrice come se fosse un vettore int Mx[2][2]={1,2,3,4}; Quando ci sono abbastanza inizializzatori, la prima dimensione può essere omessa (viene calcolata dal compilatore) int Mx[][2]={1,2,3,4};

62 Inizializzazione di matrice 17 Esercizi 18 Le graffe sono necessarie per non specificare tutti valori intermedi (posti a 0) int Mx[100][4]={{1,2,3},{4,5}}; 1 a riga: 1, 2, 3, 0 2 a riga: 4, 5, 0, 0 le altre 98 righe: 0, 0, 0, 0 Senza graffe vengono assegnati in sequenza int Mx[100][4]={1,2,3,4,5}; 1 a riga: 1, 2, 3, 4 2 a riga: 5, 0, 0, 0 le altre 98 righe: 0, 0, 0, 0 Per inizializzare tutta una matrice a 0 int Mx[100][100]={0}; 1. Scrivere un programma che chieda quanti siano i valori che verranno introdotti dalla tastiera (max 100), li chieda tutti e li stampi dall ultimo al primo. 2. Scrivere un programma che chieda quanti siano i valori che verranno introdotti dalla tastiera (max 100), li chieda tutti e visualizzi prima tutti i pari nell ordine in cui sono stati inseriti e poi tutti i dispari nell ordine inverso. Esempio: dati i valori , il programma visualizza: Esercizi 19 Esercizi Scrivere un programma che definisca 2 vettori A e B di uguali dimensioni (la dimensione sia chiesta in input, max 100), chieda in input tutti i valori del primo e POI tutti i valori del secondo (devono comparire sul video: Introdurre il 1 valore di A, Introdurre il 2 valore di A ecc. e poi Introdurre il 1 valore di B, Introdurre il 2 valore di B ecc. (Continuazione) Il programma deve creare un terzo vettore C della stessa dimensione di A e B contenente nel 1 o elemento la somma del 1 o elemento di A e del 1 o elemento di B, nel 2 o elemento la somma del 2 o elemento di A e del 2 o elemento di B etc. Alla fine il programma deve visualizzare tutti gli elementi di posizione pari di C e poi tutti quelli di posizione dispari. Esempio: vettori di lunghezza 4, in A sono stati messi i valori e in B i valori , verranno calcolati e messi in C i valori e quindi stampati i valori

63 Esercizi 4. Scrivere un programma che chieda quanti siano i valori che verranno introdotti dalla tastiera (max 100), li chieda tutti e li collochi in un vettore. Successivamente di questi determini il massimo, il minimo, la somma e la media. 21 Esercizi 5. Scrivere un programma che chieda quanti siano i valori che verranno introdotti dalla tastiera, li chieda tutti e calcoli e stampi la media mobile a 3 valori di questi numeri. La media mobile è una media aritmetica su alcuni valori (in questo caso 3), ad esempio se viene data la sequenza di valori: il programma deve calcolare la media di 2, 4 e 1 e stamparla, poi la media di 4, 1 e 6 e stamparla, poi 1, 6 e 3 e stamparla, fino a 3, 5 e 3. Si controlli che il numero di valori sia almeno pari a 3. Si usino variabili di tipo reale. 22 Esercizi 6. Scrivere un programma che acquisisca da tastiera un vettore di (max 100) valori di tipo intero e identifichi la più lunga sequenza di numeri consecutivi uguali. Se vengono identificate più sequenze della stessa lunghezza, si consideri solo la prima identificata. Il programma deve indicare il valore ripetuto e il numero di ripetizioni dello stesso. Esempio: Input: Output: numero 9 ripetizioni 4 23 Esercizi 7. Scrivere un programma che definisca una matrice di valori interi e di dimensioni richieste di volta in volta dall utente (massimo 10x10) mediante input quali quante righe? e quante colonne?. Successivamente di questi determini il massimo, il minimo, la somma e la media. 24

64 Esercizi 8. Scrivere un programma che definisca una matrice MX di valori interi e di dimensioni richieste di volta in volta dall utente (massimo 20x26) mediante input quali quante righe? e quante colonne? e immetta in ciascuna cella un valore casuale (usare rand) compreso tra 0 e 99. Il programma deve poi visualizzare la matrice con i valori allineati correttamente (%3d nella printf) e contare quanti valori sono pari e quanti sono dispari. Si tenga separata la parte relativa allo riempimento della matrice dalle operazioni successive. 25 Esercizi 9. Definire la matrice MX come nel precedente esercizio, questo programma deve indicare se se almeno un quarto dei valori della matrice è pari. Non interessa sapere quanti siano i valori pari, ma solo sapere se ce n è almeno un certo numero noto a priori (num_righe * num_colonne / 4, attenzione: tronca la parte frazionaria); quindi quando si arriva a contarne esattamente quel numero si può uscire dai due cicli annidati per non perdere tempo inutilmente (è inutile contare anche gli altri). Per uscire da più cicli annidati si può utilizzare il goto. 26 Esercizi 10. Si acquisisca da tastiera una matrice di int di dimensioni uguale a R righe e C colonne (con R e C costanti predefinite), chieda all utente le dimensioni r e c di una matrice tali che r R e c C, visualizzi tutte le sottomatrici di dimensioni r c della matrice data la cui somma degli elementi è uguale a zero. Esempio con R=4, C=5, r=2, c=2: Output prodotto: Esercizi 11. Scrivere un programma che definisca una matrice 20x26 di valori int casuali (0-99), chieda all utente di introdurre una matrice (dimensioni e valori) e determini se questa seconda è contenuta nella prima, indicando le coordinate di ogni ricorrenza della stessa Trovata alle coordinate (1,2) Trovata alle coordinate (2,3) 28

65 Homework 1 Si scriva un programma per calcolare il prodotto di due matrici. Le dimensioni delle matrici vengano chieste all utente (max 100x100 ciascuna). Si controlli che le dimensioni delle matrici siano tali da permettere che esse vengano moltiplicate. La matrice risultante deve essere visualizzata. 29

66 I caratteri e le stringhe Ver Claudio Fornaro - Corso di programmazione in C Il codice ASCII Per memorizzare i simboli grafici corrispondenti ai caratteri bisogna associare un numero intero a ciascuno di essi Il codice ASCII / æski/ (American Standard Code for Information Interchange) è una tabella che elenca le corrispondenze tra simboli grafici e numeri (ad es. A corrisponde al 65, B al 66) I numeri del Codice ASCII standard sono a 7 bit 2 7 =128 caratteri diversi Curiosità: il codice ASCII delle cifre 0-9 si può ottenere dal corrispondente valore in BCD impostando il nibble alto a Il codice ASCII standard Contiene solo i 95 caratteri di base (es. no lettere accentate) Ha 4 sezioni importanti: Spazio (car. visibile di codice <) Cifre (0-9 in ordine crescente) Maiuscole (A-Z in ordine crescente) Minuscole (a-z in ordine crescente) Le 4 sezioni sono separate da altri caratteri generici (punteggiatura, parentesi, simboli matematici, etc.) I caratteri di controllo non vengono visualizzati ma producono un effetto (es. inserire un ritorno a capo, produrre un beep, etc.) 3 Il codice ASCII standard z 97 a Z 65 A Altri caratteri Lettere minuscole (a-z) Altri caratteri Lettere maiuscole (A-Z) Altri caratteri Cifre (0-9) Altri caratteri Carattere SPAZIO Caratteri di controllo 4

67 Il codice ASCII esteso 5 Caratteri 6 Per sfruttare gli 8 bit di un byte, ai 128 caratteri del Codice ASCII standard si aggiungono altri 128 caratteri, corrispondenti ai valori da 128 a 255 I 128 caratteri aggiuntivi definiscono altri simboli grafici (es. le lettere accentate, à, ë, î) La parte aggiunta non è standard ed è diversa per ciascun sistema operativo Può essere diversa anche nello stesso sistema operativo a seconda della lingua Per memorizzare un carattere, il C memorizza il numero intero corrispondente al suo codice ASCII ('A' equivale a 65, '0' a 48, etc.) Costanti carattere: indicate tra apici singoli ('A'), oppure indicandone il codice ASCII in: decimale: 65 ottale: '\101' esadecimale: '\0x41' Essendo numeri interi a tutti gli effetti, i caratteri si possono usare nelle operazioni: x = c '0'; se c contiene il codice ASCII di una cifra, x ne è il valore corrispondente (es. se c='7', allora '7' '0' viene valutato come 55 48, cioè 7) Caratteri 7 Caratteri 8 Variabili carattere: variabili di tipo intero su 8 bit, sono definite di tipo char char y; y = 'A'; y = 65; y = '\101'; y = '\0x41'; Tutte le assegnazioni precedenti sono equivalenti ed assegnano il carattere 'A' (codice ASCII 65) alla variabile y. y è sia il numero 65 sia il carattere 'A', dipende dal contesto dove y è usata Alcuni caratteri speciali si possono anche ottenere utilizzando il carattere di escape \ : Caratteri speciali: \' \" \? \\ Ad es. per introdurre l apice in una variabile char si deve scrivere '\'' dove il carattere di escape \ permette di considerare l apice che lo segue come carattere normale e non come delimitatore di una costante di tipo carattere apice = '\''; Poiché il carattere \ ha un significato speciale, quando esso serve come carattere normale deve essere raddoppiato (anche nelle stringhe) backslash = '\\';

68 Caratteri I caratteri corrispondenti ai caratteri di controllo del codice ASCII non hanno un aspetto grafico, ma quando vengono visualizzati producono un effetto quale un beep, un ritorno a capo, etc. Si possono facilmente ottenere mediante sequenze di escape (oltre ad indicarne il codice ASCII in decimale, ottale o esadecimale): \a - quando viene visualizzato fa emettere un beep \n - corrisponde al carattere new line (10) \r - corrisponde al carattere carriage return (13) \t - corrisponde al carattere Tab char beep = '\a'; 9 Stringhe Vettori di char terminati da un carattere di codice ASCII pari a 0, il terminatore non è il carattere '0' che ha valore 48 nel codice ASCII, ma il carattere '\0' Stringhe costanti C I A O\0 Sequenze di caratteri racchiuse da doppi apici: "ciao ciao" è composta da 9+1 caratteri, l ultimo è '\0' Per includere in una stringa i caratteri \ e " è necessario precederli dal carattere di escape \ "vero\\falso" memorizza: vero\falso "premi \"invio\" per terminare" memorizza: premi "invio" per terminare 10 Stringhe Due stringhe costanti consecutive (separate da nulla, spazi, Tab o ritorni a capo) vengono concatenate dal compilatore in una sola: "ciao" "ciao" "ciao" "ciao" viene memorizzata come fosse stata scritta "ciaociaociaociao" (17 caratteri) Una stringa costante potrebbe essere collocata dal calcolatore in una parte di memoria a sola lettura (ad esempio in programmi eseguiti non su un calcolatore ma su un qualche apparecchio elettronico) 11 Stringhe Variabili stringa Sono vettori di char di dim. fissa, l ultimo carattere deve essere il terminatore '\0' char nome[15]; definisce una variabile stringa composta di 15 char, può contenere fino a 14 caratteri utili (deve esserci spazio per il '\0') Il 1 o carattere è nome[0], il 2 o nome[1], etc. Il terminatore permette di occupare solo parzialmente una stringa (lo spazio relativo ai restanti caratteri è inutilizzato ma riservato) nome: C I A O\0?????????? 12

69 Stringhe 13 Stringhe 14 La lunghezza di una stringa è il numero di caratteri contenuti (fino al '\0' escluso), non la sua capienza (nell es. precedente è 4) Si noti la differenza tra: 'a' il carattere 'a' (il numero 97) "a" la stringa contenente 'a' e '\0' Essendo una stringa un vettore di char, il nome di una stringa viene usato dal compilatore come sinonimo dell indirizzo di memoria del primo carattere Le stringhe di caratteri ASCII terminate da uno 0 vengono anche dette stringhe ASCIIZ Si possono inizializzare le variabili stringa in 2 modi equivalenti: char s[20]="ciao"; char s[20]={'c','i','a','o'}; Il carattere di posizione 4 (il 5 o ) è '\0' in entrambi i casi: essendo vettori, se l inizializzazione non riempie completamente la stringa, i caratteri successivi sono tutti 0 (cioè '\0') I singoli elementi della stringa sono caratteri: s[2] è 'a' s[0] = 'M'; modifica s in "Miao" Per assegnare una stringa NON si può scrivere s="ciao" ma ci vuole una funzione I/O di caratteri <stdio.h> 15 I/O di stringhe <stdio.h> 16 printf("%c", varchar); scanf("%c", &varchar); putchar(varchar); varint = getchar(); getchar restituisce il carattere letto o la costante EOF che segnala la fine dell input, varint deve essere di tipo int per potervi memorizzare anche EOF che è di tipo int. EOF viene definito in <stdio.h> con una #define, in molti compilatori vale 1 puts(varstringa); visualizza varstringa e aggiunge un '\n' alla fine (cioè va a capo). Dà EOF in caso di errore gets(varstringa); legge da tastiera tutta la stringa in input (spazi e Tab inclusi) fino al ritorno a capo incluso, la mette in varstringa senza il '\n', aggiunge '\0' alla fine. Dà NULL in caso di errore o fine file printf("%s", varstringa); %s visualizza una stringa (inclusi eventuali spazi, Tab e caratteri '\n' contenuti in essa)

70 I/O di stringhe <stdio.h> 17 I/O di stringhe <stdio.h> 18 scanf("%s", varstringa); SENZA & %s salta i white space (spazi, Tab, '\n', etc.) iniziali e legge solo fino al primo white space (che lascia nel buffer), Un qualsiasi white space nella stringa di formato richiede che tutti i white space in quel punto dell input vengano saltati (quindi il carattere '\n' non viene considerato come carattere ordinario: non indica alla scanf che deve attendere l introduzione di un ritorno a capo) Una direttiva di conversione della forma %[abc] equivale alla %s salvo che si ferma solo quando trova un carattere diverso da quelli indicati tra le parentesi quadre Esempio scanf("%[abcfrq]", s); Se si dà in input abbaino, s contiene abba mentre ino resta disponibile per le successive funzioni di input I/O di stringhe <stdio.h> 19 I/O di stringhe <stdio.h> 20 Una direttiva di conversione della forma %[^abc] equivale alla %s salvo che si ferma solo quando trova un carattere uguale a uno di quelli indicati tra le parentesi quadre Se si dà in input albero alla funzione: scanf("%[^nmrst]", s); contiene albe mentre ro resta disponibile per le successive funzioni di input Per leggere fino a fine riga con la scanf si può quindi utilizzare la direttiva %[^\n]%*c che legge tutti i caratteri che trova finché non incontra il ritorno a capo '\n', la parte %*c consuma il ritorno a capo Se si danno in input più caratteri di quelli che la variabile stringa può contenere, si invade la zona di memoria successiva alla stringa, producendo comportamenti anomali (buffer overflow ) Soluzione non preventiva: richiedere al compilatore di aggiungere controlli opportuni (il linguaggio C standard non li prevede) Soluzioni preventive: Non si può evitare il problema con una gets, ma questa può essere sostituita da una fgets nella quale invece si può specificare il numero massimo di caratteri da leggere (è trattata nelle slide sui file) Se si usa una scanf, si può specificare la dimensione massima del campo in input: es. %20s

71 Ritorno a capo A seconda del sistema operativo utilizzato, il ritorno a capo è in realtà costituito da una sequenza di uno o più caratteri: MS-DOS/Win: CR+LF (codici ASCII 13 e 10: \r\n) Unix/Linux: LF (codice 10: \n) Le funzioni di I/O considerano in carattere \n come il generico ritorno a capo: in input la sequenza di ritorno a capo viene trasformata in \n in output il carattere \n viene sostituito con la sequenza propria del sistema operativo. 21 Problemi di I/O scanf("%d",&x); gets(s); Si supponga di inserire un valore e premere Invio: la scanf legge il valore, ma il ritorno a capo \n (prodotto dal tasto Invio) viene prelevato (e scartato) dalla gets che quindi non aspetta altro input e mette in s solo il '\0' come primo carattere (stringa vuota): la gets sembra essere saltata Bisogna quindi consumare il ritorno a capo dato dopo l introduzione del numero da assegnare a x 22 Problemi di I/O Soluzioni possibili: 1. si aggiunge dopo la direttiva %d la direttiva %*c che legge e scarta il successivo carattere in input (il ritorno a capo): scanf("%d%*c",&x); 2. si previene il problema evitando di mescolare scanf e gets: si usano solo scanf: funziona perché le direttive (esclusa %c) scartano i white space iniziali (tra cui il \n ) si usano solo gets: funziona perché la funzione gets legge una riga e scarta il ritorno a capo \n alla fine della riga stessa 3. tra la scanf e la gets si può chiamare la funzione fflush(stdin) che svuota il buffer, ma l applicazione di fflush a stdin non è standard 23 Problemi di I/O Si chiede l introduzione di due caratteri dalla tastiera (due input diversi) mediante il codice: puts("inserisci un carattere: "); scanf("%c", &c); puts("inserisci un carattere: "); scanf("%c", &d); la prima scanf preleva il carattere per c, ma lascia nel buffer della tastiera il ritorno a capo e questo viene prelevato dalla seconda per d 24

72 Problemi di I/O Soluzioni: 1. nella prima scanf si usa %c%*c 2. si sostituisce la 2 a scanf con: scanf("%1s",s); d = s[0]; avendo definito: char s[2]; Infatti la direttiva %s legge caratteri dopo aver saltato i white space (anche eventuali \n ), in questo caso deve prelevare un solo carattere. Attenzione che produce una stringa (e non un carattere) e questa è composta dal carattere letto e dal '\0'. 25 Libreria caratteri <ctype.h> Le seguenti funzioni danno risultato vero (valore!=0) se il carattere c è del tipo indicato isdigit(c) cifra decimale isalpha(c) lettera isalnum(c) carattere alfanumerico isxdigit(c) cifra esadecimale islower(c) lettera minuscola isupper(c) lettera maiuscola iscntrl(c) carattere di controllo isspace(c) white space (' ','\t','\n',...) isprint(c) char stampabile, incluso lo spazio isgraph(c) char stampabile, escluso lo spazio ispunct(c) stampabile, no spazio, no alfanum if (isdigit(c)) printf("e una cifra"); 26 Libreria caratteri <ctype.h> Le seguenti funzioni producono un valore int contenente il codice ASCII del carattere c eventualmente convertito, se possibile: toupper(c) in maiuscolo tolower(c) in minuscolo Altrimenti il valore prodotto resta c (invariato) Se si vuole convertire in maiuscolo/minuscolo tutta una stringa è necessario applicare la funzione a ciascuno dei caratteri: for (i=0; i<strlen(s); i++) s[i] = (char)toupper(s[i]); notare il cast: toupper produce un int 27 Funzioni su stringhe <stdlib.h> varint = atoi(stringa) converte stringa in int x=atoi("123"); 123 in complem. a 2 varlong = atol(stringa) converte stringa in long y=atol("123"); 123 in complem. a 2 vardouble = atof(stringa) converte stringa in double z=atof("1.23e5"); in floating point 28

73 Confronto tra stringhe 29 Libreria stringhe <string.h> 30 Avviene confrontando i caratteri di posizione corrispondente delle due stringhe secondo i loro codici ASCII ( < significa precede ) cane < gatto cane > Gatto cane < cavallo cavallo < cavallone cavallo < cavallo cavallo < cavallo 21 > 123 Attenzione! Lunghezza di una stringa strlen(str) restituisce un valore intero pari alla lunghezza di str ('\0' escluso), il tipo restituito non è int, ma size_t: si tratta di un tipo adatto a contenere la lunghezza di una stringa sul compilatore in uso (su alcuni potrebbe essere equivalente ad un int, su altri ad un long) int l; char s[30]="ciao"; l=strlen(s); l vale 4 Libreria stringhe <string.h> 31 Libreria stringhe <string.h> 32 Copia di una stringa strcpy(str1,str2) copia str2 in str1 ('\0' incluso) Il modo corretto di cambiare valore ad una variabile stringa è dunque il seguente: char s[30]; strcpy(s, "ciao"); Non è invece corretto scrivere: s = "ciao"; Solo nell inizializzazione si può scrivere: char s[30] = "ciao"; Copia parziale di una stringa strncpy(str1,str2,n) copia i primi n caratteri di str2 in str1 (quelli presenti se è più corta), non aggiunge il '\0' finale se non è tra i primi n caratteri di str2 char s[30]="hello"; strncpy(s, "ciao", 4); "ciaoo" strncpy(s, "hi", 2); "hiaoo" Mentre char s[30]="buongiorno"; strcpy(s, "ciao"); "ciao"

74 Libreria stringhe <string.h> Concatenazione di stringhe strcat(str1,str2) concatena str2 alla fine di str1 ('\0' incluso) char s[30]="dove", t[30]=" vai"; strcat(s,t); s vale "dove vai" strncat(str1,str2,n) concatena i primi n caratteri di str2 alla fine di str1 (quelli presenti se è più corta), non aggiunge il '\0' finale se non è tra i primi n caratteri di str2 33 Libreria stringhe <string.h> Confronto tra stringhe strcmp(str1,str2) confronta str1 e str2 in base ai codici ASCII, restituisce un intero: minore di 0 se str1<str2 uguale a 0 se str1=str2 maggiore di 0 se str1 > str2 strncmp(str1,str2,n) confronta i primi n caratteri di str1 e str2 (quelli presenti se almeno una è più corta) 34 Libreria stringhe <string.h> Ricerca in stringhe strchr(str, carattere) cerca carattere in str a partire dal suo primo carattere all ultimo, dà NULL se non lo trova if (strchr(s, 'z')!= NULL) printf("trovata una z!"); strrchr(str, carattere) cerca carattere in str a partire dal suo ultimo carattere al primo, dà NULL se non lo trova 35 Libreria stringhe <string.h> Ricerca in stringhe strstr(str1, str2) cerca str2 in str1, dà NULL se non la trova if (strstr(s, "ciao")!= NULL) printf("trovata stringa ciao!"); Dopo aver studiato i puntatori: le funzioni di ricerca e copia (strchr, strstr, strcpy, strtok, etc.) in realtà restituiscono il puntatore a quanto trovato, NULL se non lo trovano 36

75 I/O da/su stringhe <stdio.h> 37 I/O da/su stringhe <stdio.h> 38 sscanf(stringa, formato, variabili); identica alla scanf, ma preleva da stringa i caratteri come se provenissero dalla tastiera) Esempio se stg contiene 12 ciao 23.2, la funzione: sscanf(stg, "%d%s%f", &a, s, &b); legge e assegna 12 ad a, ciao ad s, 23.2 a b Utile per analizzare (parsing) una riga di cui non è noto a priori il numero di elementi che la compongono (per sapere quanti sono i valori letti si valuta il valore restituito dalla sscanf) Esempio gets(s); if (sscanf(s,"%d%d",&a,&b)==2)... s abcd... s non contiene valori numerici, nessuna delle variabili viene assegnata e la sscanf restituisce 0 s 12 abcd... s contiene 1 solo valore, questo viene assegnato ad a (b resta invariato) e la sscanf restituisce 1, s abcd... s contiene 2 valori, questi sono assegnati ad a e a b e la sscanf restituisce 2 s abcd... s contiene più di 2 valori, i primi due sono assegnati ad a e a b, i restanti non vengono considerati e la sscanf restituisce 2 I/O da/su stringhe <stdio.h> 39 Altre funzioni di parsing 40 sprintf(stringa, formato, variabili); identica alla printf, ma scrive in stringa i caratteri che la printf manderebbe su stdout (video) Esempio se g contiene 23 e m contiene febbraio sprintf(str, "Il %d %s", g, m); mette in str: Il 23 febbraio Utile per assemblare una stringa composta da parti di tipo diverso provenienti da variabili Oltre alla sscanf, per la suddivisione (parsing) di una stringa composta da più parti (token) si possono usare le seguenti funzioni (si rimanda ad altra documentazione per i dettagli, vedere bibliografia): In <string.h>: strspn, strcspn, strpbrk, strtok In <stdlib.h> (token di tipo numerico): strtod, strtol, strtoul

76 Vettori di stringhe Una matrice di char è un vettore di stringhe: char str[4][20]={"uno", "due"}; dichiara un vettore di 4 stringhe di 20 char (i caratteri sono inizializzati a \0 se almeno parte della matrice viene inizializzata) Le 4 stringhe sono identificate da str[i] e utilizzabili come normali stringhe: scanf("%s", str[2]); es."ciao" str[3][0]='x'; str: str[0] u n o \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 str[1] d u e \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 str[2] c i a o \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 str[3] X \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 41 Vettori di stringhe Per memorizzare una sequenza di N stringhe di lunghezza LEN in un vettore di stringhe si può utilizzare il metodo seguente: char str[n][len]; for (i=0; i<n; i++) gets(str[i]); 42 Esercizi 1. Scrivere un programma che date due stringhe in input stampi la più lunga. La prima se sono di uguale lunghezza. 2. Scrivere un programma che date due stringhe in input stampi la maggiore. 3. Scrivere un programma che chieda in input una stringa e calcoli da quanti caratteri è composta (senza usare la funzione strlen ma cercando il carattere '\0') 4. Scrivere un programma che data una stringa in input, la converta tutta in maiuscolo. 5. Scrivere un programma che data una stringa in input verifichi se essa contiene almeno una A tra i primi 10 caratteri. 43 Esercizi 6. Scrivere un programma che richieda in input una stringa e conti quante cifre essa contiene. Esempio Ciao2004! C6? deve dare Scrivere un programma che richieda in input una stringa e conti di quante lettere maiuscole, lettere minuscole, cifre e altri caratteri è composta Esempio Ciao2004! C6? deve dare: maiuscole:2, minuscole: 3, cifre: 5, altri: 3. 44

77 Esercizi 45 Esercizi Scrivere un programma che date in input due stringhe di lunghezza diversa indichi se la più corta è contenuta solo una volta nella più lunga. 9. Scrivere un programma che verifichi se la stringa data in input è palindroma o no ( kayak, otto, elle, anilina ). 10. Scrivere un programma che verifichi se la stringa data è composta di due parti uguali, trascurando il carattere centrale se la lunghezza è dispari (es. CiaoCiao, CiaoXCiao ). 11. Un immagine è composta da 256x256 punti di colore bianco o nero ed è rappresentata da una matrice di caratteri. Ogni elemento della matrice rappresenta un puntino e vale: 1 per rappresentare un punto nero 0 per rappresentare un punto bianco. Si scriva un programma che codifichi l immagine sostituendo, per ciascuna riga di punti, le sequenze consecutive dello stesso carattere ( 0 o 1 ) con il carattere stesso seguito dal numero delle sue ripetizioni. Si ripeta codificando le colonne. Esercizi 47 Homework 2 48 Continuazione: Ad es. l immagine seguente (solo 6x3): (ci sono 10 zeri) (1 zero, 7 uno, 2 zeri) (1 zero, 1 uno, 5 zeri, 1 uno, 2 zeri) viene codificata per righe come segue: e per colonne come segue: Scrivere un programma che chieda all utente di inserire una frase (max 128 caratteri), conti quante sono le parole (sequenze di lettere dell alfabeto) che la compongono e la lunghezza media delle parole stesse. Esempio Se viene dato in input: Ieri... sono andato a mangiare all'una! il programma deve indicare che ci sono 7 parole e che la lunghezza media è 4.14 caratteri.

78 L indentazione del codice 2 Complementi - 1 Ver 2.2 Ha lo scopo di evidenziare i blocchi di codice Ignorata dal compilatore, serve al programmatore per cogliere visivamente la struttura del programma Si indenti il codice mentre lo si sviluppa, non successivamente per renderlo bello L indentazione è sempre dello stesso numero di spazi, in genere 3 o 4 (in genere si può definire il tasto Tab in modo che introduca quel numero di spazi) Claudio Fornaro - Corso di programmazione in C L indentazione del codice 3 L indentazione del codice 4 L indentazione esprime dipendenza, controllo: l istruzione non indentata precedente controlla il blocco indentato Esempio: for (i=0; i<10; i++) { scanf("%d", &a); printf("%d\n", a*2); } Le due istruzioni scanf e printf sono controllate dall istruzione for precedente, la printf non è controllata dalla scanf e per questo è allo stesso livello (non è indentata ) Esempio: for (i=1; i<100; i++) { printf("%d", i); if (i % 7 == 0) { printf(" divisibile per 7"); cont++; } printf("\n"); } Le istruzioni printf, if e printf del blocco del for sono allo stesso livello, il blocco dell if è ulteriormente indentato

79 L indentazione del codice 5 L indentazione del codice 6 L indentazione è necessaria anche in assenza delle parentesi graffe (opzionali) Esempio: for (i=0; i<10; i++) for (j=0; j<20; j++) printf("%d,%d", i, j); L istruzione printf è controllata dall istruzione for precedente che a sua volta è controllata dall istruzione for più esterna Nel posizionamento delle parentesi graffe che racchiudono il blocco si suggerisce di utilizzare la modalità K&R2: la graffa di inizio blocco è collocata sotto il primo carattere della parola chiave che controlla il blocco, in una riga a sé stante tutte le istruzioni del blocco sono indentate (es. 4 spazi) la graffa di fine blocco è allineata sotto quella di inizio blocco, in una riga a sé Ulteriori esempi di indentazione corretta e coerente possono essere trovati nelle soluzioni degli esercizi Magic numbers 7 Magic numbers 8 Per chiarezza, si preferisce limitare la presenza di valori numerici nei programmi Si preferisce definire delle costanti Esempio: for (i=0; i<26; i++) printf("%c", 'A'+i); Che cos è quel 26? #define LETTERE_ALFAB for (i=0; i<lettere_alfab; i++) printf("%c", 'A'+i); Esempio: int v[80]; for (i=0; i<80; i++) scanf("%d", &v[i]); Ma che cosa rappesenta quel valore 80? define MAXVETT int v[maxvett]; for (i=0; i<maxvett; i++) scanf("%d", &v[i]); MAXVETT è auto-esplicativo, inoltre si ha un ulteriore vantaggio: se si vuole cambiare la dimensione del vettore nel programma è sufficiente cambiare la sola define

80 Uscita dal programma 9 Effetti collaterali 10 Il programma termina quando viene eseguita un istruzione return: return status; Possono esserci più istruzioni return Nel main la return termina il programma e passa il valore status al S.O. per informarlo sull esito dell esecuzione: il valore 0 indica che il programma ha eseguito quanto doveva: terminazione con successo valori diversi da 0 indicano che il programma ha avuto problemi (es. non ha trovato un file): terminazione con malfunzionamento Includendo <stdlib.h>, per status si possono usare le costanti EXIT_SUCCESS (al posto di 0) ed EXIT_FAILURE (generico 0) Un espressione può produrre un risultato e/o avere effetti collaterali (side-effect ) Esempio x = 3 * i++; 3*i èun calcolo che produce un valore, mentre i++ ha l effetto collaterale di incrementare i di 1 Quando tutti i side-effect di un espressione sono stati effettuati, si dice che si è raggiunto un sequence point Prima del raggiungimento del sequence point, il valore delle variabili soggette ad side-effect è indefinito e quindi non devono essere usate Effetti collaterali 11 Effetti collaterali 12 In una stessa espressione quindi NON può comparire più di una volta una variabile soggetta a side-effect Il sequence point viene raggiunto solo prima di passare all istruzione successiva, salvo: dopo l esecuzione della prima parte delle espressioni contenenti gli operatori &&,,?: e l'operatore virgola, quando vengono valutate le espressioni di while, for, do, if, switch e return dopo che tutti gli argomenti di una funzione sono stati valutati, ossia subito prima della chiamata alla funzione stessa Esempi di errori x = i++ * i++; l incremento/decremento postfisso non è garantito venga effettuato subito dopo la valutazione (uso) della variabile: le due i potrebbero essere incrementate solo subito prima di passare all istruzione successiva (in questo caso x erroneamente conterrebbe i elevato al quadrato) x = ++a * --a; x = v[++i] - v[++i]; v[i] = i++; v[++i] = ++i; N.B. In queste espressioni ci sono anche problemi di ordine di valutazione

81 Ordine di valutazione Le regole di precedenza tra gli operatori e le parentesi impongono solo un parziale ordinamento nella valutazione delle espressioni Ad esempio in un espressione come x = f() + g() * h(); viene calcolata prima la moltiplicazione e poi la somma, ma non è noto in quale ordine le funzioni siano chiamate (e producano i valori) Per garantire un determinato ordine si usano variabili per mantenere i risultati intermedi Si può talvolta utilizzare un costrutto che inserisce un sequence point (&&,,, etc.) 13 Ordine di valutazione Esempi (di errori): printf("%d %f\n", ++n, tan(1./n)); non si sa se viene prima valutata l espressione ++n e poi calcolata la funzione tan()o viceversa, quindi non si sa se il valore di n usato in tan() è quello prima o dopo l incremento. La virgola che li separa non è un operatore, ma un separatore e quindi non garantisce alcun ordine di valutazione x = sin(1/++x) + cos(1/++x); non è noto se viene calcolata prima sin o prima cos, per cui gli argomenti di sin e cos possono essere diversi utilizzando compilatori diversi 14 Ordine di valutazione Esempi (di errori): x = sin(x++) + sin(x++); non è noto quale delle funzioni sin venga calcolata prima; essendo uguali non è importante, ma solo perché la chiamata a funzione inserisce un sequence point e quindi permette a x di essere riutilizzata nella stessa espressione x = i++ * i++; qui invece, non essendoci funzioni, non viene richiesto alcun sequence point, quindi NON è accettabile x = v[++i] - v[++i]; non si sa quali siano gli indici effettivi delle due v[++i] in quanto non è noto quale dei due venga valutato per primo prima di calcolare la sottrazione 15 Ordine di valutazione Esempi (di errori): in questi casi non è noto se venga valutata prima la parte a sinistra o quella a destra dell uguale, inoltre in una stessa espressione NON può comparire più di una volta una variabile soggetta a side-effect v[i] = ++i; v[i] = i++; non è noto quale valore di i venga usato in v[i] v[++i] = ++i; non è noto quale valore di i venga incrementato prima 16

82 Operatore virgola Unisce due espressioni in un unica espressione Permette di inserire due espressioni dove sintatticamente ne è prevista una sola: for (i=0, j=10; i<j ; i++, j--) L operatore virgola inserisce un sequencepoint: l espressione di destra viene valutata solo dopo che l espressione di sinistra è stata valutata completamente (side-effect inclusi) Le due espr. possono essere di tipo diverso: il tipo e il valore dell espressione composta sono quelli dell espressione di destra il valore dell espressione di sinistra è scartato 17 Operatore virgola In un contesto dove la virgola ha il significato di separatore, per inserire l operatore virgola si includono questo e suoi operandi tra parentesi: funz(a, (b=1,b+2), c); funz ha 3 parametri il secondo vale 3, b vale 1 Esempi x=2*4, 5*6; x=8, 30 scartato (la prima espressione è x=2*4, l altra 5*6) x=(2*4, 5*6); x=30, 8 scartato x=2*4, y=5*6; x=8, y=30 x=(2*4, y=5*6); 8 scartato, y=30, x=30 x=(y=2*4, 5*6); y vale 8, x = Espressione condizionale 19 Espressione condizionale 20 E un unica espressione che può assumere due valori in base ad una condizione x = (condizione)? espr1 : espr2; equivale a: if (condizione) x = espr1; else x = espr2; condizione viene valutata completamente prima delle expr (inserisce un sequence point), le parentesi sono opzionali ma consigliabili per chiarezza Viene calcolata una sola delle due expr Esempi Il massimo tra a e b: x = (a>b)?a:b; Il valore assoluto di a: x = (a>0)?a:-a; Plurale di una parola printf("%d oggett%c\n", k, (k==1)?'o':'i');

83 Espressioni condizionali Il tipo del risultato è sempre il più capiente tra quelli prodotti dalle due expr (flag == 1)? 11 : 12.0; restituisce sempre un valore double perché 12.0 è un double mentre 11 un int (quindi restituisce 11.0 quando flag vale 1) Ha associatività da destra a sinistra: x=(a>b && a>c)? a : (b>c)? b:c; equivale a: x=(a>b && a>c)? a : ((b>c)? b:c); Un espressione condizionale non produce un L-value quindi non si può scrivere: (a>b)?a:b = 12; 21 Precedenza e associatività () [] ->. S D! ~ ++ + * & (cast ) sizeof S D * / % S D + - (somma e sottrazione) S D << >> S D < <= > >= S D ==!= S D & S D ^ S D S D && S D S D?: S D = += = *= /= %= &= ^= = <<= >>= S D, S D S D: da Sinistra a Destra, S D: da Dst. a Sin. 22 La funzione system Esegue un comando di shell E definita in <stdlib.h> Sintassi system(stringa_con_comando); Esempio Pulisce lo schermo system("cls"); DOS/Windows system("clear"); Unix/Linux Esempio Sospende l esecuzione di un programma system("pause"); DOS/Windows 23 Operatori bitwise Operano su valori interi (con o senza segno) a livello dei singoli bit: ~ complemento a 1 ~x & AND bit a bit x & y OR bit a bit x y ^ EXOR bit a bit x ^ y << SHIFT a sinistra x << 2 >> SHIFT a destra x >> 2 Di solito si applicano a valori senza segno Hanno priorità inferiore agli oper. matematici Il risultato viene sempre convertito secondo le regole delle promozioni integrali (ad esempio, anche se x è di tipo short, ~x è di tipo int) 24

84 Operatori bitwise ~ << >> L operatore ~ inverte tutti i bit: ~0 dà un valore int pari a ( 1 in CA2) Gli operatori << e >> attuano uno shift del numero di posizioni indicato dall operando di destra: y = x << 2; Il numero di posizioni deve essere maggiore o uguale a zero e strettamente minore del numero di bit dell operando di sinistra L operatore << aggiunge sempre bit 0 a destra L operatore >> aggiunge bit 0 a sinistra solo se x è unsigned; se è signed non è definito Questi operatori hanno forma di assegnamento abbreviata: &= ^= = <<= >>= 25 Operatore bitwise & z = x & y; Ciascuno dei bit di z viene determinato calcolando l AND dei corrispondenti bit di x e y L operatore & viene spesso usato per azzerare ( unset o clear ) alcuni dei bit di un valore dato (x) lasciando invariati gli altri Per specificare quali bit debbano essere azzerati si predispone una maschera da mettere in AND bit a bit con x 26 Operatore bitwise & La maschera è un numero intero, i bit del numero dato x in corrispondenza di bit a 0 della maschera vengono azzerati (mascherati, non passano attraverso la mashera), mentre quelli in corrispondenza di bit a 1 restano invariati (passano attraverso la maschera) Esempio y = x & 015; valore: x = maschera: risultato: y Operatore bitwise z = x y; Ciascuno dei bit di z viene determinato calcolando l OR dei corrispondenti bit di x e y L operatore viene spesso usato per impostare a 1 ( set ) alcuni dei bit di un valore dato (x) lasciando invariati gli altri Per specificare quali bit debbano essere impostati a 1 si predispone una maschera da mettere in OR bit a bit con x 28

85 Operatore bitwise La maschera è un numero intero, i bit del numero dato x in corrispondenza di bit a 1 della maschera vengono impostati a 1, mentre quelli in corrispondenza di bit a 0 restano invariati Esempio y = x & 012; valore: x = maschera: risultato: y Operatore bitwise ^ z = x ^ y; Ciascuno dei bit di z viene determinato calcolando l EXOR dei corrispondenti bit di x e y L operatore ^ viene spesso usato per invertire alcuni dei bit di un valore dato (x) lasciando invariati gli altri Per specificare quali bit debbano essere invertiti si predispone una maschera da mettere in EXOR bit a bit con x 30 Operatore bitwise ^ La maschera è un numero intero, i bit del numero dato x in corrispondenza di bit a 1 della maschera vengono invertiti, mentre quelli in corrispondenza di bit a 0 restano invariati Esempio y = x & 016; valore: x = maschera: risultato: y Operatori bitwise e logici Quando le espressioni collegate dagli operatori hanno solo valori 0 e 1, si potrebbero utilizzare gli operatori bitwise al posto degli operatori logici, ma: la valutazione delle espressioni logiche non si ferma non appena il valore del risultato è individuabile non c è la garanzia di esecuzione da sinistra a destra degli operatori Potrebbe invece essere utile utilizzare l operatore bitwise EXOR in quanto non esiste il corrispondente operatore logico 32

86 Esercizi 1. Determinare di quanti bit è composto un tipo char, short, int e long contando i bit. 2. Visualizzare in binario il valore intero (decimale con segno) dato in input. 3. Scrivere un programma che chieda un valore intero decimale, lo visualizzi in binario e calcoli quanti sono i bit pari a 1 che contiene. 4. Scrivere un programma che chieda un valore decimale senza segno (da collocare in una variabile unsigned int) e ruoti i bit di n posizioni a sinistra o a destra a richiesta ( ruotare i bit significa che quelli che escono da una parte entrano dall altra). 33 Homework 3-4 Metodo di ordinamento Radix Sort Per ordinare numeri interi positivi Lavora facendo riordinamenti parziali prima sulle unità, poi sulle decine, poi sulle centinaia, ecc. Esempio Dati i valori: 7, 12, 9, 25, 36, 11, 20, 4 Scriverli utilizzando per tutti lo stesso numero di cifre: 07, 12, 09, 25, 36, 11, 20, 04 Definire una matrice avente per righe i valori delle cifre e per colonne i numeri dati Collocare i numeri sulla riga corrispondente alla cifra delle unità, ma nella stessa colonna 34 Homework 3-4 Collocamento dei valori in base alla cifra unità Homework 3-4 Raccogliere i valori riga per riga e ricollocarli in base alla cifra decine Raccoglierli nuovamente riga per riga (se ci fossero più cifre l operazione andrebbe ripetuta sulle centinaia, sulle migliaia, etc.)

87 Homework 3 Scrivere un programma che implementi il metodo Radix Sort di valori decimali (max 100 valori). 37 Homework 4 Scrivere un programma che implementi il metodo Radix Sort di valori decimali (max 100 valori) considerandone il valore binario equivalente (utilizzare gli operatori bitwise sulle variabili, assegnate con valori decimali). 38

88 I file 2 I file Ver Claudio Fornaro - Corso di programmazione in C File: sequenza di byte a cui il Sistema Operativo dà un nome ed una collocazione Stream: flusso di caratteri (come quelli provenienti dalla tastiera o mandati al video) I byte di un file sono idealmente raggruppati in blocchi detti record o campi (field) Un record è un blocco di byte che costituisce un oggetto (numero, stringa, struttura, etc.) Ogni lettura/scrittura legge/scrive record Tutte le funzioni e le costanti che riguardano i file sono definite in <stdio.h> Classificazione per accesso 3 Classificazione per contenuto 4 Per accedere ad uno specifico record è necessario posizionarsi al byte da dove inizia File ad accesso sequenziale hanno record di lunghezza variabile per accedere al record numero n bisogna leggere tutti i precedenti n -1 record per determinare il byte da dove esso inizia File ad accesso diretto (casuale, random) hanno record di lunghezza fissa la lunghezza L del record viene decisa dal programmatore file per file per accedere al record numero n si determina il byte da dove esso inizia con il calcolo: n *L File di testo Sono sequenze di caratteri organizzati in righe Ogni riga è terminata da un ritorno a capo Il file può essere terminato da un carattere di fine file Le funzioni di I/O per stringhe e caratteri gestiscono il carattere '\n' come generico ritorno a capo File binari Sono sequenze grezze di byte: nessun carattere né sequenza di caratteri viene interpretata in modo speciale (fine riga, fine file)

89 Ritorno a capo nei file di testo 5 La costante EOF 6 A seconda del sistema operativo utilizzato, il ritorno a capo nei file di testo è in realtà costituito da una sequenza di uno o più car.: MS-DOS/Win: CR+LF (codici ASCII 13 e 10: \r\n) Unix/Linux: LF (codice 10: \n) Le funzioni di I/O su file di testo considerano il carattere \n come un generico ritorno a capo, indipendentemente dalla sequenza di caratteri effettivamente utilizzata dal S.O. in uso: in input la sequenza di ritorno a capo propria del S.O. in uso viene trasformata in \n in output il carattere \n viene sostituito con la sequenza propria del sistema operativo in uso E una costante di tipo int definita da una #define e di solito pari al valore 1 Viene restituita da alcune funzioni di I/O per segnalare il raggiungimento della fine del file (alcune la usano anche come generica segnalazione di errore) Il valore numerico di EOF ( 1) non corrisponde al codice ASCII del carattere di fine file effettivamente usato alla fine di un file di testo (si tratta del carattere con codice ASCII 26 = CTRL-Z in DOS/Windows, del carattere 4 = CTRL-D in UNIX/Linux) Il tipo FILE 7 Apertura di un file 8 Per utilizzare un file bisogna definire una variabile, detta file pointer, che lo associ ad una struttura dati di tipo FILE (maiuscolo) che contiene le caratteristiche del file stesso: FILE *variabile; FILE *fp; La struttura dati FILE contiene un campo denominato offset (o file position pointer ) che tiene conto di quanti byte sono già stati letti o scritti (più precisamente: indica la posizione del prossimo byte da leggere o scrivere, calcolata rispetto al primo byte del file) Informa il Sistema Operativo che si vuole utilizzare un file in un certo modo e crea la struttura dati FILE da associare ad esso fp=fopen(nome, modo); nome è una stringa (costante o variabile) contenente il nome del file ed l eventuale percorso, la dimensione massima di tale stringa può essere FILENAME_MAX (costante definita in <stdio.h> modo è una stringa indicante la modalità di apertura del file (lettura o scrittura) Restituisce NULL se non riesce ad aprire il file (restituisce il puntatore alla struttura FILE corrispondente al file che è stato aperto)

90 Apertura di un file 9 Apertura di un file 10 Modi di apertura semplice per file di testo: "r" sola lettura (read), il file deve già esistere "w" sola scrittura (write), crea vuoto il file da scrivere; se esiste già, lo elimina e lo ricrea nuovo (quindi se ne perde il contenuto) "a" sola scrittura (append), se non esiste viene creato nuovo e vuoto, se invece esiste NON viene cancellato e i dati vengono aggiunti al fondo di quelli già presenti Le modalità "r+", "w+", "a+" sono dette di aggiornamento e permettono di leggere e scrivere lo stesso file (si veda più avanti) Verificare sempre il valore della fopen: un file in input potrebbe non esistere (o il percorso è sbagliato) un file in output potrebbe non poter essere creato (ad es. si trova su un disco o su una chiavetta USB protetta dalla scrittura, o il file da scrivere esiste già ed è protetto dalla cancellazione read only) E possibile aprire contemporaneamente più file o anche lo stesso file con lo stesso modo o in modi diversi, ma bisogna fere attenzione alla bufferizzazione dei file in output (bisogna fare il flush dei buffer, vedere più avanti) Apertura di un file 11 Chiusura di un file 12 if ((fp=fopen("c:\\file.txt","r"))==null) { fprintf(stderr, "Errore apertura\n"); return EXIT_FAILURE; } Notare le parentesi sottolineate: permettono di assegnare prima il valore restituito dalla fopen a fp e poi verificare se il valore di fp è NULL La doppia \\ serve per memorizzare una sola \ nella stringa: in una stringa una sola \ viene considerata carattere di escape per il successivo printf("file da aprire: "); gets(miofile); if ((fp=fopen(miofile,"r"))==null) { Informa il Sistema Operativo che è terminato l utilizzo di un certo file fclose(fp); Libera le risorse di sistema legate a quel file Automatica al termine del programma Fa il flush dei buffer di output (vedere più avanti)

91 Scrittura in un file di testo 13 Buffer di output 14 Ogni scrittura aggiunge caratteri in fondo al file Simile alla scrittura su video: in più bisogna specificare il file pointer (fp) associato al file fprintf(fp,stringaformato,listaespressioni); vedere printf fputc(c, fp); manda il carattere c nel file fp, c deve essere un valore intero (codice ASCII); dà EOF in caso di errore; se c è \n produce i caratteri che costituiscono il ritorno a capo nel S.O. in uso fputs(str, fp); come puts, salvo che non aggiunge un '\n' al fondo; dà NULL in caso di errore Per efficienza, normalmente i dati diretti a file/stream di output vengono temporaneamente accumulati in memoria e scritti sul disco solo quando i buffer sono pieni Questa porzione di memoria è detta buffer Lo svuotamento dei buffer è detto flush Il programmatore può chiedere lo svuotamento del buffer associato a fp con: fflush(fp); mentre per svuotare i buffer di tutti gli stream aperti in output: fflush(null); Lettura da un file di testo 15 Lettura da un file di testo 16 Ogni lettura inizia dal carattere successivo a quello dove era terminata la precedente lettura fscanf(fp,stringaformato,listavariabili); dà il numero di elementi letti da fp, EOF in caso di errore e fine file, vedere scanf x = fgetc(fp); legge un carattere da fp e lo mette in x (che deve essere un int); dà EOF in caso di errore o fine file; per restituire un carattere \n deve leggere tutta la sequenza di caratteri che nel S.O. in uso costituiscono il ritorno a capo (in DOS/Windows deve leggere un CR e un LF) fgets(str, n, fp); legge una riga intera da fp (fino al '\n' incluso) e la mette in str (compreso '\n'); legge fino a n-1 caratteri: se nel file la riga è più lunga gli altri caratteri restano per la lettura successiva; dà NULL in caso di errore e fine file

92 Buffer di input 17 Perché la fgetc richiede un int? 18 Non esiste una funzione standard C89 che permetta di svuotare i buffer di stream di input Lo standard C89 non prevede che la funzione fflush sia applicata a stream di input Alcuni compilatori permettono di svuotare i buffer di input utilizzando fflush, ma se si vuole un programma standard C89 non la si usi e si trovino soluzioni alternative che non richiedono flush (non è detto esistano) La funzione fgetc : preleva un singolo carattere da uno stream e ne restituisce il codice ASCII deve comunicare l eventuale raggiungimento della fine del file Poiché essa produce un solo valore, questo deve veicolare entrambe le informazioni I possibili valori restituiti possono essere 257: le 256 combinazioni degli 8 bit di un char un altro valore (EOF) per segnalare la fine del file quindi gli 8 bit di un char non bastano e ci vuole un tipo intero con almeno 16 bit: un int Perché la fgetc richiede un int? 19 Lettura da un file di testo 20 Esempio Supponiamo che un int abbia 16 bit e la costante EOF sia definita 1, il valore prodotto dalla fgetc: nel caso venga letto un carattere (no fine file) è il codice ASCII di quel car. (es. A = ): e un cast a char elimina la parte di sinistra nulla nel caso si sia riscontrata la fine del file è 1: N.B. 1 in complemento a 2 è composto da tutti 1 Bisogna sempre controllare se si è giunti alla fine del file Per determinare se si è raggiunta la fine del file (ossia non c è più niente da leggere): si controlla il valore restituito dalle funzioni di input mentre cercano di leggere si usa la funzione feof prima di fare la lettura Esempio corretto di lettura da file: while (fscanf(fp,"%d", &x)!= EOF) { istruzioni }

93 Lettura da un file di testo 21 Lettura da un file di testo 22 Esempio errato: while (fscanf(fp,"%d%d",&x,&y)!=eof) { istruzioni } La fscanf restituisce EOF solo se non riesce a leggere nessun valore, altrimenti restituisce il numero di valori letti e assegnati In questo esempio, se nel file è rimasto un solo valore la fscanf dà 1 e non EOF L esempio è corretto se a priori si sa che nel file i valori da leggere sono sempre a coppie La funzione feof(fp) verifica se si è raggiunta la fine del file Dà un risultato: diverso da 0 (vero) se il file è terminato (è stato letto tutto) 0 (falso) se c è almeno un byte da leggere (ma non indica quanti) Fornisce risultati corretti solo dopo la prima lettura (o tentativo di lettura se il file è vuoto), quindi non si può usare per stabilire se un file è vuoto oppure no prima di iniziare a leggerlo Lettura da un file di testo 23 Lettura da un file di testo 24 Esempio equivalente al precedente: if (fgets(riga, MAXRIGA, fp)==null) { fprintf(stderr, "File vuoto\n"); return EXIT_FAILURE; } rewind(fp); riporta l offset all inizio while (!feof(fp)) { fscanf(fp, "%d", &x); istruzioni } Le funzioni di lettura restituiscono la segnalazione di fine file quando la lettura di un record fallisce, NON alla lettura dell ultimo valore Quando le funzioni leggono i caratteri di un file, cercano di leggerne quanti più è possibile per costituire i record di input (esclusa la fgetc che legge un solo carattere per volta) Mentre leggono i caratteri per costituire l ultimo record di input, le funzioni (esclusa la fgetc) trovano la fine del file e quindi la feof può segnalarlo

94 Lettura da un file di testo 25 Lettura da un file di testo 26 La funzione ungetc(c, fp) rimanda il carattere c nel file fp come se non fosse mai stato letto Si può rimandare indietro un solo carattere dopo ogni lettura Il carattere c non viene in realtà rimandato nel file, ma viene collocato in un buffer di 1 byte (quindi non si può scrivere un file con chiamate successive a ungetc) Tutte le funzioni di input prima di prelevare i caratteri dal file prelevano il byte dal buffer (se c è) Esempio di utilizzo di ungetc Leggendo da un file, si vogliono saltare tutti i caratteri che precedono il primo numero intero per leggere quel numero intero (ad es. da Alfa beta123abc estrae il numero 123) while((c=fgetc(fp))!=eof &&!isdigit(c)) ; ungetc(c, fp); fscanf(fp, "%d", &x); Il ciclo while continua a leggere e scartare caratteri finché si tratta di caratteri non-cifra Quando trova l 1 esce e unget lo rimanda in fp Ora la fscanf può leggere il 123 Stream preesistenti 27 Stream preesistenti 28 Quando il programma viene mandato in esecuzione, il sistema operativo gli fornisce 3 stream già aperti: stdin collegato alla tastiera stdout collegato al video stderr collegato al video (non bufferizzato) Vi sono due stream di output perché: stdout dovrebbe essere utilizzato solo per visualizzare i risultati dell elaborazione stderr dovrebbe essere utilizzato solo per la diagnostica (gli errori del programma, non sono i dati prodotti come risultato dell elaborazione) fprintf(stderr, "File vuoto"); Si noti che: fprintf(stdout,"%d", ); equivale in tutto alla funzione printf("%d", ); Il sistema operativo può essere impostato in modo da mandare su dispositivi di output diversi gli stream dei risultati (stdout) e quello degli errori (stderr) mediante la redirezione dell I/O, le modalità dipendono dal SO utilizzato, a titolo di esempio in DOS/Windows si ha: programma >file_per_stdout programma 2>file_per_stderr programma >file_stdout 2>file_stderr programma >file_stdout_e_stderr 2>&1

95 Altre funzioni sui file 29 Gestione degli errori 30 remove(filename) cancella il filename (stringa) dal disco, restituisce un valore intero non nullo se non riesce rename(filevecchio, filenuovo) cambia nome a filevecchio (stringa) sul disco, restituisce un valore intero non nullo se non riesce rewind(fp) riporta l offset del file all inizio in modo che la successiva lettura/scrittura inizi dal primo carattere; azzera l eventuale indicazione di presenza di errori e di EOF fseek/ftell e fgetpos/fsetpos riposizionano l indicatore dell offset del file (per i file di testo ha problemi ed è usata raramen.) Quando si verifica un errore, alla variabile errno viene assegnato un valore intero che lo identifica (richiede <errno.h>, vedere la documentazione del compilatore per i valori) strerror(n) restituisce [un puntatore ad] una stringa contenente la descrizione dell errore il cui codice è n (normalmente n è errno), richiede <string.h> Gestione degli errori 31 Modalità di aggiornamento 32 ferror(fp) restituisce un valore intero non nullo se si è verificato un errore sullo stream fp, richiede <stdio.h> clearerr(fp) cancella le indicazioni di errori e di EOF relativi a fp, richiede <stdio.h> perror(stringa) visualizza stringa e un messaggio di errore corrispondente al valore in errno, richiede <stdio.h> L apertura di un file in modalità di aggiornamento permette la lettura e la scrittura dello stesso file mediante lo stesso file pointer (fp) Modi di apertura in aggiornamento: "r+" lettura/scrittura, il file deve esistere "w+" lettura/scrittura, crea il file vuoto "a+" lettura/scrittura al fondo, lo crea se non esiste, aggiunge alla fine se esiste già La differenza tra "r+" e "w+" è solo nell apertura: "r+" se il file non esiste dà errore "w+" se il file esiste già, lo ricrea vuoto

96 Modalità di aggiornamento 33 Modalità di aggiornamento 34 Il modo "a+" non cancella il contenuto di un file già esistente, posiziona il file position pointer alla fine del file e permette di scrivere solo in fondo al file Anche se con fseek si sposta l offset indietro, la scrittura avviene sempre aggiungendo record in fondo al file e i dati precedenti non possono quindi essere modificati (neppure quelli appena scritti, nemmeno se il file non è stato ancora chiuso) Con opportuna fseek è possibile leggere i dati da qualsiasi punto del file Tra un operazione di scrittura e una di lettura (o viceversa) sullo stesso file è necessario che ci sia una chiamata a fflush o a una funzione di posizionamento dell offset quale: rewind, fseek o fsetpos Se si deve leggere dal file un qualcosa che è possibile sia ancora nei buffer, è necessario chiamare la funzione fflush Considerazioni sull efficienza 35 Esercizi 36 Tutte le operazioni di I/O relative ai file sono intrinsecamente e notevolmente più lente di quelle che usano solo la memoria, nonostante la presenza di buffer e cache Conviene ridurre il più possibile l accesso al file (ma se è molto piccolo potrebbe essere contenuto interamente nel buffer e l accesso al file essere un mero accesso alla memoria) Quando è necessario utilizzare molte volte i dati contenuti in un file, conviene in genere caricarne il contenuto in memoria in opportune strutture dati 1. Scrivere con un editor un file contenente dei numeri, uno per riga. Sviluppare un programma che chieda all utente il nome di questo file, lo apra e calcoli e stampi a video quanti sono i valori letti, il valore massimo, il valore minimo, la somma e la media di tutti i valori presenti nel file. 2. Scrivere un programma che chieda all utente il nome di un file di testo e conti quante righe e quanti caratteri esso contiene (non si considerino i caratteri di ritorno a capo ed eventuale fine file).

97 Esercizi 3. Scrivere un programma che legga un file contenente coppie di numeri interi (x e y), una per riga e scriva un secondo file che contenga le differenze x y, una per riga. Si supponga che il file di input non abbia errori e che nomi dei file vengano chiesti all utente. File File Esercizi 4. Si scriva un programma che chieda il nome di un file contenente un testo qualsiasi e di questo conti quante sono le parole che iniziano con ciascuna lettera dell alfabeto. Esempio di output: Parole che iniziano con A: 45 Parole che iniziano con B: 12 Parole che iniziano con C: 27 Parole che iniziano con Z: 3 38 Esercizi 5. Un file di testo contiene, in ciascuna riga separati da spazi, 2 o 3 valori reali: 1. nel primo caso si tratta della base e dell altezza di un triangolo 2. nel secondo caso si tratta della base minore, della base maggiore e dall altezza di un trapezio Si scriva un programma che chieda all utente il nome di questo file, lo analizzi e indichi qual è la riga del file corrispondente alla figura geometrica di area maggiore e il valore di quest area. Se si trovano figure di aree uguali, si consideri la prima di queste. 39 Esercizi 6. Scrivere un programma che legga da un file un testo e spezzi su più righe quelle più lunghe di N caratteri (N chiesto all utente). La riga viene spezzata anche in mezzo ad una parola o prima di uno spazio. L output deve essere salvato in un altro file. Ad esempio, se viene letta la riga seguente (è una sola, qui è scritta su due righe perché troppo lunga): Mi devo recare all ufficio postale precipitevolissimevolmente l output (con N pari a 22) deve essere: Mi devo recare all uf ficio postale precipi tevolissimevolmente 40

98 Esercizi 7. Come il precedente, ma le righe si possono spezzare solo dove c è uno spazio (che non va visualizzato a capo), se negli N caratteri non c è uno spazio, si spezzi la parola all N-esimo carattere come nell esercizio precedente. Esempio di output con N pari a 22: Mi devo recare all ufficio postale precipitevolissimevolm ente 41 Esercizi 8. Merge di due file Due file di testo contengono, disposti variamente su più righe, dei numeri interi. Ciascuno dei due file è già ordinato in senso CRESCENTE e non è noto a priori quanti elementi contenga (possono anche avere un numero di valori differente). Si scriva un programma che legga questi due file e ne produca un terzo contenente i valori dei due file ordinati tutti quanti in senso CRESCENTE. Non si utilizzino vettori né algoritmi di ordinamento. I nomi dei tre file vengano chiesti all utente. 42 Esercizi 9. Il cifrario di Cesare Si scriva un programma che cifri/decifri un file di testo in base ad un codice segreto. Il programma deve chiedere il nome del file di input, il nome del file di output e il codice segreto (un numero intero). La cifratura avviene sostituendo le sole lettere (maiuscole e minuscole separatamente) con altre lettere in base al valore del codice. 43 Esercizi (Continuazione) Il valore del codice indica di quanti caratteri si deve traslare ciascuna delle lettere: se ad es. il codice è 3, allora A D, B E,... X A, Y B, Z C. La corrispondenza completa delle lettere maiuscole (per le minuscole è analogo) è: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z D E F G H I J K L M N O P Q R S T U V W X Y Z A B C Ad esempio, con codice=3 Ciao diventa Fldr. Si noti che per decifrare un testo basta inserire come codice il valore opposto a quello usato per la cifratura (qui 3). 44

99 Esercizi 10. Simile al precedente, ma la sostituzione delle lettere avviene secondo il seguente algoritmo. Il codice segreto di cifratura/decifratura è costituito da una parola composta di sole lettere tutte diverse. La prima lettera del codice viene fatta corrispondere alla lettera A, la seconda alla B, e così via fino all ultima lettera del codice. Le lettere non indicate nel codice vengono riordinate alfabeticamente al contrario (dalla Z alla A) e fatte corrispondere alle restanti lettere dell alfabeto (maiuscole e minuscole separatamente). 45 Esercizi (Continuazione) Il programma chiede l introduzione di una parola chiave da cui, eliminando i caratteri duplicati e le non-lettere, si ha il codice segreto. Ad esempio, dalla parola chiave Fragola si ottiene il codice segreto FRAGOL e la corrispondenza delle lettere è: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z F R A G O L Z Y X W V U T S Q P N M K J I H E D C B Ad esempio, la parola Ciao viene cifrata in Axfq (notare maiuscole e minuscole). Il programma deve chiedere se si vuole cifrare o decifrare il file. 46 Esercizi 11. Si scriva un programma che chieda all utente il nome di un file contenente la griglia di valori di un Sudoku risolto (N righe con N valori). Il programma deve verificare ciascuna riga, ciascuna colonna e ciascun riquadro e segnalare le righe, le colonne e i riquadri che non soddisfano le condizioni di validità. Condizione di validità: non ci devono essere valori ripetuti. Il programma accetti automaticamente griglie di lato massimo 25 numeri. 47 Esercizi 12. Una superficie viene descritta da una matrice di valori in floating-point in doppia precisione composta di R righe e C colonne (costanti note a priori). Ogni elemento della matrice corrisponde ad un quadrato di dimensioni 1cm x 1cm. I valori rappresentano l altezza media di quella superficie in quel punto e sono letti da un file. Tale file contiene C valori (separati da spazi) per ciascuna delle R righe e non ha errori. Il primo elemento della matrice corrisponde alla parte di superficie situata più a nord-ovest e ha coordinate (0,0). 48

100 Esercizi 49 Esercizi 50 (Continuazione) Si noti che le coordinate X corrispondono alle colonne, le Y alle righe. Si suppone di voler simulare il comportamento di una pallina che viene collocata su uno dei riquadri della superficie (le cui coordinate sono richieste all utente) e poi lasciata libera. Il programma deve visualizzare la sequenza delle coordinate di tutte le posizioni che vengono attraversate dalla pallina fino a quando si ferma, inclusa la posizione iniziale, con l indicazione dell altezza in quel punto. (Continuazione) Si usa un modello fisico semplificato dove la pallina: 1. può scendere in ciascuna delle 8 caselle che la circondano (se ci sono, ossia non è ai bordi) 2. scende lungo il cammino più ripido, ossia delle 8 caselle raggiunge quella più in basso (se ce ne sono più di una all altezza minima si scelga la prima trovata) 3. non ha inerzia (cioè scende sempre lungo il cammino più ripido) 4. non può/deve uscire dai limiti della superficie 5. si ferma quando non ci sono caselle più in basso della posizione che occupa Esercizi 51 Esercizi 52 (Continuazione) Posizione iniziale - coordinata X (1-20): 5 - coordinata Y (1-30): 3 Percorso della pallina (5,3) [20.42] 1 - (6,2) [15.21] 2 - (7,3) [11.11] 3 - (7,4) [7.235] 4 - (8,5) [3.66] 5 - (9,4) [-1.7] 6 - (9,3) [-3.45] 13. Si modifichi l esercizio precedente considerando un modello fisico in cui la pallina possa spostarsi solo in diagonale 14. Come il precedente, ma la pallina possa spostarsi solo in direzione nord/sud/est/ovest (orizzontale/verticale).

101 Homework 5 Si scriva un programma per gestire un agenda elettronica. I dati relativi alle persone sono: nome, cognome, indirizzo, telefono e nota; NON possono contenere spazi e sono contenuti in un file di testo denominato database.txt. Viene presentata un interfaccia a menu per a) inserire, b) togliere, c) cercare i dati relativi ad una persona (solo in base al cognome), d) visualizzare tutte le coppie nome/cognome senza dettagli ed e) uscire dal programma. Le persone sono al massimo 200, i dati devono essere copiati tutti in memoria quando il programma parte e scritti solo alla fine. 53

102 Algoritmi Ver. 2.2 Algoritmi di ordinamento Si vuole ordinare in senso [de]crescente il contenuto di un vettore di N elementi senza utilizzare un secondo vettore Esiste molta letteratura scientifica a riguardo, ma qui ci si limita a tre algoritmi di esempio molto semplici per l ordinamento in senso crescente (o meglio, non-decrescente): Selection sort Bubble sort Insertion sort Claudio Fornaro - Corso di programmazione in C Selection sort semplificato - I Si confronta il primo elemento del vettore con tutti gli altri, man mano che si trova un valore minore del primo, lo si scambia con il primo. Dopo questa operazione il valore minimo è nella prima posizione (pos 0) del vettore for (j= 1; j<n; j++) if (v[j] < v[0]) { tmp = v[j]; v[j] = v[0]; v[0] = tmp; } è il successivo a Selection sort semplificato - II Se si ripete lo stesso identico procedimento per tutti gli elementi a partire dal secondo, si determina il secondo valore più piccolo e lo si colloca al secondo posto for (j= 2; j<n; j++) if (v[j] < v[1]) { tmp = v[j]; v[j] = v[1]; v[1] = tmp; } è il successivo a

103 Selection sort semplificato - III Se si ripete lo stesso identico procedimento per tutti gli elementi a partire dall i-esimo, si determina l i-esimo valore più piccolo e lo si colloca all i-esimo posto è il successivo a i for (j=i+1; j<n; j++) if (v[j] < v[i]) { tmp = v[j]; v[j] = v[i]; v[i] = tmp; } 5 Selection sort semplificato -IV Se si ripete questo procedimento per tutti i valori di i da 0 fino al penultimo (l ultimo va a posto da sé) si ottiene l ordinamento in senso crescente di tutto il vettore for (i=0; i<n-1; i++) for (j=i+1; j<n; j++) if (v[j] < v[i]) { tmp = v[j]; v[j] = v[i]; v[i] = tmp; } 6 Selection sort - I Il primo passo è quello di individuare il minimo tra gli elementi del vettore e scambiarlo con quello nella prima posizione, il primo valore è ora al posto giusto jmin = 0; for (j= 1; j<n; j++) if (v[j] < v[jmin]) jmin = j; tmp = v[jmin]; v[jmin] = v[0]; v[0] = tmp; è il successivo a Selection sort - II Se si ripete lo stesso identico procedimento per tutti gli elementi a partire dal secondo, si determina il secondo valore più piccolo e lo si colloca al secondo posto jmin = 1; for (j= 2; j<n; j++) if (v[j] < v[jmin]) jmin = j; tmp = v[jmin]; v[jmin] = v[1]; v[1] = tmp; è il successivo a

104 Selection sort - III Se si ripete lo stesso identico procedimento per tutti gli elementi a partire dall i-esimo, si determina l i-esimo valore più piccolo e lo si colloca all i-esimo posto è il successivo a i jmin = i; for (j=i+1; j<n; j++) if (v[j] < v[jmin]) jmin = j; tmp = v[jmin]; v[jmin] = v[i]; v[i] = tmp; 9 Selection sort - IV Se si ripete questo procedimento per tutti i valori di i da 0 fino al penultimo (l ultimo va a posto da sé) si ottiene l ordinamento in senso crescente di tutto il vettore for (i=0; i<n-1; i++) { jmin = i; for (j=i+1; j<n; j++) if (v[j] < v[jmin]) jmin = j; tmp = v[jmin]; v[jmin] = v[i]; v[i] = tmp; } 10 Bubble sort - I Se si scorrono tutti gli elementi di un vettore e ogni volta che si trovano due valori ADIACENTI non in ordine (il più piccolo dei 2 a destra del più grande) li si scambia: il più grande di tutti risale a destra for (j=0; j<n-1; j++) if (v[j] > v[j+1]) { tmp = v[j]; v[j] = v[j+1]; v[j+1] = tmp; } Bubble sort - II Ripetendo N-1 volte questa operazione, tutti i valori risalgono verso destra fino ad occupare la posizione corretta e quindi vengono ordinati in senso crescente for (i=0 ; i<n-1; i++) for (j=0; j<n-1; j++) if (v[j] > v[j+1]) { tmp = v[j]; v[j] = v[j+1]; v[j+1] = tmp; } 12

105 Bubble sort - III Inefficienza: gli ultimi valori vengono in ogni caso confrontati, anche quando sono già stati collocati; per evitare perdita di tempo in questi controlli inutili si ferma prima il ciclo interno, sfruttando il ciclo esterno for (i=n-1; i>0 ; i--) for (j=0; j<i ; j++) if (v[j] > v[j+1]) { tmp = v[j]; v[j] = v[j+1]; v[j+1] = tmp; } 13 Bubble sort - IV Inefficienza: se pochi valori sono fuori posto e l ordinamento si ottiene prima delle N-1 passate, i cicli continuano ad essere eseguiti. Soluzione: termina se non ci sono stati scambi: scambi = SI; for (i=n-1; i>0 && scambi ;i--) { scambi = NO; } for (j=0; j<i ; j++) if (v[j] > v[j+1]) { scambi = SI; tmp = v[j]; v[j] = v[j+1]; v[j+1] = tmp; } continua solo se ci sono stati scambi 14 Insertion sort - I Per ciascuno degli elementi a partire dal 2 o (l elemento da sistemare si chiama chiave): 1. si copia il valore della chiave in una variabile (key) 2-3. si scalano a destra di una posizione tutti gli elementi precedenti alla chiave finché non ne viene trovato uno con valore inferiore a key 4. al posto di quest ultimo viene messo key Notare che gli elementi a sinistra della chiave sono sempre già ordinati in senso crescente grazie alle passate precedenti Insertion sort - II for (j=1; j<n; j++) { key = v[j]; for (i=j-1; i>=0 && v[i]>key; i--) v[i+1] = v[i]; v[i+1] = key; } key 3 1 ordinati

106 Esercizi 17 Algoritmi di ricerca Per ciascuno degli algoritmi di ordinamento precedentemente descritti (variazioni incluse), si scriva un programma completo che ordini in senso crescente (o meglio, non-decrescente) un vettore di N numeri interi. 2. Per ciascuno degli algoritmi di ordinamento precedentemente descritti (variazioni incluse), si scriva un programma completo che ordini in senso decrescente (o meglio, noncrescente) un vettore di N numeri interi. Si vuole cercare un dato valore val in un vettore vett di N elementi Si possono considerare i due casi: vettore non ordinato vettore ordinato Ricerca in vettore non ordinato 19 Ricerca in vettore ordinato Lineare 20 Bisogna scorrere tutto il vettore for (i=0; i<n; i++) if (val == vett[i]) break; if (i == N) printf("non trovato\n"); else printf("trovato\n"); Quando lo trova, interrompe il ciclo Se non lo trova, i raggiunge il valore N Bisogna determinare se il ciclo for è terminato per effetto del break (trovato) o no Al massimo fa N controlli Non serve scorrere tutto il vettore, si ferma non appena supera il valore cercato for (i=0; i<n && vett[i]<val; i++) ; if (i<n && val == vett[i]) printf("trovato\n"); else printf("non trovato\n"); Quando vett[i] >= val interrompe il ciclo e controlla se è stato trovato Al massimo fa N controlli

107 Ricerca in vettore ordinato Dicotomica (o binaria) left=0; right=n-1; while (right>=left) { m=(left+right)/2; if (val==vett[m]) break; if (val<vett[m]) right=m-1; elimina la metà destra else left=m+1; elimina la metà sinistra } if (val==vett[m]) printf("trovato\n"); else printf("non trovato\n"); 21 Ricerca in vettore ordinato Dicotomica (o binaria) Inizializza due indici left e right al primo e all ultimo indice del vettore (0 e N-1) Calcola l indice del valore centrale: m Se vett[m] è il valore cercato, termina Altrimenti se val è minore del valore centrale, arretra l indice destro right al centro m (così dimezza il numero di valori in cui cercare) Altrimenti se val è maggiore del valore centrale, avanza l indice sinistro left al centro Ripetendo questa operazione, si dimezza ogni volta il vettore Veloce: al massimo fa log 2 N + 1 controlli 22 Programmazione a stati Per stato si intende la situazione del programma in un dato istante (es. sta leggendo i caratteri di una parola ) Mentre il programma viene eseguito esso cambia di stato in seguito al verificarsi di eventi (es. lettura di un carattere da analizzare), la transizione può portare allo stesso stato di partenza (anello) 23 Programmazione a stati Graficamente: gli stati sono disegnati come cerchi o ovali le transizioni sono disegnate come archi orientati nei punti di partenza degli archi si indicano gli eventi che causano le transizioni di stato se appropriato (c è spazio, non sono troppe informazioni) sugli archi si indicano anche le azioni da intraprendere nelle transizioni evento Stato1 evento azione azione azione azione evento Stato2 evento 24

108 Programmazione a stati 25 Programmazione a stati 26 Nel codice l informazione sullo stato viene mantenuta tramite una variabile di stato, in genere di tipo enum o int Tipicamente i valori che la variabile di stato può assumere sono definiti da enum o con #define Se i valori sono costanti e dagli stati partono diverse possibili transizioni, lo switch è spesso il costrutto di elezione Esempio (es. 4 sui file) Si scriva un programma che chieda il nome di un file contenente un testo qualsiasi e di questo conti quante sono le parole che iniziano con ciascuna lettera dell alfabeto. Esempio di output: Parole che iniziano con A: 45 Parole che iniziano con B: 12 Parole che iniziano con C: 27 Parole che iniziano con Z: 3 Programmazione a stati 27 Programmazione a stati 28 La variabile di stato indicherà se si stanno leggendo i caratteri di una parola (si è dentro una parola) o no (si è fuori da una parola) e quindi assumerà 2 possibili valori: DENTRO FUORI L evento che può causare il cambiamento di stato è la lettura del carattere successivo In questo esempio ci sono solo 4 casi: stato FUORI e si legge un carattere alfabetico stato DENTRO e si legge un carattere alfabetico stato DENTRO e si legge un carattere non-alfabetico stato FUORI e si legge un carattere non-alfabetico Più in dettaglio, se: 1. stato FUORI e si legge un carattere alfabetico: entra DENTRO e incrementa il contatore di quel car. 2. stato DENTRO e si legge un carattere alfabetico: resta DENTRO e non fa nulla 3. stato DENTRO e si legge un carattere non-alfab.: va FUORI dalla parola e non fa nulla 4. stato FUORI e si legge un carattere non-alfabetico: resta FUORI e non fa nulla

109 Programmazione a stati 29 Esercizi 30 Rappresentazione grafica corrispondente all esempio (no-op=no operation): FUORI no-op 1.lettera 4.non-lettera incrementa contatore lettera no-op 3.non-lettera no-op DENTRO 2.lettera 3. (Es. 4 sui file) Si scriva un programma che chieda il nome di un file contenente un testo qualsiasi e di questo conti quante sono le parole che iniziano con ciascuna lettera dell alfabeto. Lo si risolva con una soluzione a stati. Esempio di output: Parole che iniziano con A: 45 Parole che iniziano con B: 12 Parole che iniziano con C: 27 Parole che iniziano con Z: 3 Homework 6 31 Homework 6 32 Si scriva un programma che per ciascuno degli algoritmi di ordinamento visti (bubble semplice e con variazioni, selection semplificato, selection, insertion) per M volte (es. 100) riempia un vettore di N valori interi casuali (es. 1000) e lo riordini. Il programma deve misurare i tempo impiegato per gli M ordinamenti per ciascuno degli algoritmi. Impostare M e N in modo da eseguire un numero significativo di prove con vettori di dimensioni grandi ( ), medi ( ) e piccoli (100). Trarne le conclusioni. Continuazione: Includendo <time.h> si possono usare le seguenti funzioni: time(null) restituisce un numero intero di tipo time_t che corrisponde all istante attuale difftime(time_t t2, time_t t1) restituisce (come valore double) il numero di secondi intercorsi tra t1 e t2 Nota: il tipo time_t è in genere un sinonimo di long e si possono definire variabili di tale tipo: time_t ora, t1, t2; t1=time(null);

110 Puntatore 2 I puntatori Ver Claudio Fornaro - Corso di programmazione in C E una variabile che contiene l indirizzo di memoria di un oggetto (variabile o costante) Esempio p è un puntatore e punta alla variabile x che in questo esempio ha indirizzo di memoria A55) x:a55 p A55 12 A55 A54 A53 A52 A51 A A55 x p Definizione 3 Assegnazione 4 Sintassi tipo *nomevariabile; Esempi int *punt; char x, *p, *q, y; x e y sono variabili di tipo char p e q sono variabili di tipo puntatore-a-char Ad un puntatore si possono assegnare solo indirizzi di memoria o il valore NULL che indica che il puntatore non punta a nulla La costante 0 in un contesto dove è atteso un puntatore (inizializzazione e assegnamento di un puntatore, confronto con un puntatore) viene convertita in NULL dal compilatore L operatore di indirizzo & ( ampersand ) calcola l indirizzo di memoria di una variabile (si dice impropriamente che ne dà il puntatore) int x = 12, y = 23; int *p = &x; inizializza p con l indirizzo di x p = &y; assegna a p l indirizzo di y

111 Utilizzo All oggetto puntato da un puntatore si accede per mezzo dell operatore di deferimento * int x=12, *p = &x; p punta a x *p = 24; x ora vale 24 *p += 6; x ora vale 30 p è il puntatore *p è l oggetto puntato (qui è x) Sia p sia *p sono L-value modificabili L operatore * ha priorità superiore a quella degli operatori matematici x = 6 * *p; equivale a: x = 6 * (*p); Per visualizzare il valore di un puntatore si può utilizzare la direttiva %p in una printf 5 Tipi e puntatori L informazione relativa al tipo è necessaria per permettere ai puntatori di conoscere la dimensione dell oggetto puntato (usata nell aritmetica dei puntatori) Poiché oggetti di tipo diverso possono avere dimensione diversa, l assegnazione tra puntatori di tipo diverso è in genere errata e il compilatore genera un warning int *p, x=12; long *q, y=26; p = &x; OK! q = &y; OK! q = p; NO! Warning q = &x; NO! Warning 6 Puntatori a void Sono puntatori generici e non possono essere deferenziati (non si possono usare come *p), possono essere utilizzati solo come contenitori temporanei di valori di tipo puntatore (a qualsiasi tipo) void *h; Non serve il cast (void *) per copiare un puntatore non-void in un puntatore void h = p; (supponendo ad esempio int *p) Qualsiasi tipo di puntatore può essere confrontato con un puntatore a void NULL è definito come: (void *)0 7 Puntatori a void Per deferenziare il valore di un puntatore a void bisogna prima assegnarlo ad un puntatore del tipo appropriato (non void) Può essere necessario il cast (tipo *) per copiare un puntatore void in un puntatore non-void (i compilatori C puri non lo richiedono, i compilatori C++ sì) int *q; q = h; OK, compilatore C q = (int *)h; OK, compilatore C++ *q = 23; x ora contiene 23 8

112 Puntatori e vettori Il nome (senza parentesi) di un vettore-di-t è un valore costante (impropriamente detto puntatore) di tipo puntatore-a-t, corrisponde all indirizzo di memoria del primo elemento di vettore int vett[100]; int *p; p = vett; l indirizzo di memoria di vett viene messo in p, equivale a scrivere: p = &vett[0] (le parentesi hanno priorità maggiore di &) 9 Puntatori e vettori Attenzione: vett = p; NO! Non si può assegnare un valore a vett in quanto NON è una variabile puntatore, ma un sinonimo di un indirizzo di memoria Gli indirizzi di memoria sono valori costanti stabiliti dal compilatore e non hanno uno spazio in memoria modificabile per contenere un valore 10 Equivalenza puntatori e vettori Una variabile di tipo puntatore-a-t, assegnata in modo che punti a (cioè contenga l indirizzo di) un oggetto di tipo vettore-di-t, può essere utilizzata come un vettore-di-t int vett[25]; int *p = vett; vett: p Ad esempio, qui p[3] equivale a vett[3] Il compilatore internamente trasforma le espressioni con notazione vettoriale [] in espressioni con i puntatori 11 Aritmetica dei puntatori Quando un puntatore punta ad un vettore, gli può essere sommato un valore intero N, il risultato è l indirizzo di memoria dell elemento di posizione N del vettore vett: p p+3 p punta a (contiene l'indirizzo di) vett p+3 punta a (produce l'indir. di) vett[3] *(p+3) equivale a vett[3] 27 12

113 Aritmetica dei puntatori L istruzione p++ porta p a puntare a vett[1] (ne contiene l indirizzo) quindi ora p punta a vett[1] e p[3] corrisponde a vett[4] p vett: p+3 E lecito sottrarre un valore N ad un puntatore se l elemento puntato risultante fa ancora parte del vettore (nell esempio precedente p-1 punta a vett[0]) Equivalenza puntatori e vettori Il nome di un vettore può essere utilizzato come puntatore costante: *(vett+2) equivale a: vett[2] vett++ è un errore (vett è costante!) Il nome di un vettore di T (es. int) è una costante di tipo indirizzo-di-vettore-di-t, ma in C viene decade al tipo puntatore-a-t, così che vett è visto come puntatore (costante) a int e vett+1 punta a vett[1] Eccezione: &vett è di tipo puntatore-a-vettore-di-int (non decade) 14 Equivalenza puntatori e vettori Una variabile di tipo puntatore-a-t non sa se il valore (scalare) a cui punta è singolo o è l elemento di un vettore, lo sa solo il programmatore e sta a questi utilizzarlo in modo coerente int x = 10, vett[10], *p, *q; p = &x; p++; NO! Non esiste l oggetto puntato da p+1 q = p+1; NO! Idem p = vett; p++; SI! Ora p punta a vett[1] q = p+1; SI! Ora q punta a vett[2] 15 Aritmetica dei puntatori Attenzione che il puntatore non sfori i limiti del vettore (lo standard non richiede che il compilatore faccia controlli, molti compilatori lo offrono opzionalmente, ma ciò riduce le prestazioni) E lecito che un puntatore punti a quello che sarebbe l elemento del vettore successivo all ultimo, ma questo puntatore può essere utilizzato solo per calcolare la differenza tra puntatori (vedere prossima slide) 16

114 17 18 Aritmetica dei puntatori Priorità dell operatore * Due puntatori a elementi dello stesso vettore possono essere sottratti, il valore ottenuto + 1 è il numero di elementi del vettore compresi tra quelli puntati dai due puntatori (inclusi): p = &vett[4]; q = &vett[10]; d = q-p+1; 7: numero degli elementi dalla posizione 4 alla posizione 10 inclusi Due puntatori possono essere confrontati solo se fanno parte dello stesso vettore oppure uno dei due è NULL (o 0) Dalla tabella delle priorità si vede che l operatore di deferimento * ha priorità quasi massima, inferiore solo alle parentesi (e a -> e a. ) e associatività da destra a sinistra Quindi, considerando che gli operatori * e ++ hanno stessa priorità e associatività da D a S: *p++ equivale a *(p++) incrementa p *++p equivale a *(++p) incrementa p ++*p equivale a ++(*p) incrementa *p (*p)++ incrementa *p *p+1 equivale a (*p)+1 e non a *(p+1) Copia di stringhe 1 a versione 19 Copia di stringhe 2 a versione 20 La stringa y viene copiata in x char x[30], y[30], *t=x, *s=y; int i=0; gets(y); while (s[i]!= '\0') { t[i] = s[i]; i++; } t[i] = '\0'; printf("%s\n", x); Il '\0' viene copiato fuori dal ciclo Qui s e t vengono inutilmente usati come semplici sinonimi di x e y, non come puntatori La stringa y viene copiata in x char x[30], y[30], *t=x, *s=y; int i=0; gets(y); while ((t[i] = s[i])!= '\0') i++; printf("%s\n", x); Il '\0' viene copiato nel ciclo stesso Qui s e t vengono inutilmente usati come semplici sinonimi di x e y, non come puntatori Nota: non si può scrivere t[i] = s[i++] nella condizione del while (side effect)

115 Copia di stringhe 3 a versione 21 Copia di stringhe 4 a versione 22 La stringa y viene copiata in x char x[30], y[30], *t=x, *s=y; gets(y); while ( (*t = *s)!= '\0') { t++; s++; } printf("%s\n", x); Il '\0' viene copiato nel ciclo stesso Nota:!= '\0' può essere omesso La stringa y viene copiata in x char x[30], y[30], *t=x, *s=y; gets(y); while (*t++ = *s++) ; printf("%s\n", x); Il '\0' viene copiato nel ciclo stesso Nota: l'espressione nulla è più chiara se scritta in questo modo Puntatori e stringhe Si noti la differenza tra le seguenti definizioni: char str[100]; RISERVA spazio per contenere i caratteri, è una variabile e il suo contenuto può essere modificato char *s; NON RISERVA spazio per contenere i caratteri, quindi per essere utilizzata come stringa deve essere assegnata con una stringa vera Assegnata con stringa variabile: s = str; scanf("%s", s); SI Assegnata con stringa costante: s = "salve"; scanf("%s", s); NO 23 Puntatori e stringhe Si considerino i seguenti esempi: char str[] = "ciao"; E l inizializzazione di una variabile stringa: il compilatore riserva memoria per str e vi copia i caratteri di "ciao"; la stringa costante "ciao" non esiste in memoria: è stata usata dal compilatore per inizializzare la stringa str, ma esiste in memoria la stringa variabile "ciao" str[0]='m'; SI char *s = "hello"; E l inizializzazione di una variabile puntatore: il compilatore determina l indirizzo della stringa costante "hello" (che esiste in memoria) e lo assegna alla variabile puntatore s s[0]='b'; NO! "hello" è costante! 24

116 Puntatori e stringhe Si considerino i seguenti esempi (cont.): s = "salve"; E l assegnazione ad una variabile puntatore: il compilatore determina l indirizzo della stringa costante "salve" (che esiste in memoria) e lo assegna alla variabile puntatore s s[4]='o'; NO! "salve" è costante! s = str; E l assegnazione ad una variabile puntatore: il compilatore determina l indirizzo della stringa variabile str (che esiste in memoria) e lo assegna alla variabile puntatore s s[0]='m'; SI 25 Funzioni sui blocchi di byte Blocchi di byte: sequenze di caratteri (byte) qualsiasi, il carattere '\0' è un carattere come gli altri e non viene utilizzato come terminatore (non c è alcun terminatore) Una porzione di memoria (riservata in qualsiasi modo, ad esempio definendo una variabile, una stringa, etc.) viene trattata come semplice blocco di byte da alcune funzioni contenute in <string.h> Nelle seguenti funzioni s (sta per source) e t (sta per target ) sono puntatori a void (possono ad esempio essere nomi di stringhe che però sono trattate come blocchi di byte) 26 Funzioni sui blocchi di byte memcpy(s,t,n) copia n byte da t a s memmove(s,t,n) copia n byte da t a s (i blocchi possono anche essere sovrapposti) memcmp(s,t,n) confronta byte per byte secondo il codice ASCII i primi n byte di s e di t, il valore restituito è un int come quello della strcmp memchr(s,c,n) cerca il byte c tra i primi n byte di s, dà NULL se non lo trova o il puntatore ad esso se trova memset(s,c,n) copia il byte c in tutti i primi n byte di s 27 Puntatori const Puntatore variabile a valore costante int const *p; const int *p; }sono equivalenti p è una variabile di tipo puntatore-a-costante (a un oggetto costante di tipo int) int x, y; const int *p; puntatore-a-costante p = &x; SI, p è una variabile p = &y; SI, p è una variabile *p = 13; NO, *p è costante Nell ultimo esempio si può modificare il valore di y direttamente con y=13 perché l oggetto puntato da p ha un nome (cioè non è accessibile esclusivamente tramite il puntatore) e non è stato definito come costante 28

117 Puntatori const Puntatore variabile a valore costante Assegnare ad un puntatore-a-variabile un valore di tipo puntatore-a-costante (l indirizzo di un valore costante) genera un Warning da parte del compilatore perché dà un modo per by-passare la restrizione (const) int *p; puntatore-a-variabile const int *q; puntatore-a-costante const int x = 12; p = &x; Warning: *p=5 non darà errore q = &x; OK: *p=5 darà errore 29 Puntatori const Puntatore costante a valore variabile int * const p; p è una costante di tipo puntatore-a-variabile (a un oggetto variabile di tipo int) Le costanti possono essere solo inizializzate int x, y; int * const p = &x; inizializzazione *p = 13; SI, *p è una variabile p = &y; NO, p è una costante 30 Esercizi 1. Si scriva un programma che chieda una stringa all utente e conti quanti siano i caratteri che la costituiscono, NON si usi la funzione strlen della libreria standard. 2. Scrivere un programma che verifichi se la stringa data in input è palindroma o no 3. Scrivere un programma che chieda n valori interi (massimo 100), li collochi in un vettore e inverta il vettore (scambiando il primo elemento con l'ultimo, il secondo con il penultimo, etc.). Si usi la notazione vettoriale. 4. Come il precedente, ma si usino i puntatori. 31 Esercizi 5. Scrivere un programma che data una stringa in input dica se la stessa contiene almeno una A tra i primi 10 caratteri. 6. Si scriva un programma che chieda all utente 2 stringhe e concateni la seconda alla fine della prima, NON si usi la funzione strcat della libreria standard, si usino i puntatori e non la notazione vettoriale. Attenzione a terminarla con il carattere \0. 7. Si scriva un programma che chieda all utente 2 stringhe e indichi se la seconda è uguale alla parte terminale della prima. 32

118 Esercizi 8. Scrivere un programma che chieda N valori (massimo 100), li collochi in un vettore e li ordini in senso crescente (senza usare vettori ausiliari). 9. Scrivere un programma che verifichi se la stringa data è composta di due parti uguali, trascurando il carattere centrale se la lunghezza è dispari (es. CiaoCiao, CiaoXCiao ). 10. Scrivere un programma che date 2 stringhe in input indichi quante volte la più corta è contenuta nella più lunga. 33 Esercizi 11. Scrivere un programma che legga da un file un testo e spezzi su più righe quelle più lunghe di N caratteri (N chiesto all utente). Le righe si possono spezzare solo dove c è uno spazio (che non va riportato nella riga successiva). L output deve essere salvato in un altro file. 34 Vettori di puntatori Gli elementi di questi vettori sono puntatori int *vett[10]; definisce un vettore di 10 puntatori a int (le [ ] hanno priorità maggiore dell operatore *) Esempio di inizializzazione int a, b, c; int *vett[10]={&a, &b, &c}; NO! E errato perché gli inizializzatori sono indirizzi di variabili automatiche, se le variabili fossero invece di classe static sarebbe stato corretto, i valori di vett si devono assegnare: vett[0]=&a; vett[1]=&b; vett[2]=&c; 35 Puntatori a puntatori Variabili che contengono l'indirizzo di memoria di un puntatore Esempio int a, *b, **c; a = 12; b = &a; c = &b; Il puntatore c punta ad una variabile (b) che punta ad un int (a) a:b4ac780 c:5d67f0 A5B2340 b:a5b2340 B4AC

119 Puntatori a puntatori Esempio int a=10, b=20, c=30; int *v[3], int **w; v[0]=&a; v[1]=&b; v[2]=&c; w = v; w++; v è un vettore di puntatori, cioè è l'indirizzo di memoria ( puntatore ) di un puntatore, quindi v+1 è l'indirizzo del secondo puntatore del vettore v (ossia è pari a &v[1], punta a b) Anche w è un puntatore ad un puntatore, quando viene incrementato, punta al puntatore successivo, come v 37 Puntatori e matrici Una matrice è un vettore di vettori, quindi, considerando che l associatività di [] è da sinistra a destra, si ha che: int Mx[7][5]; definisce un vettore di 7 elementi ciascuno delle quali è un vettore di 5 int Gli elementi del vettore Mx sono i 7 vettori identificati da Mx[i]; quindi Mx[i] è l indirizzo di memoria di ciascuno dei 7 vettori di 5 int Poiché il tipo del nome di un vettore-di-t decade al tipo puntatore-a-t, gli Mx[i] che sono di tipo vettore-di-int decadono al tipo puntatore-a-int, ma non essendo variabili, non si può assegnare loro un valore 38 Puntatori e matrici Il decadimento del tipo del nome di un vettore a puntatore può avvenire 1 sola volta Il tipo del nome di una matrice decade al tipo puntatore-a-vettore (quindi incrementando questo puntatore si punta al vettore di 5 int successivo) Mx non è di tipo puntatore-a-int come: int *p Mx non è di tipo puntatore-a-puntatore-a-int: int **p Mx è di tipo puntatore-a-vettore-di-5-int come: int (*p)[5] quindi Mx+1 punta al secondo vettore di 5 int 39 Puntatori e matrici Nella definizione di puntatore seguente int (*p)[5] è necessario che la dimensione delle colonne (5) sia specificata perché definisce un puntatore-a-vettore-di-cinque-interi Poiché Mx è un puntatore-a-vettore-di-5-interi (e non un puntatore ad un intero), allora incrementandolo di 1 esso deve puntare all elemento successivo, che a sua volta è un vettore di 5 interi: Mx+1 aumenta il valore Mx del numero corretto di byte per puntare al secondo vettore di 5 int 40

120 Puntatori e matrici Ricapitolando: Mx[1][2] è un valore scalare è di tipo int è modificabile Mx[1][2]+1 somma 1 al contenuto di Mx[1][2] Mx[1] è l indirizzo di un vettore di 5 int è di tipo puntatore-a-int ( decade ) non è modificabile Mx[1]+1 punta a Mx[1][1] 41 Puntatori e matrici Ricapitolando (continuazione): Mx è l indirizzo di un vettore di 7 vettori di 5 int è di tipo puntatore-a-vettore-di-int ( decade una volta sola ), più precisamente è un puntatore-a-vettore-di-5-int (senza il 5 nel tipo il compilatore non saprebbe di quanti byte spostarsi per puntare al vettore-di-5-int successivo quando si scrive Mx+1) non è modificabile Mx+1 è l indirizzo di memoria di Mx[1] 42 Puntatori e matrici Si notino le differenze tra: int *p; puntatore-a-int int (*q)[5]; puntatore-a-vett-di-5-int Poiché Mx è un puntatore-a-vettore-di-5-int: p = Mx; è errata q = Mx; è corretta e q+1 è l indirizzo del secondo vettore di 5 elementi (equivale a Mx[1]) p = &Mx[0][0]; è corretta e p è l indirizzo del primo elemento dei Mx[0] 43 Vettori di stringhe Essendo una stringa un vettore di caratteri, un vettore di stringhe è in realtà un vettore di vettori di caratteri, cioè una matrice di char char str[4][20]={"uno", "due"}; dichiara un vettore di 4 stringhe di 20 char Le 4 stringhe sono identificate da str[i] str: str[0] u n o \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 str[1] d u e \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 str[2] \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 str[3] \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 44

121 Vettori di puntatori e matrici Si notino le differenze tra: char a[4][8]={"hi","ciao","ola"}; char *b[4]={"hi","ciao","ola"}; a: b: hi\0 hi\ ciao\0000 ciao\0 ola\00000 \ ola\0 NULL a[i]è l indirizzo costante della riga i di a, tale riga è una stringa variabile di 8 char b[i] è una variabile puntatore con l indirizzo della riga i, stringa costante di 4 caratteri: ad esempio b[2] contiene l indirizzo di "ola\0" 45 Vettori di puntatori e matrici a[2] = "hello"; ERRORE: a[2]non è un puntatore strcpy(a[2], "hello"); CORRETTO b[2] = "hello"; CORRETTO: b[2] è una variabile puntatore a cui viene assegnato l indirizzo di memoria di una stringa costante strcpy(b[2], "hello"); ERRORE: b[2] punta a una stringa costante Entrambi a[1][0] e b[1][0] sono il carattere 'c' a[1][0]='m'; SI! L oggetto puntato da a[1] è una stringa variabile b[1][0]='m'; NO! L oggetto puntato da b[1] è una stringa costante 46 Const per puntatori a puntatori 1. int** x; x è una variabile di tipo puntatore a un puntatore variabile a un oggetto variabile di tipo int 2. const int** x; x è una variabile di tipo puntatore a un puntatore variabile a un oggetto costante di tipo int 3. int ** const x; x è una costante di tipo puntatore a un puntatore variabile a un oggetto variabile di tipo int 47 Const per puntatori a puntatori 4. int * const * x x è una variabile di tipo puntatore a un puntatore costante a un oggetto variabile di tipo int 5. const int * const * x x è una variabile di tipo puntatore a un puntatore costante a un oggetto costante di tipo int 6. const int * const * const x x è una costante di tipo puntatore a un puntatore costante a un oggetto costante di tipo int 48

122 Homework 7 Scrivere un programma che legga da un file al massimo un certo numero MAXRIGHE di righe di testo e le memorizzi in una matrice di caratteri (MAXRIGHE x 100), una riga del file per ciascuna riga della matrice. Si definisca un vettore di puntatori a carattere e lo si inizializzi in modo che il primo puntatore punti alla prima riga, il secondo alla seconda, ecc. Si ordinino le stringhe scambiando tra di loro solo i puntatori e le si visualizzino ordinate. Facoltativo: misurare il rapporto tra il tempo necessario per ordinare scambiando le stringhe e scambiando i puntatori. 49 Homework 7 (esempio) prima Nel mezzo del cammin di nostra vita mi ritrovai per una selva oscura ché la diritta via era smarrita. Ahi quanto a dir qual era è cosa dura esta selva selvaggia e aspra e forte dopo Nel mezzo del cammin di nostra vita mi ritrovai per una selva oscura ché la diritta via era smarrita. Ahi quanto a dir qual era è cosa dura esta selva selvaggia e aspra e forte 50

123 Le funzioni Ver Claudio Fornaro - Corso di programmazione in C Struttura modulare Per semplificare la struttura di un programma complesso è possibile suddividerlo in moduli Un modulo è un blocco di codice che assolve ad un compito preciso (ad es. calcola la radice quadrata) e a cui è stato dato un nome Un programma consta di un modulo principale (il main) ed eventuali altri moduli di supporto Quando un modulo richiama un altro modulo, il chiamante viene sospeso finché il chiamato non ha terminato la sua esecuzione In C i moduli sono chiamati funzioni Ogni funzione può richiamare (far eseguire) qualsiasi altra funzione 2 Struttura modulare Le due chiamate del modulo StampaCiao fanno eseguire ogni volta le istruzioni che lo costituiscono (sospendendo il chiamante) Modulo chiamante scanf... StampaCiao() for for switch printf StampaCiao() if if (x==2) then Modulo chiamato (StampaCiao) printf("### # # ###\n"); printf("# # # # #\n"); printf("# # # # # #\n"); printf("# # ### ### # #\n"); printf("### # # # ###\n"); 3 Struttura modulare Vantaggi della programmazione modulare: poiché i moduli nascondono al loro interno i dettagli di come una certa funzionalità venga realizzata, il programma complessivo ha un livello di astrazione maggiore: il modulo viene visto come un insieme di macro-istruzioni ( black-box ) il codice per ottenere una certa funzionalità viene scritto una volta sola e viene richiamato ogni volta che è necessario (ma la chiamata richiede tempo) il codice complessivo è più corto essendo più piccoli, i moduli sono più semplici da realizzare e da verificare il codice di un modulo correttamente funzionante può essere riutilizzato in altri programmi 4

124 Variabili locali 5 Parametri e valore restituito 6 Ogni funzione è un piccolo programma a sé stante, isolato dalle altre funzioni All interno di una funzione possono essere definite delle variabili locali (cioè hanno scope locale): le altre funzioni non le vedono Variabili con lo stesso nome in funzioni diverse sono quindi completamente scorrelate (possono dunque anche essere di tipo diverso) Hanno allocazione automatica: vengono create ogni volta che si entra nella funzione e distrutte (perdendo il valore) quando si esce Le inizializzazioni avvengono ad ogni chiamata, senza inizializzazione il contenuto è indefinito Essendo le variabili interne locali, private, per passare ad una funzione i dati da elaborare è necessario utilizzare variabili speciali dette parametri La funzione comunica al modulo chiamante il risultato della sua elaborazione producendo un unico valore detto valore restituito o valore di ritorno main parametri risultato funzione Definizione di funzioni 7 Definizione di funzioni 8 tipo nomefunzione(parametri) { definizione_variabili_locali } istruzioni eventuale return corpo della funzione tipo indica il tipo del valore restituito (es. sqrt restituisce un double) Se la funzione non restituisce valori (ad esempio StampaCiao visualizza soltanto) bisogna indicare il tipo void: void StampaCiao(...) Se non si mette nulla viene supposto int Se la funzione non ha parametri (ad esempio StampaCiao), si indica void tra le parentesi: void StampaCiao(void) Se non si indica nulla, il compilatore C non attua alcun controllo sui parametri (invece per un compilatore C++ è come se l argomento fosse esplicitamente void)

125 Chiamata di funzione 9 Ritorno da una funzione 10 Per chiamare una funzione se ne indica il nome seguito da una coppia di parentesi contenenti i valori da elaborare (separati da virgole) eleva(y,2); Se la funzione non richiede parametri, le parentesi restano vuote, ma devono esserci: StampaCiao(); Il valore restituito può essere assegnato ad una variabile o utilizzato in un espressione, altrimenti viene semplicemente scartato: x = eleva(y,2); y = 3*eleva(2,k)-4*k; eleva(3,5); La funzione termina (cioè l esecuzione torna al modulo chiamante) quando viene eseguita l istruzione return risultato; Una funzione può avere più istruzioni return risultato è il valore restituito dalla funzione al chiamante, è un espressione qualsiasi return x*2; Se il tipo restituito dalla funzione è void, non si deve indicare risultato, inoltre la return che precede la graffa di chiusura (solo questa) può essere omessa I valori delle variabili locali vengono persi Ritorno da una funzione Nel main la return termina il programma Per terminare un programma dall interno di una funzione (e passare lo status al Sistema Operativo) si utilizza la funzione exit(status) definita in <stdlib.h>: exit(exit_success); 11 Esempio di funzione #include <stdio.h> int eleva(int b, int e) { int k=1; while (e-- > 0) k *= b; return k; } Continua (stesso file)... 12

126 Esempio di funzione 13 Tipo di una funzione 14 Continuazione (stesso file) vedi esercizio main() { int x, y; printf("introduci numero: "); scanf("%d", &x); y = eleva(x, 2); printf("%d^%d = %d\n", x,2,y); return 0; } Alla chiamata, la x del main viene copiata nella b di eleva, mentre il 2 del main viene copiato nella e di eleva Il tipo di una funzione è determinato dal tipo del valore restituito e dal tipo, numero e ordine dei suoi parametri int eleva(int b, int e) eleva è una funzione che ha un primo paramero int, un secondo parametro int e restituisce un int Scope di una funzione 15 Scope di una funzione 16 Lo scope di una funzione (nome e tipo) si estende dal punto in cui viene definita fino a fine file: la funzione può essere utilizzata solo dalle funzioni (main incluso) che nello stesso file seguono la sua definizione f1() {...} f2() {...} main() {...} f1 non vede e non può quindi usare f2, f2 vede f1, il main vede f1 e f2 Il compilatore verifica che le chiamate a funzione siano coerenti con le corrispondenti definizioni (cioè abbiano lo stesso tipo) E necessario che la chiamata a funzione sia nello scope della funzione stessa Se il compilatore trova una funzione di cui non conosce il tipo (non è in scope, ad esempio f1 che chiama f2), allora presuppone che essa sia dichiarata altrove e quindi: non fa controlli sugli argomenti presuppone che restituisca un int segnala il possibile problema con un Warning

127 Prototipo di una funzione Il prototipo di una funzione estende lo scope della funzione (nome e tipo) Il corpo della funzione (la definizione) può quindi essere collocato: in un punto successivo a dove viene chiamata (nell esempio seguente, eleva è dichiarata dopo il main dove viene utilizzata) in un altro file di codice sorgente C in una libreria (compilata) Lo scopo primario degli header file è quello di fornire al compilatore i prototipi delle funzioni delle librerie del C (ad es. stdio.h contiene i prototipi di scanf, printf, getchar, etc.) 17 Esempio di funzione #include <stdio.h> int eleva(int b, int e); prototipo main() { int x, y; printf("introduci numero: "); scanf("%d", &x); y = eleva(x, 2); printf("%d^%d = %d\n", x,2,y); return 0; } 18 Continua (stesso file)... Esempio di funzione Continuazione (stesso file) int eleva(int b, int e) { int k=1; while (e-- > 0) k *= b; return k; } 19 Prototipo di una funzione I prototipi possono essere collocati: prima del main (come nell esempio eleva) tra una funzione e l altra insieme alle definizioni delle variabili locali di una funzione Il prototipo estende lo scope della funzione (nome e tipo) dal punto dove è indicato: fino a fine file se esso è collocato prima del main o tra due funzioni (è esterno alle funzioni) fino a fine funzione se collocato con le variabili locali di una funzione (è interno ad una funzione) 20

128 Prototipo di una funzione Il prototipo di una funzione è simile alla definizione della funzione, salvo che: manca il corpo i nomi dei parametri possono essere tralasciati (ma i tipi devono essere presenti!) ha un punto e virgola finale int eleva(int, int); I nomi dei parametri dei prototipi: possono non essere presenti se presenti, possono essere diversi da quelli usati nella definizione della funzione sono indipendenti da tutti gli altri identificatori sono utili per descrivere il significato dei parametri: int eleva(int base, int esponente); 21 Parametri attuali e formali Parametri attuali (o argomenti): sono i valori (variabili, costanti o espressioni) indicati tra le parentesi alla chiamata di una funzione eleva(x,2) Parametri formali: sono le variabili indicate tra le parentesi nella definizione della funzione int eleva(int b, int e) 22 Parametri attuali e formali Nella chiamata ad una funzione bisogna indicare un argomento per ciascuno dei parametri formali I parametri attuali e quelli formali corrispondono in base alla posizione (il primo attuale al primo formale, etc.) I nomi dei parametri formali sono scorrelati (e quindi tipicamente diversi) dai nomi di eventuali variabili usate come argomenti (inoltre gli argomenti possono essere valori costanti o il risultato di espressioni) I parametri formali hanno lo stesso scope delle variabili locali 23 Passaggio dei parametri I dati possono essere passati ad una funzione esclusivamente per valore (by value) : alla chiamata della funzione vengono create nuove variabili con i nomi di ciascuno dei parametri formali e in esse viene copiato il valore del corrispondente parametro attuale main() x 2 b e eleva() 24

129 Passaggio dei parametri 25 Variabili static 26 Come per le assegnazioni, se il parametro attuale e il corrispondente formale sono di tipo diverso, vi è una conversione automatica al tipo del parametro formale (se è di tipo meno capiente viene prodotto un Warning) Poiché in memoria i parametri formali e quelli attuali sono completamente distinti e indipendenti, cambiare il valore di un parametro formale non modifica il parametro attuale corrispondente, neppure se questo è una semplice variabile (è ovviamente impossibile modificare una costante o il risultato di un espressione): nell esempio, la modifica di b non si ripercuote su x Le variabili locali di classe di allocazione statica non vengono mai rimosse dalla memoria per cui non perdono il loro valore quando la funzione termina Quando la stessa funzione viene chiamata nuovamente, il valore delle variabili statiche è quello che avevano prima della terminazione Non richiedendo la ri-allocazione ad ogni chiamata della funzione, generano un programma più veloce Si richiede una classe di allocazione statica e non automatica mediante la keyword static: static int cont = 0; Variabili static 27 Variabili static 28 L inizializzazione delle variabili locali static avviene idealmente solo la prima volta che si esegue la funzione (in realtà i valori vengono inizializzati dal compilatore) Possono essere inizializzate dal compilatore con espressioni costanti : numeri e #define valori enum indirizzi di memoria di variabili statiche Non possono essere inizializzate con: valori const variabili e risultati di funzioni indirizzi di memoria di variabili automatiche Se non inizializzate esplicitamente, vengono inizializzate automaticamente a 0 (o NULL) int conta(void) { static int cont = 0; return ++cont; } Ogni volta che viene chiamata conta, essa incrementa il contatore cont e ne restituisce il valore, se non fosse statica ma automatica restituirebbe sempre il valore 1 perché cont verrebbe ri-inizializzata ogni volta a 0

130 Variabili static char *nomemese(int n) { static char *nome[] = { "inesistente", "gennaio", "febbraio", etc... }; if (n<1 n>12) return nome[0]; else return nome[n]; } La stringa di cui viene restituito il puntatore può essere utilizzata dal chiamante in quanto essendo statica non viene rimossa dalla memoria 29 Passaggio dei parametri Il passaggio di parametri nella modalità per riferimento (by reference) prevede che la modifica del parametro formale si ripercuota identica sul corrispondente parametro attuale (deve essere una variabile) In C non esiste il passaggio per riferimento, ma questo può essere simulato passando per valore alla funzione il puntatore al dato da passare (che deve essere una variabile, non può essere il risultato di un calcolo) Nella scanf le variabili scalari sono precedute da & perché devono essere modificate dalla funzione e quindi se ne passa l indirizzo 30 Passaggio dei parametri #include <stdio.h> void fz(int *); prototipo main() { int x=2; fz(&x); printf("%d\n", x); return EXIT_SUCCESS; } void fz(int *p) { *p = 12; } 31 Passaggio dei parametri Nell esempio: il main alloca x, gli assegna il valore 2 e chiama fz passandole l indirizzo di x (&x) presente in una variabile temporanea ( senza nome ) alla chiamata di fz, l indirizzo di x (che è BF32F0) viene copiato by-value in p fz accede a x come *p, modificandola in 12 quando fz termina, x vale 12 main() fz(int*p) &x BF32F0 BF32F0 p x 2 fz(&x); *p=12; x 12 32

131 Passaggio dei parametri 33 Passaggio di vettori 34 void swap(int *, int *); prototipo main() { int x=12, y=24; swap(&x, &y); } void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } Per passare un vettore come argomento, si indica il suo nome senza parentesi x = media(vettore); Il parametro formale corrispondente definirà un vettore dello stesso tipo (in genere senza dimensione in quanto ininfluente) float media(int v[]); La funzione deve conoscere in qualche modo la dimensione del vettore (indicarlo tra le parentesi quadre non serve a nulla): viene passato come argomento float media(int v[], int lung); è noto a priori (es. una #define) si usano variabili esterne (vedere più avanti) Passaggio di vettori 35 Passaggio di matrici 36 Possono essere passati vettori con dimensioni diverse, ma dello stesso tipo T Quando si passa un vettore-di-t, poiché si indica il nome del vettore, in realtà si passa l indirizzo di memoria del suo primo elemento (e questa non dà alcuna informazione né restrizione sulla lunghezza del vettore) Il parametro formale è quindi in realtà un puntatore-a-t, la forma v[] viene convertita automaticamente in *v, si possono usare le due definzioni indifferentemente float media(int *v); Per passare una matrice come argomento, si indica il suo nome senza parentesi x = media(matrice); Il parametro formale corrispondente definirà una matrice dello stesso tipo (in genere senza la prima dimensione in quanto ininfluente) void funz(int matrice[][10]) La funzione deve conoscere in qualche modo le dimensioni del vettore (indicarle tra le parentesi quadre non serve a questo scopo) Possono essere passate matrici con diverso numero di righe, ma devono avere lo stesso numero di colonne e gli elementi devono essere dello stesso tipot

132 Passaggio di matrici 37 Passaggio di matrici 38 Poiché una matrice è un vettore-di-vettori, quando essa viene passata ad una funzione, viene in realtà passato l indirizzo del primo elemento del vettore-di-vettori Il tipo del parametro formale corrispondente è quindi un puntatore-a-vettore e NON un puntatore-a-puntatore (il decadimento da vettore a puntatore avviene una volta sola) Nel caso dell esempio, il parametro formale è un puntatore-a-vettore-di-10-int: int (*matrice)[10]) La forma matrice[][10] viene convertita automaticamente in (*matrice)[10], si possono usare le due definzioni indifferentem. void funz(int (*matrice)[10]) E quindi un errore scrivere: void funz(int **matrice) oltre all errore di tipo, si perde la dimensione delle colonne e quindi non si può determinare la posizione degli elementi della matrice: in memoria l elemento Mx[i][j] viene determinato con il calcolo indirizzo_di_mx+nc*i+j, dove NC è il numero delle colonne, come si vede il numero delle righe non serve Per passare ad una funzione una matrice con qualsiasi numero di righe e qualsiasi numero di colonne vi sono diverse soluzioni, si rimanda al set di slide relative all allocazione dinamica Passaggio di vettori multidim. 39 Parametri const 40 Quanto visto per le matrici può essere esteso ai vettori multidimensionali In particolare: nel parametro formale si può tralasciare la dimensione del solo primo parametro il parametro formale è un puntatore ad un vettore di X vettori di Y vettori di Z vettori di... di tipo T la funzione deve conoscere i valori di tutte le dimensioni possono essere passate matrici con la sola prima dimensione diversa Il modificatore const applicato ai parametri formali impedisce che all interno della funzione si possa modificarne il valore int funzione(const int v) Permette di proteggere i parametri da una successiva incauta modifica (per prevenire errori di programmazione) Ad esempio, questo richiede al compilatore di segnalare se un puntatore a dato costante viene assegnato ad un puntatore a dato variabile (ad esempio passandolo come parametro), cosa che by-passerebbe la restrizione

133 Parametri puntatori const 41 Parametri puntatori const Puntatore variabile a dati variabili int f(int *p) { *p = 12; OK, dato variabile p++; } OK, puntatore variabile Si può passare un int* Non si può passare un const int* (non c è conversione automatica di tipo perché dentro la funzione nulla vieterebbe di poter cambiare il valore alla variabile puntata): int x=12; const int *y=&x; f(y); ERRORE 2. Puntatore variabile a dati costanti int f(const int *p) /* int const */ {*p = 12; NO, dato costante p++;} OK, puntatore variabile Si può passare un int* (c è conversione di tipo automatica in quanto si passa ad un tipo più restrittivo) Si può passare un const int* Note: Tipicamente utilizzato per passare un puntatore ad una struct o ad un vettore impedendo che possano essere modificati Non si può passare senza cast un Tipo** dove è richiesto un const Tipo** Parametri puntatori const 43 Variabili esterne Puntatore costante a dati variabili int f(int * const p) {*p = 12; OK, dato variabile p++;} NO, puntatore costante Si può passare un int* 4. Puntatore costante a dati costanti int f(const int * const p) {*p = 12; NO, dato costante p++;} NO, puntatore costante Si può passare un int* Vengono definite (riservando memoria) esternamente al corpo delle funzioni: in testa al file, tipicamente dopo le direttive #include e #define oppure tra una funzione e un altra Sono visibili e condivisibili da tutte le funzioni che nello stesso file seguono la definizione Possono essere utilizzate come metodo alternativo per comunicare dati ad una funzione e per riceverne A questo scopo si usino con parsimonia: rendono poco evidente il flusso dei dati all interno del programma

134 Variabili esterne 45 Variabili esterne 46 Hanno classe di allocazione statica: non vengono mai rimosse dalla memoria e, salvo inizializzazione esplicita, vengono inizializzate automaticamente a 0 (i puntatori a NULL) Una variabile locale (interna ad una funzione) con lo stesso nome di una esterna copre la visibilità di quella esterna alla quale quindi non può accedere con quel nome (non è buona pratica di programmazione) #include<...> int uno; main() { uno = 12; } long due; void fun1() { uno = 21; due=55; } int tre; int fun2() { return uno + due + tre; } tre due uno extern La clausola extern permette di estendere la visibilità delle variabili esterne Indica al compilatore che la variabile è definita (la sua memoria è allocata) altrove: più avanti nello stesso file in un altro file Viene premessa ad una dichiarazione di variabile (non riserva memoria) extern int x; Da qualche parte ci sarà una definizione della stessa variabile (senza extern) dove viene riservata memoria In seguito, tutte le dichiarazioni vengono ricondotte all unica definizione 47 extern La clausola extern permette di usare una variabile esterna anche in una funzione che, nello stesso file, precede la sua definizione Nelle funzioni che, nello stesso file, seguono la definizione di una variabile esterna, la dichiarazione (con extern) è opzionale fun3() { extern long abc; dichiarazione abc = 12;} utilizzo long abc; definizione fun4() { extern long abc; dich. opzionale abc = 21;} utilizzo 48

135 extern La dichiarazione di una variabile extern può essere sia interna ad una funzione, sia esterna #include<...> extern int due; funz4() { extern long uno; int tre; tre = uno + due; } long uno = 0, due = 0; 49 Documentazione delle funzioni E utile scrivere sempre la documentazione relativa allo scopo e all uso di una funzione come commento iniziale contenente: Scopo: a cosa serve la funzione Parametri: tipo e descrizione di ciascuno Valore restituito: tipo e descrizione Possibilmente anche: Pre-condizioni: requisiti particolari sui parametri che devono essere soddisfatti da chi invoca la funzione (es. param > 29) Post-condizioni: garanzie date dalla funzione sul valore restituito o sullo stato del programma, purché le precondizioni siano state soddisfatte (es. risultato >= 23 && risultato <= 32) 50 Chiamata di funzione - dettagli Quando un programma viene mandato in esecuzione, la porzione di memoria ad esso riservata (address space) viene organizzata 0xFF...FF STACK come indicato a lato La parte denominata stack contiene le inutilizzata variabili del main() L heap contiene i HEAP blocchi di memoria allocati da malloc() VAR STATICHE Stack e heap CODICE 0x crescono nel senso indicato dalle frecce 51 Chiamata di funzione - dettagli Quando viene chiamata una funzione, sullo stack vengono prima copiati i valori dei suoi argomenti e poi vi viene allocato un Activation Record (o stack frame) per contenere tutte le variabili locali (e altro) Quando la funzione termina, gli argomenti e l AR vengono rimossi dallo stack che quindi ritorna nello stato precedente la chiamata 0xFF...FF 0x STACK inutilizzata HEAP VAR STATICHE CODICE 52

136 Chiamata di funzione - dettagli Nell Activation Record viene anche memorizzato l indirizzo di ritorno dalla funzione: la locazione di memoria che contiene l istruzione del chiamante da cui continuare dopo che la funzione è terminata Queste operazioni di allocazione e deallocazione di spazio sullo stack e in generale il meccanismo di chiamata e ritorno da una funzione richiedono tempo In casi estremi di necessità di elevate performance si può cercare di limitare il numero delle chiamate a funzione, a costo di dover ricopiare lo stesso codice in più punti (eventualm. utilizzando macro con argomenti ) 53 Esercizi 1. Si scriva un programma che per 10 volte chieda all utente un valore e ne calcoli il logaritmo in base 2. Per il calcolo del logaritmo si scriva una funzione con prototipo: double log2(double a); che calcoli il valore utilizzando la formula: log log 2 x = log e x e 2 54 Esercizi 2. Si scriva un programma che chieda all utente 10 valori e di questi calcoli la radice quadrata. Per il calcolo della radice quadrata si scriva una funzione con prototipo: double radice(double a, double prec); che calcoli il valore approssimato della radice quadrata di a con il metodo di Newton: 1 a x i+ 1 = xi + 2 xi x i sono approssimazioni successive della radice quadrata di a. Si assuma x 0 = a e si iteri fintanto che x i x i+1 > prec. 55 Esercizi 3. Si scriva una funzione con prototipo: double media(double v[], int len); che calcoli e restituisca la media di un vettore di double passato come argomento. Si scriva un programma che riempia due vettori di lunghezza differente (es. a[8] e b[10], li passi a media e visualizzi il risultato per ciascuno di essi. La funzione non esegua operazioni di input/output. 56

137 Esercizi 4. Si scriva una funzione con prototipo: void rovescia(char s[]); che rovesci la stringa passata come argomento (modifica del parametro). Si scriva un programma che chieda una stringa, la passi a rovescia e la visualizzi. 5. Si scriva una funzione con prototipo: int contastr(char a[], char x[]); che conti quante volte la stringa x sia contenuta in a. N.B. bb in bbb : 2 volte 6. Si scriva una funzione undup che modifichi una stringa eliminandone i caratteri duplicati: esempio: ciao come va? ciao mev? 57 Esercizi 7. Si scriva una funzione con prototipo: void ordina(int v[], int len, int ord); che ordini in senso crescente e decrescente il vettore di int passato come argomento. Il senso dell ordinamento venga indicato dal parametro ord (decrescente=0, crescente=1). Si scriva un main di test. 58 Esercizi 8. Si scriva la funzione sommavett che calcoli la somma di due vettori. Si scriva un main che chieda la dimensione dei vettori (max 100), ne chieda i valori, li passi alla funzione e visualizzi il vettore dei risultati. Non si alteri il contenuto dei due vettori da sommare. La funzione non faccia input/output. Per non usare variabili esterne o variabili locali static, alla funzione bisogna passare anche il vettore dei risultati (il contenuto iniziale non è rilevante). 59 Esercizi 9. Un file di testo denominato Parole.txt contiene una lista di parole, una per riga. Non è noto a priori di quante righe sia composto. Si scriva un programma che chieda all utente di introdurre una parola e visualizzi tutte le parole presenti nel file che anagrammate danno la parola introdotta. Si scrivano due funzioni: una che riordina alfabeticamente le lettere di una stringa ( telefono eeflnoot ) e un altra che modifichi una stringa trasformandone tutti i caratteri in minuscolo. 60

138 Esercizi 10. Si scriva una funzione con prototipo: double media(double M[][10], int righe, int colonne); che calcoli e restituisca la media di una generica matrice di 10 colonne passata come argomento. Si scriva un programma che riempia due matrici di dimensioni massime 8x10 e 12x10 (il massimo di 10 colonne ciascuna è per coerenza con il prototipo), le passi a media e visualizzi il risultato per ciascuna di esse. La funzione non faccia input/output. 61 Esercizi 11. Scrivere una funzione con prototipo int parsetointvect(char *stringa, int vett[], int n); che analizzi la stringa data estraendo da essa i primi n valori numerici interi e li inserisca nel vettore vett[]. Se vi sono più di n valori, i successivi vengano ignorati. La funzione restituisca il numero di elementi letti (che possono quindi essere meno di n). Si supponga che il file non contenga caratteri diversi da cifre e whitespace. Si scriva un main di test. 62 Esercizi Soluzione a stati ws=white space t=stringa per contenere i caratteri da trasf. in numero cont=contatore valori c=carattere i-esimo della stringa FUORI!ws ws cont++ c t[0] t vett[cont] c t[i]!ws DENTRO ws 63 Esercizi 12. Si scriva un programma che permetta di calcolare il prodotto di 2 numeri interi senza segno introdotti dall utente e composti ciascuno da un massimo di 1000 cifre. Conviene modularizzare il codice utilizzando opportune funzioni di supporto (ad esempio per moltiplicare un numero per una cifra, per fare lo shift di n posizioni a sinistra di un numero e per sommare due numeri). 64 no-op t vett[cont]

139 Progetti multi-file 65 Progetti multi-file 66 E possibile suddividere le funzioni che costituiscono un eseguibile su più file (detti translation unit ) Ciascun file viene compilato separatemente e il linker li assembla per costituire un unico file eseguibile Uno solo dei file deve contenere la definizione della funzione principale main L insieme dei file sorgenti viene spesso chiamato progetto In ciascun file si collocano funzioni che insieme forniscono una certa funzionalità (ad es. la gestione di uno stack) Ciascuno dei file si comporta come una libreria di funzioni, salvo che queste vengono compilate (e non solo linkate) con il programmma principale Un file con funzioni specifiche per fornire una determinata funzionalità può essere facilmente riutilizzato in altri programmi: basta includerlo nel progetto Progetti multi-file 67 Progetti multi-file 68 Ciascun file ha bisogno delle sole direttive #include e #define necessarie al codice di quel file Per usare in un file una funzione (non può essere static) dichiarata in un altro file, si deve indicarne il prototipo (extern opzionale) Spesso si raggruppano tutte le #define, le variabili esterne e i prototipi di tutte le funzioni (non può essere static) di un progetto in un unico file.h e i file.c del progetto che ne abbisognano lo includono (con virgolette): #include "mioheader.h" Le variabili esterne usate in tutti i file devono essere definite solo in uno dei file, mentre gli altri devono avere la dichiarazione extern corrispondente (vedere le slide sulla compilazione condizionale) Non si può usare extern con variabili esterne definite static in un altro file (sono utilizzabili solo dalle funzioni di quel file)

140 Progetti multi-file extern 69 Progetti multi-file static 70 La clausola extern permette di usare una variabile esterna anche in altri file, purché faccia parte dello stesso progetto È compito del linker: controllare che le varie dichiarazioni siano conformi alla definizione controllare che la definizione sia unica associare definizione e dichiarazioni alla stessa zona di memoria Le variabili esterne con modificatore static hanno scope limitato alle funzioni definite in quel file (solo a quelle successive alla definizione): sono private ad uso esclusivo delle funzioni di quel file Le funzioni con modificatore static hanno scope limitato alle funzioni definite in quel file (solo a quelle successive alla definizione): sono private per le funzioni di quel file static int funzione(int x) La keyword static si riferisce alla funzione, non al valore restituito Linkage di variabili e funzioni 71 Linkage di variabili e funzioni 72 Il linkage esprime la corrispondenza di identificatori (variabili o costanti) omonimi presenti in più blocchi e/o in più translation unit diverse (linkate insieme) e/o in librerie Un identificatore con linkage esterno è visibile in più translation unit (es. variabili e funzioni esterne non static) Un identificatore con linkage interno è visibile solo nella translation unit dove è definito (es. variabili e funzioni static) Un identificatore non ha linkage se è locale al blocco dove è definito (es. variabili locali, parametri di funzioni, tag, membri, etc.) Un identificatore con linkage esterno è significativo almeno nei primi 6 caratteri, maiuscole e minuscole rappresentano caratteri uguali Un identificatore con linkage interno è significativo almeno nei primi 31 caratteri, maiuscole e minuscole rappresentano caratteri diversi Il linkage di un identificatore preceduto dalla clausola extern è quello stabilito dalla sua precedente dichiarazione nella stessa translation unit (ad es. una dichiarazione con extern relativa ad una variabile definita static più sopra nel file ha linkage interno)

141 Lo stack Uno stack (o pila) è una struttura dati di tipo LIFO (Last In First Out) in cui i valori vengono prelevati nell ordine inverso a quello di introduzione Introduzione: push Prelievo: pop A B A C B A B A A 73 Esercizi 13. Scrivere in un file separato la realizzazione di uno stack basato su un vettore di interi. Si realizzino le funzioni push e pop (con opportuni parametri ) che restituiscano 1 in caso di errore (stack pieno o vuoto) e 0 altrimenti (non devono fare I/O). Si scriva un main a menu in grado di verificarne il funzionamento. I prototipi siano in un file.h. Il vettore deve essere static perché solo push e pop ne abbiano accesso. Si utilizzi un puntatore p che punti alla prima locazione libera utilizzando le seguenti espressioni: per push: *p++ = val; per pop: val = *--p; 74 La coda Una coda è una struttura dati di tipo FIFO (First In First Out) in cui i valori vengono prelevati nello stesso ordine di introduzione Introduzione: enqueue Prelievo: dequeue coda A B A C B A testa C B C 75 Esercizi 14. Scrivere in un file separato la realizzazione di una coda basata su un vettore di interi. Si scrivano le funzioni enqueue e dequeue (con opportuni parametri ) che restituiscano 1 in caso di errore (coda piena o vuota) e 0 altrimenti (non devono fare I/O). Si scriva un main a menu in grado di verificarne il funzionamento. I prototipi siano in un file.h. Si utilizzino due puntatori: testa punta alla cella con il prossimo valore da prelevare, coda punta alla prossima cella da riempire. Il vettore sia static. Si può notare che la enqueue è identica alla push. 76

142 77 78 Il buffer circolare (coda) Il buffer circolare (coda) Testa e coda non sono più fisse, ma si rincorrono E necessario un contatore delle posizioni libere per discriminare la condizione pieno da vuoto in cui testa e coda coincidono Aggiunta (in coda) if ( non pieno ) aggiungi in coda fa avanzare coda posti liberi 1 coda 5 4 C B A 3 2 coda testa C B A Prelievo (dalla testa) if ( non vuoto ) preleva valore fa avanzare la testa posti liberi testa Esercizio Scrivere in un file separato la realizzazione di una coda come buffer circolare basato su un vettore di interi. Si scrivano le funzioni enqueue e dequeue con identico tipo di quelle dell esercizio precedente così da utilizzare lo stesso identico main. Si noti che è necessario utilizzare un contatore delle posizioni libere (o occupate) per verificare se è possibile inserire/togliere un valore, non c è modo di stabilirlo confrontando semplicemente i puntatori testa e coda.

143 Strutture Ver. 2.2 Tipi di dati aggregati Il termine tipo aggregato si riferisce ai vettori e ai tipi struct, entrambi sono aggregati di valori: un vettore è un raggruppamento di variabili dello stesso tipo una struct è un raggruppamento di variabili anche di tipo diverso Claudio Fornaro - Corso di programmazione in C Tipo struct Insieme di più variabili (membri ) in genere di tipo diverso identificate da un nome comune (tag) per facilitarne la gestione In memoria i membri sono allocati contiguamente, ma possono esserci spazi intermedi di allineamento della memoria non indirizzabili e dal contenuto indefinito. Esempio (short su 16 bit e long su 32 in una macchina con allineamento a 32 bit): short inutilizzato long 32 bit 32 bit 3 Dichiarazione di struct struct nome_tag { tipo1 nome_membro1; tipo2 nome_membro2;... }; La dichiarazione non riserva memoria, ma crea un nuovo tipo di dato Dichiarazioni di struct con contenuto identico ma diverso tag sono considerate di tipo diverso Una dichiarazione di struct senza tag (anonima ) è sempre considerata avente tipo diverso da quello di ogni altra struct (con tag o anonima), anche se ha gli stessi membri 4

144 Dichiarazione di struct Lo scope del nome dei membri è confinato alla sola struttura dove sono dichiarati: le variabili del programma e i membri di altre strutture possono avere gli stessi nomi di un membro Esempio struct punto { int x; int y; }; dove: x y punto è il tag x e y sono i due membri, scalari di tipo int 5 Definizione di variabili struct Riserva memoria Ha la consueta forma: tipo var1, var2, var3,... salvo che qui il tipo è una struct La definizione può essere contestuale alla dichiarazione del tipo (il tag può essere omesso se non serve definire in seguito altre variabili di questo tipo) struct punto { int x; int y; pt1 x y } pt1, pt2, pt3; 3 variabili 6 Definizione di variabili struct La definizione può essere separata dalla dichiarazione del tipo (tag non omesso) struct punto pt4, pt5, pt6; In entrambi i casi è possibile inizializzare una variabile struct: con valori costanti (tra parentesi graffe) nel caso di variabili automatiche, anche mediante assegnamento di un altra variabile struct dello stesso tipo o chiamando una funzione che restituisca una struct dello stesso tipo struct punto pt7 = {12, 14}; struct punto pt8 = p7; struct punto pt9 = creapunto(5,7); 7 Operazioni su struct Per accedere ai singoli membri di una variabile di tipo struct si usa la forma: nomevar.nomemembro Si noti che nomevar è il nome della variabile, NON quello del tag Assegnazione di valore ad un membro scalare pt1.x = 24; L assegnazione di una variabile struct con un altra avviene mediante copia del contenuto (non del puntatore), l assegnazione è possibile solo se sono dello stesso tipo pt1 = pt2; Determinazione dell indirizzo di un membro int *q = &pt4.y; 8

145 Operazioni su struct Determinazione dell indirizzo di una variabile struct punto *p; p = &pt4; L operatore. ha priorità maggiore dell operatore di deferimento * per cui la scrittura *p.x equivale a *(p.x) e queste identificherebbero l oggetto puntato da x (che però dovrebbe essere un puntatore, mentre nell esempio è un int) p x y 12 9 Operazioni su struct Invece (*p).x indica il membro x della variabile di tipo struct puntata da p (*p).x = 12; x y 12 p (*p).x viene preferibilmente scritta mediante l operatore freccia come p->x quindi l istruz. (*p).x = 12; diventa p->x = 12; La priorità dell operatore -> è massima, quindi ++p->x equivale a ++(p->x) (incrementa x, non p) 10 Funzioni e struct Nelle funzioni, una variabile di tipo struct viene passata per valore: chiamata: funz(pt1); definizione del parametro formale: int funz(struct punto pt) {...} Una funzione può restituire una struct chiamata: pt1 = funz(...); dichiarazione del tipo della funzione: struct punto funz(...) {...} 11 Funzioni e struct Perché chiamante e chiamato conoscano la stessa struct, questa deve avere un unica dichiarazione esterna Sono considerate di tipo diverso (e quindi non si possono assegnare le une con le altre e neppure sono compatibili come parametri): dichiarazioni identiche di struct con lo stesso tag esterne e interne (hanno scope locale) dichiarazioni identiche di struct con lo stesso tag interne in funzioni diverse (scope locale) dichiarazioni di struct anche identiche ma con diverso tag (interne e/o esterne) dichiarazioni di struct anche identiche ma anonime (interne e/o esterne) 12

146 Membri di una struct I membri possono essere di tipo scalare, o aggregato (vettoriale, altre struct, etc.), l inizializzazione avviene come già indicato, le parentesi graffe interne possono essere tralasciate (vedere inizializzazione di matrici) struct rettangolo { struct punto basso_sinistra; struct punto alto_destra; } rett = {{2,3},{12,9}}; L accesso ai membri interni richiede l indicazione del percorso da seguire: rett.alto_destra.x = 14; variabile membro membro 13 Campi di bit Sono insiemi di bit che costituiscono un valore di tipo intero (signed o unsigned) struct cartadagioco { unsigned valore : 4; unsigned seme : 2; unsigned colore : 1; }; Il numero intero a destra di ciascun membro indica per ciascun campo da quanti bit esso sia costituito I singoli campi si comportano come valori interi e quindi possono comparire in espressioni, essere assegnati, confrontati, etc. 14 Campi di bit La dimensione massima di ciascun campo è la dimensione di un int (caratteristica non portabile, inoltre dipendente dal compilatore) I campi vengono accorpati a costituire gruppi di byte delle dimensioni di un int (non è specificato se da sinistra a destra o viceversa) Non si può determinare il puntatore/offset di un campo di bit (può essere in mezzo ad un byte) 15 Campi di bit Un campo anonimo (senza nome della variabile) può servire come riempitivo (padding) con quel numero di bit, ma non può essere utilizzato per contenere valori Un campo anonimo con dimensione 0 forza l allineamento di memoria del membro seguente ad al successivo int struct cartadagioco { unsigned valore : 4; unsigned : 5; unsigned seme : 2; unsigned : 0; unsigned colore : 1; }; allocati nel primo int allocato nel secondo int 16

147 Vettori di struct Ogni elemento di un vettore di struct è una variabile di tipo struct: struct numparole struct numparole { parola num char parola[20]; int num; } pn[5] = {{"ciao",2},{"hi",4}}; Il vettore pn ha 5 elementi, ciascuno è una struct numparole, i primi 2 elementi sono inizializzati, i successivi sono "" e 0 (le graffe interne possono essere omesse) pn: ciao\0 2 hi\0 4 \0 0 \0 0 \0 0 pn[0] pn[1] pn[2] pn[3] pn[4] 17 Confronto di variabili struct Per verificare se due variabili dello stesso tipo struct sono uguali (stesso contenuto), si deve confrontare ciascun membro if (pt1.x==pt2.x && pt1.y==pt2.y) NON si possono confrontare direttamente: if (pt1 == pt2) ERRORE NON si possono confrontare con memcmp perché i campi anonimi hanno contenuto indefinito e quindi eventualmente diverso (ma se sono stati precedentemente azzerati, ad esempio con una funzione quale memset o calloc, allora è possibile) 18 Tipo union Simili alle strutture, contengono un solo valore il cui tipo può essere cambiato scegliendo il nome del campo tra quelli dei suoi membri union tris { int x; double y; char nome[10]; } var; In questo es. la variabile var è considerata di tipo int se viene chiamata come var.x, di tipo double se chiamata var.y, stringa di 10 caratteri se chiamata var.nome Sta al programmatore ricordarsi il tipo attuale 19 Tipo union Assegnazione: alfa.y = 23.23; ora alfa è di tipo double e non esistono né alfa.x né alfa.nome alfa.x = 12; ora alfa è di tipo int e non esistono né alfa.y né alfa.nome strcpy(alfa.nome, "ciao"); ora alfa è di tipo vettore-di-char e non esistono né alfa.x né alfa.y Una variabile di tipo union può essere inizial. solo come variabile del primo dei tipi indicati union tris alfa = 23; inizializza x 20

148 Operatore typedef Dichiara il nome di un nuovo tipo di dato a partire da altri tipi (scalari, aggregati, etc.) typedef tipoesistente nuovotipo; La dichiarazione di tipo è identica alla definizione di una variabile, ma è preceduta dalla clausola typedef. Per comprendere correttamente che cosa produce una dichiarazione typedef, è utile pensare al tipo che avrebbe la variabile se non ci fosse typedef e poi considerare che il nome della variabile è in realtà il nome del nuovo tipo 21 Operatore typedef Esempi typedef char string[80]; Se non ci fosse typedef, string sarebbe una variabile di tipo vettore-di-80-char; mentre grazie a typedef, string è il tipo vettore-di-80-char string parola; definisce la variabile parola di tipo string, cioè char[80] typedef char *str; definisce il tipo str come puntatore a char str par; definisce la variabile par di tipo str, cioè char* 22 Operatore typedef I nomi dei tag di struct e union possono essere omessi quando li si usa soltanto nella typedef (cioè la forma struct TAG non viene usata altrove) Esempi typedef struct rett { struct punto basso_sinistra; struct punto alto_destra; } rettangolo; definisce il tipo rettangolo come struct rett rettangolo r = {{2,3},{12,9}}; definisce la variabile r di tipo struct rett 23 Operatore typedef typedef struct { int x; int y; } vpunti[10]; definisce il tipo vpunti come vettore di 10 elementi di quel tipo struct lì dichiarato (anonimo) vpunti vett; definisce la variabile vett di tipo vpunti, ossia vettore-di-10-struct (la struct anonima dichiarata sopra); utilizzabile ad esempio così: vett[0].x = 12; 24

149 Operatore typedef 25 Typedef e portabilità 26 I nomi dei nuovi tipi non devono essere nomi utilizzati da altri identificatori I nomi dei tag sono scorrelati dai nomi di variabili, costanti, tipi, etc. (tecnicamente appartengono a name space diversi), quindi è possibile definire un nome di tipo con lo stesso nome di un tag typedef struct rett { int x; int y; } rett; Si preferisce utilizzare un nome di tipo con iniziale maiuscola (Rett) o terminante con _t (rett_t) come d uso nella libreria standard L operatore typedef viene spesso utilizzato per nascondere come il compilatore realizza internamente una certa funzionalità, fornendo al programmatore un comportamento standard Questo si traduce in una migliore portabilità del codice: indipendenza dalla piattaforma hardware, dal sistema operativo, dal compilatore, etc. Typedef e portabilità 27 Typedef e funzioni 28 Ad esempio, per qualsiasi compilatore ANSI C il tipo size_t è sempre il tipo più appropriato (per quella combinazione hardware/s.o./compilatore) per memorizzare la dimensione in byte di una variabile o la lunghezza di una stringa. Internamente un compilatore potrebbe usare un unsigned int, un altro un long; ma usando size_t non ci si deve preoccupare di questi dettagli Il valore restituito richiede un cast per l assegnazione ad una variabile di altro tipo: int len; len=(int)strlen(stringa); E più chiaro definire esternamente (anche mediante un include) con typedef quei nuovi tipi che verranno utilizzati in più funzioni Ciò non è strettamente necessario in quanto la compatibilità di tipo di due variabili viene determinata smontando la dichiarazione typedef nella sua definizione basata sui tipi primitivi typedef int * intp; intp p; int *q; q=p; lecito perché sono dello stesso tipo

150 Typedef e funzioni Quando un tipo è definito con typedef su strutture aggregate anonime (struct e union senza tag), le variabili di quel nuovo tipo sono considerate dello stesso tipo (idealmente, nell operazione di smontaggio le strutture aggregate anonime ricevono lo stesso tag fittizio, diverso per ogni typedef) typedef struct { int a; int b;} Miastruct; Miastruct a={0,0}; Miastruct b; b=a; lecito perché sono dello stesso tipo 29 Typedef e const Se il tipo T è definito con una typedef, la posizione della const non è significativa perché il modificatore const si applica all intera typedef Quindi le due definizioni seguenti sono equivalenti const T var; T const var; Se ad esempio T è definito come: typedef int* T; entrambe le defininizioni sono equivalenti a: int * const var; 30 Typedef e const Attenzione: se invece T viene definito con una #define, T non è un vero nuovo tipo e la posizione della const è significativa Se ad esempio T è definito come: #define int* T la definizione const T var equivale a const int* var; e la definizione T const var equivale a int* const var; (non è equivalente alla precedente) 31 Operatore sizeof Restituisce il numero di byte di cui è composto un tipo di dato o una variabile (scalare o aggregata) E un operatore (non una funzione) e viene valutato in fase di compilazione, può essere usato in una #define (ma non in una #if) Sintassi sizeof(nome_di_tipo) sizeof nome_di_variabile Le parentesi sono necessarie se si indica il nome di un tipo di dato, facoltative se si indica il nome di una variabile 32

151 Operatore sizeof 33 Operatore sizeof 34 Esempi sizeof(double) dà il numero di byte richiesti da un double sizeof pt1 dà il n. di byte richiesti da una struct punto Il tipo del valore restituito è size_t Il valore restituito richiede un cast per l assegnazione ad una variabile (ad es. di tipo int) in quanto size_t potrebbe essere in realtà un unsigned long (Warning) int len; len=(int)sizeof(double); Definendo la variabile vett come: struct x { int b; } vett[10], s; sizeof produce i seguenti valori: n = sizeof vett; dà il numero di byte richiesti da un vettore di 10 struct x n = sizeof vett / sizeof(struct x); dà il numero di elementi del vettore vett n = sizeof vett / sizeof s; dà il numero di elementi del vettore vett Esercizi 1. Un file contiene, su ciascuna riga, separati da spazi, 4 campi relativi a: nome, cognome, età, e salario (in Euro) di un certo numero di persone (max 100). Scrivere un programma che crei un vettore di 100 struct, lo riempia con i dati letti dal file, lo passi ad una funzione che lo riordini in base al cognome e quindi salvi il risultato ordinato in un secondo file mantenendo lo stesso formato del file originale (campi separati da uno spazio). N.B. Il campo in base al quale viene riordinato un vettore viene detto chiave. 35 Esercizi 2. Si scriva un programma che definisca il seguente tipo di dati che descrive un generico poligono regolare struct poligono { int nlati; double lato; }; Si scrivano le seguenti funzioni e le si chiamino da un main di prova: 1. struct poligono creapoli(void); chiede all utente il numero lati e la lunghezza lato, quindi restituisce una struct poligono 36

152 Esercizi 2. Seguito 2. double areapoli(struct poligono p); calcola l area del poligono passato n l A = π 4 tan n 3. double perimpoli(struct poligono p); calcola il perimetro del poligono passato 4. void doppiopoli(struct poligono *pp); raddoppia il lato dello stesso poligono passato 3. Come il precedente, ma si usi la typedef 2 37

153 Struttura a record 2 File ad accesso diretto Il file è organizzato in record aventi tutti la stessa struttura e quindi dimensione record 0 record 1 record 2 Ver Claudio Fornaro - Corso di programmazione in C Ogni record viene letto/scritto in un unica operazione (tutti i suoi byte insieme) L accesso al record N-esimo è molto veloce in quanto per determinare dove esso inizia (il suo offset) basta calcolare: N * L (essendo L è la lunghezza dei record) offset: distanza dall inizio del file (in byte) Modalità di apertura 3 Modalità di apertura 4 Nella fopen si indica che si usano file binari (dati grezzi) specificando i modi: "rb", "wb", "ab", "r+b", "w+b" e "a+b" La differenza tra "r+" e "w+" è nell apertura: "r+" se il file non c è dà errore, "w+" anche se il file c è lo ricrea vuoto Il modo "a+" è simile a "w+", ma permette di scrivere solo in fondo al file; anche se con fseek si può spostare l offset indietro, la scrittura avviene sempre al fondo del file e i dati precedenti non possono essere modificati Nelle modalità "ab" e "a+b" il file position pointer è pronto per aggiungere dati in fondo al file La chiusura dei file binari si ottiene sempre con la funzione fclose I file binari non sono portabili

154 Lettura 5 Lettura 6 La lettura di un blocco di byte si ottiene mediante la funzione fread(p, dim_oggetto, num_oggetti, fp); legge num_oggetti di dimensione dim_oggetto byte dal file fp e li colloca nel vettore di oggetti puntato da p restituisce il numero di oggetti (non di byte) effettivamente letti (può essere inferiore a num_oggetti in caso di errore o fine file) fa avanzare il file position pointer del numero di byte effettivamente letti si devono usare le funzioni feof e ferror per distinguere gli errori dalla fine del file struct X var1, var2; fread(&var1, sizeof(var1), 1, fp); fread(&var2,sizeof(struct X),1,fp); ogni fread legge un blocco di byte (oggetto, record) della dimensione di una struct X e lo mette nella variabile (var1 e var2) di cui viene fornito il puntatore struct X vet[100]; fread(vet,sizeof(struct X),100,fp); vengono letti 100 record corrispondenti a 100 struct X e memorizzati ordinatamente nel vettore vet Scrittura 7 Scrittura 8 La scrittura di un blocco di byte si ottiene mediante la funzione fwrite(p, dim_oggetto, num_oggetti, fp); scrive num_oggetti di dimensione dim_oggetto byte prelevati dal vettore di oggetti puntati da p nel file fp restituisce il numero di oggetti (non di byte) effettivamente scritti (può essere inferiore a num_oggetti in caso di errore) fa avanzare il file position pointer del numero di byte effettivamente scritti struct X var; fwrite(&var, sizeof var, 1, fp); fwrite(&var,sizeof(struct X),1,fp); ogni fwrite scrive un blocco di byte (oggetto, record) della dimensione di una struct X prelevandolo dalla variabile var di cui viene fornito il puntatore struct X vet[10]; fwrite(vet,sizeof(struct X),10,fp); vengono scritti 10 record della dimensione di una struct X prelevandoli ordinatamente dal vettore vett

155 Scrittura Attenzione! Per scrivere due volte la stessa var non si può usare una sola fwrite con num_oggetti = 2: così facendo verrebbero prelevati 2*sizeof(var) byte a partire dal primo byte di var (sforando la dimensione di var) come se fosse un vettore 9 Inizializzazione In alcuni casi è utile prevedere una inizializzazione del file con strutture vuote, ad esempio per rendere più semplice verificare se un record è già stato scritto struct X vett[max], vuot={"",0}; fp = fopen(file, "wb"); for (i=0; i<max; i++) fwrite(&vuot, sizeof vuot,1,fp); fclose(fp); Come già detto, non si può sostituire al ciclo for una fwrite con num_oggetti = MAX 10 Posizionamento Le funzioni fread e fwrite posizionano automaticamente il file position pointer (offset) all inizio del record successivo Il posizionamento generico si ottiene con la funzione fseek fseek( fp, offset, origine); dove: offset (di tipo long) indica su quale carattere, a partire da origine, spostare il file position pointer origine indica da dove calcolare l offset: SEEK_SET da inizio file SEEK_CURR dalla posizione corrente SEEK_END da fine file 11 Posizionamento Esempio fseek(fp, 20, SEEK_SET); sposta il file position pointer al 21 o carattere del file (il primo ha posizione 0) La fseek può indicare un offset oltre la fine del file, questo allunga il file (i byte non scritti hanno valore indeterminato) Per conoscere la posizione attuale del file position pointer (quindi l offset) si può usare la funzione ftell posiz = ftell(fp) posiz è una variabile di tipo long ftell restituisce -1L in caso di errore 12

156 Posizionamento Per tornare all inizio del file si può usare la funzione fseek: fseek(fp, 0, SEEK_SET); Ma la soluzione migliore è: rewind(fp); che oltre a eseguire la fseek precedente esegue anche la clearerr(fp) che azzera l indicazione di errori e del raggiungimento della fine del file 13 Posizionamento fgetpos(fp, ptr) memorizza in ptr l offset corrente del file fp, dà un valore non nullo in caso di errore fsetpos(fp, ptr) posiziona l offset corrente del file fp con il valore indicato in ptr, dà un valore non nullo in caso di errore ptr deve essere definito con: fps_t *ptr; il tipo fps_t è un tipo appropriato per contenere il valore di un qualsiasi offset, mentre fseek/ftell possono usare solo valori long (potrebbe essere una limitazione) 14 Troncamento 15 Esercizi 16 La libreria standard del C non fornisce alcuna funzione in grado di accorciare un file Possono esistere funzioni proprietarie quali truncate, ftruncate e altre Non è altresì possibile mediate funzioni standard eliminare dati dalla testa di un file La soluzione standard per entrambi i casi consiste nel creare un nuovo file copiandovi i dati da tenere ed escludendo quelli da rimuovere 1. Si scriva un programma per gestire un agenda elettronica. I dati relativi alle persone sono: nome, cognome, indirizzo, telefono, nota, possono contenere spazi e sono organizzati in una struct persona. Si definisca una dimensione massima per il numero di nominativi. Un file binario ad accesso diretto denominato database.dat contiene i record di tutti i nominativi. (continua)

157 Esercizi (Continuazione) Il programma deve fornire un interfaccia a menu per inserire, cancellare, cercare i dati relativi ad una persona (in base al cognome), visualizzare alfabeticamente tutti i cognomi/nomi senza dettagli. I dati completi siano mantenuti esclusivamente sul file, in memoria si tenga invece un indice alfabetico dei nomi (vettore di strutture contenenti solo cognome, nome e numero del record sul file) per accedere al nominativo. 17

158 Il preprocessore C Ver. 2.2 Funzionalità Modifica il codice C prima che venga eseguita la traduzione vera a propria Le direttive al preprocessore riguardano: inclusione di file (#include) definizione di simboli (#define) sostituzione di simboli (#define) compilazione condizionale (#if) macroistruzioni con parametri (#define) Non essendo istruzioni C non richiedono il ; finale Claudio Fornaro - Corso di programmazione in C Inclusione di file La riga con #include viene sostituita dal contenuto testuale del file indicato #include <stdio.h> Il nome del file può essere completo di pathname Ha due forme: #include <file.h> Il file viene cercato nelle directory del compilatore #include "file.h" Il file viene cercato prima nella directory dove si trova il file C e poi, se non trovato, nelle directory del compilatore 3 Inclusione di file I file inclusi possono a loro volta contenere altre direttive #include La direttiva #include viene in genere collocata in testa al file sorgente C, prima della prima funzione Generalmente, i file inclusi non contengono codice eseguibile, ma solo dichiarazioni (es. prototipi e variabili extern) e altre direttive 4

159 Definizione di simboli #define nome definisce il simbolo nome #define DEBUG I simboli vengono utilizzati dalle altre direttive del preprocessore (ad es. si può verificare se un simbolo è stato definito o no con #if) Lo scope di nome si estende dalla riga con la definizione fino alla fine di quel file sorgente nome ha la stessa sintassi di un identificatore (nome di variabile), non può contenere spazi e viene per convenzione scritto in maiuscolo #undef nome annulla una #define precedente 5 Sostituzione di simboli Dette anche macro o macroistruzioni #define nome testo_sostitutivo sostituisce (espande) nome con testo_sostitutivo in ogni punto del programma dove nome appare come identificatore (sembra una var.) #define MAX for (i=0; i<max; i++) Poiché la sostituzione avviene prima della compilazione vera e propria, il compilatore non vedrà la riga di codice precedente, ma la seguente: for (i=0; i<100; i++) 6 Sostituzione di simboli nome ha la sintassi degli identificatori, viene per convenzione scritto tutto maiuscolo se testo_sostitutivo è una quantità costante (ossia non si tratta di una macro con argomenti ) testo_sostitutivo termina a fine riga: può contenere spazi può utilizzare nomi di #define precedenti se manca, si ha la semplice definizione di nome e tutte le ricorrenze di nome vengono eliminate dal file può continuare su più righe purché ogni riga da continuare termini con il carattere \ (dal punto di vista logico si tratta di una sola lunga riga) 7 Sostituzione di simboli Dopo una sostituzione, la riga viene valutata nuovamente per verificare se il risultato necessita di un altra sostituzione (non la stessa) #define B C #define A B trasforma ogni A e ogni B in C: le A vengono prima sostituite con B e la successiva rivalutazione sostituisce le A con C nome non può essere definito più volte Attenzione: se un nome è stato sostituito da un espansione, se compare nelle ri-valutazioni successive non viene sostituito di nuovo 8

160 Macro con argomenti 9 Macro con argomenti 10 Sembra una chiamata di funzione, invece è un espansione di macro: esecuzione più veloce, programma più grande #define max(a,b) ((A)>(B)?(A):(B)) La prima parte non può avere spazi intermedi I simboli A e B vengono sostituiti da quelli che appaiono come argomenti nella chiamata della macro Esempio x = max(p+q,r+s); viene espansa in x = ((p+q)>(r+s)?(p+q):(r+s)); Si noti che funziona per tutti i tipi di dati Attenzione agli effetti collaterali: max(i++,j++) viene sostituita da ((i++)>(j++)?(i++):(j++)) e il valore più grande tra i e j viene incrementato due volte Attenzione alle parentesi: #define quadrato(x) (x)*(x) chiamata come quadrato(z+1) viene correttamente sostituita da (z+1)*(z+1) #define quadrato(x) x*x chiamata come quadrato(z+1) viene erroneamente sostituita da z+1*z+1 Macro con argomenti 11 Macro composte 12 In testo_sostitutivo il nome di un simbolo preceduto da # viene sostituito dall argomento tra virgolette #define eprint(expr) \ printf(#expr "=%g\n", expr) la macro eprint(x/y) viene sostituita da: printf("x/y" "=%g\n", x/y) e le due stringhe vicine vengono concatenate dal compilatore in "x/y=%g\n" L operatore di preprocessore ## concatena due argomenti rimuovendo gli spazi intermedi #define concat(uno,due) uno ## due concat(nome,6) viene sostituito da nome6 Il corpo della macro (testo_sostitutivo) può contenere più istruzioni C #define swap(x,y) t=x; x=y; y=t; La macro precedente viene chiamata come: swap(a,b) Per chiamarla come fosse una funzione void, è sufficiente non indicare all estrema destra nella definizione il punto e virgola swap(a,b); CON ;

161 Macro composte 13 Macro composte 14 Perché la macro composta restituisca un valore come una funzione, se possibile si separano le istruzioni mediante virgole (dà come valore quello dell espr. più a destra) #define maxswap(x,y) \ (t=x, x=y, y=t, x>y?x:y) chiamata come: maxswap(a,b); Se serve definire una variabile temporanea per la macro, si crea un blocco con una coppia di parentesi graffe: #define swap(x,y) { \ int t; t=x; x=y; y=t; } Questa macro deve essere chiamata senza il ; swap(a,b) Per poter chiamare anche la macro precedente con il ; a fine istruzione così che sembri una funzione, si scrivono le istruzioni all interno di un ciclo do-while fittizio (sempre falso, non lo ripete mai) e privo del ; finale: #define swap(x,y) \ do { \ int t; t=x; x=y; y=t; \ }while (0) Questa macro deve essere chiamata con il ; swap(a,b); Inclusione condizionale 15 Inclusione condizionale 16 Permette di include o escludere parte del codice dalla compilazione e dal preprocessing stesso #if espressione_1 istruzioni #elif espressione_2 istruzioni... #else istruzioni #endif Solo uno dei gruppi di istruzioni sarà elaborato dal preprocessore e poi compilato Le espressioni devono essere costanti intere (non possono contenere sizeof(), cast o costanti enum), sono considerate vere se!=0 L espressione defined(nome) produce 1 se nome è stato definito (con #define), 0 altrimenti #ifdef nome equivale a: #if defined(nome) e verifica che nome sia definito #ifndef nome equivale a: #if!defined(nome) e verifica che nome non sia definito

162 Inclusione condizionale Esempio Per far compilare parti diverse a seconda del sistema operativo, si può usare lo schema seguente (il simbolo _WIN32 viene definito da tutti i compilatori Win32): #ifdef _WIN32 if (_oneexit(funz)==null) abort(); #else atexit(funz); #endif Sono due funzioni quasi equivalenti, la prima esiste solo sui sistemi Win32, la seconda è standard 17 Inclusione condizionale Nel caso in cui un file incluso ne includa a sua volta altri, per evitare di includere più volte lo stesso file, si può usare lo schema seguente (quello che segue è il file hdr.h): #ifndef HDR #define HDR... contenuto di <hdr.h> #endif Se venisse incluso una seconda volta, il simbolo HDR sarebbe già definito e il contenuto non verrebbe nuovamente incluso nella compilazione 18 Inclusione condizionale Per escludere dalla compilazione un grosso blocco di codice (anche con commenti): #if 0 codice da non eseguire #endif Per isolare istruzioni da usare solo per il debug: #ifdef DEBUG printf("valore di x: %d\n", x); #endif La #define DEBUG viene eliminata a programma ultimato (spesso il simbolo DEBUG viene definito dal compilatore stesso se in configurazione debug) 19 Macro predefinite Sono simboli definiti dal preprocessore: DATE Data di compilazione del file sorgente nel formato di asctime() (in <time.h>) FILE Nome del file corrente tra virgolette LINE Numero di linea della riga corrente nel file sorgente, può essere alterato da #line STDC Indica che il compilatore è completamente aderente allo standard ANSI e non fornisce alcuna estensione TIME Ora di compilazione del file sorgente nella forma hh:mm:ss. TIMESTAMP Data e ora di compilazione del file 20

163 Macro predefinite 21 Esercizi 22 La direttiva #line può essere seguita da un numero intero per impostare il valore di LINE della riga corrente del codice C, la numerazione delle righe successive prosegue da quel valore #line 100 La direttiva #line può inoltre impostare un nuovo nome di file #line 100 "file1.c" Il valore di LINE e il nome del file vengono usati nelle informazioni di diagnostica fornite dal compilatore 1. Scrivere una macro swap(t,x,y) che scambi il valore di due argomenti di tipo t. 2. Scrivere una macro printarray(v,n) che visualizzi il contenuto del vettore v di lunghezza n. 3. Scrivere una macro sumarray(v,n,sum) che sommi gli n valori del vettore v e metta il risultato in sum.

164 Allocazione automatica 2 Allocazione dinamica della memoria Ver Claudio Fornaro - Corso di programmazione in C Il blocco di memoria viene allocato: dal programma durante l esecuzione (cioè al runtime ) nello stack del programma L allocazione avviene alla chiamata di una funzione (main incluso) per la definizione di una variabile automatica La dimensione è costante (nota al compiletime, limite del C89/C90) Il blocco non è rilasciabile esplicitamente, il rilascio avviene automaticam. quando termina la funzione dove è definita la corrisp. variabile int vett[1000]; (interno ad una funzione) Allocazione statica 3 Allocazione dinamica 4 Il blocco di memoria viene allocato: dal compilatore prima dell esecuzione (cioè al compile-time ) nel segmento dati del programma L allocazione avviene per la definizione di una variabile di classe di allocazione statica (es. una variabile esterna) La dimensione è costante (nota al compiletime ) Il blocco è non rilasciabile (non è riutilizzabile per altre allocazioni) int vett[1000]; (esterno alle funzioni) Il blocco di memoria viene allocato: dal programma durante l esecuzione (cioè al runtime ) nello heap del programma L allocazione avviene su richiesta di opportune funzioni La dimensione è specificata dalle funzioni ad ogni richiesta (più grande è il blocco richiesto, più tempo ci mette la funzione a riservarlo) Il blocco è rilasciabile (per essere utilizzato per altre allocazioni) solo esplicitamente per mezzo di opportune funzioni

165 Allocazioni 5 Funzioni di allocazione 6 Il compilatore riserva memoria: per il codice eseguibile per le variabili statiche per la zona inutilizzata dove crescono stack e heap La zona inutilizzata può esaurirsi per effetto di ripetute chiamate a funzione o allocazioni dinamiche In genere i compilatori permettono di specificare la quantità di memoria da riservare per la zona inutilizzata STACK inutilizzata HEAP DATI CODICE Si trovano in <stdlib.h> Chiedono l allocazione di un blocco di byte e restituiscono un valore di tipo puntatore-avoid che si riferisce a questo Un opportuno cast sul puntatore restituito fa sì che il blocco di byte sia considerato dal compilatore come una struttura dati del tipo indicato nel cast (ad esempio un cast come (int *) fa sì che il compilatore consideri il blocco di byte come un vettore di interi) Funzioni di allocazione Il cast esplicito non sarebbe necessario per un compilatore C aderente allo standard (l assegnazione di un puntatore void ad un altro non richiede cast infatti), ma molti compilatori, in particolare quelli che compilano anche codice C++, lo richiedono comunque e quindi è bene aggiungerlo Non si può applicare l operatore sizeof a un blocco di memoria allocato dinamicamente 7 Funzioni di allocazione punt = (tipo*)malloc(dimensione) richiede un blocco di byte (non inizializzato) di dimensione byte, restituisce un puntatore void al primo byte (NULL in caso di errore: non riesce ad allocare la memoria richiesta) double *p; p=(double *)malloc(sizeof(double)); punt = (tipo*)calloc(numogg, dimogg) richiede un blocco di byte inizializzati a 0 di dimensioni tali da contenere un vettore di numogg elementi, ciascuno di dimensioni dimogg byte, restituisce un puntatore void al primo byte (NULL se non riesce) 8

166 Funzioni di allocazione void *realloc(void *p, size_t dim) modifica la dimensione dell oggetto puntato da p portandola a dim byte, restituisce il puntatore al nuovo blocco di byte I contenuti del blocco sono preservati (tranne la parte che viene rimossa se dim è diminuita) Se la nuova dimensione è maggiore della precedente, la parte aggiuntiva non è inizializzata In caso di errore restituisce NULL e non modifica l oggetto puntato da p 9 Funzioni di allocazione Poiché per aumentare il blocco di memoria e mantenerne le celle contigue potrebbe essere necessario allocare un blocco nuovo e ricopiarvi i valori del blocco di partenza, il puntatore restituito può essere diverso da quello che punta al blocco di partenza p 10 Rilascio della memoria La memoria allocata dinamicamente viene rilasciata automaticamente solo al termine del programma (il C non ha un garbage collector che raccolga la memoria inutilizzata) Il rilascio esplicito al run-time permette di riutilizzare quella porzione di memoria per servire successive allocazioni free(void *p) rilascia il blocco di memoria puntata dal puntatore p (il sistema mantiene memoria della quantità allocata) Alcuni compilatori (in particolare i compilatori C++) in alcuni casi potrebbero richiedere un cast di p a void *: free((void *)p) 11 Rilascio della memoria Se un puntatore p punta ad una variabile dinamica contenente un puntatore q ad un altra variabile dinamica, bisogna rilasciare prima q e poi p : free(p->q); free(p); p struct a b q c 12

167 Esempi di allocazione Variabile scalare Istanziazione di una variabile scalare: double *p; p=(double *)malloc(sizeof(double)); Utilizzo: *p = 1.9; 13 Esempi di allocazione Vettore unidimensionale int *p; p=(int *)malloc(sizeof(int)*100); p=(int *)calloc(100, sizeof(int)); In entrambi i casi viene allocato lo spazio necessario per 100 int e il cast lo trasforma in puntatore-a-int, così può essere utilizzato come un vettore-di-int Utilizzo: *p = 3; equivalenti p[0] = 3; *(p+12) = 19; equivalenti p[12] = 19; 14 Esempi di allocazione Vettore di strutture struct s {int x; int y;} *p, var; p=(struct s *)malloc(sizeof(struct s)*100); p=(struct s *)malloc(sizeof(var)*100); p=(struct s *)malloc(sizeof(*p)*100); Le tre malloc precedenti sono equivalenti. Allocano un blocco di memoria adeguato per contenere 100 elementi (consecutivi) di tipo struct s e il cast (implicito o esplicito) fa sì che il compilatore lo consideri come un vettore di struct s p[12].x = 19; 15 Esempi di allocazione Matrice bidimensionale Con vettore di puntatori a Tipo, array1 blocco non contiguo: int **array1; array1=(int **)malloc(nr*sizeof(int *)); for (i=0; i<nr; i++) array1[i]=malloc(nc*sizeof(int)); Utilizzo: array1[riga][colonna] = 12; Crea un vettore di NR puntatori e per ogni puntatore alloca un vettore di int di lunghezza NC; NR e NC possono essere variabili 16

168 Esempi di allocazione Matrice bidimensionale Con vettore di puntatori a Tipo, blocco contiguo: array2 int **array2; array2=(int **)malloc(nr*sizeof(int *)); array2[0]=(int*)malloc(nr*nc*sizeof(int)); for (i=1; i<numrighe; i++) array2[i]=array2[0]+i*numcol; Utilizzo: array2[riga][colonna] = 12; Crea un vettore di NR puntatori, alloca un blocco per tutta la matrice e calcola e assegna ogni puntatore con l indirizzo di ciascuna riga; NR e NC possono essere variabili 17 Esempi di allocazione Matrice bidimensionale Con vettore di Tipo, simulato con calcolo esplicito: array3 int *array3; array3=(int*)malloc(nr*nc*sizeof(int)); Utilizzo: array3[riga*nc + colonna] = 12; Crea un blocco per tutta la matrice e accede agli elementi calcolandone la posizione (offset) riferita al primo elemento, la matrice in realtà è un vettore; NR e NC possono essere variabili 18 Esempi di allocazione Matrice bidimensionale Con puntatore a vettori di Tipo: int (*array4)[numcol]; array4 array4=(int (*)[NUMCOL]) malloc(nr*sizeof(*array4)); Utilizzo: array4[riga][colonna] = 12; Crea un blocco per tutta la matrice e lo fa puntare da un puntatore (notare il cast); NR può essere variabile, NUMCOL è una costante Il tipo di array4 è puntatore a vettore di NUMCOL int, *array4 è un vettore di NUMCOL int e la sua dimensione quella di una riga della matrice (NUMCOL) 19 Esempi di allocazione Passaggio di matrice dinamica Funzione che accetta una matrice contigua di dimensioni note al run-time (tipo array3): int f2(int *arrayp,int rows,int cols) {... per accedere a matrice[i][j], nella funzione si deve usare arrayp[i*cols+j] } Chiamata: f2(&matrice[0][0], righe, colonne); Vede la matrice come un vettore, non è conforme allo standard perché l accesso ad elementi oltre la fine della prima riga è indefinito 20

169 Esempi di allocazione Passaggio di matrice dinamica Funzione che accetta una matrice anche non contigua di dimensioni note al run-time: int f2(int **array,int rows,int cols) { si può accedere direttam. ad array[i][j] } Chiamata: f3(matrice, righe, colonne); Non si può passare una matrice definita come int a[row][col] perché, nel passaggio alla funzione, a decade non in un *int, non in un **int, ma in un int (*)[Col] (puntatore a vettore di Col int) 21 Esercizi 1. Si scriva un programma che ordini in senso crescente i valori contenuti in un file di testo e li scriva in un altro. Non è noto a priori quanti siano i valori contenuti nel file. Si utilizzi una funzione per l ordinamento. Il programma deve allocare un vettore dinamico della dimensione appropriata, quindi: 1. conta quanti sono i valori leggendoli dal file e scartandoli 2. crea il vettore dinamico 3. lo riempie rileggendo il file 4. lo passa alla funzione di ordinamento 5. scrive il file di output con il contenuto del vettore riordinato. 22 Esercizi 2. Si scrivano due funzioni int somma1(int *v,int nrows,int ncols); int somma2(int **v,int nrows,int ncols); che calcolino la somma degli elementi di una matrice di int passata per argomento insieme alle sue dimensioni. Si predisponga un main che crei una matrice statica ms 10x20 e una dinamica md 20x30 contigua come vettore di puntatori a int (come array2) e per ciascuna si chiamino le due funzioni. Il caso problematico è il passaggio della matrice statica a somma2(): è necessario definire una struttura dati temporanea. 23

170 Indirizzo di una funzione 2 Puntatori a funzioni Ver. 2.2 Si può chiamare una funzione tramite l indirizzo di memoria dal quale inizia il codice eseguibile della funzione stessa L indirizzo di memoria di una funzione può essere assegnato ad una variabile puntatore, memorizzato in un vettore, passato a una funzione, restituito da una funzione Claudio Fornaro - Corso di programmazione in C Definizione di variabili 3 Definizione di variabili 4 tipo (*nome)(parametri ); definisce la variabile nome come puntatore a una funzione che restituisce un valore del tipo indicato e richiede i parametri indicati Le parentesi intorno al nome sono necessarie altrimenti si avrebbe: tipo *nome(parametri ); e questa costituisce il prototipo di una funzione che restituisce un puntatore del tipo indicato e richiede i parametri indicati double (*fp)(); dichiara fp come variabile puntatore a una funzione che restituisce un double (nulla è indicato per i suoi parametri, quindi non vengono controllati; un compilatore C++ invece considera l argomento come void) double (*fp)(double, double); dichiara fp come variabile puntatore a una funzione che ha come parametri due double e restituisce un double

171 Definizione di variabili int (*fpv[3])(long); dichiara fpv come vettore di 3 elementi, ciascuno è un puntatore a una funzione che ha come parametri un long e restituisce un int Le definizioni esterne inizializzano a NULL le variabili puntatori a funzioni 5 Inizializzazione Il nome di una funzione equivale al suo indirizzo di memoria (come per i vettori) Avendo le seguenti funzioni (prototipi): double pow(double,double); double atan2(double,double); int f1(long), f2(long); si possono avere le seguenti inizializzazioni: double (*fp)(double,double) = NULL; double (*fp1)(double,double) = pow; int (*fpv[3])(long)={f1,f2}; /*+NULL*/ int (*fpv1[3])(long)={null};/*3null*/ 6 Utilizzo con typedef typedef può definire un tipo puntatore a funzione con determinati parametri e valore restituito typedef double (*fptype)(double,double); dichiara fptype come puntatore a funzione con due argomenti double che restituisce un double fptype fp = pow; fptype fpv[5] = {pow,null,atan2}; Nota: fpv[3] e fpv[4] sono inizializzati a NULL perché l inizializzazione dei primi elementi impone 0 (NULL) per i rimanenti 7 Assegnazione Il nome di una funzione equivale al suo indirizzo di memoria (come per i vettori) L operatore & è ininfluente se applicato al nome di una funzione Esempi (l operatore & può essere omesso): fp = &atan2; fpv[2] = &f1; 8

172 Confronto È un normale confronto tra puntatori, permette ad esempio di determinare se il puntatore si riferisce ad una certa funzione o se è NULL if (fp == pow) printf("fp punta a pow\n"); 9 Chiamata della funzione La funzione il cui puntatore è stato memorizzato nella variabile fp può essere richiamata in due modi: (*fp)(argomenti) fp(argomenti) Il primo metodo rende più evidente che si tratta di un puntatore a funzione e non del nome di una funzione, il secondo è più leggibile x = (*fp)(2.23, 4.76); x = fp(2.23, 4.76); i = (*fpv[1])(22); i = fpv[1](22); 10 Cast 11 Passaggio a funzione 12 Il cast di un puntatore a funzione (o anche di una funzione) deve indicare il tipo della funzione risultante (che è composto sia dal tipo delle variabili sia da quello del valore restituito) fp=(double (*)(double,double))f1; Il cast fa sì che la funzione f1 appaia come funzione che ha 2 argomenti double e restituisce un double x=((double(*)(double,double))fp)(2.,2.); Il cast fa sì che il puntatore a funzione fp appaia come funzione che ha 2 argomenti double e restituisce un double. Senza le parentesi intorno al cast, il cast sarebbe stato riferito al tipo restituito da fp Un puntatore a funzione può essere passato ad una funzione come argomento. Il parametro attuale deve essere il nome di una funzione Il parametro formale deve essere il prototipo delle funzioni chiamabili (è lì che viene definito il nome del puntatore visto dall interno della funzione stessa)

173 Passaggio a funzione int calc(int x,int y,int (*funz)(int,int)) { return (*funz)(x,y); È qui che viene definita funz } int piu(int a,int b) {return a+b;} int meno(int a,int b) {return a-b;} main() { int m, n; m = calc(12, 23, piu); n = calc(12, 23, meno); Valore restituito da funzione Una funzione può restituire un puntatore a funzione Primo metodo Si definisce il tipo della funzione con l istruzione typedef: typedef int (*TipoFunz)(int,int); TipoFunz è un nuovo tipo: puntatore-afunzione-che-ha-2-argomenti-int-erestituisce-un-int 14 Valore restituito da funzione typedef int (*TipoFunz)(int,int); int piu(int a,int b) {return a+b;} int meno(int a,int b) {return a-b;} TipoFunz operaz(int f) { static TipoFunz fpv[2]={piu,meno}; return fpv[f]; } main() { int y; } static per poter usare il puntatore dopo che la funzione è terminata TipoFunz op; /* var. puntatore */ op = operaz(1); /* scelta op. */ y = (*op)(12, 23); 15 Valore restituito da funzione Secondo metodo (meno chiaro) Si scrive ogni cast esplicitamente int (* operaz(int f))(int,int) { static int (* fpv[2])(int,int) ={piu,meno}; return fpv[f]; } f è l argomento di operaz (int,int) sono i parametri della funzione restituita la parte esterna (non sottolineata) è il tipo restituito static è riferito a fpv 16

174 Valore restituito da funzione Secondo metodo (Continuazione) main() { int y; int (*op)(int,int);/*puntatore */ op = operaz(1); /* scelta op. */ y = (*op)(12, 23); } 17 Passaggio a funzione con cast qsort La funzione qsort in <stdlib.h> ordina un vettore, deve essere chiamata indicando il nome della funzione da usare per il confronto degli elementi, il suo prototipo è: void qsort( void *base, puntatore al vett da riordinare size_t n, sua dim (numero di elementi) size_t size, dim di ogni elemento (in byte) int (*cmp)(const void*,const void*)) cmp è un puntatore alla funzione da utilizzare per confrontare due elementi del vettore 18 Passaggio a funzione con cast qsort La funzione passata per cmp deve: avere come argomenti due puntatori agli elementi del vettore da confrontare (qsort passerà a cmp gli indirizzi dei due elementi come &base[i]) restituire un valore int La clausola const indica che gli oggetti da confrontare non saranno modificati Il tipo void permette di passare puntatori di qualsiasi tipo: sarà la funzione passata ad utilizzarli con il tipo corretto Il valore restituito da cmp deve essere <0 se il 1 o elemento è minore del 2 o, 0 se sono uguali, >0 se il 1 o è maggiore del 2 o 19 Passaggio a funzione con cast qsort su vettore di interi #include <stdlib.h> #include <stdio.h> int cfr(const int *a, const int *b) { if (*a < *b) return -1; else if (*a > *b) return +1; else return 0; } main() { int vett[10] = {3,2,5,4,7,9,0,1,6,8}; qsort(vett, 10, sizeof(int), (int (*)(const void*,const void*))cfr ); } 20

175 21 Passaggio a funzione con cast qsort su vettore di interi Nel caso precedente la funzione passata cfr ha prototipo non identico a quello richiesto, ma compatibile; quindi viene fatto un cast della funzione passata: (int (*)(const void*,const void*))cfr I valori che la funzione qsort passa a cfr per essere confrontati sono i vari &vett[i] Passaggio a funzione con cast qsort su vettore di stringhe Per ordinare con qsort un vettore di stringhe non si può utilizzare direttamente strcmp Gli elementi che qsort() passerebbe alla funzione di confronto sarebbero &vs[i], cioè i puntatori (indirizzi) ai puntatori agli elementi, mentre strcmp() richiede semplici puntatori a char 22 Passaggio a funzione con cast qsort su vettore di stringhe Bisogna definire una funzione ausiliaria: int cp(const void *p1,const void *p2) { return strcmp(*(char * const *)p1, *(char * const *)p2); } Gli argomenti di cp sono generici puntatori costanti a void (const void * ), il cast (char * const *) li riconverte nel loro vero tipo (puntatore a puntatore costante a char) e la deferenziazione produce un puntatore costante a char come la strcmp richiede 23 Passaggio a funzione con cast qsort su vettore di strutture Per ordinare un vettore di struct bisogna definire una funzione di confronto ausiliaria: int structcmp(const void *p1, const void *p2) { const struct nome *sp1; const struct nome *sp2; sp1=(const struct nome *)p1; sp2=(const struct nome *)p2; if (sp1->membro > sp2->membro) return +1; else if (... 24

176 Esercizi 1. Scrivere un programma che riordini con qsort un vettore di stringhe 2. Scrivere un programma che 1. definisca un vettore di 10 struct contenenti due valori interi x e y 2. inizializzi il vettore 3. ordini il vettore con qsort in base alla somma dei valori (se due somme sono uguali, il confronto è tra i primi membri, es. {4,6}>{3,7}) 4. cerchi un elemento, dati i due valori in input, con la bsearch di <stdlib.h> e ne fornisca l indice 25

177 Complementi sul C - 2 Ver Claudio Fornaro - Corso di programmazione in C Precedenza e associatività () [] ->. S D! ~ ++ + * & (cast ) sizeof S D * / % S D + - (somma e sottrazione) S D << >> S D < <= > >= S D ==!= S D & S D ^ S D S D && S D S D?: S D = += = *= /= %= &= ^= = <<= >>= S D, S D S D: da Sinistra a Destra, S D: da Dst. a Sin. 2 Esempi *p.x equivale a *(p.x) *p->n++ equivale a *((p->n)++) char **q equivale a char *(*q) q è un puntatore a puntatore a char int mx[5][7] equivale a int (mx[5])[7] mx è vettore di 5 vettori di 7 int int *vett[5] equivale a int *(vett[5]) vett è un vettore di 5 puntatori a int int (*vett)[5] vett è un puntatore a un vettore di 5 int 3 Esempi int (*f)(long) f è un puntatore a funzione con un argomento long e che restituisce un int int *f(long) f è una funzione con argomento un long e che restituisce un puntatore a int 4

178 Esempi 5 Esempi 6 char (*(*x())[])() x è una funzione che restituisce un puntatore (le () hanno precedenza sull *) ad un array di puntatori a funzione che restituiscono un char char (*(*x[3])(double,double))[5] x è un array di 3 puntatori a funzione (con parametri due double) che restituiscono un puntatore a un array di 5 char char *(*(*x[n])())() x è un array di N puntatori a funzione che restituiscono un puntatore a funzione che restituiscono un puntatore a char void (*signal(int sig, void (*handler)(int)) )(int) signal è una funzione che ha come parametri un int (sig) un puntatore a funzione (handler) che ha come parametro un int e come tipo restituito un void e restituisce un puntatore a funzione che ha come argomento un int e restituisce un void Argomenti nella riga di comando Permettono di passare al programma dei valori dalla riga di comando (di shell) C:\>stampav ciao come va C:\>stampav ciao come va ciao come va C:\>_ x 7 Argomenti nella riga di comando Bisogna definire il main in uno di questi modi: main(int argc, char *argv[]) main(int argc, char **argv) Viene automaticamente creato un vettore di puntatori a stringhe, ciascuna delle quali contiene uno degli argomenti. In dettaglio: argc è il numero di elementi sulla riga di comando, incluso il nome del comando argv è un puntatore ad un vettore di puntatori a carattere: contiene gli indirizzi delle singole stringhe che costituiscono la riga di comando. L ultimo elemento del vettore è sempre NULL 8

179 Argomenti nella riga di comando La struttura prodotta per argv è la seguente argv argc 4 argv[0] argv[1] argv[2] argv[3] NULL argv[4] stampav\0 ciao\0 come\0 va\0 9 Argomenti nella riga di comando Quindi nell esempio: argc è il numero di parametri, incluso il comando (il nome del programma), vale 4 argv[0] è il nome del programma ( stampav ) argv[1] è il primo parametro ( ciao ) argv[2] è il secondo parametro ( come ) argv[3] è il terzo parametro ( va ) argv[argc] è il terminatore NULL E importante verificare sempre che il numero di parametri effettivamente passati sia concorde con quanto richiede il programma 10 Parsing della riga di comando 11 Parsing della riga di comando 12 Esempio di stampav: visualizza in colonna tutti gli argomenti passati sulla riga di comando (1 o modo) main(int argc, char *argv[]) { int i; for (i=1; i<argc; i++) printf("%s\n", argv[i]) return EXIT_SUCCESS; } N.B. Questo programma funziona con qualsiasi numero di argomenti Esempio di stampav: visualizza in colonna tutti gli argomenti passati sulla riga di comando (2 o modo) main(int argc, char *argv[]) { while (--argc > 0) printf("%s\n", *++argv) return EXIT_SUCCESS; } N.B. Questo programma funziona con qualsiasi numero di argomenti

180 Parsing della riga di comando 13 Parsing della riga di comando 14 Altro esempio: programma che elabora il file indicato sulla riga di comando (se c è) main(int argc, char *argv[]) { FILE *fp; if (argc == 2) controllo n. argomenti { if ((fp=fopen(argv[1],"r"))== NULL) { fprintf ("File %s non aperto\n", argv[1]); return EXIT_FAILURE; } Gli argomenti sulla riga di comando sono stringhe, per ottenere il valore numerico nel caso si trattino di numeri, si possono usare le già citate funzioni seguenti (<stdlib.h>): varint = atoi(stringa) converte stringa in un valore int varlong = atol(stringa) converte stringa in un valore long vardouble = atof(stringa) converte stringa in un valore double Assert 15 Abort 16 Macro per il debug definita in <assert.h> void assert(int condizione); Se la condizione è diversa da 0 (condizione soddisfatta) il programma continua Se la condizione è pari a 0 (condizione fallita): viene mandato su stderr (in console mode) un messaggio contenente: la condizione stessa, il nome del file e il numero della riga dove è stata valutata l assert viene invocata la funzione abort per terminare il programma con segnalazione di anomalia Tutte le macro assert possono essere disabilitata definendo la costante NDEBUG prima della #include<assert.h> Manda su stderr (in console mode) il messaggio abnormal program termination e quindi invoca la funzione raise(sigabrt). L azione di default per SIGABRT è di terminare il processo e restituire un exit code di valore 3 abort(); Non restituisce alcun valore Non fa il flush dei buffer e non chiama le funzioni di atexit Definita in <stdlib.h>

181 Atexit 17 Atexit 18 Registra i nomi delle funzioni da chiamare automaticamente poco prima della terminazione normale del programma (return, exit()) atexit(nomefunzione) nomefunzione è l indirizzo di una funzione (nome o puntatore) con prototipo void nomefunzione(void) Ogni chiamata registra una funzione, le funzioni vengono chiamate in ordine LIFO Restituisce 0 se OK,!=0 se c è qualche errore Definita in <stdlib.h> #include <stdlib.h> #include <stdio.h> void fn1() { printf("dopo\n"); } void fn2() { printf("viene "); } void fn3() { printf("questo "); } main() { atexit(fn1); atexit(fn2); atexit(fn3); printf("questo viene prima\n"); } Esercizi 19 Esercizi Scrivere un programma denominato calcola che richieda 3 parametri sulla riga di comando: 1. operando 1 2. operatore (+,-,*,/) 3. operando 2 e calcoli: op1 oper op2. In caso di errore (numero parametri scorretto o operatore non riconosciuto) lo segnali. Esempio c:\> calcola 12 * Scrivere un programma denominato cat che concateni tutti i file (di testo) passati sulla riga di comando (tranne l ultimo) nell ultimo file indicato: cat file1 file2... file concatena file1, file2,... in file N.B. i file da concatenare potrebbero non avere il ritorno a capo in fondo all ultima riga, lo si aggiunga se manca (non per l ultimo file da concatenare)

182 2 Notations Ver. 1.3 Relative Numbers When binary numbers are stored in an n -bit machine, the sign itself must be coded by using some of the available n bits There are only 2 signs: + and so just one bit is required Claudio Fornaro 3 4 Sign-and-Magnitude Notation To have a range of negative values, the simplest way is to mirror a range of positive values around value 0 Thus negative values are obtained by just changing the sign of the corresponding positive value This is the usual way in decimal notation Sign-and-Magnitude Notation Range of negative values Range of positive values

183 5 6 Sign-and-Magnitude Notation The Sign-and-Magnitude (SM) notation stores positive and negative values by dividing the n total bits in 2 parts: 1. 1 bit for the sign 2. n 1 bits for the absolute value (module) Sign is located at the MSB and: 0 is used for values 0 1 is used for values 0 S Module Sign-and-Magnitude Notation There are 2 different bit patterns for 0, but must be considered the same value SM Notation Examples value +5 in SM on 6 bits: value +5 in SM on 8 bits: value 5 in SM on 6 bits: value 5 in SM on 8 bits: value 0 in SM on 6 bits: and Exercises Convert the values as requested +12 SM on 8 bits 12 SM on 8 bits +23 SM on 6 bits 127 SM on 8 bits SM () SM () SM () SM () 10 Exercises Solutions SM SM SM SM 127

184 9 10 Sign-and-Magnitude Range Consider an n bit SM number: If we ignore the sign bit, we get an n 1 bit pure binary number, thus its range is: 0 (2 n-1 1) Adding the sign bit with value 0, we get: +0 +(2 n-1 1) [2 n-1 positive values] while adding sign bit with value 1, we get: (2 n-1 1) 0 [2 n-1 negative values] Composing the two ranges: (2 n-1 1) 0, +0 +(2 n-1 1) [2 n val.] Sign-and-Magnitude Range Examples an 8 bits SM value can have values from 127 to +127 (with 2 zeroes) a 16 bit SM value can have values from to (with 2 zeroes) Sign-and-Magnitude Pros and Cons 11 Two s Complement Notation 12 It is easy to understand, as we normally use it with base 10 (placing a sign) It is easy to convert to and from SM notation There are 2 different bit patterns with the same meaning: computations are more complex and slow (adjustments are required) special circuits must deal with this, so more money is spent by the buyer This is the most used notation for relative integer values in microprocessors There is no double-zero problem Computations are easier and faster than using SM

185 13 14 Two s Complement Notation To have a range of negative values, the 2C notation adopt the solution to translate a range of positive values to the left of the zero Negative values are obtained by adding a negative constan to the corresponding positive value This constant is the width of the positive range + 1 (turned negative) Two s Complement Notation Range of negative values Constant value: 5 Range: 0 4 ± Range of positive values Two s Complement Notation Positive values and zero The notation is the same as for n -bit SM Sign bit is zero 0 Module Negative values The n bits (including sign) are calculated from the corresponding positive value. The sign bit becomes 1 by effect of the computation (not by definition as in SM) Two s Complement Notation To get a negative value from a positive one, instead of adding the negative constant to the positive value, you use the Two s Complement Operation (the result is the same) This is called 2 s-complementing the value (often abbreviated in just complementing) More correctly, complementing a value just changes its sign, thus complementing a negative value results in the corresponding positive value

186 17 18 Two s Complement Notation Do not confuse 2C NOTATION and 2C Operation: 2C NOTATION: tells how bits must be interpreted to result in a value 2C OPERATION: is just a computation that operates on a value and produces a value The 2C Operation is used to compute the opposite of a value already expressed in the 2C Notation Two s Complement Notation The bits of a negative 2C value are the result of a computation and they are not to be considered a positional notation: = 1 x x x 2 2 NO Actually a 2C negative number CAN be considered in a positional notation notation if the MSB is given a negative meaning ( that negative constant!): = 1*2 7 +1*2 4 +1*2 2 = = = Two s Complement Operation 1 st method 1 s-complement all bits (i.e. invert 0 1) add one (to the LSB) Example given: inversion: add one: 1 = result: CPUs have fast 1C and Add_1 operations Two s Complement Operation 2 nd method from RIGHT to LEFT, copy all bits with value 0 (if any) until you get a bit with value 1 copy this bit with value 1 1 s-complement (invert) the remaining bits Example given: result:

187 21 22 Two s Complement Notation Examples value +5 in 2C on 6 bits: value +5 in 2C on 8 bits: value 5 in 2C on 6 bits: value 5 in 2C on 8 bits: value 0 in 2C on 6 bits: Two s Complement Notation Example Convert C to decimal. This value is positive, so the module is , which is a pure binary number (5) Result Two s Complement Notation Example Convert C to decimal. This value is negative, so IS NOT THE MODULE; the corresponding positive value must be determined by the 2C-Oper.: C, NOW the module is (83), so the result is 83 (note the sign!) Alternatively: = 1*2 7 +1*2 5 +1*2 3 +1*2 2 +1*2 0 = = 83 Two s Complement Notation What about values like C? Trying to convert them to decimal, you find that the corresponding positive value is identical! ( ) is a special number: it is the negative constan that is added to a positive n bit value to get the corresponding negative value

188 25 26 Two s Complement Notation What about values like C? It is the most negative number that can be written by using n bits, its value is: 2 n-1 It is the 128 in the previous example The first bit is then used both as sign and as part of the module, to compute the decimal value it is useful to imagine this bit doubled and find the corresponding value: (1) Two s Complement Range Consider an n bit SM number X: If X is positive, the range is the same as the SM notation: +0 +(2 n-1 1) [2 n-1 positive values] If X is negative, it comes from the corresponding positive value by applying the 2C-Operation to it, so at least we have: (2 n-1 1) 0 [2 n-1 negative values] Two s Complement Range Continuation: But 0 and +0 are just the same: Value corresponds to 2 n-1 which is located to the left of (2 n-1 1) Thus the 2C Notation range is: (2 n-1 ) 0 +(2 n-1 1) [2 n val.] Examples an 8 bits 2C value can have values from 128 to +127 (with 1 zero) a 16 bit 2C value can have values from to (with 1 zero) Conversion Exercises Convert the following values to SM and 2C on 6 bits (when possible) VALUE SM C

189 29 30 Conversion Exercises Conversion Exercises Solutions: VALUE SM IMP IMP 2C IMP Convert to decimal the following values assuming they are in the SM notation first and in the 2C notation last VALUE VALUE SM +21 VALUE 2C Conversion Exercises 2C s Sign Extension Solutions: VALUE VALUE SM IMP VALUE 2C To convert a value from a n -bit 2C notation to a m -bit 2C notation (m >n): if the value is positive, m-n bits must be added to the module (at the left side of the module, after the sign bit)

190 C s Sign Extension but if the value is negative, just adding m- n zeroes is not correct it is however possible to: complement the value (2C Op) to the corresponding positive value, add the m-n zeroes to the module complement the extended value back to a negative value C s Sign Extension In both cases the same result can be obtained by adding m-n bits with the same value as of the sign to the left of the value Actually, the first identical left bits of a value constitute its sign C s Sign Extension Thanks to the sign extension property, to reduce the number of bits of a 2C value it is possible to suppress the first bits if they all have the same value and the resulting value maintains the old sign OK NO! OK OK NO! Comparison Exercises Each line of the following table contains 2 values, compare the values assuming they are SM values first and 2C values last, place a sign like >, < or = in the central columns (do not convert to dec.) VALUE SM < 2C < VALUE

191 37 38 Comparison Exercises Solutions: VALUE SM < > > < > 2C < > < > = VALUE Notes positive values are always > negative values for negative values, 2C and SM comparisons are always opposite (unless they are the same value ) 2C Addition Adding two 2C values (with the same number of bits), the result is already a 2C value (positive or negative, with the same number of bits) Subtraction is obtained by adding the opposite of the second value: A B = A + ( B) = A + 2Coperation(B) Any carry from the MSBs must be discarded, it is NOT an overflow error C Addition There is an overflow error (and the result is not meaningful) when two values with the same sign are added and the result has the opposite sign, that is: positive + positive = negative negative + negative = positive When the two values have different signs, the overflow error cannot occur 2C Addition Examples of 4-bit 2C values addition = OK = OVERFLOW

192 41 42 CARRY 2C Addition Examples of 4-bit 2C values addition = OK = OK 2C Addition Exercises Convert the following values to 5-bit 2C (if possib.) and compute the expressions C Addition Exercises Solutions: = Overflow NoCarry = OK NoCarry 7 10 = OK NoCarry 7 5 = OK Carry 9 19 = IMP (19 on 5 bits?) = Overflow Carry = IMP (+16 on 5 bits?) 2C Shift A shift operation of a 2C value results in a 2C value, unless an overflow occur Right shift each bit is shifted to the right the sign bit is shifted too, but also mantains its value (sign extension) C / 2 = C C / 2 = C being the fractional part discarded: repeated shift of a positive value gives 0 repeated shift of a negative value gives 1

193 C Shift Left shift each bit is shifted 1 position to the left a 0 bit is inserted to the right the sign bit is shifted away C * 2 = C C * 2 = C 2C Shift Left shift - Overflow As the value grows, it is possible to get an overflow (the result must be discarded) In each intermediate shift (*2) the value must retain the sign, otherwise there is an overflow error C * 8 = C * 2 = C 1 st SHIFT OK C * 2 = C OVERFLOW C * 2 = C there was an OV in the previous step, so the value is incorrect C * 2 = C OVERFLOW C Shift Exercises Convert the following values to 5-bit 2C and then compute the expressions 6 * 2 10 * 4 2 * 8 4 * 4 4 * 4 4 / 4 4 / 4 4 / 8 4 / 8 4 * 8 2C Shift Exercises Solutions: 6 * « OK 10 * « OV (1 st shift) 2 * « OK 4 * « OV (2 nd shift) 4 * « OK 4 / » OK 4 / » OK ( 1) 4 / » OK 4 / » OK ( 1!!!) 4 * « OV (3 rd shift)

194 C Multiplication and Division To multiply or divide 2C values, negative values must be converted to the corresponding positive values, then the result is adjusted to have the correct sign Multiplications and divisions by powers of 2 are not achieved by using shift operation 2C Operation Exercises Evaluate the following expressions after having converted the decimal values to 8-bit 2C notation and notify any overflow (use shifts for multiplications and divisions) 1. A=5, B= 2 A/4 B*2 2. A= 16, B=7 (A/2 B*4) / 4 3. A=11, B= 16 (A*2 B/8) * C Operation Exercises Solution (1): A=+5, B= 2 A/4 B*2 = A/4 + 2C-Op(B*2) +5 = A = +5= = B = 2 = A/4 = B*2 = B*2 = A/ B*2= = (NO OV) 2C Operation Exercises Solution (2): A= 16, B=+7 (A/2 B*4) / 4 = (A/2 + 2C-Op(B*4)) / = A = 16 = = B = +7 = A/2 = B*4 = B*4 = A/ B*4= = (NO OV) / (-9)

195 C Operation Exercises Solution (3): A=+123, B= 16 (A*2 B/8) * 4 = (A*2 + 2C-Op(B/8)) * = A = +123 = = B = 16 = A*2 = (OVERFLOW) Compact notation Sometimes, a generic sequence of bits is expressed in a hexadecimal format for ease of transcription, but it is NOT a value in base 16! 2C5 2C = C = A26 2C = C = C5 SM = SM = A26 SM = SM =

196 Scopo 2 Salti non locali Ver Claudio Fornaro - Corso di programmazione in C Permettono ad una funzione annidata a livelli profondi di restituire direttamente il controllo ad un livello più esterno Esempio: a()chiama b()che chiama c()che chiama d() Un salto non locale permette ad esempio a d() di tornare immediatamente ad a() senza passare dalle funzioni intermedie c() e b() Questo metodo viene utilizzato soprattutto per la gestione degli errori e dei segnali (ad es. divisione per 0, interrupt, etc.) Funzioni e strutture dati sono in <setjmp.h> Funzionamento Lo stato attuale del programma (alcuni registri della CPU, tra cui Program Counter, Stack Pointer e Frame Pointer) viene salvato in una variabile di tipo jmp_buf dalla funzione setjmp Il salto non locale avviene tramite la funzione longjmp che ripristina lo stato antecedente alla chiamata a setjmp utilizzando i dati memorizzati in jmp_buf: quindi l esecuzione torna alla setjmp e prosegue dall istruzione successiva ad essa 3 Preparazione al salto int setjmp(jmp_buf env) salva lo stato del programma nella variabile indicata (env). La chiamata a longjmp ritornerà a questo punto e l esecuzione continuerà dall istrzuzione successiva. Restituisce valore 0 dopo la chiamata a setjmp, valore diverso da 0 se di ritorno dalla longjmp Nota: la funzione che contiene la longjmp deve poter accedere a env, quindi env: viene passato di funzione in funzione fino a quella che contiene la longjmp oppure viene definito come variabile esterna 4

197 Esecuzione del salto void longjmp(jmp_buf env, int val) ripristina lo stato salvato in env, l esecuzione continua come se fosse appena stata eseguita la setjmp che però ora non restituisce 0, ma val (che non può essere pari a 0) Poiché viene ripristinato lo stack alla situazione precedente la chiamata a setjmp, lo stack stesso viene liberato da tutte le allocazioni dovute alle chiamate di funzione successive alla chiamata setjmp Non ha senso chiamare una longjmp per ripristinare la situazione memorizzata (da una setjmp) da una funzione ormai terminata 5 Schema della setjmp if (setjmp(env) == 0) /* esegue questo blocco se si tratta della chiamata diretta che salva lo stato */ else /* esegue questo blocco se è di ritorno dalla longjmp */ 6 Esempio #include <stdio.h> #include <setjmp.h> jmp_buf buf; void f2() { longjmp(buf,1); } void f1() { f2(buf); } main() { if (setjmp(buf)==0) { printf("chiamata SETJMP\n"); f1(buf); } else printf("chiamata LONGJMP\n"); } 7 Gestione dell errore La setjmp permette di isolare (proteggere) blocchi di codice che possono generare problemi e farli seguire da un blocco di codice per il trattamento del caso di errore if (setjmp(buf)==0) {blocco A} else {gestione errore del blocco A} if (setjmp(buf)==0) {blocco B} else {gestione errore del blocco B} 8

198 Restrizione sulla setjmp 9 Le variabili automatiche che sono state modificate dopo la chiamata a setjmp, al ritorno per effetto di una longjmp hanno contenuto indefinito Per preservarne il contenuto si deve usare la clausola volatile davanti alle definizione (questo indica al compilatore di non fare alcuna ottimizzazione, ad es. di non collocarle nei registri): volatile int x; struct nodo * volatile sp;

199 Definizione 2 Funzioni con numero variabile di argomenti Ver Claudio Fornaro - Corso di programmazione in C Le funzioni che ricevono un numero variabile di parametri sono dette funzioni variadic o funzioni varargs tipo nomefunzione(arg1, arg2,...); L ellissi... indica che ci possono essere altri parametri dopo quelli fissi Deve esserci almeno un parametro fisso L ellissi può essere indicata solo come ultimo elemento di una lista di parametri Le macro e le definizioni da utilizzare sono indicate in <stdarg.h> Inizializzazione 3 Utilizzo 4 All interno di una funzione variadic bisogna definire una variabile di tipo va_list, questa (argument pointer) punterà in sequenza a ciascuno degli argomenti aggiuntivi ( anonimi ), elencati dopo quelli fissi: va_list argp; La macro va_start inizializza la variabile argp in modo che punti al primo degli argomenti anonimi, è necessario fornire il nome dell ultimo argomento fisso (nell esempio seguente è arg2): va_start(argp, arg2); La macro va_arg(argp, tipo) restituisce un valore del tipo indicato prelevandolo dall elemento anonimo puntato da argp nella lista variabile, dopodiché modifica argp affinché punti al successivo elemento anonimo: x = va_arg(argp,int); Ogni chiamata a va_arg preleva il successivo valore dalla lista variabile

200 Terminazione Dopo che gli argomenti anonimi sono stati elaborati, per indicare che non si intende più scandire gli elementi di argp, si deve chiamare la macro va_end prima che termini la funzione che la chiama: va_end(argp); I parametri possono essere scanditi più volte: è sufficiente chiamare va_end e poi nuovamente va_start 5 Esempio #include <stdarg.h> #include <stdarg.h> void stampa(int quanti,...); main() { stampa(0); stampa(1, "END"); stampa(2, "END", "with no exit code"); stampa(3, "END", "with code ", 8); return 0; } 6 Esempio void stampa(int quanti,...) { va_list ap; va_start(ap, quanti); if (quanti >= 1) } quanti: ultimo argomento fisso printf(" %s", va_arg(ap, char*) ); if (quanti >= 2) printf(" %s", va_arg(ap, char*) ); if (quanti >= 3) printf(" %d", va_arg(ap, int) ); printf("\n"); va_end(ap); 7 Numero di argomenti anonimi Per indicare alla funzione quanti sono i parametri, si può: passare tale valore come argomento fisso terminare la lista degli argomenti facoltativi con un valore speciale (sentinella) Esempio Chiamata: maxstr(4, s0,s1,s2,s3); Nella funzione si ha: while (quanti-- > 0) printf("%s", va_arg(ap, char*)); 8

201 Numero di argomenti anonimi Esempio Chiamata (tutti valori interi): sumposit(1, 2, 3, 0); Nella funzione si ha: while ((x=va_arg(ap, int))!= 0) printf("%s", x); Esempio Chiamata (tutte stringhe): Notare il cast maxstr(s0,s1,s2,s3,(char *)NULL); Nella funzione si ha: while ((p=va_arg(ap,char*))!=null) printf("%s", p); 9 Tipi dei parametri Per i parametri anonimi non c è alcuna indicazione di tipo nel prototipo della funzione, quindi si hanno le promozioni di default: per gli interi si applicano le promozioni integrali un valore float viene convertito in double Una chiamata come va_arg(ap,float) non è corretta perché i float vengono convertiti in double 10 Tipi dei parametri Soluzione Per passare float e double usare double Per passare char, int e short usare int Per passare vettori usare un puntatore Un puntatore sentinella a NULL deve avere il cast (come nel secondo esempio di maxstr) Attenzione: anche l ultimo argomento fisso deve essere di un tipo che non subisce promozioni di default Per passare puntatori a funzione conviene definire il tipo della funzione con typedef 11 Esercizi Scrivere e collaudare la funzione char * mstrcat(char *prima,...) che concateni un numero arbitrario di stringhe nella prima passata come argomento. Per il resto abbia lo stesso comportamento della strcat. 12

202 Liste Lista concatenata Struttura dati in cui ogni oggetto ha un riferimento ad un altro oggetto (linked list ) head Ver Claudio Fornaro - Corso di programmazione in C Strutture autoreferenzianti struct nodo { valore next int valore; struct nodo *next; }; next è un puntatore al tipo dell oggetto che si sta definendo nello stesso momento E possibile definire un puntatore ad una struttura che verrà definita successivamente, quindi si può utilizzare typedef così: typedef struct nodo * NodoP; typedef struct nodo { int valore; NodoP next; } Nodo; 3 Strutture autoreferenzianti Per lo stesso motivo, è possibile definire strutture mutuamente referenzianti: struct due { int datodue; struct uno *puntuno; }; struct uno { int datouno; struct due *puntdue; }; Un tipo di dato (come struct uno) utilizzato prima di conoscerne la dim. è detto incompleto 4

203 Testa e coda della lista 5 Inserimento in testa 6 struct nodo { int valore; struct nodo *next; } *head = NULL; Situazione iniziale Caso generico: la lista non è vuota head head è un puntatore a struct nodo, tutta la lista inizierà da questo puntatore La lista termina con un elemento in cui in membro next vale NULL La lista è inizialmente vuota, quindi head viene inizializzato a NULL p... x 88 Inserimento in testa Si crea la nuova struct nodo da inserire: p=malloc(sizeof(struct nodo)) head Inserimento in testa Si immette il valore da memorizzare nella lista: p->valore = x; head p?? p 88? x 88 x 88

204 9 10 Inserimento in testa Inserimento in testa Si fa puntare il nuovo elemento al 1 o : p->next = head; Si fa puntare head al nuovo elemento: head = p; head head p 88 p 88 x 88 x Inserimento in testa Rimozione dalla testa Riassumendo: if ((p=malloc(sizeof(struct nodo)))!= NULL) { p->value = x; p->next = head; head = p; } Situazione iniziale head p x...

205 13 14 Rimozione dalla testa Rimozione dalla testa Si preleva il valore dal 1 o elemento: x = head->valore; head Si salva il puntatore all elemento da rimuovere: p = head; head p... p x 15 x Rimozione dalla testa Rimozione dalla testa Si fa puntare head al 2 o elemento: head = head->next; Si dealloca lo spazio del 1 o elemento: free(p); head head free! p p x 15 x 15

206 Rimozione dalla testa 17 Rimozione dalla testa 18 Situazione finale head p? Riassumendo: if (head!= NULL) { x = head->valore; p = head; head = head->next; free(p); } x 15 Considerazioni 19 Esercizi 20 Una lista di strutture, rispetto ad un vettore di strutture: Non ha problemi di dimensioni: varia all occorrenza Lo scambio di elementi è molto veloce (copia di puntatori) L accesso è più lento (deve percorrere la lista) 1. Scrivere in un file separato (con controlli di errore) le seguenti funzioni di gestione di una lista dinamica di int stabilendo per esse appropriati argomenti e valori restituiti. La struttura dati sia static. Produrre un main a menu per il test. AddToHead() AddToTail() RemoveFromHead() RemoveFromTail() ClearAll() svuota la lista

207 Esercizi 2. Utilizzando le funzioni dell esercizio 1 (senza modificare il file), scrivere le funzioni push/pop ed enqueue/dequeue con gli stessi identici prototipi definiti per stack e coda realizzata con vettori. Il main deve essere lo stesso identico usato con le realizzazioni con i vettori (e quindi compatibile con la nuova realizzazione). Per evitare l overhead della doppia chiamata a funzione (es. la chiamata a push che a sua volta chiama AddToHead), le funzioni richieste vengano realizzate come macro (nel file.h da includere nel main). 21 Variazioni Lista con valori dummy Per semplificare il codice di alcune operazioni (meno casi particolari) può essere utile che la lista abbia uno o più elementi fittizi aggiuntivi (non contengono valori significativi): come primo elemento fisso puntato da head come ultimo elemento fisso della lista (sentinella) Lista circolare L ultimo elemento punta al primo Multi-lista Ogni elemento della lista ha due o più puntatori ai nodi successivi, ci possono essere quindi più modi di percorrerla (es. diversi ordinamenti) 22 Homework 8 Scrivere in un file separato le funzioni di gestione di una lista di int, passando anche la testa della lista (per avere più liste). La lista potrà avere due stati: ordinata o non ordinata; alcune funzioni riordineranno preventivamente la lista (se necessario), altre la lasceranno non più ordinata; alcune funzioni lavoreranno in modo diverso a seconda che la lista sia ordinata o no. Può essere utile avere un puntatore all ultimo elemento e un contatore degli elementi Ricordarsi di eliminare (free) tutti gli elementi che non servono più Si scriva un main di test 23 Homework 8 (Continuazione) Funzioni di base listaddtohead (rende la lista non ordinata) listaddtotail (rende la lista non ordinata) listremovefromhead (e ne restituisce il valore) listremovefromtail (e ne restituisce il valore) listclearall (svuota la lista) listcount (restituisce il numero degli elementi della lista) listtestisempty (test se la lista è vuota) 24

208 Homework 8 (Continuazione) Funzioni di ordinamento e ricerca listsort (ascendente e discendente) listaddordered (aggiunge in lista ordinata al posto giusto, se necessario la lista venga ordinata) listfind (cerca il valore dato nella lista a partire dall elemento di indice indicato, restituisce l indice della prima occorrenza, se la lista è ordinata deve fermarsi appena scopre che l elemento non c è) listremovevalue (rimuove dalla lista l elemento contenente la prima occorrenza del valore dato e lo restituisce) 25 Homework 8 (Continuazione) Funzioni assolute di scansione della lista listgetvalueat (restituisce il valore dell elemento di indice i, il primo abbia indice 1) listsetvalueat (cambia il valore dell elemento di indice i) listaddat (aggiunge un elemento in modo che occupi la posizione di indice i, facendo avanzare gli altri elementi) listremoveat (elimina l elemento di indice i) 26 Homework 9 Per accedere ai singoli elementi della lista si può definire un cursore (un puntatore) che si riferisca (punti) ai singoli elementi della lista Il cursore può essere spostato avanti e indietro nella lista per identificare un particolare elemento. Si continui l Homework precedente aggiungendo le seguenti funzioni per l uso dei cursori. Si faccia attenzione a gestire correttamente i cursori quando la lista viene modificata con le altre funzioni e viceversa. Il main di test sia identico a quello dell Hw 8 27 Homework 9 (Continuazione) Funzioni di scansione della lista con cursore listgetcursor (restituisce l indice dell elemento puntato dal cursore, il primo abbia indice 1) listsetcursor (posiziona il cursore all elemento specificato dall indice fornito) listmovecursor (muove il cursore al primo elemento, al successivo, al precedente, all ultimo utilizzare una enum per dare i nomi delle posizioni primo, successivo, etc.) listgetvalueatcursor (restituisce il valore dell elemento puntato dal cursore) listsetvalueatcursor (cambia il valore dell elemento puntato dal cursore) 28

209 Homework 9 (Continuazione) Funzioni di scansione della lista con cursore listaddatcursor (aggiunge un elemento alla posizione del cursore, facendo avanzare gli altri elementi) listremoveatcursor (elimina l elemento puntato dal cursore) listtestcursoratfirst (test se punta al primo elemento) listtestcursoratlast (test se punta all ultimo elemento) 29 Liste bidirezionali La bidirezionalità si ottiene mediante un link al nodo precedente (doubly-linked list ) struct nodo { prev valore next int valore; struct nodo *prev; struct nodo *next; } *head=null; head Homework Liste di liste 32 Come gli Homework 8 e 9, ma si usino liste bidirezionali. Il main deve essere identico a quello degli Homework 8 e 9. Alcuni dei membri dei nodi di una lista principale sono le teste di liste secondarie head a e y

210 Considerazioni Problema: in uno stesso programma servono liste con nodi di tipo diverso Ad es., in una lista di liste la lista principale e quelle secondarie hanno nodi di tipo diverso Soluzione dei linguaggi procedurali (C): una serie di funzioni diverse per ciascun tipo di lista Soluzione ideale: una serie di funzioni generiche che gestiscano qualsiasi tipo di elemento (oggetto) Si Si usano allora linguaggi ad oggetti 33 Tabelle di hash Per velocizzare la ricerca di un elemento in una lista, si distribuiscono gli elementi su più liste (più corte quindi più veloci da utilizzare) Le teste di queste liste sono in un vettore di puntatori La scelta di quale lista debba ospitare i singoli elementi viene stabilito da un opportuno calcolo (funzione di hash) sull elemento stesso, il risultato è l indice che in quel vettore indica quale lista usare 34 Tabelle di hash Esempio Esempio Suddivisione dei valori in base alla cifra delle unità hashtab: Tabelle di hash Esempio La funzione di hash è: int hash(int n){ return abs(n) % 10;} Il vettore di puntatori è: static struct nodo *hashtab[10]; La testa della lista relativa ad x è: hashtab[hash(x)] Per trovare x nella lista che gli compete: listfind(hashtab[hash(x)], x); Per inserire x nella lista che gli compete: listadd(hashtab[hash(x)], x); 36

211 Tabelle di hash 37 Homework Una buona funzione di hash genera valori con distribuzione uniforme, ossia le liste prodotte hanno lunghezze più o meno uguali Esempio di funzione di hash per stringhe: unsigned hash(char *s) { unsigned hashval = 0; for ( ; *s!= '\0'; s++) hashval = *s + 31 * hashval; return hashval % HASHSIZE; } Si scriva un programma per generare l indice analitico di un (lungo) file di testo. Il programma deve memorizzare le singole parole (si usi una lista ordinata per ciascuna delle lettere iniziali) e per ciascuna deve memorizzare i numeri di tutte le righe dove è presente (sotto-lista ordinata). Un secondo file di testo elenca le parole da non tenere in considerazione (es. articoli, preposizioni, etc.), per questa si usi un opportuna funzione hash. Il risultato della generazione dell indice analitico sia scritto in un file.

212 Divide et impera 2 La ricorsione Ver Claudio Fornaro - Corso di programmazione in C Metodo di approccio ai problemi che consiste nel dividere il problema dato in problemi più semplici I risultati ottenuti risolvendo i problemi più semplici vengono combinati insieme per costituire la soluzione del problema originale Generalmente, quando la semplificazione del problema consiste essenzialmente nella semplificazione dei DATI da elaborare (ad es. la riduzione della dimensione del vettore da elaborare), si può pensare ad una soluzione ricorsiva La ricorsione 3 La ricorsione 4 Una funzione è detta ricorsiva se chiama se stessa Se due funzioni si chiamano l un l altra, sono dette mutuamente ricorsive La funzione ricorsiva sa risolvere direttamente solo casi particolari di un problema detti casi di base: se viene invocata passandole dei dati che costituiscono uno dei casi di base, allora restituisce un risultato Se invece viene chiamata passandole dei dati che NON costituiscono uno dei casi di base, allora chiama se stessa (passo ricorsivo) passando dei DATI semplificati/ridotti Ad ogni chiamata si semplificano/riducono i dati, così ad un certo punto si arriva ad uno dei casi di base Quando la funzione chiama se stessa, sospende la sua esecuzione per eseguire la nuova chiamata L esecuzione riprende quando la chiamata interna a se stessa termina La sequenza di chiamate ricorsive termina quando quella più interna (annidata) incontra uno dei casi di base Ogni chiamata alloca sullo stack (in stack frame diversi) nuove istanze dei parametri e delle variabili locali (non static)

213 Esempio 5 Esempio 6 Funzione ricorsiva che calcola il fattoriale di un numero n Premessa (definizione ricorsiva): se n 1 n! = 1 se n > 1 n! = n * (n-1)! int fatt(int n) Semplificazione { dei dati del if (n<=1) problema return 1; Caso di base else return n * fatt(n-1); } La chiamata a fatt(n-1) chiede a fatt di risolvere un problema più semplice di quello iniziale (il valore è più basso), ma è sempre lo stesso problema La funzione continua a chiamare se stessa fino a raggiungere il caso di base che sa risolvere immediatamente Esempio 7 Esempio 8 Quando viene chiamata fatt(n-1), le viene passato come argomento il valore n-1, questo diventa il parametro formale n della nuova esecuzione: ad ogni chiamata la funzione ha un suo parametro n dal valore sempre più piccolo I parametri n delle varie chiamate sono tra di loro indipendenti (sono allocati nello stack ogni volta in stack frame successivi) Supponendo che nel main ci sia: x=fatt(4); 1 a chiamata: in fatt ora n=4, non è il caso di base e quindi richiede il calcolo 4*fatt(3), la funzione viene sospesa in questo punto per calcolare fatt(3) 2 a chiamata: in fatt ora n=3, non è il caso di base e quindi richiede il calcolo 3*fatt(2), la funzione viene sospesa in questo punto per calcolare fatt(2) 3 a chiamata: in fatt ora n=2, non è il caso di base e quindi richiede il calcolo 2*fatt(1), la funzione viene sospesa in questo punto per calcolare fatt(1) 4 a chiamata: in fatt ora n=1, è il caso di base e quindi essa termina restituendo il valore 1 alla 3 a chiamata, lasciata sospesa nel calcolo 2*fatt(1)

214 Esempio 3 a chiamata: ottiene il valore di fatt(1) che vale 1 e lo usa per il calcolo lasciato in sospeso 2*fatt(1), il risultato 2 viene restituito dalla return alla 2 a chiamata, lasciata sospesa 2 a chiamata: ottiene il valore di fatt(2) che vale 2 e lo usa per il calcolo lasciato in sospeso 3*fatt(2), il risultato 6 viene restituito dalla return alla 1 a chiamata, lasciata sospesa 1 a chiamata: ottiene il valore di fatt(3) che vale 6 e lo usa per il calcolo lasciato in sospeso 4*fatt(3), il risultato 24 viene restituito dalla return al main 9 Esempio x = fatt(3); 3 6 int fatt(int n) { if (n<=1) return 1; else return n * fatt(n-1); } main() n=3 n=2 n= int fatt(int n) { if (n<=1) return 1; else return n * fatt(n-1); } fatt(3) fatt(2) fatt(1) fatt() fatt() 10 int fatt(int n) { if (n<=1) return 1; else return n * fatt(n-1); } fatt() Analisi L apertura delle chiamate ricorsive semplifica il problema, ma non calcola ancora nulla Il valore restituito dalle funzioni viene utilizzato per calcolare il valore finale man mano che si chiudono le chiamate ricorsive: ogni chiamata genera valori intermedi a partire dalla fine Nella ricorsione vera e propria non c è un mero passaggio di un risultato calcolato nella chiamata più interna a quelle più esterne, ossia le return non si limitano a passare indietro invariato un valore, ma c è un elaborazione intermedia 11 Quando utilizzarla PRO Spesso la ricorsione permette di risolvere un problema anche molto complesso con poche linee di codice CONTRO La ricorsione è poco efficiente perché richiama molte volte una funzione e questo: richiede tempo per la gestione dello stack (allocare e passare i parametri, salvare l indirizzo di ritorno, e i valori di alcuni registri della CPU) consuma molta memoria (alloca un nuovo stack frame ad ogni chiamata, definendo una nuova ulteriore istanza delle variabili locali non static e dei parametri ogni volta) 12

215 13 14 Quando utilizzarla Quando utilizzarla CONSIDERAZIONE Qualsiasi problema ricorsivo può essere risolto in modo non ricorsivo (ossia iterativo), ma la soluzione iterativa potrebbe non essere facile da individuare oppure essere molto più complessa CONCLUSIONE Quando non ci sono particolari problemi di efficienza e/o memoria, l approccio ricorsivo è in genere da preferire se: è più intuitivo di quello iterativo la soluzione iterativa non è evidente o agevole Una funzione ricorsiva non dovrebbe, in generale, eseguire a sua volta più di una chiamata ricorsiva Esempio di utilizzo da evitare: long fibo(long n) { if (n<=2) return 1; else return fibo(n-1) * fibo(n-2); } Ogni chiamata genera altre 2 chiamate, in totale vengono effettuate 2 n chiamate (complessità esponenziale) Quando utilizzarla Esercizi Inoltre fibo(n) chiama fibo(n-1) e fibo(n-2), anche fibo(n-1) chiama fibo(n-2), ecc.: si hanno calcoli ripetuti, inefficiente! fibo(4) fibo(5) fibo(3) 1. Scrivere una funzione ricorsiva per determinare se una stringa è palindroma. 2. Scrivere una funzione ricorsiva per stampare una stringa a rovescio (non la deve invertire, ma solo stampare). 3. Scrivere una funzione ricorsiva che determini il minimo di un vettore di interi. fibo(3) fibo(2) fibo(2) fibo(1) fibo(2) fibo(1)

216 Esercizi 4. Scrivere un programma per risolvere il problema delle torri di Hanoi: spostare tutti i dischi da 1 a 2 usando 3 come temporaneo. Si può spostare un solo pezzo per volta. Un pezzo grande non può stare sopra uno più piccolo. Suggerimento: usare la funzione muovi(quanti, from, to, temp) che muove un disco direttamente da from a to solo se quanti vale Ricorsione in coda (tail) Si ha quando una funzione ricorsiva chiama se stessa come ultima istruzione prima di terminare e quando termina (se non è void) passa al chiamante il risultato senza alcuna successiva elaborazione int funzione(int x) {... return funzione(y); } Il risultato viene calcolato man mano che si chiamano le funzioni ricorsive (al contrario della ricorsione pura) e si ottiene solo all ultima chiamata 18 Ricorsione in coda Esempio Funzione mcd che calcola il MCD di due valori x e y. Algoritmo (formula di Euclide): se y vale 0, allora gcd(x,y) è pari a x altrimenti gcd(x,y) è pari a gcd(y,x%y) int gcd(int x, int y) { if (y==0) return x; return gcd(y,x%y); } Il valore finale passa attraverso la 2 a return di tutte le chiamate senza subire elaborazioni 19 Ricorsione in coda Quando vi è il calcolo di valori intermedi che costituiscono successive approssimazioni del risultato finale, le variabili contenenti questi valori (dette accumulatori ) vengono passate ad ogni chiamata della funzione (o sono variabili esterne o locali static) int fatt(int n, int accum) { if (n>1) { accum = accum * n; return fatt(n-1, accum); } return accum; caso di base } La prima chiamata deve essere fatt(val,1) 20

217 Esercizi 5. Scrivere una funzione ricorsiva per cercare un valore all interno di un vettore non ordinato (ricerca lineare). Risultato: 1 se non lo trova, altrimenti l indice della posizione dove è stato trovato. 6. Scrivere una funzione ricorsiva per cercare un valore all interno di un vettore ordinato (ricerca dicotomica). Risultato: 1 o l indice. 7. Scrivere una funzione che realizzi un selection sort (cerca il valore più piccolo e lo mette in testa, ecc.) in modo ricorsivo su un vettore di interi. 21 Ricorsione in coda (tail) Considerazioni Escluso il risultato, tutti i dati della chiamata a funzione (salvati sullo stack di sistema) non servono più e alla chiusura vengono solo scartati (serve solo l indirizzo di ritorno) Si potrebbe quindi pensare di poterli eliminare dallo stack, ma il Linguaggio C non prevede questa ottimizzazione (tail optimization) Una funzione che realizza la ricorsione in coda è facilmente riscrivibile come funzione iterativa con il vantaggio di consumare meno memoria e di essere più veloce (non ci sono chiamate a funzione) 22 Eliminazione di ricorsione tail Funzione ricorsiva: tipo Fr(tipo x) { if (casobase(x)) { istruzioni_casobase; return risultato; } else { istruzioni_nonbase; return Fr(riduciComplessita(x); } } Le cancellazioni si applicano se FR è void 23 Eliminazione di ricorsione tail Funzione iterativa equivalente: tipo Fi(tipo x) { while (!casobase(x)) { istruzioni_nonbase; x=riducicomplessita(x); } istruzioni_casobase; return risultato; } risultato potrebbe essere x o altro 24

218 Esercizi 8. Eliminare la ricorsione nell esercizio 4 secondo la modalità illustrata. 9. Eliminare la ricorsione nell esercizio 5 secondo la modalità illustrata. 10. Eliminare la ricorsione nell esercizio 6 secondo la modalità illustrata. 25 Esempio di soluzione ricorsiva Scrivere un programma che stampi tutti gli anagrammi di una stringa data permutandone tutti i caratteri Il main chiama semplicemente la funzione ricorsiva permuta passandole la stringa da anagrammare (ed eventualmente altro) Algoritmo ricorsivo la funzione permuta: prende (scambia) uno per volta i caratteri della stringa passata e li mette all inizio della stringa permuta tutti gli altri caratteri chiamando permuta sulla stringa privata del primo carattere rimette a posto il car. che aveva messo all inizio Caso di base: lunghezza = 1, stampa stringa 26 Esempio di soluzione ricorsiva void permuta(char *s, char *stringa) { int i, len = strlen(s); if (len == 1) caso di base printf("%s\n", stringa); else { for (i=0; i<len; i++) { swap(s[0], s[i]); scambia il 1 o chr permuta(s+1, stringa); swap(s[i], s[0]); ripristina il 1 o chr } } 27 Esempio di soluzione ricorsiva La stringa privata di volta in volta del primo carattere è puntata da s La stringa intera è puntata da stringa e viene passata per poter essere visualizzata a partire dal suo primo carattere (s punta a solo una parte di stringa) La funzione esegue elaborazioni anche dopo la chiamata (ripristina il primo carattere), quindi l eliminazione dello stack frame non sarebbe possibile; per questo non è una ricorsione di tipo tail 28

219 Albero binario 2 Alberi binari (introduzione) Struttura di dati bidimensionale formata da nodi costituiti ciascuno dai dati da memorizzare e da due link Ver Claudio Fornaro - Corso di programmazione in C Terminologia 3 Alberi di ricerca binaria (BST) 4 Nodo radice: nodo da cui discende un albero Figli destro e sinistro di un nodo: nodi puntati dai link di quel nodo (padre) Sottoalbero destro e sinistro di un nodo: alberi che hanno come radici i figli destro e sinistro di quel nodo Nodi fratelli: nodi figli dello stesso nodo padre Foglia: nodo terminale (senza figli) I valori contenuti in ogni sottoalbero sinistro sono minori del valore contenuto nei padri I valori contenuti in ogni sottoalbero destro sono maggiori del valore contenuto nei padri Non ci sono nodi duplicati

220 5 6 Inserimento di un valore Attraversamento dell albero insnode(tipo valore, Nodo **radice) if (*radice == NULL) { crea un nodo ; inserisci il valore nel nodo; imposta a NULL i puntatori nel nodo; aggancia il nodo a radice; } else { if (valore > (*radice)->valore) insnode(valore,&(*radice)->destra); else if (valore < (*radice)->valore) insnode(valore,&(*radice)->sinist); else gestisce la duplicazione; } Si deve poter cambiare il contenuto di radice (che è un puntatore): se ne passa il puntatore In order (simmetrica fornisce i valori ordinati) Visita il sottoalbero del figlio di sinistra Elabora il valore del nodo attuale Visita il sottoalbero del figlio di destra Pre-order (anticipata) Elabora il valore del nodo attuale Visita il sottoalbero del figlio di sinistra Visita il sottoalbero del figlio di destra Post-order (posticipata) Visita il sottoalbero del figlio di sinistra Visita il sottoalbero del figlio di destra Elabora il valore del nodo attuale Attraversamento dell albero 7 Rimozione di un valore Il nodo da eliminare è una foglia 8 A livelli (dalla radice, da sinistra a destra) Inserire il nodo radice in una coda Finché la coda non è vuota: Prendere un elemento dalla coda Utilizzare il valore dell elemento Se il figlio di sinistra non è nullo inserirlo nella coda Se il figlio di destra non è nullo inserirlo nella coda Nel nodo padre si imposta a NULL il puntatore all elemento da rimuovere (22) free!

221 Rimozione di un valore Il nodo da eliminare ha un solo nodo figlio Nel nodo padre (26) il puntatore al nodo figlio da rimuovere (31) viene assegnato con il puntatore all unico figlio (40) del nodo da rimuovere free! 35 9 Rimozione di un valore Il nodo da eliminare ha due nodi figli - 1/7 Si identifica l elemento da rimuovere (19) Rimozione di un valore Il nodo da eliminare ha due nodi figli - 2/7 Si cerca il nodo sostitutivo (15): è il nodo contenente il più grande valore minore di quello nel nodo da rimuovere (19), lo si trova percorrendo tutto il ramo destro del suo figlio di sinistra Rimozione di un valore Il nodo da eliminare ha due nodi figli - 3/7 Il link del padre (26) al nodo da rimuovere (19) viene fatto puntare al nodo sostitutivo (15)

222 Rimozione di un valore Il nodo da eliminare ha due nodi figli - 4/7 Il link destro del padre (12) del nodo sostitutivo viene fatto puntare al sottoalbero sinistro del nodo sostitutivo (14), se non c è si mette NULL. N.B. non può esserci il sottoalbero destro Rimozione di un valore Il nodo da eliminare ha due nodi figli - 5/7 Il link destro del nodo sostitutivo (15) viene fatto puntare al sottoalbero destro (22) del nodo da sostituire, il link sinistro al sinistro (12) Rimozione di un valore Il nodo da eliminare ha due nodi figli - 6/7 Si dealloca il nodo da rimuovere 15 Rimozione di un valore Il nodo da eliminare ha due nodi figli - 7/7 Risultato finale free!

223 Bilanciamento 17 Esercizi 18 La forma dell albero cambia a seconda dell ordine con cui vengono memorizzati i valori Un albero bilanciato è circa simmetrico e il tempo di ricerca è logaritmico rispetto al numero dei valori memorizzati Se i valori vengono introdotti già ordinati la struttura dell albero degenera in una lista e il tempo di ricerca è lineare 1. Si scriva un programma costruisca un albero binario introducendo dei valori. I valori duplicati vengano conteggiati. Si scrivano funzioni per: 1. la visita dell albero nei modi: inorder, preorder e postorder 2. la ricerca di un elemento Esercizi Scrivere un programma che analizzi un testo memorizzando e contando ogni occorrenza delle parole che lo costituiscono. Si usi una struttura ad albero che tenga conto del numero delle ripetizioni delle parole. L output finale delle parole ordinate alfabeticamente con il corrispondente numero di ripetizioni avvenga in un file di testo sequenziale.

224 Abstact Data Type 2 Abstact Data Type Ver. 2.2 ADT, definisce un concetto astratto e il suo comportamento Viene utilizzato come una scatola nera (oggetto) di cui è visibile solo che cosa fa e non come Ha funzioni (dette metodi nella programmazione a oggetti) che possono essere chiamate per far uso dell ADT Claudio Fornaro - Corso di programmazione in C Pubblico e privato 3 Struttura di un ADT 4 Le parti pubbliche dell ADT sono visibili e accessibili da parte di tutte le funzioni del programma (scope globale) I metodi sono pubblici Le parti private dell ADT sono visibili e accessibili solo dalle funzioni definite nello stesso file dove è definito l ADT (scope locale al file di implementazione) Le strutture dati e le funzioni di supporto ai metodi sono private: non sono accessibili dall esterno dell ADT Un ADT è costituito da: un implementazione: uno o più file che contengono il codice che realizza le funzioni/metodi (pubbliche e private) e le strutture dati interne private un interfaccia: file che descrive le funzioni pubbliche (metodi) che il resto del programma può chiamare per utilizzare l ADT

225 Sostituibilità 5 Esempio Specifiche richieste 6 Se ADT diversi risolvono lo stesso scopo (es. gestiscono uno stack) e hanno la stessa interfaccia (es. le funzioni push e pop con lo stesso prototipo), possono essere utilizzati uno in sostituzione dell altro La scelta di utilizzare un implementazione piuttosto che un altra per risolvere un problema è dettata dalle migliori caratteristiche una di esse in relazione al problema specifico (un implementazione potrebbe essere più veloce, ma avere uno stack di dimensione fissa; un altra più lenta ma con uno stack di dimensione variabile) Si vuole definire il tipo di dato Counter Deve poter essere: visualizzato azzerato incrementato di 1 I metodi forniti devono essere: getcounter() resetcounter() inccounter() Esempio 1a soluzione Scelta della struttura dati 7 Esempio 1a soluzione counter.h Interfaccia 8 Serve una variabile intera da incrementare: la struttura dati che conterrà il contatore sarà di tipo int: typedef int Counter; L interfaccia contiene i tipi di dati e i prototipi dei metodi typedef int Counter; int getcounter(counter c); void resetcounter(counter *c); void inccounter(counter *c); Per modificare il parametro c, questo deve essere passato per riferimento (o meglio, tramite puntatore)

226 Esempio 1a soluzione counter.c Implementazione 9 Esempio 1a soluzione main() 10 typedef int Counter; int getcounter(counter c) { return c; } void resetcounter(counter *c) { *c = 0; } void inccounter(counter *c) { (*c)++; } #include <stdio.h> #include "counter.h" main() { Counter x; } resetcounter(&x); inccounter(&x); printf("valore: %d\n", getcounter(x)); printf("valore: %d\n", x); return 0; Esempio 1a soluzione Problema 11 Richiamo sulla typdef 12 La definizione di Counter è pubblica, chiunque può accedere al valore di una variabile di tipo Counter anche senza tramite delle funzioni (ad es. la seconda printf) Se nel programma si accede direttamente al valore interno di Counter, viene meno la sostituibilità e non si può sostituire con un altro ADT implementato in un altro modo In sostanza: non è Abstract Funziona, ma solo grazie all autodisciplina del programmatore Si può definire (ma non deferenziare) una variabile puntatore ad una struct TAG (Tag non omesso) che sarà dichiarata successivamente o in un altro file Si può definire un tipo puntatore (con typedef) ad una struct TAG che sarà dichiarata successivamente o in un altro file File 1: struct TAG { int t; }; File 2: typedef struct TAG *p; Questo secondo file non conosce i dettagli interni di struct TAG

227 Struttura di un ADT in C L interfaccia viene realizzata da un header file (.h) contenente: la definizione del nome dell ADT i prototipi dei metodi pubblici dell ADT L interfaccia viene inclusa con #include (con le virgolette) in tutti i file che fanno uso dell ADT e dai file di implementazione dell ADT stesso 13 Struttura di un ADT in C L implementazione viene realizzata da uno o più file (.c) contenenti: l #include dell interfaccia (il file.h) strutture di immagazzinamento dei dati (private) funzioni di creazione di oggetti di quel tipo (costruttori, pubblici) funzioni di accesso per leggere i dati (pubbliche) funzioni di manipolazione per modificare i dati (pubbliche) funzioni di rimozione di oggetti di quel tipo (distruttori, pubblici) altre funzioni e variabili di supporto (private) 14 Esempio 2a soluzione Scelta della struttura dati Serve una variabile intera da incrementare: la struttura dati che conterrà il contatore sarà di tipo int Per evitare che sia accessibile senza il tramite delle funzioni, la si nasconde in una struct locale dichiarata dentro il file counter.c: struct CounterInnerType {int x;}; 15 Esempio 2a soluzione Scelta della struttura dati Il tipo Counter deve essere noto a tutte le funzioni in tutti i file, quindi viene definito in counter.h che viene incluso in tutti i file.c Perché una funzione possa modificare i valori dell oggetto Counter (ossia modificare il membro nascosto x) bisogna passarle il puntatore all oggetto stesso, definiamo il tipo Counter già come puntatore: typedef struct CounterInnerType *Counter; Si accederà a ciascun oggetto di tipo Counter mediante questo puntatore detto handler 16

228 Esempio 2a soluzione I metodi L accesso all ADT avviene sempre attraverso il corrispondente handler: i costruttori costruiscono un oggetto (o istanza) e ne restituiscono l handler i distruttori distruggono un oggetto, quello di cui viene passato l handler i metodi di accesso accedono tramite l handler all oggetto, non lo modificano i metodi di manipolazione accedono tramite l handler all oggetto, lo modificano 17 Esempio 2a soluzione counter.h /* Tipo Counter */ typedef struct CounterInnerType *Counter; /* Costruttore di un Counter = 0 */ Counter newcounter(void); /* Costruttore di un Counter con val */ Counter newinitializedcounter(int initialvalue); /* Restituisce il valore del Counter */ int getcounter(counter c); /* Restituisce il numero di Counter */ int howmanycounter(void); /* Azzera un Counter */ void resetcounter(counter c); /* Incrementa un Counter */ void inccounter(counter c); /* Distruttore di un Counter */ void deletecounter(counter c); 18 Esempio 2a soluzione counter.c #include <stdlib.h> #include "counter.h" /*prototipi e tipo Counter*/ /* Struttura su cui si basa Counter */ /* è definito qui e non nell'header perche' non sia visibile fuori dal file */ struct CounterInnerType { int x; }; 19 Esempio 2a soluzione counter.c /****************************/ /* VARIABILI PRIVATE LOCALI */ /****************************/ /* Variabile (globale) che tiene conto di quanti Counter sono stati creati. Deve essere privata dell'adt, quindi static */ static int numcounters = 0; 20 /* Tipo Counter, così come visto dal programma */ /* deve essere visibile pubblicamente quindi viene definito nell'header e non qui */ /* typedef struct CounterInnerType *Counter; */

229 Esempio 2a soluzione counter.c 21 Esempio 2a soluzione counter.c 22 /****************************/ /* FUNZIONI PRIVATE LOCALI */ /****************************/ /*****************/ /* COSTRUTTORI 1 */ /*****************/ /* Imposta il valore di un Counter al valore dato, e' una funzione di supporto: non deve essere utilizzata se non dai metodi di questo ADT, quindi static per renderla locale, inoltre non e' nell interfaccia */ static void setcounter(counter c, int value) { c->x = value; } /* Costruttore di un Counter con val iniz 0 */ Counter newcounter(void) { Counter c; } c = (Counter)malloc(sizeof(CounterInnerType)); resetcounter(c); numcounters++; return c; Esempio 2a soluzione counter.c 23 Esempio 2a soluzione counter.c 24 /*****************/ /* COSTRUTTORI 2 */ /*****************/ /*********************/ /* METODI DI ACCESSO */ /*********************/ /* Costruttore di un Counter con il valore iniziale indicato */ Counter newinitializedcounter(int initialvalue) { Counter c = newcounter(); setcounter(c, initialvalue); /* chiamata ad una funzione locale */ return c; } /* Restituisce il valore del Counter indicato */ int getcounter(counter c) { return c->x; } /* Restituisce il numero di Counter creati */ int howmanycounter(void) { return numcounters; }

230 Esempio 2a soluzione counter.c 25 Esempio 2a soluzione counter.c 26 /***************************/ /* METODI DI MANIPOLAZIONE */ /***************************/ /***************/ /* DISTRUTTORI */ /***************/ /* Azzera un Counter */ void resetcounter(counter c) { c->x = 0; } /* Incrementa un Counter */ void inccounter(counter c) { (c->x)++; } /* Distruttore di un Counter */ void deletecounter(counter c) { free(c); numcounters--; } Esempio 2a soluzione main.c 27 Esercizi 28 #include<stdio.h> #include "counter.h" main() { int v; Counter x = newcounter(); Counter y = newinitializedcounter(10); v = getcounter(x); v = howmanycounter(); inccounter(x); v = getcounter(x); resetcounter(x); v = getcounter(x); deletecounter(y); v = howmanycounter(); deletecounter(x); } 1. Si realizzi un ADT che implementi uno stack con un vettore di interi, il numero di elementi venga passato al costruttore. Stessa interfaccia dell esercizio visto nelle funzioni, salvo che serve passare anche l handler allo stack da utilizzare. Il main deve poter agire sulle due istanze degli stack a scelta. 2. Si realizzi un ADT che implementi una coda circolare con un vettore di interi, il numero di elementi venga passato al costruttore. Il resto è come per l esercizio precedente.

231 Homework Homework Si realizzi un ADT che implementi un tipo di dato denominato Matrice contenente una matrice di valori int, con metodi per: creare una matrice date le dimensioni distruggerla dato l handler azzerare una matrice inserire un valore in una casella (riga,colonna) restituire il valore di una casella visualizzare tutta la matrice sommare due matrici moltiplicare due matrici Si facciano gli opportuni controlli sui possibili errori Si realizzi un ADT (con controlli di errore e documentazione) che realizzi con liste le funzionalità di uno stack e di una pila di int. Si usino opportune typedef o #define per poter permettere di cambiare facilmente il tipo primitivo int in un altro. Fare il test dell ADT mediante un main che istanzi due o più pile e stack. Homework Homework Si continui l Homework precedente aggiungendo i metodi seguenti. listshowall (mostra tutta la lista da head) listmerge (mette insieme due liste, in modo che la prima contenga anche i valori della seconda, la seconda sarà azzerata; se una delle due è ordinata, la lista risultante deve essere ordinata) listsplit (divide una lista ordinata o no in due liste, viene passato come argomento l indice dell elemento che sarà il primo della seconda lista, restituita dal metodo come valore di ritorno) listclearfrom (elimina la parte della lista che inizia dall elemento indicato, incluso) Inoltre si modifichi la gestione dei cursori in modo che ce ne possa essere più di uno, a tal scopo si aggiungano opportune typedef per il tipo e i metodi: listnewcursor (crea un nuovo cursore) listdeletecursor (cancella un cursore)

Variabili e tipi di dato

Variabili e tipi di dato Variabili e tipi di dato Tutte le variabili devono essere dichiarate, specificandone il tipo La dichiarazione deve precedere l uso Il tipo è un concetto astratto che esprime: L allocazione di spazio per

Подробнее

LINGUAGGI DI PROGRAMMAZIONE

LINGUAGGI DI PROGRAMMAZIONE LINGUAGGI DI PROGRAMMAZIONE Il potere espressivo di un linguaggio è caratterizzato da: quali tipi di dati consente di rappresentare (direttamente o tramite definizione dell utente) quali istruzioni di

Подробнее

Introduzione alla programmazione in C

Introduzione alla programmazione in C Introduzione alla programmazione in C Testi Consigliati: A. Kelley & I. Pohl C didattica e programmazione B.W. Kernighan & D. M. Ritchie Linguaggio C P. Tosoratti Introduzione all informatica Materiale

Подробнее

Linguaggio C. Fondamenti. Struttura di un programma.

Linguaggio C. Fondamenti. Struttura di un programma. Linguaggio C Fondamenti. Struttura di un programma. 1 La storia del Linguaggio C La nascita del linguaggio C fu dovuta all esigenza di disporre di un Linguaggio ad alto livello adatto alla realizzazione

Подробнее

Dall Algoritmo al Programma. Prof. Francesco Accarino IIS Altiero Spinelli Sesto San Giovanni

Dall Algoritmo al Programma. Prof. Francesco Accarino IIS Altiero Spinelli Sesto San Giovanni Dall Algoritmo al Programma Prof. Francesco Accarino IIS Altiero Spinelli Sesto San Giovanni IL PROGRAMMA Gli algoritmi sono modelli di descrizione astratti e per controllarne il funzionamento devono essere

Подробнее

Introduzione al Linguaggio C

Introduzione al Linguaggio C Introduzione al Linguaggio C File I/O Daniele Pighin April 2009 Daniele Pighin Introduzione al Linguaggio C 1/15 Outline File e dati Accesso ai file File I/O Daniele Pighin Introduzione al Linguaggio C

Подробнее

COS È UN LINGUAGGIO? LINGUAGGI DI ALTO LIVELLO LA NOZIONE DI LINGUAGGIO LINGUAGGIO & PROGRAMMA

COS È UN LINGUAGGIO? LINGUAGGI DI ALTO LIVELLO LA NOZIONE DI LINGUAGGIO LINGUAGGIO & PROGRAMMA LINGUAGGI DI ALTO LIVELLO Si basano su una macchina virtuale le cui mosse non sono quelle della macchina hardware COS È UN LINGUAGGIO? Un linguaggio è un insieme di parole e di metodi di combinazione delle

Подробнее

Informatica. Rappresentazione dei numeri Numerazione binaria

Informatica. Rappresentazione dei numeri Numerazione binaria Informatica Rappresentazione dei numeri Numerazione binaria Sistemi di numerazione Non posizionali: numerazione romana Posizionali: viene associato un peso a ciascuna posizione all interno della rappresentazione

Подробнее

Appunti del corso di Informatica 1 (IN110 Fondamenti) 6 Introduzione al linguaggio C

Appunti del corso di Informatica 1 (IN110 Fondamenti) 6 Introduzione al linguaggio C Università di Roma Tre Facoltà di Scienze M.F.N. Corso di Laurea in Matematica Appunti del corso di Informatica 1 (IN110 Fondamenti) 6 Introduzione al linguaggio C Marco Liverani ([email protected])

Подробнее

Alcune regole di base per scrivere un programma in linguaggio C

Alcune regole di base per scrivere un programma in linguaggio C Alcune regole di base per scrivere un programma in linguaggio C Un programma il linguaggio C (listato) viene scritto in formato testo ed archiviato in un file: *.c Per scrivere un listato C si utilizza

Подробнее

Appunti del corso di Informatica 1. 6 Introduzione al linguaggio C

Appunti del corso di Informatica 1. 6 Introduzione al linguaggio C Università di Roma Tre Dipartimento di Matematica e Fisica Corso di Laurea in Matematica Appunti del corso di Informatica 1 (IN110 Fondamenti) 6 Introduzione al linguaggio C Marco Liverani ([email protected])

Подробнее

Tipi elementari, costanti. Tipi di dati. VALORI: un insieme dei valori del tipo OPERAZIONI: per operare su tali valori. Tipi. intero reale carattere

Tipi elementari, costanti. Tipi di dati. VALORI: un insieme dei valori del tipo OPERAZIONI: per operare su tali valori. Tipi. intero reale carattere Tipi elementari, costanti 1 Tipi di dati VALORI: un insieme dei valori del tipo OPERAZIONI: per operare su tali valori Tipi Semplici intero reale carattere Strutturati 2 1 Tipo intero Gli interi servono

Подробнее

Gian Luca Marcialis studio degli algoritmi programma linguaggi LINGUAGGIO C

Gian Luca Marcialis studio degli algoritmi programma linguaggi LINGUAGGIO C Università degli Studi di Cagliari Corso di Laurea in Ingegneria Biomedica (Industriale), Chimica, Elettrica, e Meccanica FONDAMENTI DI INFORMATICA 1 http://www.diee.unica.it/~marcialis/fi1 A.A. 2010/2011

Подробнее

Esercizi di programmazione in C

Esercizi di programmazione in C Esercizi di programmazione in C Esercizio 1 Scrivere un programma in linguaggio C che legga da tastiera una sequenza di lunghezza ignota a priori di numeri interi positivi. Il programma, a partire dal

Подробнее

Obiettivi dell Analisi Numerica. Avviso. Risoluzione numerica di un modello. Analisi Numerica e Calcolo Scientifico

Obiettivi dell Analisi Numerica. Avviso. Risoluzione numerica di un modello. Analisi Numerica e Calcolo Scientifico M. Annunziato, DIPMAT Università di Salerno - Queste note non sono esaustive ai fini del corso p. 3/43 M. Annunziato, DIPMAT Università di Salerno - Queste note non sono esaustive ai fini del corso p.

Подробнее

Programmazione C Massimo Callisto De Donato [email protected] www.cs.unicam.it/massimo.callisto

Programmazione C Massimo Callisto De Donato massimo.callisto@unicam.it www.cs.unicam.it/massimo.callisto Università degli studi di Camerino Scuola di scienze e tecnologia - Sezione Informatica Programmazione C Massimo Callisto De Donato [email protected] www.cs.unicam.it/massimo.callisto LEZIONE

Подробнее

Gli array. Gli array. Gli array. Classi di memorizzazione per array. Inizializzazione esplicita degli array. Array e puntatori

Gli array. Gli array. Gli array. Classi di memorizzazione per array. Inizializzazione esplicita degli array. Array e puntatori Gli array Array e puntatori Laboratorio di Informatica I un array è un insieme di elementi (valori) avente le seguenti caratteristiche: - un array è ordinato: agli elementi dell array è assegnato un ordine

Подробнее

Codifica: dal diagramma a blocchi al linguaggio C++

Codifica: dal diagramma a blocchi al linguaggio C++ Codifica: dal diagramma a blocchi al linguaggio C++ E necessario chiarire inizialmente alcuni concetti. La compilazione Il dispositivo del computer addetto all esecuzione dei programmi è la CPU La CPU

Подробнее

3 - Variabili. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

3 - Variabili. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo 3 - Variabili Programmazione e analisi di dati Modulo A: Programmazione in Java Paolo Milazzo Dipartimento di Informatica, Università di Pisa http://www.di.unipi.it/ milazzo milazzo di.unipi.it Corso di

Подробнее

Sistemi di Numerazione Binaria NB.1

Sistemi di Numerazione Binaria NB.1 Sistemi di Numerazione Binaria NB.1 Numeri e numerali Numero: entità astratta Numerale : stringa di caratteri che rappresenta un numero in un dato sistema di numerazione Lo stesso numero è rappresentato

Подробнее

Funzioni in C. Violetta Lonati

Funzioni in C. Violetta Lonati Università degli studi di Milano Dipartimento di Scienze dell Informazione Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica Funzioni - in breve: Funzioni Definizione di funzioni

Подробнее

Le variabili. Olga Scotti

Le variabili. Olga Scotti Le variabili Olga Scotti Cos è una variabile Le variabili, in un linguaggio di programmazione, sono dei contenitori. Possono essere riempiti con un valore che poi può essere riletto oppure sostituito.

Подробнее

Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12};

Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12}; ESERCIZI 2 LABORATORIO Problema 1 Definire all'interno del codice un vettore di interi di dimensione DIM, es. int array[] = {1, 5, 2, 4, 8, 1, 1, 9, 11, 4, 12}; Chiede all'utente un numero e, tramite ricerca

Подробнее

4 3 4 = 4 x 10 2 + 3 x 10 1 + 4 x 10 0 aaa 10 2 10 1 10 0

4 3 4 = 4 x 10 2 + 3 x 10 1 + 4 x 10 0 aaa 10 2 10 1 10 0 Rappresentazione dei numeri I numeri che siamo abituati ad utilizzare sono espressi utilizzando il sistema di numerazione decimale, che si chiama così perché utilizza 0 cifre (0,,2,3,4,5,6,7,8,9). Si dice

Подробнее

Capitolo Quarto...2 Le direttive di assemblaggio di ASM 68000...2 Premessa...2 1. Program Location Counter e direttiva ORG...2 2.

Capitolo Quarto...2 Le direttive di assemblaggio di ASM 68000...2 Premessa...2 1. Program Location Counter e direttiva ORG...2 2. Capitolo Quarto...2 Le direttive di assemblaggio di ASM 68000...2 Premessa...2 1. Program Location Counter e direttiva ORG...2 2. Dichiarazione di dati: le direttive DS e DC...3 2.1 Direttiva DS...3 2.2

Подробнее

Università di Torino Facoltà di Scienze MFN Corso di Studi in Informatica. Programmazione I - corso B a.a. 2009-10. prof.

Università di Torino Facoltà di Scienze MFN Corso di Studi in Informatica. Programmazione I - corso B a.a. 2009-10. prof. Università di Torino Facoltà di Scienze MFN Corso di Studi in Informatica Programmazione I - corso B a.a. 009-10 prof. Viviana Bono Blocco 9 Metodi statici: passaggio parametri, variabili locali, record

Подробнее

SISTEMI DI NUMERAZIONE E CODICI

SISTEMI DI NUMERAZIONE E CODICI SISTEMI DI NUMERAZIONE E CODICI Il Sistema di Numerazione Decimale Il sistema decimale o sistema di numerazione a base dieci usa dieci cifre, dette cifre decimali, da O a 9. Il sistema decimale è un sistema

Подробнее

Corso di Informatica Generale (C. L. Economia e Commercio) Ing. Valerio Lacagnina Rappresentazione in virgola mobile

Corso di Informatica Generale (C. L. Economia e Commercio) Ing. Valerio Lacagnina Rappresentazione in virgola mobile Problemi connessi all utilizzo di un numero di bit limitato Abbiamo visto quali sono i vantaggi dell utilizzo della rappresentazione in complemento alla base: corrispondenza biunivoca fra rappresentazione

Подробнее

Introduzione allo Scilab Parte 1: numeri, variabili ed operatori elementari

Introduzione allo Scilab Parte 1: numeri, variabili ed operatori elementari Introduzione allo Scilab Parte 1: numeri, variabili ed operatori elementari Felice Iavernaro Dipartimento di Matematica Università di Bari http://dm.uniba.it/ iavernaro 6 Giugno 2007 Felice Iavernaro (Univ.

Подробнее

Uso di base delle funzioni in Microsoft Excel

Uso di base delle funzioni in Microsoft Excel Uso di base delle funzioni in Microsoft Excel Le funzioni Una funzione è un operatore che applicato a uno o più argomenti (valori, siano essi numeri con virgola, numeri interi, stringhe di caratteri) restituisce

Подробнее

Arduino: Programmazione

Arduino: Programmazione Programmazione formalmente ispirata al linguaggio C da cui deriva. I programmi in ARDUINO sono chiamati Sketch. Un programma è una serie di istruzioni che vengono lette dall alto verso il basso e convertite

Подробнее

Parte II Indice. Operazioni aritmetiche tra valori rappresentati in binario puro. Rappresentazione di numeri con segno

Parte II Indice. Operazioni aritmetiche tra valori rappresentati in binario puro. Rappresentazione di numeri con segno Parte II Indice Operazioni aritmetiche tra valori rappresentati in binario puro somma sottrazione Rappresentazione di numeri con segno modulo e segno complemento a 2 esercizi Operazioni aritmetiche tra

Подробнее

Excel. A cura di Luigi Labonia. e-mail: [email protected]

Excel. A cura di Luigi Labonia. e-mail: luigi.lab@libero.it Excel A cura di Luigi Labonia e-mail: [email protected] Introduzione Un foglio elettronico è un applicazione comunemente usata per bilanci, previsioni ed altri compiti tipici del campo amministrativo

Подробнее

INFORMATICA 1 L. Mezzalira

INFORMATICA 1 L. Mezzalira INFORMATICA 1 L. Mezzalira Possibili domande 1 --- Caratteristiche delle macchine tipiche dell informatica Componenti hardware del modello funzionale di sistema informatico Componenti software del modello

Подробнее

Gestione dei File in C

Gestione dei File in C Gestione dei File in C Maurizio Palesi DIIT Università di Catania Viale Andrea Doria 6, 95125 Catania [email protected] http://www.diit.unict.it/users/mpalesi Sommario In questo documento saranno introdotte

Подробнее

Linguaggi e Paradigmi di Programmazione

Linguaggi e Paradigmi di Programmazione Linguaggi e Paradigmi di Programmazione Cos è un linguaggio Definizione 1 Un linguaggio è un insieme di parole e di metodi di combinazione delle parole usati e compresi da una comunità di persone. È una

Подробнее

ESEMPIO 1: eseguire il complemento a 10 di 765

ESEMPIO 1: eseguire il complemento a 10 di 765 COMPLEMENTO A 10 DI UN NUMERO DECIMALE Sia dato un numero N 10 in base 10 di n cifre. Il complemento a 10 di tale numero (N ) si ottiene sottraendo il numero stesso a 10 n. ESEMPIO 1: eseguire il complemento

Подробнее

Sommario. Definizione di informatica. Definizione di un calcolatore come esecutore. Gli algoritmi.

Sommario. Definizione di informatica. Definizione di un calcolatore come esecutore. Gli algoritmi. Algoritmi 1 Sommario Definizione di informatica. Definizione di un calcolatore come esecutore. Gli algoritmi. 2 Informatica Nome Informatica=informazione+automatica. Definizione Scienza che si occupa dell

Подробнее

Esercitazione Informatica I AA 2012-2013. Nicola Paoletti

Esercitazione Informatica I AA 2012-2013. Nicola Paoletti Esercitazione Informatica I AA 2012-2013 Nicola Paoletti 4 Gigno 2013 2 Conversioni Effettuare le seguenti conversioni, tenendo conto del numero di bit con cui si rappresenta il numero da convertire/convertito.

Подробнее

LABORATORIO DI PROGRAMMAZIONE 2012 2013 EDIZIONE 1, TURNO B

LABORATORIO DI PROGRAMMAZIONE 2012 2013 EDIZIONE 1, TURNO B LABORATORIO DI PROGRAMMAZIONE 2012 2013 EDIZIONE 1, TURNO B 23.XI.2012 VINCENZO MARRA Indice Esercizio 1 1 Menu 1 Tempo: 35 min. 2 Commento 1 2 Esercizio 2 2 Ordinamento e ricerca binaria con la classe

Подробнее

Architettura degli Elaboratori I Esercitazione 1 - Rappresentazione dei numeri

Architettura degli Elaboratori I Esercitazione 1 - Rappresentazione dei numeri Architettura degli Elaboratori I Esercitazione 1 - Rappresentazione dei numeri 1 Da base 2 a base 10 I seguenti esercizi richiedono di convertire in base 10 la medesima stringa binaria codificata rispettivamente

Подробнее

Alessandro Pellegrini

Alessandro Pellegrini Esercitazione sulle Rappresentazioni Numeriche Esistono 1 tipi di persone al mondo: quelli che conoscono il codice binario e quelli che non lo conoscono Alessandro Pellegrini Cosa studiare prima Conversione

Подробнее

Logica e codifica binaria dell informazione

Logica e codifica binaria dell informazione Politecnico di Milano Corsi di Laurea in Ingegneria Matematica e Ingegneria Fisica Dipartimento di Elettronica ed Informazione Logica e codifica binaria dell informazione Anno Accademico 2002 2003 L. Muttoni

Подробнее

Programmare in Java. Olga Scotti

Programmare in Java. Olga Scotti Programmare in Java Olga Scotti Linguaggi di programmazione linguaggio macchina assembler linguaggi ad alto livello Linguaggi ad alto livello istruzioni comprensibili linguaggio simile a quello naturale

Подробнее

Allocazione dinamica della memoria - riepilogo

Allocazione dinamica della memoria - riepilogo Università degli studi di Milano Dipartimento di Scienze dell Informazione Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica In breve Storage duration Allocazione dinamica della

Подробнее

FONDAMENTI di INFORMATICA L. Mezzalira

FONDAMENTI di INFORMATICA L. Mezzalira FONDAMENTI di INFORMATICA L. Mezzalira Possibili domande 1 --- Caratteristiche delle macchine tipiche dell informatica Componenti hardware del modello funzionale di sistema informatico Componenti software

Подробнее

Convertitori numerici in Excel

Convertitori numerici in Excel ISTITUTO DI ISTRUZIONE SUPERIORE G. M. ANGIOY CARBONIA Convertitori numerici in Excel Prof. G. Ciaschetti Come attività di laboratorio, vogliamo realizzare dei convertitori numerici con Microsoft Excel

Подробнее

Fasi di creazione di un programma

Fasi di creazione di un programma Fasi di creazione di un programma 1. Studio Preliminare 2. Analisi del Sistema 6. Manutenzione e Test 3. Progettazione 5. Implementazione 4. Sviluppo 41 Sviluppo di programmi Per la costruzione di un programma

Подробнее

Rappresentazione dei numeri in un calcolatore

Rappresentazione dei numeri in un calcolatore Corso di Calcolatori Elettronici I A.A. 2010-2011 Rappresentazione dei numeri in un calcolatore Lezione 2 Università degli Studi di Napoli Federico II Facoltà di Ingegneria Rappresentazione dei numeri

Подробнее

Introduzione. Informatica B. Daniele Loiacono

Introduzione. Informatica B. Daniele Loiacono Introduzione Informatica B Perchè studiare l informatica? Perchè ha a che fare con quasi tutto quello con cui abbiamo a che fare ogni giorno Perché è uno strumento fondamentale per progettare l innovazione

Подробнее

Informatica B a.a 2005/06 (Meccanici 4 squadra) PhD. Ing. Michele Folgheraiter

Informatica B a.a 2005/06 (Meccanici 4 squadra) PhD. Ing. Michele Folgheraiter Informatica B a.a 2005/06 (Meccanici 4 squadra) Scaglione: da PO a ZZZZ PhD. Ing. Michele Folgheraiter Architettura del Calcolatore Macchina di von Neumann Il calcolatore moderno è basato su un architettura

Подробнее

Codifica binaria dei numeri

Codifica binaria dei numeri Codifica binaria dei numeri Caso più semplice: in modo posizionale (spesso detto codifica binaria tout court) Esempio con numero naturale: con 8 bit 39 = Codifica in virgola fissa dei numeri float: si

Подробнее

Strutture. Strutture e Unioni. Definizione di strutture (2) Definizione di strutture (1)

Strutture. Strutture e Unioni. Definizione di strutture (2) Definizione di strutture (1) Strutture Strutture e Unioni DD cap.10 pp.379-391, 405-406 KP cap. 9 pp.361-379 Strutture Collezioni di variabili correlate (aggregati) sotto un unico nome Possono contenere variabili con diversi nomi

Подробнее

Fondamenti di Informatica Ingegneria Clinica Lezione 19/10/2009. Prof. Raffaele Nicolussi

Fondamenti di Informatica Ingegneria Clinica Lezione 19/10/2009. Prof. Raffaele Nicolussi Fondamenti di Informatica Ingegneria Clinica Lezione 19/10/2009 Prof. Raffaele Nicolussi FUB - Fondazione Ugo Bordoni Via B. Castiglione 59-00142 Roma Docente Raffaele Nicolussi [email protected] Lezioni

Подробнее

Architettura hardware

Architettura hardware Architettura dell elaboratore Architettura hardware la parte che si può prendere a calci Sistema composto da un numero elevato di componenti, in cui ogni componente svolge una sua funzione elaborazione

Подробнее

Sistema operativo: Gestione della memoria

Sistema operativo: Gestione della memoria Dipartimento di Elettronica ed Informazione Politecnico di Milano Informatica e CAD (c.i.) - ICA Prof. Pierluigi Plebani A.A. 2008/2009 Sistema operativo: Gestione della memoria La presente dispensa e

Подробнее

EXCEL PER WINDOWS95. sfruttare le potenzialità di calcolo dei personal computer. Essi si basano su un area di lavoro, detta foglio di lavoro,

EXCEL PER WINDOWS95. sfruttare le potenzialità di calcolo dei personal computer. Essi si basano su un area di lavoro, detta foglio di lavoro, EXCEL PER WINDOWS95 1.Introduzione ai fogli elettronici I fogli elettronici sono delle applicazioni che permettono di sfruttare le potenzialità di calcolo dei personal computer. Essi si basano su un area

Подробнее

Corso di Informatica

Corso di Informatica Corso di Informatica Modulo T2 3-Compilatori e interpreti 1 Prerequisiti Principi di programmazione Utilizzo di un compilatore 2 1 Introduzione Una volta progettato un algoritmo codificato in un linguaggio

Подробнее

IL SISTEMA OPERATIVO IL SISTEMA OPERATIVO INTERFACCE TESTUALI INTERFACCE TESTUALI FUNZIONI DEL SISTEMA OPERATIVO INTERFACCE GRAFICHE

IL SISTEMA OPERATIVO IL SISTEMA OPERATIVO INTERFACCE TESTUALI INTERFACCE TESTUALI FUNZIONI DEL SISTEMA OPERATIVO INTERFACCE GRAFICHE IL SISTEMA OPERATIVO Insieme di programmi che opera al di sopra della macchina fisica, mascherandone le caratteristiche e fornendo agli utenti funzionalità di alto livello. PROGRAMMI UTENTE INTERPRETE

Подробнее

Il Software e Il Sistema Operativo. Prof. Francesco Accarino IIS Altiero Spinelli A.S. 09/10

Il Software e Il Sistema Operativo. Prof. Francesco Accarino IIS Altiero Spinelli A.S. 09/10 Il Software e Il Sistema Operativo Prof. Francesco Accarino IIS Altiero Spinelli A.S. 09/10 Cosa Impareremo Programmi e Processi Struttura del Sistema Operativo Sviluppo di Programmi I files e la loro

Подробнее

Laboratorio di Programmazione 1. Docente: dr. Damiano Macedonio Lezione 18 31/03/2014

Laboratorio di Programmazione 1. Docente: dr. Damiano Macedonio Lezione 18 31/03/2014 Laboratorio di Programmazione 1 1 Docente: dr. Damiano Macedonio Lezione 18 31/03/2014 Funzioni: Dichiarazione e Definizione La dichiarazione di una funzione serve a comunicare al compilatore quali sono

Подробнее

GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL

GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL GUIDA RAPIDA PER LA COMPILAZIONE DELLA SCHEDA CCNL GUIDA RAPIDA BOZZA 23/07/2008 INDICE 1. PERCHÉ UNA NUOVA VERSIONE DEI MODULI DI RACCOLTA DATI... 3 2. INDICAZIONI GENERALI... 4 2.1. Non modificare la struttura dei fogli di lavoro... 4 2.2. Cosa significano

Подробнее

Cosa è un foglio elettronico

Cosa è un foglio elettronico Cosa è un foglio elettronico Versione informatica del foglio contabile Strumento per l elaborazione di numeri (ma non solo...) I valori inseriti possono essere modificati, analizzati, elaborati, ripetuti

Подробнее

Esempi di algoritmi. Lezione III

Esempi di algoritmi. Lezione III Esempi di algoritmi Lezione III Scopo della lezione Implementare da zero algoritmi di media complessità. Verificare la correttezza di un algoritmo eseguendolo a mano. Imparare a valutare le prestazioni

Подробнее

Programmazione in Java (I modulo) Lezione 3: Prime nozioni

Programmazione in Java (I modulo) Lezione 3: Prime nozioni Programmazione in Java (I modulo) Lezione 3: Prime nozioni La volta scorsa Abbiamo avuto un primo assaggio! Abbiamo visto come usare l editor per scrivere un programma Java. Abbiamo analizzato riga per

Подробнее

Fogli Elettronici: MS Excel

Fogli Elettronici: MS Excel Fogli Elettronici: MS Excel Informatica - A.A. 2010/2011 - Excel 7.0 Foglio Elettronico Un foglio elettronico (o spreadsheet) è un software applicativo nato dall esigenza di: organizzare insiemi di dati

Подробнее

Informazione analogica e digitale

Informazione analogica e digitale L informazione L informazione si può: rappresentare elaborare gestire trasmettere reperire L informatica offre la possibilità di effettuare queste operazioni in modo automatico. Informazione analogica

Подробнее

NOZIONI BASE PER ESERCITAZIONI

NOZIONI BASE PER ESERCITAZIONI NOZIONI BASE PER ESERCITAZIONI Shahram Rahatlou Laboratorio di Calcolo, Anno Accademico 2015-16 http://www.roma1.infn.it/people/rahatlou/labcalc/ Sistema Operativo Hardware Software n Routine e programmi

Подробнее

Fondamenti di Informatica e Laboratorio T-AB T-16 Progetti su più file. Funzioni come parametro. Parametri del main

Fondamenti di Informatica e Laboratorio T-AB T-16 Progetti su più file. Funzioni come parametro. Parametri del main Fondamenti di Informatica e Laboratorio T-AB T-16 Progetti su più file. Funzioni come parametro. Parametri del main Paolo Torroni Dipartimento di Elettronica, Informatica e Sistemistica Università degli

Подробнее

(71,1), (35,1), (17,1), (8,1), (4,0), (2,0), (1,0), (0,1) 0, 7155 2 = 1, 431 0, 431 2 = 0, 862 0, 896 2 = 1, 792 0, 724 2 = 1, 448 0, 448 2 = 0, 896

(71,1), (35,1), (17,1), (8,1), (4,0), (2,0), (1,0), (0,1) 0, 7155 2 = 1, 431 0, 431 2 = 0, 862 0, 896 2 = 1, 792 0, 724 2 = 1, 448 0, 448 2 = 0, 896 2 Esercizio 2.2 La rappresentazione esadecimale prevede 16 configurazioni corrispondenti a 4 bit. Il contenuto di una parola di 16 bit può essere rappresentato direttamente con 4 digit esadecimali, sostituendo

Подробнее

PROVA INTRACORSO TRACCIA A Pagina 1 di 6

PROVA INTRACORSO TRACCIA A Pagina 1 di 6 PROVA INTRACORSO DI ELEMENTI DI INFORMATICA MATRICOLA COGNOME E NOME TRACCIA A DOMANDA 1 Calcolare il risultato delle seguenti operazioni binarie tra numeri interi con segno rappresentati in complemento

Подробнее

Dispensa 3. 1.1 YACC: generalità

Dispensa 3. 1.1 YACC: generalità Dispensa 3 1.1 YACC: generalità Il tool Yacc (acronimo per Yet Another Compiler Compiler) è uno strumento software che a partire da una specifica grammaticale context free di un linguaggio scritta in un

Подробнее

Automatizzare i compiti ripetitivi. I file batch. File batch (1) File batch (2) Visualizzazione (2) Visualizzazione

Automatizzare i compiti ripetitivi. I file batch. File batch (1) File batch (2) Visualizzazione (2) Visualizzazione Automatizzare i compiti ripetitivi I file batch Anno accademico 2000-01 1 Spesso capita di dover eseguire ripetutatmente una data sequenza di comandi Introdurli uno a uno da tastiera è un processo lento

Подробнее

INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI

INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI Prima di riuscire a scrivere un programma, abbiamo bisogno di conoscere un metodo risolutivo, cioè un metodo che a partire dai dati di ingresso fornisce i risultati attesi.

Подробнее

5.3 TABELLE 5.3.1 RECORD 5.3.1.1 Inserire, eliminare record in una tabella Aggiungere record Eliminare record

5.3 TABELLE 5.3.1 RECORD 5.3.1.1 Inserire, eliminare record in una tabella Aggiungere record Eliminare record 5.3 TABELLE In un sistema di database relazionali le tabelle rappresentano la struttura di partenza, che resta poi fondamentale per tutte le fasi del lavoro di creazione e di gestione del database. 5.3.1

Подробнее

----------------------------------------------------------------

---------------------------------------------------------------- ---------------------------------------------------------------- utilizzo frequente di chiamate a.. A differenza del Pascal, il C permette di operare con assegnamenti e confronti su dati di tipo diverso,

Подробнее

Nozione di algoritmo. Gabriella Trucco

Nozione di algoritmo. Gabriella Trucco Nozione di algoritmo Gabriella Trucco Programmazione Attività con cui si predispone l'elaboratore ad eseguire un particolare insieme di azioni su particolari informazioni (dati), allo scopo di risolvere

Подробнее

La selezione binaria

La selezione binaria Andrea Marin Università Ca Foscari Venezia Laurea in Informatica Corso di Programmazione part-time a.a. 2011/2012 Introduzione L esecuzione di tutte le istruzioni in sequenza può non è sufficiente per

Подробнее

Architettura dei calcolatori e sistemi operativi. Assemblatore e Collegatore (Linker) Capitolo 2 P&H Appendice 2 P&H

Architettura dei calcolatori e sistemi operativi. Assemblatore e Collegatore (Linker) Capitolo 2 P&H Appendice 2 P&H Architettura dei calcolatori e sistemi operativi Assemblatore e Collegatore (Linker) Capitolo 2 P&H Appendice 2 P&H Sommario Il processo di assemblaggio Il collegatore (linker) 2 Assemblatore: traduzione

Подробнее

Risolvere un problema significa individuare un procedimento che permetta di arrivare al risultato partendo dai dati

Risolvere un problema significa individuare un procedimento che permetta di arrivare al risultato partendo dai dati Algoritmi Algoritmi Risolvere un problema significa individuare un procedimento che permetta di arrivare al risultato partendo dai dati Il procedimento (chiamato algoritmo) è composto da passi elementari

Подробнее

MODULO 4: FOGLIO ELETTRONICO (EXCEL)

MODULO 4: FOGLIO ELETTRONICO (EXCEL) MODULO 4: FOGLIO ELETTRONICO (EXCEL) 1. Introduzione ai fogli elettronici I fogli elettronici sono delle applicazioni che permettono di sfruttare le potenzialità di calcolo dei Personal computer. Essi

Подробнее

MATLAB. Caratteristiche. Dati. Esempio di programma MATLAB. a = [1 2 3; 4 5 6; 7 8 9]; b = [1 2 3] ; c = a*b; c

MATLAB. Caratteristiche. Dati. Esempio di programma MATLAB. a = [1 2 3; 4 5 6; 7 8 9]; b = [1 2 3] ; c = a*b; c Caratteristiche MATLAB Linguaggio di programmazione orientato all elaborazione di matrici (MATLAB=MATrix LABoratory) Le variabili sono matrici (una variabile scalare equivale ad una matrice di dimensione

Подробнее

Esame di Informatica CHE COS È UN FOGLIO ELETTRONICO CHE COS È UN FOGLIO ELETTRONICO CHE COS È UN FOGLIO ELETTRONICO. Facoltà di Scienze Motorie

Esame di Informatica CHE COS È UN FOGLIO ELETTRONICO CHE COS È UN FOGLIO ELETTRONICO CHE COS È UN FOGLIO ELETTRONICO. Facoltà di Scienze Motorie Facoltà di Scienze Motorie CHE COS È UN FOGLIO ELETTRONICO Una tabella che contiene parole e numeri che possono essere elaborati applicando formule matematiche e funzioni statistiche. Esame di Informatica

Подробнее

Alfabeto ed elementi lessicali del linguaggio C

Alfabeto ed elementi lessicali del linguaggio C Programmazione M-Z Ingegneria e Scienze Informatiche - Cesena A.A. 2015-2016 Alfabeto ed elementi lessicali del linguaggio C Pietro Di Lena - [email protected] s t a t i c s h o r t l e g s ; i n

Подробнее

Corso di Calcolo Numerico

Corso di Calcolo Numerico Corso di Calcolo Numerico Dott.ssa M.C. De Bonis Università degli Studi della Basilicata, Potenza Facoltà di Ingegneria Corso di Laurea in Ingegneria Meccanica Sistemi di Numerazione Sistema decimale La

Подробнее

RAPPRESENTAZIONE BINARIA DEI NUMERI. Andrea Bobbio Anno Accademico 1996-1997

RAPPRESENTAZIONE BINARIA DEI NUMERI. Andrea Bobbio Anno Accademico 1996-1997 1 RAPPRESENTAZIONE BINARIA DEI NUMERI Andrea Bobbio Anno Accademico 1996-1997 Numeri Binari 2 Sistemi di Numerazione Il valore di un numero può essere espresso con diverse rappresentazioni. non posizionali:

Подробнее

Informatica Generale 02 - Rappresentazione numeri razionali

Informatica Generale 02 - Rappresentazione numeri razionali Informatica Generale 02 - Rappresentazione numeri razionali Cosa vedremo: Rappresentazione binaria dei numeri razionali Rappresentazione in virgola fissa Rappresentazione in virgola mobile La rappresentazione

Подробнее

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo.

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo. DALLE PESATE ALL ARITMETICA FINITA IN BASE 2 Si è trovato, partendo da un problema concreto, che con la base 2, utilizzando alcune potenze della base, operando con solo addizioni, posso ottenere tutti

Подробнее

CLASSE III A I.T.I. (ABACUS) SISTEMI DI ELABORAZIONE E TRASMISSIONE DEI DATI VERIFICA DI RECUPERO

CLASSE III A I.T.I. (ABACUS) SISTEMI DI ELABORAZIONE E TRASMISSIONE DEI DATI VERIFICA DI RECUPERO CLASSE III A I.T.I. (ABACUS) SISTEMI DI ELABORAZIONE E TRASMISSIONE DEI DATI VERIFICA DI RECUPERO 1 Domanda [1 punto] Dato il formato in virgola mobile su 32 bit così definito (precisione singola): o 1

Подробнее

Corso di Fondamenti di Informatica

Corso di Fondamenti di Informatica Corso di Fondamenti di Informatica I tipi strutturati: gli array e le strutture Claudio De Stefano - Corso di Fondamenti di Informatica 1 arrays un array (o vettore) è una sequenza di oggetti dello stesso

Подробнее

FORMULE: Operatori matematici

FORMULE: Operatori matematici Formule e funzioni FORMULE Le formule sono necessarie per eseguire calcoli utilizzando i valori presenti nelle celle di un foglio di lavoro. Una formula inizia col segno uguale (=). La formula deve essere

Подробнее

Struttura di un programma Java

Struttura di un programma Java Struttura di un programma Java Un programma in Java è un insieme di dichiarazioni di classi. Una classe non può contenere direttamente delle istruzioni, ma può contenere la dichiarazione di metodi, che

Подробнее

Database. Si ringrazia Marco Bertini per le slides

Database. Si ringrazia Marco Bertini per le slides Database Si ringrazia Marco Bertini per le slides Obiettivo Concetti base dati e informazioni cos è un database terminologia Modelli organizzativi flat file database relazionali Principi e linee guida

Подробнее

Introduzione al Foglio Elettronico

Introduzione al Foglio Elettronico Microsoft Excel Introduzione al Foglio Elettronico Il Foglio Elettronico Si presenta come una grande tabella su un foglio di carta Le celle contengono differenti dati Numeri Testo Date Ecc I dati possono

Подробнее

Architettura (10/9/2003) Pag. 1/6. Cognome e Nome (in stampatello):

Architettura (10/9/2003) Pag. 1/6. Cognome e Nome (in stampatello): Architettura (10/9003) Pag. 1/6 Esame di Architettura (matr.0-1) del 10/9003 Per Fondamenti di Architettura NON rispondere Per le domande a risposta multipla cerchiare la risposta scelta. Non alle domande

Подробнее

I sistemi di numerazione

I sistemi di numerazione I sistemi di numerazione 01-INFORMAZIONE E SUA RAPPRESENTAZIONE Sia dato un insieme finito di caratteri distinti, che chiameremo alfabeto. Utilizzando anche ripetutamente caratteri di un alfabeto, si possono

Подробнее

Il memory manager. Gestione della memoria centrale

Il memory manager. Gestione della memoria centrale Il memory manager Gestione della memoria centrale La memoria La memoria RAM è un vettore molto grande di WORD cioè celle elementari a 16bit, 32bit, 64bit (2Byte, 4Byte, 8Byte) o altre misure a seconda

Подробнее

Introduzione agli algoritmi e alla programmazione in VisualBasic.Net

Introduzione agli algoritmi e alla programmazione in VisualBasic.Net Lezione 1 Introduzione agli algoritmi e alla programmazione in VisualBasic.Net Definizione di utente e di programmatore L utente è qualsiasi persona che usa il computer anche se non è in grado di programmarlo

Подробнее