Vittoria de Nitto Personè Appunti per il corso Fondamenti di informatica 1 A.A. 2000 / '01 Facoltà di Ingegneria - Università degli studi di Roma «Tor Vergata» Premessa Queste pagine raccolgono degli appunti che ho scritto per il corso di Fondamenti di Informatica 1. Il materiale è suddiviso per ideali lezioni e alla fine troverete esercizi consigliati per ogni lezione. Non è detto ci sarà corrispondenza precisa tra ciò che viene raccolto qui come unica lezione e ciò che si riuscirà a realizzare in classe in un'unica lezione. Suggerisco alla fine di ogni settimana di provare ad eseguire gli esercizi relativi alle lezioni svolte in quella settimana. Il corso si svolge, secondo il nuovo ordinamento degli studi, in un trimestre. Proprio per questo ho deciso di divulgare del materiale che negli anni precedenti non ho mai divulgato per la sua naturale frammentarietà. Da quest'anno accademico i tempi sono rapidissimi e tutto ciò che può agevolare l'apprendimento "rapido" delle conoscenze va utilizzato. Il rischio, che vorrei scongiurare in ogni modo, è che si possa pensare questo materiale come sostitutivo del libro (o libri) di testo o sostitutivo della presenza attenta e attiva in classe. Sarebbe un grave errore che porterebbe senza dubbio ad un "rapido" fallimento. Mi scuso per l'incompletezza, errori, imprecisioni o quant'altro troverete in questi appunti, e ringrazio tutti coloro che vorranno segnalarmeli o suggerirmi qualunque miglioramento. 3 settembre 2001 2 Indice Lezione1: Codifica dei dati ............................................................................................................................. 5 Anatomia di un computer ............................................................................................................... 6 Dal codice sorgente all'eseguibile .................................................................................................. 7 Lezione2: Il primo programma in C++ ........................................................................................ 8 Lezione3: Algoritmo ....................................................................................................................................... 11 Macchina C++............................................................................................................................... 12 Linguaggio di programmazione .................................................................................................... 13 Lezione 4: Lessico del C++, Sintassi e semantica: dichiarazioni ................................................................. Tipi fondamentali, Dichiarazione di variabile ............................................................................. Dichiarazione di costante ............................................................................................................. Comandi: assegnamento .............................................................................................................. 14 15 16 18 Lezione 5: Input / Output ............................................................................................................. 19 Lezione 6: Comandi ....................................................................................................................................... 22 Controllo del flusso ....................................................................................................................... 23 Lezione 7: Rappresentazione dei numeri interi .............................................................................................. 31 Conversione di base ...................................................................................................................... 32 Numeri relativi: rappresentazione in modulo e segno .................................................................. 33 Lezione 8: Rappresentazione dei numeri reali, rappresentazione in virgola mobile ..................................... 34 Errore di arrotondamento ............................................................................................................. 35 Lezione 9: Rappresentazione dei numeri reali................................................................................................ 38 Conversione di base ...................................................................................................................... 40 3 settembre 2001 3 Lezione 10: Unità funzionali, funzioni senza parametri ............................................................... 41 Lezione 11: Unità funzionali con parametri ................................................................................. 46 Lezione 12: Definizione nuovi tipi ..................................................................................................................... 49 Tipi strutturati ................................................................................................................................ 50 Lezione 13: Definizione nuovi tipi .................................................................................................................... 53 Le classi.......................................................................................................................................... 57 Esercizi Lezione 1: introduzione, architettura, compilazione...................................................................... Lezione 2: primo programma......................................................................................................... Lezione 4: tipi predefiniti, assegnamento ..................................................................................... Lezione 5: input / output, ambiente e stato ................................................................................... Lezione 6: espressioni logiche, controllo del flusso ..................................................................... Lezione 7: rappresentazione posizionale, conversione di base ..................................................... Lezione 8: rappresentazione in virgola mobile ............................................................................. Lezione 9: caratteristiche intervallo, conversione di base ............................................................ Lezione 10: funzioni ..................................................................................................................... Lezione 11: passaggio per riferimento ......................................................................................... Lezione 12: array .......................................................................................................................... Lezione 13: classi ......................................................................................................................... 3 settembre 2001 4 61 61 62 62 63 65 65 66 67 68 69 70 Lezione 1 Codifica dei dati • l'informazione viene codificata in numeri (binari) numeri naturali numeri reali dati non numerici: caratteri a→0 b→1 c→2 … es. bac → 102 onde sonore immagini esercizio di codifica: codificare i giorni della settimana {lunedì, martedì, mercoledì, giovedì, venerdì, sabato, domenica} in forma binaria lunedì → 0 martedì → 1 mercoledì → 2 giovedì → 3 venerdì → 4 sabato → 5 domenica → 6 forma binaria → 000 → 001 → 010 → 011 → 100 → 101 → 110 arriviamo alla stessa codifica "ragionando" su insiemi: regola dato un insieme, questo viene suddiviso in due sottoinsiemi di uguale cardinalità (o al più con differenza pari a 1 se l'insieme di partenza ha cardinalità dispari) associando al primo sottoinsieme codifica 0 e al secondo sottoinsieme codifica 1 3 settembre 2001 5 insieme di partenza: {lunedì, martedì, mercoledì, giovedì, venerdì, sabato, domenica} {lunedì, martedì, mercoledì, giovedì} {venerdì, sabato, domenica} {lunedì, martedì} {mercoledì, giovedì} {venerdì, sabato} {domenica} 0 1 lunedì 000 martedì mercoledì 001 010 giovedì 011 venerdì 100 sabato domenica 101 110 00 01 10 11 Anatomia di un computer • CPU (central process unit) unità centrale di elaborazione • Memoria CPU: esegue il programma, svolge funzioni di calcolo e di trasferimento dati - individua ed esegue le istruzioni del programma - effettua le operazioni aritmetiche (somme, sottrazioni…) - reperisce dati dalla memoria o da apparecchiature periferiche e li rimanda indietro (tutti i dati devono transitare dalla CPU quando vengono spostati da una posizione all'altra, salvo eccezioni) Memoria: memoria primaria: veloce ma costosa RAM (random access memory) ROM (read only memory) 3 settembre 2001 6 ROM: contiene quei particolari programmi che devono essere sempre presenti (es. il codice necessario per avviare il computer) RAM: contiene programmi e dati in fase di modifica svantaggi: perde tutti i dati quando si spegne il computer memoria secondaria: meno costosa e i dati perdurano in assenza di elettricità disco rigido, dispositivi di memoria esterni: floppy, CD-ROM, nastri Per interagire con l'utente un computer ha bisogno di altri dispositivi esterni: tastiera, schermo, altoparlante, stampanti… CPU, memoria e dispositivi esterni sono interconnessi mediante un bus (insieme di linee elettriche) i dati transitano lungo il bus dalla memoria e dai dispositivi periferici verso la CPU e vicecersa Dal codice sorgente all'eseguibile (sintassi, semantica, errori sintattici e errori logici) 3 settembre 2001 7 Lezione 2 Il primo programma in C++ (prerequisiti: concetti minimi di architettura, compilazione, esecuzione) #include <iostream.h> class stringa { char st[100]; public: stringa () { char saluto[100]="buon giorno!"; st=saluto;} void stampa () {cout <<st <<'\n';} }; main() { stringa x; x.stampa(); } • il programma visualizza un semplice saluto: buon giorno! esaminiamo la struttura del programma • il programma è composto da tre parti • la prima parte è la riga : #include <iostream.h> questa "aggiunge" al nostro programma la libreria di nome iostream.h def una libreria è un insieme di funzioni scritte nel linguaggio, già compilate e tradotte in formato eseguibile; def una funzione è una "raccolta" (sequenza) di istruzioni del linguaggio ogni funzione ha un nome a cui segue tra {} la sequenza di istruzioni che compongono quella funzione (corpo della funzione); ogni istruzione è terminata da ";" 3 settembre 2001 8 per eseguire la funzione si utilizza il suo nome la libreria iostream.h raccoglie tutte le funzioni necessarie per l'ingresso/uscita • la seconda parte è composta dalle righe: class stringa { char st[100]; public: stringa () { char saluto[100]="buon giorno!"; st=saluto;} void stampa () {cout <<st <<'\n';} }; è la definizione di una classe def una classe serve per "creare" oggetti e definisce le funzioni che creano e "manipolano" questi oggetti (metodi o primitive) def un oggetto è un elemento che un programma può manipolare nell'esempio la classe definisce oggetti di "tipo" stringa: nella prima parte della classe la riga char st[100]; definisce come l'oggetto è "realizzato internamente": un oggetto stringa è realizzato mediante una sequenza di 100 caratteri e questa sequenza si chiama st la seconda parte, preceduta dalla parola "public:", definisce le due funzioni che possono manipolare l'oggetto stringa: le funzioni sono due e si chiamano stringa (costruttore) e stampa. In questo primo es non siamo interessati alle classi come "fabbrica di oggetti", ma alla scrittura di un semplice messaggio di saluto; quindi il costruttore stringa crea oggetti uguali a buon giorno! 3 settembre 2001 9 e la funzione stampa, mediante l'istruzione cout <<st <<'\n'; , invia st al dispositivo di output standard cioè lo schermo (vedremo che esistono molti altri posti ai quali un programma può inviare st, cioè i dati in uscita). cout è una delle funzioni di uscita della libreria iostream. • la terza parte è composta dalle righe: main() { stringa x; x.stampa(); } è la definizione di una funzione "speciale" di nome main. Una funzione main deve sempre essere presente in un programma C++ e l'esecuzione del programma è l'esecuzione della funzione main. Nel nostro es il corpo della funzione main è formato da due istruzioni: la prima stringa x; "chiama" (attiva) il costruttore stringa (che viene quindi eseguito) che crea un oggetto di nome x e di tipo stringa la seconda x.stampa(); chiama la primitiva stampa sull'oggetto x OSSERVAZIONI CONCLUSIVE • in un programma C++ deve essere presente una funzione main che "guida" l'esecuzione; • il meccanismo delle classi è centrale per organizzare i programmi, definendo gli "oggetti" su cui il programma opera e le relative funzioni primitive che manipolano gli oggetti. 3 settembre 2001 10 Lezione 3 Algoritmo analisi del problema e identificazione di una soluzione esempio: determinare il maggiore di n numeri interi scomposizione in sottoproblemi Pr1: determinare il maggiore di n numeri interi P1. trovare il maggiore fra i primi 2 numeri; P2. trovare il maggiore fra il terzo numero e il risultato del sottoproblema precedente; P3. trovare il maggiore fra il quarto numero e il risultato del sottoproblema precedente; … Pn-1. trovare il maggiore fra l'ultimo numero e il risultato del sottoproblema precedente. Se l'esecutore sa risolvere il problema di trovare il maggiore fra due numeri, non bisogna scomporre ulteriormente, il problema di partenza è risolto possiamo esprimere la soluzione in una forma più sintetica: Pr1: determinare il maggiore di n numeri interi P1. trovare il maggiore fra i primi due numeri; predicato azione P2. finchè ci sono numeri da verificare ripetere il passo P3; P3. trovare il maggiore fra il nuovo numero da esaminare e il più grande trovato in precedenza. P2. è una struttura iterativa Se l'esecutore non sa risolvere il problema di trovare il maggiore fra due numeri, ma sa valutare la differenza fra due interi e valutare il segno di un numero, il problema di trovare il maggiore fra due numeri può essere risolto come segue: Pr2: determinare il maggiore fra due numeri interi x e y P1. determinare la differenza δ fra x e y (δ ← x-y) assegnamento 3 settembre 2001 11 predicato P2. valutare se δ >0 • in caso affermativo la soluzione è x • in caso negativo la soluzione è y. δ è una variabile strutture condizionali ora l'algoritmo è definito ad un livello di dettaglio sufficiente per il nostro esecutore nell'individuazione di procedure risolutive, i seguenti due principi • scomposizione in sottoproblemi • individuazione di problemi noti sono generalmente adottati. Un algoritmo deve possedere 5 caratteristiche essenziali: • effettività • finitezza • definitezza • ingresso • uscita Macchina C++ • MEMORIA: insieme di contenitori di "oggetti" (locazioni di memoria) (rappresentazioni di oggetti) quali oggetti? quali rappresentazioni? (nome, oggetto) oggetti: valori appartenenti a 4 classi: Z interi relativi R numeri reali C caratteri B valori logici 3 settembre 2001 12 • ESECUTORE: entità in grado di eseguire "azioni": a. calcolare nuovi valori a partire da valori dati b. modificare il contenuto di locazioni c. leggere / scrivere (da / verso l'esterno) • DISPOSITIVO DI COMUNICAZIONE: utilizzato per ricevere dall'esterno "oggetti" da memorizzare e per restituire risultati il linguaggio che useremo per interagire con la Macchina C++ (con l'esecutore) ci deve consentire di specificare: 1. quali oggetti sono presenti in M e con che nome (dichiarazioni) 2. quali azioni di tipo a., b., c. compiere (comandi) 3. in che ordine eseguire azioni di tipo a., b., c. (controllo del flusso) Linguaggio (di programmazione): lessico, sintassi e semantica • lessico: elementi costitutivi del linguaggio (dizionario per la lingua italiana) • sintassi: regole per scrivere "frasi" corrette come struttura (il gatto mangia il il) errore sintattico • semantica: regole per scrivere "frasi" che hanno "senso" (il latte beve il gatto) errore logico 3 settembre 2001 13 Lezione 4 Lessico del C ++ cifra: 0, 1, … , 9 carattere: λ, a, b, …, A, B, …, Z operatori e segni di interpunzione: !, =, %, (, ), …, +, -, ; , , , … parole chiave: sequenze di simboli il cui significato è stabilito dal linguaggio Sintassi e semantica Dichiarazioni struttura generale della funzione main: main () {comando; comando; … comando;} parola chiave ; terminatore di comando { } blocco • non è obbligatorio, MA è buona norma mettere i "comandi-dichiarazione" in cima • lo scopo delle dichiarazioni è: dichiarare esplicitamente quali sono i dati su cui il programma opererà, in particolare il dominio (insieme di valori assumibili dai dati) e le operazioni elementari def un tipo di dato è definito da un dominio e un insieme di operazioni elementari lecite su quel dominio si distingue tra: tipi fondamentali → forniti dal linguaggio tipi derivati → definiti dal programmatore 3 settembre 2001 14 Tipi fondamentali • interi parola chiave int dominio intervallo finito sull'insieme degli interi: [-2N-1, 2N-1-1] N=16, 32 operazioni elementari +, -, *, /, % dominio sottoinsieme finito dei numeri razionali dipende dall'implementazione operazioni elementari +, -, *, / dominio insieme dei caratteri di stampa (spesso codifica ASCII, un carattere 8 bit) operazioni elementari nulla (non esist. oper. sul dominio) si usano operatori di confronto • reali parola chiave double • caratteri parola chiave char i caratteri si scrivono tra ' ' Dichiarazione di variabile SINTASSI 2 forme: tipo nome; o tipo nome1, nome2, …; tipo nome=valore; o tipo nome1=valore1, nome2=valore2, …; le due forme possono anche "mischiarsi": tipo nome1, nome2=valore2; dove: 3 settembre 2001 15 tipo → int, double, char nome → sequenza di caratteri e cifre che inizia con carattere valore → "espressione" costante dello stesso tipo SEMANTICA • crea una associazione (nome, cella di memoria) • l'associazione nasce all'istante di dichiarazione e muore alla chiusura del blocco che la contiene esempi: main () {int a; … } varianti: int a=5; double x1, x2=0.142; char iniziale, ch='z'; Dichiarazione di costante SINTASSI const tipo nome=valore; dove: o const tipo nome1=valore1, nome2=valore2, …; tipo → int, double, char nome → sequenza di caratteri e cifre che inizia con carattere valore → "espressione" costante dello stesso tipo SEMANTICA • crea una associazione permanente (nome, valore costante) • l'associazione nasce all'istante di dichiarazione e muore alla chiusura del blocco che la contiene il compilatore controlla che gli oggetti dichiarati costanti non vengano "mai" modificati 3 settembre 2001 16 esempi: main () {const int a=5; … } varianti: const int kilo=1024; const double pigreco=3.14; const char c='c'; Lo scopo delle dichiarazioni è: - predisporre la memoria della macchina per le operazioni che verranno eseguite dal programma (dati) - verificare la correttezza staticamente (CONTROLLO SUI TIPI) esempio: int a,b; char c; … l'espressione a+c è scorretta errore semantico (o logico) si verifica che non ci siano espressioni "senza senso", questo può essere scoperto grazie alla dichiarazione dei tipi (tipi incompatibili) esempio (altri errori semantici): int a,b; double a; const int z=0; int z, r; regola semantica delle dichiarazioni: ogni nome può comparire una sola volta all'interno delle dichiarazioni di un blocco ATT.NE non "usare" nomi di variabili non inizializzati; il C non segnala errore ma interpreta l'informazione che trova a seconda del tipo della variabile. Come "si danno" valori a variabili?? 3 settembre 2001 17 Comandi Assegnamento è il comando basilare, consente di definire o modificare il valore di una variabile (già usato nelle dich.) SINTASSI nome=espressione; SEMANTICA • valuta espressione • memorizza il valore di espressione nella cella di memoria associata a nome quindi regole semantiche: 1. nome deve essere stato dichiarato come variabile; 2. nome e espressione devono essere dello stesso tipo esempio (errori semantici): main () {const int a=15; int b; a=1000; viola la regola 1 b='B'; viola la regola 2 (consistenza tra tipi) } 3 settembre 2001 18 Lezione 5 Input / Output (lo abbiamo già usato nel primo programma) la comunicazione in ingresso e in uscita avviene tramite dei "flussi" (stream) che possiamo immaginare come sequenze di caratteri con accesso sequenziale cin cout flusso di ingresso flusso di uscita SINTASSI (ingresso) cin >> variabile; SEMANTICA • preleva dal flusso di ingresso la sequenza di caratteri a partire dalla "posizione corrente" (si ferma sul primo carattere non previsto da quel tipo) • verifica la consistenza tra tipi (errore a tempo di esecuzione) • assegna a variabile il valore della sequenza SINTASSI (uscita) cout << espressione; SEMANTICA • valuta espressione • trasferisce il valore sul flusso in uscita a partire dalla posizione corrente cin e cout fanno parte della libreria di I/O iostream.h che deve essere "aggiunta" al programma esempio: 3 settembre 2001 19 #include <iostream.h> main () {int a, b; double media; cout <<"Questo programma calcola la media di due valori interi dati in ingresso;\n"; cout <<"scrivi i due interi:\n"; cin >>a >>b; media=a+b; media=media/2; cout <<"la loro media e':\n" <<media <<'\n'; } NB ruolo di media nell'ultimo assegnamento, l-value/r-value utilizziamo questo programma per introdurre due importanti concetti Le dichiarazioni definiscono l' ambiente del programma (o meglio di quel blocco) def L' ambiente è l'insieme di coppie (nome, oggetto) definite dalle dichiarazioni A={(nome1, oggetto1), (nome2, oggetto2), …} dove nomei è il nome usato nella dichiarazione oggettoi è valore se nomei è const è cella di memoria se nomei è variabile altre cose che vedremo informalmente è l'insieme di tutti i nomi che è lecito usare nella parte comandi che segue le dichiarazioni l'uso di qualunque nome estraneo all'ambiente genera errore l'ambiente del programma d'esempio è: A={(a, c. di m.), (b, c. di m.), (media, c. di m.)} - unici nomi leciti, ambiente attivo 3 settembre 2001 20 l'ambiente si definisce "staticamente"; "dinamicamente" cioe' a tempo di esecuzione si definisce lo stato delle variabili def lo stato di un programma ad un certo istante della sua esecuzione è l'insieme dei valori assunti dalle variabili dell'ambiente a quell'istante di esecuzione 3 settembre 2001 21 Lezione 6 Comandi mentre le dichiarazioni definiscono i dati del problema, i comandi servono a specificare l'algoritmo risolutivo del problema in modo che sia eseguibile dalla macchina C++ per quanto visto finora siamo in grado di scrivere programmi costituiti da una lista di comandi da eseguire in sequenza: 1. I passi sono eseguiti uno alla volta 2. Ogni passo è eseguito esattamente una volta: nessuno è ripetuto nessuno è omesso 3. L'ordine in cui i passi sono eseguiti è esattamente quello in cui sono scritti 4. La terminazione dell'ultimo passo implica la terminazione dell'algoritmo è estremamente poco flessibile, il suo corso di esecuzione è fisso e non può essere modificato dalle circostanze questo è insufficiente, ci servono: - costrutti per "scegliere" tra blocchi diversi di istruzioni: SELEZIONE la "selezione" permette ad un processore di seguire diversi cammini attraverso un algoritmo in accordo alle circostanze; esempio: confrontare x, y e z per determinare il maggiore supponendo che il processore può confrontare solo 2 alla volta se (x>y) allora (scegli tra x e z)1 altrimenti (scegli tra y e z)2 1 se (x>z) allora scegli x altrimenti scegli z 2 se (y>z) allora scegli y altrimenti scegli z 3 settembre 2001 22 • senza la selezione sarebbe impossibile scrivere algoritmi di alcuna significativa utilità pratica - costrutti per "ripetere" blocchi di istruzioni: ITERAZIONE per esprimere algoritmi la cui lunghezza varia in accordo alle circostanze; c'è la necessità di poter ripetere certi passi in un algoritmo un numero arbitrario di volte esempio: calcolare il primo numero primo maggiore di un numero dato ottieni il numero di partenza aggiungi 1 controlla se il numero è primo se il numero è primo allora scrivilo altrimenti aggiungi 1 controlla se il numero è primo se il numero è primo allora scrivilo altrimenti aggiungi 1 controlla se il numero è primo … • la potenza dell'iterazione è che consente ad un algoritmo di lunghezza finita di descrivere un processo di durata indeterminata Controllo del flusso comandi iterativi: SINTASSI while (espressione) comando; espressione deve essere una espressione logica SEMANTICA 1. valuta espressione 3 settembre 2001 23 2. se espressione è vera - esegui comando - torna a 1 se espressione è falsa termina come si crea una espressione logica?? operatori di confronto operatori logici molti linguaggi hanno tra i tipi predefiniti i booleani (valori logici {vero, falso}) in C++ non esistono MA si creano valori logici mediante questi operatori operatori di confronto: ==, !=, >, >=, <, <= esempi: a==b ecc. (consistenza tra tipi) operatori logici: &&, ||, ! esempi: a==b && a==0 ecc. il comando del while spesso è un comando-composto: SINTASSI {comando; comando; … comando;} SEMANTICA considerare la sequenza comandi tra {} una unità indivisibile, cioè un blocco c'è un altro costrutto per l'iterazione, molto simile al while: SINTASSI do comando while (espressione); 3 settembre 2001 24 espressione deve essere una espressione logica SEMANTICA 1. esegui comando 2. valuta espressione: se è vera torna a 1 se è falsa termina NB differenza con il while: il comando viene eseguito sempre almeno una volta esempio: se volessimo scrivere un programma per calcolare la media tra 5 interi #include <iostream.h> main () {int a, b, c, d, e; double media; cout <<"Questo programma calcola la media di cinque valori interi dati in ingresso;\n"; cout <<"scrivi i cinque interi:\n"; cin >>a >>b >>c >>d >>e; media=a+b+c+d+e; media=media/5; cout <<"la loro media e':\n" <<media <<'\n'; } e se poi volessimo cambiare il numero di interi?? Dovremmo scriverne un altro!! 3 settembre 2001 25 #include <iostream.h> main () {int a, N, i; double media=0; cout <<"Questo programma calcola la media di N valori interi dati in ingresso;\n"; cout <<"Quale e' il valore di N?\n"; cin >>N; i=N; 1 while (i>0) 2 {cout <<"scrivi il prossimo intero:\n"; cin >>a; media=media+a; i=i-1;} 3 media=media/N; cout <<"la loro media e':\n" <<media <<'\n'; } (robustezza: e se N fosse 0???) in questo ciclo iterativo 1 2 3 è la inizializzazione è la condizione di terminazione è il decremento (o incremento) c'è un costrutto che consente di esprimere in maniera compatta 1, 2 e3 SINTASSI for (assegnamento; condizione; espressione) comando; • la variabile utilizzata viene detta variabile di controllo; (il campo d’azione della variabile di controllo si estende a tutto il blocco che contiene il for; è come se fosse definita immediatamente prima del for) • condizione deve essere una espressione logica e rappresenta la condizione che controlla l’esecuzione del ciclo SEMANTICA for (nome=exp; exp1; exp2) comandoX; 3 settembre 2001 26 1. inizializza la variab. di controllo tramite nome=exp; 2. valuta exp1: se è vera si esegue comandoX; se è falsa il for termina; 3. valuta exp2, torna a 2. #include <iostream.h> main () {int a, N, i; double media=0; cout <<"Questo programma calcola la media di N valori interi dati in ingresso;\n"; cout <<"Quale e' il valore di N?\n"; cin >>N; for (i=N; i>0; i=i-1;) {cout <<"scrivi il prossimo intero:\n"; cin >>a; media=media+a; } media=media/N; cout <<"la loro media e':\n" <<media <<'\n'; } Comandi selettivi: SINTASSI a) if (espressione) comando; b) if (espressione) comando1; else comando2; espressione deve essere una espressione logica SEMANTICA a) 1. valuta espressione 2. se espressione è vera - esegui comando - termina 3 settembre 2001 27 se espressione è falsa termina b) 1. valuta espressione 2. se espressione è vera - esegui comando1 se espressione è falsa - esegui comando2 3. termina esempio: determinare il maggiore tra due interi x e y int x, y, max; … input if (x>y) max=x; else max=y; ATTENZIONE il com. condizionale può essere ambiguo if (e1) if (e2) comandox else comandoy che significa?? se e1 è falsa: termina o esegue comandoy?? cioè b) if (e1) {if (e2) comandox} else comandoy oppure a) if (e1) {if (e2) comandox else comandoy} se non si usano le parentesi il compilatore C++ "sceglie" la semantica a), cioè: dalla fine del comando, andando a ritroso, ogni else viene legato al primo if che si incontra Consideriamo un caso in cui molti if risultano annidati, condizionati al valore di una stessa variabile esempio: frammento di un programma che legge numeri in caratteri romani e ne calcola il valore, stampandolo in notazione decimale (esercizio: completare il programma) 3 settembre 2001 28 . . . if (ch=='I') n=1; else if (ch=='V') n=5; else if (ch=='X') n=10; else if (ch=='L') n=50; else . . . . il C++ dispone di un comando più compatto ed elegante per queste scelte multiple: SINTASSI switch (espressione) { case costante1: comando1; break; case costante2: comando2; break; case costante3: case costante4: case costante5: comando3; break; … default: comando4; break;} • l’espressione deve essere di tipo discreto, e le costanti dello stesso tipo (i tipi discreti che per ora conosciamo sono int e char); SEMANTICA 1. 2. valuta espressione; se espressione=costante1 esegui comando1 termina se espressione=costante2 esegui comando2 termina se espressione=costante3 or espressione=costante4 or espressione=costante5 esegui comando3 termina … altrimenti esegui comando4 termina 3 settembre 2001 29 esempio: la cascata di if-else dell’es. precedente diventa switch (ch) { case 'I': n=1; break; case 'V': n=5; break; case 'X': n=10; break; case 'L': n=50; break; . . . . default: cout <<"il carattere non appartiene alla notazione romana"; break;} 3 settembre 2001 30 Lezione 7 Rappresentazione dei numeri interi 343 sequenza di simboli che noi interpretiamo come l'intero 343 secondo la rappresentazione posizionale: ogni simbolo ha un significato (valore) a seconda della posizione che occupa più in generale: B base {0, 1, 2, …, B-1}=SB alfabeto di simboli (cp-1 … c1c0)B rappresentazione in base B, con ci∈SB ∀ i cp-1Bp-1 + … c1B1 + c0B0 valore rappresentato esempio: B=10, S B={0, 1, …, 9} 343 (c2c1c0)10 → 3*102 + 4*101 + 3*100 = 3*100 + 4*10 + 3 con B simboli su p "posti" posso rappresentare Bp diverse configurazioni esempio: B=10 simboli, p=3 posti 103=1000 diverse configurazioni, gli interi da 0 a 999 se le diverse configurazioni sono interi a partire da 0 il max è Bp-1 B=2, max= 23-1=7 (111)2 B=5, max= 53-1=124 (444)5 B=10, max= 103-1=999 (999)10 la stessa sequenza di simboli, cambiando base, può avere significati diversi esempio: 101 B=2, (101)2 = 1*22 + 1*20 = 5 (valore "in base 10") B=5, (101)5 = 1*52 + 1*50 = 26 (valore "in base 10") B=10, (101)10 = 1*102 + 1*100 = 101 (valore "in base 10") 3 settembre 2001 31 Conversione di base (da 10 a B) dati del problema: N valore in base 10 risultato: vogliamo determinare (cp-1 … c1c0)B, con ci ∈ SB={0, 1, … B-1} ∀ i tale che cp-1Bp-1 + … c1B + c0 = N - notiamo che ci<B ∀ i perciò (cp-1Bp-1 + … c1B) > B e c0<B - se calcoliamo la divisione intera N/B otteniamo quoziente = c p-1Bp-2 + … c2B + c1 resto = c0 - riapplichiamo a quoziente e otteniamo c1 ecc. algoritmo regola dei resti (metalinguaggio C++) B=2 i=0 Ni=N while (Ni>0) {ci=Ni% 2 i=i+1 Ni=Ni-1 /2} esempio: N=13 i 0 1 2 3 4 Ni 13 6 3 1 0 ci 1 0 1 1 (1101)2 = (13)10 3 settembre 2001 32 Numeri relativi Rappresentazione in modulo e segno + 343 - 343 in base 2 utilizziamo una cifra per il segno: convenzione → 0 rappresenta + 1 rappresenta - esempio: +13 → 01101 - 13 → 11101 se utilizziamo p cifre: 1 per il segno e p-1 per il modulo il max è + Bp-1-1 il min è - (Bp-1-1) con B=2 e p=16 l'intervallo rappresentato è: [-(215-1) , +215-1] ma avremmo una doppia rappresentazione per lo zero: +0…0 -0…0 anche per questo nei calcolatori attuali la rappresentazione adottata è diversa (complemento alla base); valori tipici per gli interi sono p=16, 32, esempio: p=16 rappresentiamo 216 diverse configurazioni; sono gli interi dell'intervallo [-215, +215-1] (sottoinsieme finito) 15 -2 -1 0 15 + 2 -1 Z analogamente per p=32 3 settembre 2001 33 Lezione 8 Rappresentazione dei numeri reali la rappresentazione posizionale può essere usata per rappresentare i numeri reali: B base {0, 1, 2, …, B-1}=SB alfabeto di simboli (cp-1 … c1c0,c-1c-2c-3…)B rappresentazione in base B, con ci∈SB ∀ i cp-1Bp-1 + … c1B1 + c0B0 + c-1B-1 + c-2B-2 + c-3B-3+… valore rappresentato esempio: B=10, S B={0, 1, …, 9} 3,52 (c0,c-1c-2)10 → 3 + 5*10-1 + 2*10-2= 3+ 5 2 + 10 100 Rappresentazione in virgola mobile perchè "mobile"?? partiamo da un esempio (B=10) 0,00035 → 0,35 * 10-3 vogliamo rappresentare l'informazione 0,00035 in questa forma, ma potremmo anche scrivere: 3,5 * 10 -4 oppure 35 * 10-5 per convenzione si sceglie la prima: 0,00035 viene rappresentato dalla coppia (+0.35, -3) (m, e) mantissa e esponente sono rappresentati in modulo e segno secondo la rappr. posizionale analogamente 10,22 quindi viene rappresentato dalla coppia (+0.1022, +2) B-1≤ m <1 3 settembre 2001 34 quando m rispetta questo vincolo si dice che è normalizzata problema con un numero finito di caratteri non solo, come per gli interi, rappresentiamo un sottoinsieme finito, MA poichè un reale può avere infinite cifre la sua rappresentazione può essere approssimata → errore errore di arrotondamento ∞ x=sgn(x) B e ∑ c −i B−i x∈R i=1 1 se x ≥ 0 con sgn(x)= −1 se x < 0 si definisce k tr(x) = sgn(x) Be ∑ c −i B−i troncamento a k cifre i=1 B tr(x) se 0 ≤ c < k+1 2 arrotondamento a k cifre rd(x) = B e-k se ≤ c k+1 < B tr(x) + sgn(x)B 2 l'arrotondamento significa incrementare di 1 la k-esima cifra, infatti: k tr(x)+sgn(x)Be-k=sgn(x)Be ∑ c −i B−i +sgn(x)Be-k = sgn(x)Be i=1 k ( ∑ c−iB−i +B-k) i=1 k−1 ( ∑ c−iB−i +(ck+1) B-k) =sgn(x)Be i=1 esempio: B=10 k=3 4 5… x1=105 * 0.3874 8 1… x2=105 * 0.3878 tr(x1)=105 * 0.387 tr(x2)=105 * 0.387 rd(x1)=105 * 0.387 rd(x2)=105 * 0.387+105-3=105(0.387+10-3)= =105(0.387+0.001)=105 0.388 Qual'è l'errore commesso? 3 settembre 2001 35 Per valutare l'errore ci sono due "modi": sia x *∈R e x una sua approssimazione def errore assoluto: εa = | x-x*| εa x − x* r errore relativo: ε = * = x* x definito solo per x*≠0 l'errore relativo dà un'idea più precisa dell'ampiezza dell'errore: esempio: εa = 10-3 ha un effetto diverso se x*=350 oppure se x*=0.00035 infatti il dato approssimato sarebbe x=350 +- 10-3 → εr=2.8*10-6=2.8*10 -4% oppure x=0.00035 +- 10-3 → εr=2.8=280% nel caso dell'errore di arrotondamento, in generale si ha: εa < Be-k 1 εa ≤ Be-k 2 troncamento a k cifre arrotondamento a k cifre Intervallo di rappresentazione con un numero finito di cifre rappresentiamo un insieme finito, quindi un sottoinsieme dei numeri reali nelle attuali macchine per rappresentare i numeri reali si utilizzano 32 cifre (binarie): k=24 cifre per la mantissa h=7 cifre per l'esponente (1 per il segno) 1 cifra per il segno 1 24 7 +- e +m questa è la rappresentazione in singola precisione 3 settembre 2001 36 spesso è disponibile una rappresentazione in doppia precisione con mantissa di 56 cifre 3 settembre 2001 37 Lezione 9 Rappresentazione dei numeri reali caratteristiche dell'intervallo di rappresentazione: • è simmetrico rispetto allo 0 esaminiamo la parte positiva (B=2) Xmax=+mmax 2emax mmax=+0.11…1 k ∑2 → i=1 k utilizzando la serie nota ∑ ai = i=0 a k+1 −i k 1 = ∑ i=1 2 i −1 a k+1 − 1 e quindi ∑ a i = −1 a −1 a − 1 i=1 k otteniamo mmax=1-2-k per l'esponente sappiamo che con h-1 cifre il max intero rappresentabile è: emax=2h-1-1 Xmin=+mmin 2 emin mmin=+0.10…0=2-1 emin=-(2h-1-1) esempio: k=24, h=7 emax=26-1=63 emin=-(26-1)=-63 Xmax=+(1-2-24) 263=263-239 Xmin=+2-1 2 -63=2-64 0 +X - X max - Xmin min + X max 3 settembre 2001 38 l'insieme dei numeri rappresentati è: F ⊂ ({0} ∪ {X | Xmin ≤ |X| ≤ Xmax}) e gli altri reali rappresentati come sono distribuiti? calcoliamo X'=min{X | X>Xmin} X"=max{X | X<X max} X'=(0.10…01) 2emin=(2 -1+2-24) 2-63=2-64+2-87 X"=(0.11…10) 2emax=(1-2-24-2-24) 263=(1-2-24) 263-239 le "distanze" da Xmin e Xmax rispettivamente sono: 2-87 e 239 -87 2 2 39 + X max X" X' Xmin • i numeri rappresentati sono un sottoinsieme dei razionali, in particolare sono i numeri frazionari con una potenza di 2 al denominatore: es. 1/3, 1/10 non sono rappresentati • i numeri non in F vengono approssimati con il numero in F "più vicino" • i numeri piccoli sono "meglio" rappresentati inoltre consideriamo X1, X2 ∈ F e op ∈ {+, -, *, /}, può succedere ∈F X1 op X2 ∉F • l'aritmetica in virgola mobile è intrinsecamente approssimata in generale dati a, b, c, d ∈ F a(b+c)/d (a/d)(b+c) (ab+ac)/d danno valori diversi se calcolate in F 3 settembre 2001 39 Conversione di base (numeri reali) per la parte intera si usa l'algoritmo già visto; per la parte frazionaria l'algoritmo è simile a quello per la conversione di interi, ma la spiegazione è più complicata vediamolo tramite un esempio: x=0.625 0.625*2=1.25 0.25*2=0.5 0.5*2=1.0 termina!!! c-1=1 c-2=0 c-3=1 il dato del passo successivo è 0.25 il dato del passo successivo è 0.5 il dato del passo successivo è 0 NB le cifre si ottengono in ordine crescente di significatività 0.625 in virgola mobile e base 2 diventa (0.101, 0) la mantissa è già normalizzata esempio (infinite cifre): 0.6 0.6*2=1.2 0.2*2=0.4 0.4*2=0.8 0.8*2=1.6 0.6 … !!!!! c-1=1 il dato del passo successivo è 0.2 c-2=0 il dato del passo successivo è 0.4 c-3=0 il dato del passo successivo è 0.8 c-4=1 il dato del passo successivo è 0.6 in base 2 ci servirebbero infinite cifre 0.10011001… supponiamo di avere h=4 cifre per la mantissa (0.1001, 0) vediamo l'errore commesso 1 1 9 1*2-1+1*2-4= + = = 0.5625 2 16 16 quindi εa = |0.6-0.5625| = 0.0375 εr = 6.25 % 3 settembre 2001 40 Lezione 10 Unità funzionali Vantaggi: • fornire un supporto esplicito alla realizzazione modulare dei programmi; • consentire il riuso di funzionalità già sviluppate (in punti diversi del programma o in programmi diversi); • rendere più leggibili i programmi (rendere più evidente la loro struttura logica, questo li rende anche più manutenibili); come già detto all'inizio, un programma C è un insieme di funzioni, "main" è una funzione speciale Funzioni senza parametri SINTASSI: definizione di funzione tipo nome () {comando; … comando;} corpo funzione tipo: void , int, double, char ……… esempio in classe stringa: void stampa () {cout <<st <<'\n';} REGOLE SEMANTICHE: • una funzione ha un tipo oppure è void (in altri linguaggi le funzioni senza tipo sono chiamate "procedure"); 3 settembre 2001 41 • una funzione con tipo diverso da void deve restituire un valore di quel tipo mediante una istruzione return espressione; • una funzione deve essere dichiarata prima di essere "usata" (NOTA: questa è la stessa regola semantica di qualunque oggetto dell’ambiente); come vengono fatte le attivazioni? SINTASSI (chiamata di funzione) • una funzione viene "chiamata" utilizzando il suo nome seguito dalla coppia di parentesi tonde vuote; nome_fun () SEMANTICA la sua chiamata corrisponde all’esecuzione del blocco che costituisce il corpo della funzione MA se la funzione ha un tipo: la sua chiamata restituisce un valore (del tipo specificato) mediante l’istruzione "return"; quindi la sua chiamata è una espressione oppure è un termine di una espressione; se la funzione è "void": la sua chiamata è un comando; NB le attivazioni dei metodi delle classi seguono una sintassi particolare: il metodo viene chiamato su un oggetto della classe 3 settembre 2001 42 esempio: class contobancario {double saldo; public: contobancario() {saldo=0;} double estrattoconto() {return saldo;} void preleva (double quantita) {if (saldo>=quantita) saldo=saldo-quantita;} void versa (double quantita) {saldo=saldo+quantita;} } scriviamo una funzione che calcoli gli interessi di un contobancario x dato un certo tasso double interessi() {return x.estrattoconto()*tasso;} l'intero programma sarà: 3 settembre 2001 43 #include <iostream.h> class contobancario {double saldo; public: contobancario() {saldo=0;} double estrattoconto() {return saldo;} void preleva (double quantita) {if (saldo>=quantita) saldo=saldo-quantita;} void versa (double quantita) {saldo=saldo+quantita;} } contobancario x; double tasso; double interessi() {return x.estrattoconto()*tasso;} main() { contobancario vittoria; const double tassofisso=0.05; double inter; vittoria.versa(100000); cout <<"Il suo saldo e': " <<vittoria.estrattoconto() <<'\n'; cout <<"Gli interessi annuali ad un tasso dello " <<tassofisso <<" sono: "; x=vittoria; tasso=tassofisso; inter=interessi(); cout <<inter <<'\n'; cout <<"Dopo un anno il suo saldo sarebbe: " <<vittoria.estrattoconto()+inter <<'\n'; } • importanza della parametrizzazione (riuso e interfaccia es. di funzione matematica) SINTASSI dei parametri nella definizione della funzione si ha la lista dei parametri formali tipo nomefunzione (T1 PF1, T2 PF2, …) {…} PF1 è il nome del primo par for che è di tipo T1 PF2 è il nome del secondo par for che è di tipo T2 3 settembre 2001 44 double interessi(contobancario x, double tasso) {return x.estrattoconto()*tasso;} nella chiamata si ha la lista dei parametri attuali nomefunzione (PA1, PA2, …) PA1 è una espressione di tipo T1 PA2 è una espressione di tipo T2 inter=interessi(vittoria, tassofisso); SEMANTICA 1. data una definizione di funzione, con una certa lista di parametri formali, in ogni chiamata di quella funzione deve essere specificata una lista di parametri attuali esattamente corrispondente, per numero, tipo e posizione a quella dei parametri formali; 2. se nella def si "dichiara" il parametro PF di tipo T significa dichiarare una variabile locale alla funzione; 3. al momento della chiamata, l’espressione PA viene valutata e il suo valore viene assegnato a PF; il legame viene perso, alla prossima chiamata si potrebbe usare un’altra locazione tasso ↔ double tasso ↔ 0.05 double tasso ↔ 0.05 double tassofisso ↔0.05 double tassofisso ↔0.05 double tassofisso ↔0.05 double prima della chiamata al momento della chiam. alla fine della chiamata 3 settembre 2001 45 Lezione 11 Unità funzionali con parametri Supponiamo di voler scrivere una funzione che calcoli gli interessi annuali ed aggiorni il saldo di un contobancario: void fineanno(contobancario x, double tasso) {x.versa(interessi(x, tasso));} verrebbe utilizzata nel main nel modo seguente: main() {contobancario vittoria; const double tassofisso=0.05; double inter; vittoria.versa(100000); cout <<"Il suo saldo e': " <<vittoria.estrattoconto() <<'\n'; fineanno(vittoria, tassofisso); cout <<"Dopo la capitalizzazione degli interessi annuali al tasso di " <<tassofisso; cout <<" il suo saldo e': " <<vittoria.estrattoconto() <<'\n'; } se provate ad eseguire vedrete che il saldo non cambia!!! La spiegazione è nella semantica data del passaggio parametri: la modifica è stata fatta sul PF (x) e non riportata sul PA (vittoria)! Questa è la modalità di passaggio per valore, che non va bene quando vogliamo che le modifiche permangano sul PA; dovremmo invece utilizzare la modalità per riferimento: SINTASSI nella definizione della funzione si ha la lista dei parametri formali, il nome è preceduto dal simbolo & tipo nomefunzione (T1 &PF1, T2 PF2, …) {…} PF1 è il nome del primo par for passato per riferimento PF2 è il nome del secondo par for passato per valore 3 settembre 2001 46 void fineanno(contobancario &x, double tasso) {x.versa(interessi(x, tasso));} nella chiamata si ha la lista dei parametri attuali nomefunzione (PA1, PA2, …) PA1 è una variabile di tipo T1 (non può essere una costante o una espressione) PA2 è una espressione di tipo T2 SEMANTICA 1. un parametro dichiarato per riferimento implica una duplicazione di riferimenti alla memoria al momento della chiamata; quindi assegnamenti fatti a PF nel corpo della funzione equivalgono ad assegnamenti fatti a PA fineanno(vittoria, tassofisso); x contoba. x contoba. x contoba. vittoria ↔ saldo = 0 vittoria ↔ saldo = 0 contoba. contoba. vittoria ↔ saldo = contoba. 105000 prima della chiamata alla fine della chiamata al momento della chiam. valore: solo input riferimento: input-output NOTA BENE E’ buona regola usare per quanto possibile il passaggio per valore, specialmente nelle funzioni con tipo che sono delle espressioni (o parti di) e non devono perciò produrre effetti collaterali 3 settembre 2001 47 Vediamo degli esempi di funzioni per chiarire i concetti di ambiente, campo d’azione e località/globalità di un identificatore. int y; void P() { int x; . . y è globale a P e a main, la portata di y è estesa a tutto il programma x è locale a P, la portata di x è limitata al corpo di P; la memoria allocata per x viene rilasciata al termine del blocco di P x=3; . . } main () { . . P(); nelle attivazioni di P l’associazione (x, cella di memoria) . . viene ogni volta ricreata P(); . . } const int a=8; int x; void P() { const int a=3; . . la costante a e la variabile x sono globali all’intero programma, la loro portata è estesa a tutto il programma escluso il corpo di P il in cui il nome a è ridichiarato, quindi in P x è globale e a è una costante locale di valore 3; (la memoria allocata per a viene rilasciata al termine del blocco di P) x=a-1; . . } main () { . . P(); x=x-a; per effetto di questa attivazione la variabile globale x assume il valore 2; dopo l’assegnamento x è aggiornata . . . . sottraendole il valore globale 8, quindi vale -6 } 3 settembre 2001 48 Lezione 12 Definizione nuovi tipi oltre ai tipi base, l'utente può definire nuovi "domini" tipi elementari (gli oggetti appartenenti al dominio sono "semplici") tipi base (int, double, char) enum SINTASSI enum <nome> {nome1, nome2, …}; SEMANTICA • definisce un insieme di costanti intere identificate dai nomi specificati nella lista (enumeratori); • agli enumeratori sono associati valori interi consecutivi a partire da 0; (si possono anche associare valori diversi;) • generalmente le variabili di tipo enumerato vengono usate solo per operazioni di confronto; (si possono usare tutte le oper. definite sugli interi, che agiscono sulla loro codifica) esempi: enum giorni_settimana {Domenica, Lunedì, Martedì, Mercoledì, Giovedì, Venerdì, Sabato}; . . . gli vengono associati i valori da 0 a 6 giorni_settimana oggi; è lecito scrivere espressioni come: Lunedì < Mercoledì Lunedì < Domenica queste vengono valutate considerando l’intero associato, quindi la prima è vera, la seconda è falsa. 3 settembre 2001 49 Potremmo usare l’enumerazione per definire il tipo booleano che in C++ manca: enum booleano {falso, vero}; Tipi strutturati tipi strutturati array struct class (gli oggetti appartenenti al dominio sono "complessi") Con i meccanismi che conosciamo finora (tipi base + enumerazione), siamo in grado di definire domini che sono insiemi di oggetti semplici, singoli; spesso ci occorre poter definire domini i cui elementi sono agglomerati di oggetti, legati fra loro secondo una qualche struttura SINTASSI array tipo nome [<espressione>] ...[<espressione>]; ATT.NE stiamo dichiarando un oggetto (variabile) array non un tipo! Per dichiarare il tipo dovremmo usare il typedef SEMANTICA • un oggetto di tipo array è una collezione di elementi (in numero fissato) dello stesso tipo; fisicamente viene riservata un’area contigua di memoria; • le espressioni che definiscono le dimensioni devono essere costanti intere; • ogni elemento è individuabile esattamente specificando uno o più indici; sintassi per indicare un elemento appartenente ad un oggetto di tipo array: nome_array [espressione 1]…[espressionen]; 3 settembre 2001 50 semantica: • nome_array è il nome dell’array e espressione i deve essere una espressione costante; esempi: int A[10]; abbiamo definito un array di 10 interi; questi sono identificati dagli indici 0 - 9; quindi se scriviamo: A[0]=0; A[10]= … stiamo assegnando 0 al primo elemento dell’array; è un errore!!! L’ultimo elemento è indicato da A[9] double B [3] [4]; stiamo definendo un array di array, cioè un array bidimensionale, con 3 righe e 4 colonne; assegnamo al primo elemento il valore 1.2; assegnamo all’elemento di riga 2 e colonna 3 (è l'ultimo elemento) il valore 5; B[0][0]= 1.2; B[2][3]=5; NOTA BENE • Un array non può essere restituito da una funzione. • Quando si passa un array come parametro ad una funzione, il passaggio avviene sempre per riferimento e quindi l’array viene modificato 3 settembre 2001 51 esempio: #include <iostream.h> const int N=2, M=3; void mult (int A [N][M], int n) { for (int i=0; i<N; i=i+1) for (int j=0; j<M; j=j+1) A[i][j]=A[i][j]*n; } main () { int x; int B [N][M]={1, 2, 3, 4, 5, 6}; cout <<"Scrivi il valore intero per cui vuoi moltiplicare gli elementi della tabella\n"; cin >>x; mult (B, x); for (int i=0; i<N; i=i+1) { for (int j=0; j<M; j=j+1) cout <<B[i][j] <<" "; cout <<'\n';}; } stringhe • una stringa è un array di char • l'abbiamo vista nel primo es. di programma: class stringa { char st[100]; public: stringa () { char saluto[100]="buon giorno!"; st=saluto;} void stampa () {cout <<st <<'\n';} }; • una costante stringa si indica tra " " • si può assegnare una costante stringa solo nella definizione • la libreria string.h contiene funzioni per l'elaborazione di stringhe 3 settembre 2001 52 Lezione 13 Definizione nuovi tipi Il meccanismo “array” che abbiamo visto consente di definire agglomerati di oggetti tutti dello stesso tipo agglomerati omogenei ogni elemento dell’agglomerato è individuato da un valore particolare degli indici dell’array. Il C++ fornisce un altro meccanismo per definire agglomerati non omogenei SINTASSI struct struct nome {dichiarazione; … dichiarazione;}; ATT.NE Stiamo dichiarando un tipo! (Quindi definendo un nuovo dominio) vincoli: • nei comandi dichiarazione ci può essere qualunque tipo, anche una struttura purchè diversa da quella che la contiene; SEMANTICA • si dichiara un aggregato di oggetti di tipo diverso, in particolare gli oggetti sono dichiarati nella lista tra parentesi, sono detti membri; esempio: struct data { int giorno, mese, anno;} 3 settembre 2001 53 in questo modo abbiamo dichiarato un tipo data che è l’aggregazione dei tre membri di tipo intero e di nomi giorno, mese e anno; data oggi; in questo modo dichiariamo una variabile di tipo data; per questa viene allocata un’area di memoria pari a tre interi (ai suoi membri); oggi giorno mese anno SELEZIONE DI MEMBRO: per indicare un membro di una struttura si usa nome_strutt . nome_membro oggi.giorno OPERAZIONI: • è possibile l’assegnamento tra strutture dello stesso tipo, l’assegnamento avviene mediante la copia membro a membro es.: data oggi, domani; oggi=domani; equivale a oggi.giorno=domani.giorno oggi.mese=domani.mese oggi.anno=domani.anno • le operazioni di confronto non sono def. sulle strutture NOTA BENE • Una funzione può assumere il tipo struttura • Una struttura può essere passata per valore come argomento di funzione esempio: programma che calcola le radici di un’equazione di secondo grado 3 settembre 2001 54 #include <iostream.h> #include <math.h> struct sol {int quante; double r1,r2;}; sol radici (double a, double b, double c) { double delta; sol t; if (a==0 && b==0) t.quante=0; else if (a==0) {t.quante=1; t.r1=-c/b;} else { delta=b*b-4*a*c; if (delta<0) t.quante=0; else { t.quante=2; delta=sqrt(delta); t.r1=(-b+delta)/(2*a); t.r2=(-b-delta)/(2*a);} } return t; } main () { double aa, bb, cc; sol s; cout <<"Scrivi i coefficienti dell'equazione:\n"; cin >>aa >>bb >>cc; s=radici(aa, bb, cc); switch (s.quante) { case 0: cout <<"L'equazione non ha radici\n"; break; case 1: cout <<"La radice dell'equazione e': " <<s.r1 <<'\n'; break; case 2: cout <<"Le radici dell'equazione sono:\n" <<s.r1 <<' ' <<s.r2 <<'\n'; break; } } ))) Abbiamo visto che un tipo di dato è individuato da • un insieme di valori e • un insieme di operazioni su tali valori (operazioni primitive) Le operazioni primitive vengono realizzate facendo uso della rappresentazione degli elementi del tipo; utilizzando le funzioni primitive, possono essere definite altre operazioni, ma queste non dovrebbero mai fare riferimento alla rappresentazione interna; la creazione di un oggetto (tramite la def. di variabile) di un dato tipo, è una delle operazioni elementari valide per tutti i tipi (istanza di quel tipo) 3 settembre 2001 55 def un tipo di dato la cui struttura interna è inaccessibile e i cui oggetti sono manipolabili solo attraverso le operazioni primitive del tipo, è detto tipo di dato astratto • i tipi fondamentali del linguaggio realizzano “bene” il concetto di tipo astratto • quando utilizziamo i meccanismi per i tipi derivati (tipicamente array e struct) il linguaggio non ci “garantisce” la “protezione” della rappresentazione interna esempio: consideriamo i numeri complessi rappresentati in forma cartesiana mediante una coppia (parte reale, parte immaginaria); possiamo realizzarli mediante i tipi derivati struct comp {double re,im;}; comp iniz_compl (double re, double im) { comp x; x.re=re; x.im=im; return x;} double reale (comp x) { return x.re;} double immag (comp x) { return x.im;} comp somma (comp x, comp y) { comp z; z.re=x.re+y.re; z.im=x.im+y.im; return z;} definizione delle funzioni primitive nel programma è possibile dichiarare variabili di tipo complesso ed utilizzare le primitive secondo il principio di occultamento dell’informazione (information hiding): comp z; comp x1=iniz_compl (0.1, 0.2); comp y1=iniz_compl (1.0, 2.0); z=somma (x1, y1); → z.re=x1.re+y1.re; z.im=x1.im+y1.im; le regole di visibilità non impediscono al programmatore di utilizzare la struttura interna 3 settembre 2001 56 in C++ i tipi di dato astratti vengono realizzati mediante le classi che "estendono" il concetto di struttura Le classi SINTASSI class nome { parte privata public: parte pubblica }; i MEMBRI della classe sono divisi in membri PRIVATI e membri PUBBLICI la parte privata contiene le strutture dati che realizzano il tipo (si può anche usare lo specificatore di accesso private) la parte pubblica contiene le dichiarazioni delle funzioni primitive (dette funzioni membro) che realizzano l’INTERFACCIA • una dichiarazione di membro può essere: - una dichiarazione di tipo; - una dichiarazione di campo dato (variab non inizializzata) - una dichiarazione (o definizione) di funzione - una dichiarazione di classe; non si possono dichiarare oggetti costanti SEMANTICA • realizza un tipo di dato astratto; è accessibile solo la parte pubblica esempio: class complesso { double re,im; public: complesso (double r, double i) { re=r; im=i;}; double reale () { return re;}; double immag () { return im;}; … }; 3 settembre 2001 57 Operatori predefiniti • le funzioni membro possono usare i nomi dei campi (es. re e im) della parte privata, senza alcuna indicazione dell’oggetto di cui fanno parte; questo perchè una funzione membro viene sempre applicata ad un oggetto della classe, di conseguenza la chiamata ha la seguente SINTASSI: nome_ogg_classe.nome_fun (PA1, PA2, …) OP. DI SELEZIONE parametri attuali esempio: complesso z(0.1, 0.2); double x; x=z.reale (); • l’operatore di selezione viene anche usato per selezionare campi dato pubblici altre operazioni predefinite (come per le strutture) un oggetto (di una classe, anche detto ISTANZA) può essere • inizializzato (nella dichiarazione con un altro oggetto della stessa classe); • assegnato ad un altro oggetto; • usato come argomento di una funzione; • restituito da una funzione; l’assegnamento e il passaggio per valore avvengono (come per le strutture) mediante copia membro a membro dei campi dato opzioni • tra le funzioni membro è possibile definire la funzione costruttore: è una funzione che ha lo stesso nome della classe e inizializza gli oggetti della classe quando vengono creati (istanziati mediante 3 settembre 2001 58 una dichiarazione) complesso x(0.1, 0.2); complesso x; la variabile x viene creata e inizializzata; ERRORE, def un costruttore le variab vanno sempre inizializzatze esempio: #include <iostream.h> main() { class complesso { double re,im; public: complesso (double r, double i){ re=r; im=i; }; double reale () { return re;}; double immag () { return im;}; void somma (complesso x) {re=re+x.reale(); im=im+x.immag();} }; complesso x1(0.1, 0.2); complesso x2(1.0, 2.0); x1.somma(x2); cout <<"Parte reale = " <<x1.reale() <<"Parte immaginaria = " <<x1.immag() <<'\n'; } 3 settembre 2001 59 Esercizi 3 settembre 2001 60 Lezione 1 (introduzione, architettura, compilazione) Esercizio 1.1 Spiegare la funzione della codifica dell'informazione in formato binario e fare due esempi di codifica. Esercizio 1.2 Che cosa distingue un computer da un comune elettrodomestico? Esercizio 1.3 Quali parti di un computer possono conservare il codice dei programmi? E quali possono conservare i dati dell'utente? Esercizio 1.4 Quali parti di un computer servono per fornire le informazioni all'utente? Quali accettano l'input dell'utente? Esercizio 1.5 Classificare i dispositivi di memoria che possono fare parte del sistema di un computer, ordinati per (a) velocità, (b) costo e (c) capacità di immagazzinamento. Esercizio 1.6 Spiegare la differenza tra codice sorgente e codice eseguibile. Esercizio 1.7 In che modo si scoprono gli errori di sintassi? Come si scoprono gli errori logici? Lezione 2 (primo programma) Esercizio 2.1 Spiegare la differenza tra un oggetto e una classe. Esercizio 2.2 Spiegare la differenza tra una classe e un metodo. Esercizio 2.3 Cosa è una libreria? A cosa serve e come si usa? Esercizio 2.4 Scrivere tre versioni del programma "buon giorno!" con tre diversi errori di sintassi. 3 settembre 2001 61 Lezione 4 (tipi predefiniti, assegnamento) Esercizio 4.1 Cosa è un tipo di dato? Esercizio 4.2 Che differenza c'è tra i tipi fondamentali e i tipi derivati di un linguaggio di programmazione? Esercizio 4.3 Quali sono i tipi fondamentali del C+ +? Descrivere le loro caratteristiche. Esercizio 4.4 Quale è lo scopo delle dichiarazioni? Elencare almeno 2 motivazioni. Esercizio 4.5 Quale è la differenza tra una "variabile" e una "costante"? Esercizio 4.6 Scrivere un semplice programma che utilizzi variabili e costanti. Scriverne una versione con 2 errori sintattici e 2 errori semantici. Verificare in quale fase gli errori vengono individuati e controllare i messaggi di errore. Esercizio 4.7 Descrivere la semantica del comando assegnamento. Scrivere un semplice programma in cui si violino le due regole semantiche dell'assegnamento. Esercizio 4.8 Spiegare perchè è possibile utilizzare una stessa variabile a sinistra e a destra dell'assegnamento. Spiegare il diverso ruolo che il nome della variabile svolge nei due "posti". Lezione 5 (input/output, ambiente e stato) Esercizio 5.1 Cosa è l' "ambiente" di un programma? E lo "stato" di un programma? Elencare almeno 2 differenze tra ambiente e stato. Esercizio 5.2 Scrivere un programma per visualizzare sullo schermo del terminale il vostro nome all'interno di un rettangolo come segue: 3 settembre 2001 62 +-----------+ | Vittoria | +-----------+ Esercizio 5.3 Scrivere un programma che calcoli una espressione aritmetica a partire da dati in ingresso (interi o reali) e stampi il risultato. Curare l'interfaccia del programma: l'esecuzione del programma deve essere esplicativa per l'utente. Farne una versione con un errore di inconsistenza tra tipi. Lezione 6 (espressioni logiche, controllo del flusso) Esercizio 6.1 Completate la seguente tabella dei valori vero e falso, inserendo i valori logici delle espressioni booleane per tutte le combinazioni degli input booleani p, q e r. p falso falso falso q falso falso vero … r falso vero falso (p && q) || !r !(p && (q || !r)) altre 5 combinazioni Esercizio 6.2 Spiegate la differenza tra i seguenti frammenti di codice: s=0; if (x>0) s++; if (y>0) s++; s=0; if (x>0) s++; else if (y>0) s++; Esercizio 6.3 Scrivete un programma per ricevere dall'utente un input che descrive una carta da gioco, mediante le abbreviazioni seguenti: A 2 … 10 J Asso Punteggi delle carte Jack 3 settembre 2001 63 D R Q C P F Donna Re Quadri Cuori Picche Fiori Il programma deve stampare la descrizione completa della carta, come in questo esempio: Inserisci la sigla della carta: DP Donna di picche Esercizio 6.4 Un anno con 366 giorni è detto anno bisestile. Un anno è bisestile se è divisibile per 4 (per es. il 1980), salvo nei casi in cui è divisibile per 100 (per es. il 1900). Tuttavia, è un anno bisestile se è divisibile per 400 (per es. il 2000). Però, non esistono eccezioni prima dell'adozione del calendario gregoriano, avvenuta il 15 Ottobre 1582 (per es. il 1500 non è stato bisestile). Scrivete un programma che chieda all'utente di inserire un anno, e che calcoli se l'anno è bisestile. Esercizio 6.5 Quante ripetizioni eseguono i cicli seguenti, se i non viene modificato nel corpo del ciclo? For For For For For For For (i = 1; i <= 10; i++) … (i = 0; i < 10; i++) … (i = 10; i > 0; i--) … (i = -10; i <= 10; i++) … (i = 10; i >= 0; i++) … (i = -10; i <= 10; i = i+2) … (i = -10; i <= 10; i = i+3) … Esercizio 6.6 Riscrivete il codice seguente, sostituendo il ciclo for con un ciclo while. int s=0; for (int i=1; i <= 10; i++) s=s+i; 3 settembre 2001 64 Lezione 7 (rappresentazione posizionale, conversione di base) Esercizio 7.1 Dati i seguenti numeri in base 10 n1=17, n2=128, n3=75, n4=23, n5=44, darne la loro rappresentazione in base 2. Discutere la necessità di un numero di cifre diverso per rappresentare i cinque valori. Esercizio 7.2 Dati i numeri interi n1=(1101)2, n2=(1101)5, n3=(23)5, n4=(201)3, n5=(3012)8, rappresentati rispettivamente in base B=2, B=5, B=5, B=3, B=8, darne la loro rappresentazione in base B=10. Esercizio 7.3 Spiegare la differenza tra "valore" e "rappresentazione" di un numero. Spiegare anche tramite un esempio. Esercizio 7.4 Quale è il massimo numero intero rappresentabile con 5 cifre in base 8? E con 4 cifre e basi 2, 4, 10? Dare una spiegazione per il caso generale h cifre e base B. Esercizio 7.5 Scrivete un programma che riceve in input un numero intero rappresentato in base 10 e stampi la sua rappresentazione in base 2. Il programma può controllare che il dato in input rispetti certi vincoli in modo che il numero di cifre in output non sia eccessivo. Lezione 8 (rappresentazione in virgola mobile) Esercizio 8.1 Spiegare il significato della "normalizzazione" della mantissa nella rappresentazione in virgola mobile. Esercizio 8.2 Rappresentare in virgola mobile normalizzata i seguenti numeri (base 10): x1=0.67, x2=12.83, x3=0.00024, x4=1170, x5=4.0047. Esercizio 8.3 Spiegare la differenza tra errore assoluto e errore relativo. Fare un esempio in cui a parità di errore assoluto, l'errore relativo sia significativamente diverso. Esercizio 8.4 Dati i numeri (base 10) x1=0.56733, x2=102.89, x3=0.00017234, dare per ognuno di essi la rappresentazione troncata e arrotondata a tre cifre 3 settembre 2001 65 decimali. Valutare gli errori relativi commessi. Utilizzare in ogni caso la rappresentazione normalizzata. Esercizio 8.5 Spiegare perchè l'aritmetica in virgola mobile è "intrinsecamente" approssimata. Fare tre esempi di numeri rappresentabili con un numero finito di cifre in base 10 e non rappresentabili con un numero finito di cifre in base 2. Esercizio 8.6 Scrivete un programma per convertire la lettera di un voto scolastico nel numero corrispondente. Le lettere sono A, B, C, D e F, eventualmente seguite dai segni + o -. I loro valori numerici sono 4, 3, 2, 1 e 0. F+ e F- non esistono. Un segno + o - incrementa o decrementa il valore numerico di 0.3. Tuttavia, A+ è uguale a 4.0. Inserisci la lettera di un voto: BIl valore numerico è 2.7. Lezione 9 (caratteristiche intervallo, conversione di base) Esercizio 9.1 Descrivere e spiegare le caratteristiche dell'intervallo di rappresentazione di numeri reali per base B, k cifre per la mantissa (escluso il segno) e h cifre per l'esponente (incluso il segno). Esercizio 9.2 Determinare il massimo e il minimo numero positivo rappresentabile in virgola mobile in base 10, k=4 cifre per la mantissa (escluso il segno) e h=3 cifre per l'esponente (incluso il segno). Esercizio 9.3 Determinare la "distanza" tra i due numeri più grandi e tra i due numeri più piccoli positivi, rappresentati in base 10 virgola mobile con k=8 cifre per la mantissa (escluso il segno) e h=5 cifre per l'esponente (incluso il segno). Esercizio 9.4 Convertire in base 2 virgola mobile i seguenti numeri rappresentati in base 10: x1=0.67, x2=12.835, x3=0.083. Utilizzare 4 cifre per la mantissa (normalizzata) e 3 cifre per l'esponente. Valutare l'errore assoluto commesso. Esercizio 9.5 Determinare: 1) i parametri h e k di un intervallo di rappresentazione F in base 10 3 settembre 2001 66 2) a, b, c, d ∈ F 3) una espressione aritmetica a piacere tali che: il risultato della espressione dia risultati diversi a seconda dell'ordine di applicazione degli operatori. Per esempio a(b+c)/d ≠ (a/d)(b +c). Esercizio 9.6 Scrivete un programma che legga una serie di numeri in virgola mobile e che stampi: - il valore massimo - il valore minimo - il valore medio. Lezione 10 (funzioni) Esercizio 10.1 Fornite esempi realistici per le seguenti funzioni: - una funzione con un parametro di tipo double e con un valore restituito di tipo double - una funzione con un parametro di tipo int e con un valore restituito di tipo double - una funzione senza parametri e con un valore restituito di tipo int Descrivete solamente che cosa fanno queste funzioni, senza scrivere il codice. Esercizio 10.2 Queste affermazioni sono vere o false? - Una funzione ha esattamente un enunciato return. - Una funzione ha almeno un enunciato return. - Una funzione ha al massimo un valore restituito. - Una funzione di tipo void non ha mai un enunciato return. - Quando si esegue un enunciato return, la funzione termina immediatamente. - Una funzione senza parametri ha sempre un effetto collaterale. - Una funzione di tipo void ha sempre un effetto collaterale. - Una funzione senza effetti collaterali restituisce sempre lo stesso valore, quando viene chiamata con gli stessi parametri. Esercizio 10.3 Scrivete una funzione printSorted (int a, int b, int c) che stampi i suoi tre input in ordine crescente. 3 settembre 2001 67 Esercizio 10.4 Scrivete un programma che riceva in ingresso un numero intero e restituisca in uscita il suo fattoriale. Il fattoriale deve essere calcolato mediante una funzione di tipo int. Esercizio 10.5 La sequenza di Fibonacci è definita dalla regola seguente. I primi due valori della sequenza sono 1 e 1. Ciascun valore successivo è costituito dalla somma dei due valori che lo precedono. Se fn indica l'ennesimo valore nella sequenza di Fibonacci, allora abbiamo: f1 = 1 f2 = 1 fn = fn-1 + fn-2 se n > 2 Scrivete un programma che chieda all'utente di inserire il valore di n e che stampi l'ennesimo valore della sequenza di Fibonacci (calcolato mediante una funzione). Suggerimento: non occorre registrare tutti i valori di fn. Vi servono solamente gli ultimi due valori per calcolare quello successivo. Esercizio 10.6 Scrivete una funzione che verifichi se un numero intero positivo è primo. La funzione deve avere il prototipo int test_primo (int n) dove n è l'intero che deve essere controllato; la funzione restituisce 1 (vero) se n è primo e 0 (falso) se n non è primo. Scrivete una seconda funzione, void list_primi (int m) , che utilizzi test_primo () per stampare tutti i numeri primi fino a m. Scrivete un programma, utilizzando le funzioni test_primo () e list_primi (), che richieda in input un intero positivo e stampi tutti i numeri primi minori o uguali di tale intero. Confrontare i propri risultati con la tavola dei numeri primi. Suggerimento: semplici divisioni per successivi interi sono sufficienti per realizzare test_primo. Lezione 11 (passaggio per riferimento) Esercizio 11.1 Descrivere le differenze semantiche delle due modalità di passaggio parametri per valore e per riferimento. Esercizio 11.2 Scrivete una funzione che scambia due interi, cioè la sua chiamata swap (a, b) provoca lo scambio dei valori di a e b (es. prima della chiamata a=1, b=111, dopo la chiamata a=111, b=1). 3 settembre 2001 68 Scrivete un programma che legge quattro interi e, utilizzando swap, scambia i valori del primo con il quarto e del secondo con il terzo. Esercizio 11.3 Scrivete una funzione che calcoli la media aritmetica di N valori interi letti da tastiera. N è un parametro della funzione. Scrivete un programma che chieda in ingresso il valore N, chiami la funzione media e infine stampi il risultato. Fare due versioni: 1) la funzione calcola la media e la restituisce al chiamante mediante un parametro per riferimento; 2) la funzione è di tipo double, calcola la media e la restituisce al chiamante mediante la sua stessa chiamata. Evidenziare vantaggi/svantaggi delle due soluzioni. Lezione 12 (array) Esercizio 12.1 Per ciascuno dei seguenti gruppi di valori, scrivete un codice che riempia un array a con i valori: - 1 2 3 4 5 6 7 8 9 10 - 0 2 4 6 8 10 12 14 16 18 - 1 4 9 16 22 38 43 56 62 71 -0000000000 - 1 4 9 16 9 7 4 9 11 21 Quando è il caso, usate un ciclo. Esercizio 12.2 Che cos'è l'indice di un array? Quali sono i limiti di un array? Che cos'è un errore di limiti? Esercizio 12.3 Scrivete una funzione che riceva come parametro un array di interi e restituisca come risultato il massimo e il minimo elemento nell'array. Esercizio 12.4 Scrivete una funzione di tipo double che calcoli il prodotto scalare di due vettori matematici (rappresentati come array e passati come parametri). Se a e b sono i vettori, il loro prodotto scalare è: a0b0 + a1b1 + … + an-1bn-1 Esercizio 12.5 Vero o falso? 3 settembre 2001 69 - Tutti gli elementi di un array sono dello stesso tipo. - Gli indici degli array devono essere numeri interi. - Gli array non possono contenere stringhe come elementi. - Gli array bidimensionali hanno sempre lo stesso numero di righe e colonne. - In un array bidimensionale gli elementi di colonne diverse possono avere tipi diversi. - Una funzione non può restituire un array bidimensionale. - Una funzione può cambiare la lunghezza di un array fornito come parametro. Esercizio 12.6 Scrivete una funzione di tipo logico che verifichi se i due array di interi, passati come parametri, hanno gli stessi elementi, ignorando la molteplicità e l'ordine. Per esempio, i due array 1 4 9 16 9 7 4 9 11 e 11 11 7 9 16 4 1 hanno gli stessi elementi. Probabilmente avrete bisogno di una o più funzioni di supporto. Esercizio 12.7 Scrivete una funzione di tipo logico che riceva come parametri un array di caratteri e un carattere e restituisca vero se il carattere è presente nell'array e falso altrimenti. Esercizio 12.8 Scrivete una funzione che riceva come parametri un array di caratteri e un carattere. La funzione controlla se il carattere dato è presente nell'array. In caso affermativo restituisce la posizione dell'elemento dato, in caso negativo lo inserisce nella I posizione dell'array. Lezione 13 (classi) Esercizio 13.1 Scrivere un programma che utilizzando la classe 3 settembre 2001 70 class contobancario {double saldo; public: contobancario() {saldo=0;} double estrattoconto() {return saldo;} void preleva (double quantita) {if (saldo>=quantita) saldo=saldo-quantita;} void versa (double quantita) {saldo=saldo+quantita;} } crei variabili di tipo contobancario e effettui le operazioni previste dalle primitive "guidato" da scelte date in input. Nel programma potete: - aggiungere nella parte privata dati che si ritengano utili; - migliorare le primitive con controlli che si ritengano utili; - realizzare una funzione (non primitiva) per il trasferimento di denaro da un conto all'altro; - utilizzare la funzione double interessi(contobancario x, double tasso) {return x.estrattoconto()*tasso;} Esercizio 13.2 Realizzare una classe Product. Ciascun prodotto ha un nome e un prezzo. Fornite i seguenti metodi: - printProduct () per stampare il prodotto; - getPrice () per reperire il prezzo; - setPrice () per impostare il prezzo. Scrivete un programma per creare due prodotti e per stamparli, per poi ridurre il loro prezzo di una certa quantità e stamparli nuovamente. 3 settembre 2001 71