Fondamenti di Informatica - 1 Prof. B.Buttarazzi A.A. 2011/2012 Sommario • Operatore ? • Tipo di dato: struct • La ricorsione – Funzioni ricorsive • Esercizi proposti 26/04/2012 2 Operatore ? L'operatore ? può essere utilizzato per scrivere un'istruzione condizionale del tipo if-else in modo più rapido. Sintassi: espressione_test ? azione_true : azione_false; Esempio: (var1>var2)?x=0:x=1 Si valuta l’espressione l’espressione_test (ovvero (var1>var2) ) se è vera si esegue azione_true (cioè x=0;), altrimenti si esegue azione_false (cioè x=1;) Equivale all'istruzione if (var1>var2) x=0 else x=1; L'operatore ? viene anche denominato operatore ternario in quanto richiede tre operandi. 3 Operatore ? Esempio completo #include <iostream> using namespace std; int main() { int m, n; cout << "Digita Y o N> "; char c; cin >> c; cout << ( (( c == 'Y')|| ( c == 'y')) ? "Hai accettato!" : "Non accettato o non valido" )<< endl; return 0; } 4 Operatore ? Esempi: cout << "\nDigita valore "; int valore; cin >> valore; bool positivo=false; positivo =( (valore>=0) ? true : false); cout << "\nvalore= "<<valore<<" "<<positivo<<endl; cout << "\nDigita importo "; int aliquota,importo; cin >> importo; aliquota = ((importo>=60000) ? 45 : 33); cout << "\nimporto = "<<importo<<" aliquota = "<<aliquota;; 5 Struct Il tipo struct ( record ) è tipo di dato che permette di rappresentare in modo aggregato (sotto uno stesso nome) gruppi di valori non omogenei (ossia tipi di dato differenti) come ad esempio dati numerici (es. numero telefonico, voto, ecc.) e testuali (nome, cognome, ecc.) Un tipo struct (redocrd) è costituito da un elenco di campi, ognuno dei quali sarà accessibile singolarmente. I campi possono a loro volta essere delle strutture. struct studente{ int matricola; string materia; int g,m,a; int voto; }; struct contatto { int id; string nome; int telefono; string indirizzo; }; 6 Struct Una struct si dichiara con la seguente sintassi: struct NomeStruttura { tipo_elemento1 nome_elemento1; tipo_elemento2 nome_elemento2; ... }; In questo modo noi abbiamo definito solo un tipo di struttura, ma non abbiamo ancora creato nessuna variabile. Per creare variabili di tipo struct si può procedere in 3 modi: Prima modalità (elencando le variabili da dichiarare separate da virgola dopo le parentesi graffe della dichiarazione della struttura) struct contatto { int id; string nome; int telefono; string indirizzo; } contatto1, contatto2; 7 Struct Seconda modalità (dichiarando le variabili di tipo struct come delle normali variabili ovvero facendo precedere al nome della variabile il tipo "NomeStruttura“ precedentemente dichiarato.) struct contatto { int id; string nome; int telefono; string indirizzo; }; contatto contatto1, contatto2; Terza modalità (inizializzando un variabile struttura in fase di dichiarazione, facendo seguire al nome della variabile un uguale e i dati da mettere nei campi della struttura contenuti tra parentesi graffe e separati da virgola) contatto contatto1 = {0, "Rossi Mario", 03928723, "via XX settembre, 1"}; 8 Trattamento dei campi di un record (struct) Data la struct stud e una variabile studente di tipo stud L’input e l’output della struct ( record ) avviene un campo alla volta struct stud { string cognome; string nome; int annoNascita; }; stud studente; Esempio: La visualizzazione del cout<<“\ncognome = “<<studente.cognome; record prevede l’output dei cout<<“\nnome = “<<studente.nome; singoli campi cout<<“\nanno di nascita = “<<studente.annoNascita; Struct Ogni campo di una struct può essere considerato come una variabile singola con il nome seguente: Nome_Variabile.Nome_Campo Es. contatto1.nome contatto2.indirizzo Possiamo creare array di struct . Ad esempio, utilizzando la struct contatto (definita prima) creare una rubrica di contatti contatto rubrica[100]; In questo modo abbiamo creato una rubrica di 100 contatti, e se ad esempio vogliamo stampare il nome del primo contatto dobbiamo scrivere: cout<<rubrica[0].nome 10 Esercizio Definire una struttura adatta a memorizzare i dati di un libro( titolo del libro, costo, numero di pagine) successivamente si scriva un programma che acquisisce due libri e stampa ti dati del libro che costa di più Esercizio Definire una struttura adatta a memorizzare i dati di un libro( titolo del libro, costo, numero di pagine) successivamente si scriva un programma che acquisisce due libri e stampa ti dati del libro che costa di più struct libro { string titolo; float prezzo; int numeroPagine; }; #include <iostream> #include <string> using namespace std; struct libro { string titolo; float prezzo; int numeroPagine; }; Esercizio Definire una struttura adatta a memorizzare i dati di un libro( titolo del int main() libro, costo, numero di pagine) successivamente si scriva un programma { L1, L2; che libro acquisisce due libri e stampa ti dati del libro che costa di più cout<<"\nindica i dati del primo libro:"<<endl; cout<<"\ntitolo : "; cin>>L1.titolo; cout<<"\nprezzo : "; cin>>L1.prezzo; cout<<"\npagine : "; cin>>L1.numeroPagine; cout<<"\nindica i dati del secondo libro:"<<endl; cout<<"\ntitolo : "; cin>>L2.titolo; cout<<"\nprezzo : "; cin>>L2.prezzo; cout<<"\npagine : "; cin>>L2.numeroPagine; struct libro { string titolo; float prezzo; int numeroPagine; }; if(L1.prezzo>L2.prezzo) { cout<<"\nIl libro piu' costo e' "<<L1.titolo; cout<<" con prezzo "<<L1.prezzo; cout<<" e pagine "<<L1.numeroPagine<<endl; } else { cout<<"\nIl libro piu' costo e' "<<L2.titolo; cout<<" con prezzo "<<L2.prezzo; cout<<" e pagine "<<L2.numeroPagine<<endl; } return 0; } Esercizio Definire una struttura adatta a memorizzare i dati di uno studente e successivamente scrivere un programma C++ che permetta di inserire da input i dati degli allievi di un corso e di stampare le matricole degli allievi che hanno il campo voto > di 24. Esercizio Definire una struttura adatta a memorizzare i dati di uno studente e successivamente scrivere un programma C++ che permetta di inserire da input i dati degli allievi di un corso e di stampare le matricole degli degli allievi che hanno il campo voto > di 24. struct studente{ string nome; int matricola; string materia; int g,m,a; int voto; }; const int n=10; studente allievi[n]; //array di tipo studente La ricorsione In C++ ogni funzione può chiamare anche se stessa, secondo una tecnica detta ricorsione. Ovviamente tali funzioni devono sempre contenere un’istruzione di controllo che ha il compito di interrompere la successione delle chiamate, se si verificano certe condizioni. 16 Il calcolo del fattoriale La funzione fattoriale, molto usata nel calcolo combinatorio, è così definita n! 1 se n 0 n(n 1)! se n 0 dove n è un numero intero non negativo 17 Confronto fra definizione iterativa e ricorsiva Funzione non ricorsiva Funzione ricorsiva n!= 1*2*3*...*(n-1)*n 0!=1 n!= n*(n-1)!; fatt(n)= 1*2*3*...*(n-1)*n; fatt(n)=n*fatt(n-1) 18 Il calcolo del fattoriale • Vediamo di chiarirne il significato 0! = 1 1! = 1(1-1)! = 1·0! = 1·1 = 1 2! = 2(2-1)! = 2·1! = 2·1 = 2 3! = 3(3-1)! = 3·2! = 3·2·1 = 6 4! = 4(4-1)! = 4·3! = 4·3·2·1 = 24 5! = 5(5-1)! = 5·4! = 5·4·3·2·1 = 120 • Quindi, per ogni n intero positivo, il fattoriale di n è il prodotto dei primi n numeri interi positivi Il calcolo del fattoriale Per implementare la funzione per il calcolo del fattoriale in modo iterativo siamo costretti a fare un’analisi della definizione per scrivere l’algoritmo #include <iostream> using namespace std; int fatt(int); int main() { int n; do { cout << "\nIntroduci un numero >0 : "; cin >> n;} while (n<=0); cout << "Fattoriale di: " << n<< " =" << fatt(n) << endl; return 0; } 20 Il calcolo del fattoriale Per implementare la funzione per il calcolo del fattoriale in modo iterativo siamo costretti a fare un’analisi della definizione per scrivere l’algoritmo #include <iostream> usingsenza namespace usarestd; la ricorsione int fatt(int); int main() { int fatt(int k) int n;{ int i = 2,m =1; do {while ( i <= k ) cout << un numero { "\nIntroduci m *= i++ ; } >0 : "; cin >> n;} return m; while (n<=0); } "Fattoriale di: " << n<< " =" << fatt(n) << endl; cout << return 0; } 21 Il calcolo del fattoriale • Implementando direttamente la definizione ricorsiva, è naturale scrivere: int fatt (int k) { if (k==0)return 1;/*istruzione di controllo */ else return k*fatt(k-1);} Il calcolo del fattoriale #include <iostream> using namespace std; int fatt(int); int main() { int n; do { cout << "\nIntroduci un numero >0 : "; cin >> n;} while (n<=0); cout << "Fattoriale di: " << n<< " =" << fatt(n) << endl; return 0; } int fatt (int k) { if (k==0)return 1;/*istruzione di controllo */ else return k*fatt(k-1);} 23 Il calcolo del fattoriale La funzione ricorsiva fattoriale non necessita di iterazioni, ma include internamente il calcolo del risultato, infatti restituisce 1 se il parametro ricevuto è uguale a 0 mentre in caso contrario il valore restituito è il prodotto di n per il fattoriale (n-1), pertanto fattoriale calcola il valore da restituire chiamando se stessa e "passandosi" come parametro il parametro appena ricevuto, diminuito di uno. int fatt (int k) { if (k==0)return 1;/*istruzione di controllo */ else return k*fatt(k-1);} Questa soluzione si basa sulla invocazione di una funzione mentre si esegue la funzione stessa! Questa possibilità è permessa in tutti i linguaggi di programmazione che ammettono la ricorsione La ricorsione Per capire come utilizzare correttamente la ricorsione, vediamo innanzitutto come funziona. Quando una funzione ricorsiva invoca se stessa, vengono eseguite le stesse azioni di quando viene invocata una funzione qualsiasi – Si sospende l’esecuzione della funzione chiamante (le variabili locali rimangono congelate) – Si esegue la funzione chiamato fino alla sua terminazione (con nuove variabili locali e passando i parametri per valore) – Si riprende l’esecuzione della funzione chiamante dal punto in cui era stata sospesa (recuperando le variabili locali) 25 La ricorsione • Vediamo la sequenza delle istruzioni per calcolare 3! si chiama f(3) f(3) lascia in sospeso il calcolo 3* f(2) e chiama f(2) f(2) lascia in sospeso il calcolo 2* f(1) chiama f(1) f(1) lascia in sospeso il calcolo 1* f(0) e chiama f(0) f(0) restituisce 1 a f(1) f(1) calcola1*1 e restituisce 1 a f(2) f(2) calcola 2*1 e restituisce 2 a f(3) f(3) calcola 3*2 e restituisce 6 alla funzione chiamante • Si crea quindi una pila ( stack) di funzioni “in attesa”, ciascuno con le sue variabili locali, che si allunga e che poi si accorcia fino ad esaurirsi La ricorsione • Chiamare una funzione mentre si esegue la stessa funzione è un paradigma di programmazione che si chiama ricorsione e una funzione che ne faccia uso si chiama Funzione ricorsiva • La ricorsione è uno strumento molto potente per realizzare alcuni algoritmi (ma è anche fonte di errori di difficile diagnosi) • Esistono infatti due regole ben definite (passo base e passo ricorsivo) che vanno utilizzate per scrivere funzioni ricorsive che funzionino. 27 Prima regola La ricorsione: passo base – la funzione ricorsiva deve fornire la soluzione del problema in almeno un caso particolare, senza ricorrere ad una chiamata ricorsiva – questo caso si chiama caso base della ricorsione – nel nostro esempio, il caso base era if (n == 0) return 1; – a volte ci sono più casi base, infatti non è necessario che il caso base sia unico 28 Seconda regola La ricorsione: passo ricorsivo – la funzione ricorsiva deve effettuare la chiamata ricorsiva dopo aver semplificato il problema – nel nostro esempio, per il calcolo del fattoriale di n si invoca la funzione ricorsivamente per conoscere il fattoriale di n-1, cioè per risolvere un problema più semplice f = n * fattoriale(n - 1); – il concetto di “problema più semplice” varia di volta in volta: in generale, bisogna avvicinarsi ad un caso base 29 Ricorsione infinita • Si noti quindi che non tutte le funzioni ricorsive realizzano algoritmi! – se manca il caso base, la funzione ricorsiva continua ad invocare se stessa all’infinito – se il problema non viene semplificato ad ogni invocazione ricorsiva, la funzione ricorsiva continua ad invocare se stessa all’infinito • In questo caso la lista delle funzioni “in attesa” si allunga indefinitamente, l’ambiente runtime esaurisce la memoria disponibile per tenere traccia di questa lista, e il programma termina con un errore. 30 Ricorsione infinita Le regole appena viste • implicano che ad ogni invocazione il problema diventi più semplice avvicinandosi sempre più al caso base che non richiede ricorsione • ovvero che per quanto complesso possa essere il problema la soluzione sia calcolata in un numero finito di passi 31 Esercizio Scrivere una funzione ricorsiva e una iterativa che ricevendo in input, come parametro un intero n, calcola F(n) dove F è la funzione di Fibonacci. Esercizio • Scrivere una funzione ricorsiva per il calcolo della funzione esponenziale definita come segue: 26/04/2012 33 Esercizio Scrivere un programma che calcola il prodotto tra due numeri interi a e b mediante una funzione ricorsiva che utilizza somme successive. Suggerimento Il calcolo da svolgere deve essere del tipo a*b = a*(b-1)+a; 34 Esercizio Scrivere una funzione che, dati come parametri di input un array di interi a ed un intero n, restituisce il numero delle occorrenze di n in a. 35 Esercizio Scrivere una funzione che, dati come parametri di input un array di interi a ed un intero n, restituisce la posizione della prima occorrenza di n in a, e -1 se n non compare in a. 36 Esercizio Scrivere una funzione che, dati come parametri di input un array di interi a ed un intero n, restituisce true se n compare in a, false altrimenti. 37 Esercizio Scrivere una funzione che, dati come parametri di input un array bidimensionale di interi a ed un intero n, restituisce true se n compare in a, false altrimenti. 26/04/2012 38 Esercizio Scrivere una funzione che dati come parametri di input un array di interi restituisca true se tutti i suoi elementi sono identici, e false altrimenti. 26/04/2012 39 Esercizio Scrivere una funzione che, dati come parametri di input un array bidimensionale di interi a e due interi k ed n, restituisce true se in ogni riga a[i] di a esistono almeno k elementi maggiori di n, altrimenti la funzione restituisce false. 26/04/2012 40