M5-U6-I sottoprogrammi
Transcript
M5-U6-I sottoprogrammi
Modulo 5 – La programmazione Unità 6 – I sottoprogrammi Prof. Antonio Scanu 1 Top-down e Bottom-up Quando un problema appare immediatamente complesso, per risolverlo possiamo individuare e analizzare i sottoproblemi più semplici che lo compongono, oltre alle loro interrelazioni. In questo modo è possibile articolare la progettazione dell'algoritmo complessivo in una serie di algoritmi più semplici, che verranno poi opportunamente assemblati. La programmazione, in effetti, non è solo un processo di ideazione e formulazione di algorit mi: esige attenzione ai minimi dettagli e l'adozione di opportune tecniche di analisi dei pro blemi da risolvere. Per questo, una buona metodologia di progettazione è quella che risolve il problema per passi: partendo da un'analisi generale, si focalizza l'attenzione sui singoli punti fondamentali che lo compongono, riducendo così le difficoltà. Tale metodologia, di natura gerarchica, prende il nome di top-down, ossia "dall'alto verso il basso". Gli aggettivi alto e basso si riferiscono al livello di dettaglio o astrazione. Il livello più alto (top) è quello generale, chiamato problema principale; in esso si individuano i nodi fondamentali chiamati sottoproblemi. Ciascun sottoproblema viene dettagliato a parte e, se complesso, può essere a sua volta scomposto in ulteriori sottoproblemi più semplici. Si giunge così all'analisi e alla risoluzione di tanti problemi elementari tramite algoritmi descritti a livello programmabile (il livello più basso è down), le cui relazioni sono ricavate dalla descrizione a livello superiore. In sintesi, si scende dal generale al particolare mediante affinamenti successivi. La tecnica top-down, quindi, nasce come tecnica di analisi dei problemi e non come tecnica di progettazione: il risolutore, infatti, utilizza tale metodologia per affrontare agevolmente il processo risolutivo del problema. All'atto dell'implementazione, poi, il programmatore deciderà se implementare singolarmente i vari sottoproblemi, o se accorparne alcuni e scomporne altri. Per scomporre un problema in tanti sottoproblemi funzionali ci si sofferma su cosa debba essere fatto e non sul come, che con tale metodologia viene affrontato poco e soltanto all'ultimo livello. La tecnica top-down, quindi, parte dall'obiettivo e da esso fa scaturire la strategia più adatta a raggiungere l'obiettivo stesso; valorizza, quindi, il perché e da esso fa dipendere il come, ossia la strategia. Individua, pertanto, le risorse necessarie, precisa quelle disponibili e identifica quelle mancanti, propone successivamente ogni risorsa mancante come sotto-obiettivo, ovvero come sottoproblema in cui ciascun sotto-obiettivo richiede una sotto-strategia risolutiva. La metodologia bottom-up, ossia "dal basso verso l'alto", privilegia invece l'aspetto esecutivo rispetto a quello funzionale, procedendo dal particolare verso il generale. Il metodo bottom-up è una strategia induttiva e consente di concentrarsi subito sui punti cardine del problema che, però, potrebbero essere di difficile individuazione iniziale. Nel modello top-down si affronta il problema osservandolo più in generale e poi rifinendo ogni sua parte. Ad esempio, creo un modello di automobile e poi scendo nel dettaglio rifinendo ruote, motore e così via. Un approccio informatico è quello di servirsi di sottoprogrammi da definire in un secondo momento. È il classico approccio della programmazione procedurale. Nel modello bottom-up si affronta il problema preoccupandosi prima dei dettagli più semplici, fino ad arrivare al modello più complesso (esattamente il contrario del modello topdown). Ad esempio, creo una ruota, poi un motore e le altre parti e solo alla fine ottengo il modello complesso di un'automobile. Nella programmazione ad oggetti è utilizzato questo metodo, in quanto si ha la possibilità di creare singoli oggetti indipendenti e quindi tramite questo approccio si sviluppa cominciando dalle classi base che poi si estendono o si collegano insieme a creare un programma complesso. Nel processo di sviluppo software, gli approcci top-down e bottom-up giocano un ruolo fondamentale. L'approccio top-down enfatizza la pianificazione e una completa comprensione del sistema. È ovvio che la fase di codifica non può iniziare finché non si è raggiunto almeno un sufficiente livello di dettaglio nella progettazione di una parte significante del sistema. Questo, comunque, ritarda la fase di test delle ultime unità funzionali di un sistema fino a quando una parte rilevante della progettazione non è stata completata. Al contrario, l'approccio bottom-up enfatizza la codifica e la fase di test precoce, che può iniziare appena il primo modulo è stato specificato. Questo approccio, comunque, induce il ri- schio che i moduli possano essere codificati senza avere una chiara idea di come dovranno essere connessi ad altre parti del sistema. L'importante principio di riusabilità del codice è una delle muse ispiratrici di questo metodo. I moderni approcci alla progettazione software comunemente combinano sia la tecnica topdown sia quella bottom-up. Benché l'analisi e la comprensione del sistema completo sia tipicamente considerata necessaria per una buona progettazione (e quindi tramite l'approccio top-down), nella maggior parte dei progetti software si cerca di fare uso di codice già esistente ad alcuni livelli (tendenza bottom-up). 2 Sottoalgortimi e sottoprogrammi È possibile realizzare un sottoalgoritmo per ogni sottoproblema non più scomponibile. Unendo, alla fine, tutti i sottoalgoritmi, si ottiene l'algoritmo che risolve il problema originale. Il sottoalgoritmo, quindi, è una parte dell'algoritmo che risolve un particolare sottoproblema. Tecnicamente, è una parte dell'algoritmo risolutivo che non può essere eseguita autonomamente, ma soltanto su richiesta (invocazione) e sotto stretto controllo dell'algoritmo o del sottoalgoritmo che lo invoca, il quale per tale motivo, è denominato "chiamante". Grazie a metodologie note con il nome di tecniche di programmazione modulare, è possibile suddividere il procedimento generale che risolve un problema in: un algoritmo principale (main) che descrive globalmente il problema; s un insieme di sottoalgoritmi che risolvono i singoli sottoproblemi. ogni algoritmo viene tradotto in un programma quando si utilizza un linguaggio di programmazione. I sottoalgoritmi, una volta codificati, vengono chiamati sottoprogrammi. Pertanto, si parla correttamente di programmi e sottoprogrammi quando si passa alla fase di "codifica" in un linguaggio di programmazione. Nella nostra trattazione, anche al fine di uniformarsi alla classica e consolidata terminologia in uso in ambiente informatico, utilizzeremo anche i termini programma principale e main per riferirci all'algoritmo principale e il termine sottoprogramma per fare riferimento al sottoalgoritmo. È importante ricordare che anche il main è un sottoprogramma, una sorta di sottoprogramma "speciale" per il fatto che non può essere chiamato da nessuno e che è il primo a essere attivato (in modo automatico) in fase di esecuzione. Applicare la tecnica top-down in modo eccessivo, ossia frammentare i vari sottoproblemi riducendoli a pochissime azioni ciascuno, è sconveniente. Il programma sarà costituito, in un caso del genere, da tantissimi sottoprogrammi (spesso inutili e talvolta contenenti una sola istruzione). Diverrà, così, eccessivamente frammentato, inefficiente e scarsamente leggibile. Non esiste una formula in grado di stabilire quanti sottoprogrammi occorrono per la risoluzione di un problema e quando utilizzarli. Possiamo fornire però alcune indicazioni generali che nascono dall'esperienza. Analizziamo il seguente problema: preparare una torta gelato al cioccolato. ALGORITMO Torta INIZIO Preparare la base per la torta Preparare un gelato al cioccolato Preparare la glassa al cioccolato Farcire la torta con il gelato e la glassa FINE Tutte e quattro le azioni sono piuttosto complesse e anche di interesse generale: per questi motivi conviene descriverle per mezzo di sottoprogrammi. Continuiamo l'affinamento, a partire dalla prima azione, cioè Preparare la base per la torta. Avremo: SOTTOALGORITMO Preparare la base per la torta INIZIO Preparare l'impasto Preparare la teglia Preparare il forno Infornare Sfornare FINE L'attività Preparare l'impasto è di interesse generale, in quanto lo stesso impasto potrà essere utilizzato per molti altri tipi di torte: quindi si può descriverla, laddove fosse necessario, tramite un sottoprogramma. Le altre (da Preparare la teglia a Sfornare) sono piuttosto sem plici e pertanto possono essere dettagliate immediatamente. Analogo ragionamento andrà fatto per le altre azioni descritte nell'algoritmo principale. Uno stesso sottoprogramma può essere richiamato più volte sia dal programma principale sia dagli altri sottoprogrammi. Inoltre, può essere utilizzato all'interno di altri programmi nei quali sia necessaria la stessa operazione; ciò consente la riusabilità del codice. Conviene descrivere un'attività per mezzo di un sottoalgoritmo quando: è di interesse generale Non conviene descrivere un'attività per mezzo di un sottoalgoritmo quando è di è di scarso interesse generale non è di interesse generale ma si presenta pur pur essendo di interesse generale più volte all'interno del programma non migliora la leggibilità del programma o, addirittura, la peggiora. pur pur essendo di scarso interesse generale più permette una maggiore leggibilità del programma Riepilogando, esistono ottimi motivi che spingono a utilizzare i sottoprogrammi. In particolare essi: migliorano la leggibilità del programma in maniera considerevole; permettono l'astrazione funzionale; quando il programmatore inserisce un'istruzione di chiamata di sottoprogramma, astrae dalla realtà del sottoproblema, cioè si disinteressa, in quel momento, della sua realizzazione, perché gli interessa solo che cosa fare e non come farlo; consentono di scrivere meno codice e così occupano meno memoria; si evita, infatti, di riscrivere più volte sequenze di istruzioni identiche in punti diversi del programma; sono riutilizzabili. Molto spesso accade che il sottoproblema da affrontare sia già stato risolto altrove; se abbiamo già il sottoalgoritmo risolutivo, possiamo riutilizzarlo senza riscriverlo. Per poter essere facilmente riutilizzabile, il sottoalgoritmo deve avere una forte coesione e pochi collegamenti con l'esterno, deve, cioè, risolvere il suo compito e avere pochi dati in comune con l'esterno. 3 Le funzioni Una funzione è un sottoprogramma contenente le istruzioni che risolvono uno specifico problema, che, quando il sottoprogramma viene attivato da un'istruzione di chiamata, restituisce un valore. Il valore della funzione deve essere usato come elemento di un'istruzione. Le funzioni restituiscono un risultato, oltre a svolgere un'azione; per questo motivo la funzione può essere richiamata in un'assegnazione a una variabile oppure all'interno di una gene rica espressione. #include<iostream> using namespace std; tipodidato nomefunzione (<lista dei parametri>); Prototipo della Funzione int main(){ …......................; variabile=nomefunzione(<listavariabili>); Chiamata della Funzione ….....................; return 0; } tipodidato nome funzione(<lista dei parametri>) { ….............; return(valore); } Definizione della Funzione dove: tipodidato è uno dei tipi di dato usati in C++; <ListaParametri> è così strutturata: TipoParametro1 parametro1, TipoParametro2 parametro2, …....,TipoParametroN parametroN void (nel caso non si passino dei parametri). Valore: un numero diretto o un carattere diretto costante variabile 3.1.1 Esempio : Somma tra due numeri #include <iostream> using namespace std; int somma (int x, int y); int main () { int a,b,z; cout<<”inserisci a”; cin>>a; cout<<”inserisci b”; cin>>b; z = somma (a,b); cout << "Il risultato e' " << z; return 0; } int somma (int x, int y) { int r; r=x+y; return r;} 3.1.2 Esempio: calcolo della media di due studenti e rilevazione dello studente migliore (ogni studente ha tre voti) #include <iostream> using namespace std; int media(void); char maggiore(int x,int y); int main () { int a,b; char c; a = media(); b = media(); c = maggiore(a,b); cout << "il migliore è" << c; return 0; } int media() { int m,numero,i,somma; for(i=0;i<3;i++){ cout<<”inserisci numero”; cin>>numero; somma=somma+numero;} m=numero/3; return m; } char maggiore(int x,int y) { if(x>y) return ('a'); else return ('b'); } 4 Ambienti locale e globale Durante l'implementazione di un sottoprogramma occorre definire tutte le risorse necessarie al suo funzionamento, vale a dire il suo ambiente. Con il termine ambiente di un sottoprogramma definiamo l'insieme delle risorse (variabili, costanti, sottoprogrammi, parametri) alle quali esso può accedere. Per il momento, diciamo che l'ambiente è costituito: dall'ambiente locale, cioè dalle risorse dichiarate all'interno del sottoprogramma (risorse locali); dall'ambiente globale, ossia dalle risorse utilizzabili da tutti i sottoprogrammi (risorse globali). Programma Esempio. E’ possibile dichiarare delle variabili dentro le funzioni. Esse sono dette variabili locali e sono visibili solo dentro la funzione in cui vengono dichiarate. Perciò è possibile dichiarare variabili con lo stesso nome in funzioni (ambienti) diversi. Per il sottoprogramma A le variabili locali sono Z1,Z2,Z3. E’ possibile dichiarare delle variabili globali e sono visibili e accessibili in tutto il programma. Nel nostro caso Y1,Y2,Y3. In C++ il Main è considerato un sottoprogramma. Le variabili dichiarate nel Main sono considerate variabili locali del Main. Un corretto stile di programmazione impone di minimizzare l'uso dell'ambiente globale e di privilegiare quello locale. L'uso di variabili globali è, come già detto, fortemente sconsigliato e comunque richiede parsimonia, poiché rischia di generare interazioni di difficile controllo tra diverse parti di un programma. L'abuso di variabili globali può rendere difficile isolare gli errori (in gergo bug) e inoltre indica che il progetto di un programma non è stato pensato attentamente. L'utilizzo delle variabili locali permette di: agevolare la lettura del programma, in quanto mette in evidenza in quale ambito hanno significato le risorse; individuare facilmente errori commessi, in quanto ci si sofferma solo sulle risorse locali nell'ambito di quel sottoprogramma. Le variabili globali Le variabili locali sono allocate (e inizializzate - in ogni caso) sono allocate (e inizializzate, se richiesto) quando si entra nel sottoprogramma rimangono allocate per tutta la durata del programma rimangono allocate solo per la durata del sottoprogramma vengono inizializzate solo una volta vengono inizializzate tutte le volte che viene eseguito il sottoprogramma 5 Le regole di visibilità È ormai chiaro che all'interno di un programma ogni oggetto ha un suo campo di validità (scope), ossia un ambito in cui può essere usato e riconosciuto. È pertanto necessario definire delle regole per determinare il campo di visibilità degli oggetti globali e locali di un programma. Si parte dai seguenti principi: 1. Gli oggetti globali sono accessibili a (visibili in) tutto il programma. 2. Un oggetto dichiarato in un sottoprogramma ha significato solo in quel sottoprogramma e in tutti quelli in esso dichiarati. L'ambiente di un sottoprogramma, quindi, include anche tutte le risorse dei sottoprogrammi che contengono il sottoprogramma stesso (ambiente non locale). 3. Un oggetto non può essere usato se non è stato prima dichiarato. Nella descrizione di un algoritmo, può succedere che una variabile sia dichiarata con lo stesso nome (il tipo potrebbe anche non essere uguale) a livello globale e a livello locale all'interno di un sottoprogramma. Si tratta di due variabili diverse che occupano diversi spazi di memoria, anche se hanno uguale nome. Nel caso di omonimia si applica la regola di sovrapposizione e il concetto di shadowing, secondo il quale la variabile locale, durante l'esecuzione del sottoprogramma che la contiene, "oscura" (maschera) l'omonima variabile più esterna, impedendone la visibilità. 6 Le procedure La procedura è un sottoprogramma contenente le istruzioni che risolvono uno specifico problema. L'esecuzione di una procedura viene attivata dall'apposita istruzione di chiamata (invocazione). Essa utilizzando il passaggio per riferimento può modificare il valore di variabili che sono locali in un altro sottoprogramma. Vediamo subito la differenza con le procedure. La procedura permette l'aggregazione di una sequenza di operazioni elementari in una unica macro-operazione dotata di nome e di argomenti. In quanto costrutto sintattico, la procedura introduce regole di visibilità che nascondono a un osservatore esterno la sua struttura interna. Semanticamente essa realizza i suoi effetti modificando parametri ricevuti dal chiamante o un ambiente esterno che può consistere in un insieme di variabili, dispositivi di I/O, file e così via. La procedura è un'astrazione della nozione di istruzione: non è altro che una istruzione complessa che può essere utilizzata ovunque possa esserlo una istruzione semplice: il compito principale di una procedura è quello di modificare il contenuto di locazioni di memoria. La funzione può essere vista come una procedura con restrizioni di tipo semantico: essa non effettua alcuna azione visibile sul mondo circostante, non modifica gli argomenti ricevuti dal chiamante e realizza i suoi effetti restituendo un risultato. La funzione è un'astrazione della nozione di operatore e può essere utilizzata in qualunque valutazione di espressione: ha il compito di fornire un valore, anche se non è escluso che, nel fornirlo, assuma anche i comportamenti della procedura (noi nella nostra trattazione lo escluderemo!). Le procedure sono sempre associate a un nome simbolico che viene indicato nella prima istruzione del sottoprogramma e in ogni istruzione di chiamata del medesimo. In C++, non esistono le procedure essere sono un caso specifico di funzione. La discussione sulla differenza in C++ dei due tipi di sottoprogrammi è lunga è complessa e non verrà trattato. Si può comunque utilizzare la sintassi seguente per simulare una procedura. #include<iostream> using namespace std; void nomeprocedura (<lista dei parametri>); procedura int main(){ …......................; nomeprocedura(<listavariabili>); ….....................; return 0; } void nomeprocedura (<lista dei parametri>) { ….............; procedura } Prototipo della Chiamata della procedura Definizione della dove <ListaParametri> è così strutturata: TipoParametro1 parametro1, TipoParametro2 parametro2, …....,TipoParametroN parametroN void (nel caso non si passino dei parametri). Stare attenti ai punti e virgola. Il linguaggio C, concede la massima flessibilità nell'ordine di scrittura dei sottoprogrammi, che possono essere definiti prima o dopo il programma principale, purché prima del main venga fatta una dichiarazione "forward" (o prototipo). 7 I parametri Per rendere efficace l'uso delle procedure bisogna approfondire il concetto di parametro. I parametri sono oggetti caratterizzati da: un identificatore; un tipo; un valore; una posizione; una direzione (input/output) che dipende dalla modalità di passaggio del parametro. Grazie a essi si stabilisce come debba avvenire l'input dei dati (al sottoprogramma) e l'output dei risultati (al sottoprogramma chiamante). L'identificatore e il tipo dei parametri sono noti al momento della dichiarazione del sottoprogramma, ma il valore è noto solo all'atto della chiamata. I parametri permettono, quindi, di gestire la comunicazione del sottoprogramma con l'esterno. All'atto della chiamata del sottoprogramma occorre specificare i parametri attuali, ossia le informazioni reali che devono essere trasmesse a esso. I valori di tali parametri saranno accolti dal sottoprogramma per mezzo dei parametri formali dichiarati nell'intestazione. Ciò vale anche per le funzioni. Un sottoprogramma parametrizzato lavora con variabili fittizie nel nostro caso i parametri formali a e b), che vengono collegate al programma principale solo al momento della chiamata del sottoprogramma. È per questo motivo che tali parametri vengono detti formali. Per i parametri attuali occorre indicare solo il nome; per i parametri formali, invece, è necessario indicare il nome e il tipo. È importante tenere presente che il numero, il tipo e l'ordine dei parametri attuali devono essere sempre uguali a quelli dei corrispondenti parametri formali. Nel nostro esempio, infatti, la chiamata della procedura associa il parametro attuale p di tipo Intero al parametro formale a (anch'esso, ovviamente, di tipo intero) e il parametro attuale q di tipo intero al parametro formale a di tipo intero. I parametri attuali e i parametri formali possono anche avere casualmente lo stesso nome, ma si consiglia di utilizzare nomi diversi (per evitare inutili confusioni). Spesso, all'atto della dichiarazione di un sotto- programma, nasce il problema della scelta dei parametri. Suggeriamo di rispettare le seguenti regole: 1. identificare i dati essenziali e provenienti dall'esterno di cui il sottoprogramma ha bisogno; 2. identificare l'output che il programma chiamante vuole ottenere dal sottoprogramma. 8 Passaggio di parametri Con passaggio o trasmissione dei parametri intendiamo l'operazione con la quale il valore dei parametri attuali viene associato (trasmesso) a quello dei parametri formali. Il passaggio dei parametri può avvenire secondo due distinte modalità, ognuna rispondente a esigenze diverse: 1. passaggio per valore o per copia; 2. passaggio per indirizzo o referenza. Nel passaggio dei parametri per valore si ha soltanto una copia dei valori dei parametri attuali nei rispettivi parametri formali. Durante l'esecuzione del sottoprogramma, qualsiasi modifica apportata ai parametri formali sarà visibile solo all'interno del sottoprogramma e non verrà riportata sui parametri attuali (che continueranno, così, a conservare il valore iniziale). I parametri formali vengono considerati come variabili locali il cui valore, al ritorno dal sottoprogramma, si perde. Nel passaggio dei parametri per valore, all'atto della chiamata del sottoprogramma, viene allocata un'area di memoria utilizzata per contenere i parametri formali. Si ha, così, una du plicazione dei dati relativi ai parametri. Al passaggio, i parametri formali verranno inizializzati con il valore dei rispettivi parametri attuali. In questo modo, il processore opera su questa nuova area di memoria lasciando inalterati i parametri attuali. Al rientro dal sottoprogramma, quest'area viene rilasciata, proprio come avviene per le variabili locali (a cui i parametri formali sono assimilabili. In C++ il passaggio per valore avviene di default Vediamo come funziona la seguente procedura per l'ordinamento di due variabili. #include <iostream> using namespace std; void scambia(int x,int y); int main () { int a,b; cout<<"inserisci a"; cin>>a; cout<<"inserisci b"; cin>>b; scambia(a,b); cout<<"i numeri ordinati sono"<<a<<" "<<b; return 0; } void scambia(int x,int y) { int temp; temp=x; x=y; y=temp; } È evidente che il programma, pur essendo corretto in ogni suo punto, non risolve il problema dato: le variabili A e B, infatti, non hanno subito il dovuto scambio. Il motivo per il quale l'algoritmo precedente non produce il risultato corretto è dato dal fatto che è stato utilizzato un passaggio di parametri per valore. Per questo tipo di problemi, quando cioè vogliamo che il sot toprogramma restituisca il valore dei parametri formali (cioè alteri il valore dei pa rametri attuali), dobbiamo servirci di un passaggio per indirizzo. Nel passaggio dei parametri per indirizzo i parametri formali contengono l'indirizzo di memoria dei parametri attuali. Di conseguenza, una modifica dei parametri formali provoca una modifica dei corrispondenti attuali. Con questo tipo di passaggio, quindi, si può perdere il contenuto originale dei parametri attuali. In C++ il passaggio per indirizzo viene indicato anteponendo il simbolo & al parametro o alla lista di parametri formali interessati Tecnicamente, il passaggio per indirizzo differisce da quello per valore in quanto, all'atto della chiamata del sottoprogramma, non viene allocata nessuna area di memoria per valori dei parametri formali. Poiché essi si riferiscono alla stessa area di memoria allocata per i parametri attuali, la stessa cella di memoria sarà individuabile con due nomi distinti (alias). In questo tipo di passaggio non viene trasmesso il valore dei parametri attuali, bensì l'indirizzo della cella di memoria a essi assegnata; di conseguenza, la modifica di un parametro comporterà la modifica dell'altro. Consideriamo l'esempio precedente, ma questa volta effettuando un passaggio per indirizzo. #include <iostream> using namespace std; void scambia(int &x,int &y); int main () { int a,b; cout<<"inserisci a"; cin>>a; cout<<"inserisci b"; cin>>b; scambia(a,b); cout<<"i numeri ordinati sono"<<a<<" "<<b; return 0; } void scambia(int &x,int &y) { int temp; temp=x; x=y; y=temp; } La tabella seguente illustra la situazione della memoria durante l'esecuzione. Ora il risultato è quello che attendevamo! Concludendo, vediamo quali sono le regole generali da seguire per scegliere la modalità del passaggio dei parametri. È opportuno effettuare un passaggio per valore quando l'informazione è solo di input per il sottoprogramma. È opportuno effettuare un passaggio per indi- rizzo quando vogliamo la restituzione del valore del parametro formale (parametro di output), Riportiamo di seguito alcune utili regole da seguire per una corretta progettazione di procedure con parametri. Le procedure devono comunicare con l'esterno esclusivamente tramite parametri. Non si deve utilizzare un numero eccessivo di parametri (se ciò dovesse accadere, forse sarebbe utile rivedere la progettazione). Le procedure non devono utilizzare le variabili globali e, in particolar modo, non devono modificarle Le procedure che risolvono un certo problema non devono usare al loro interno istruzioni di input/output: devono comunicare i dati solo attraverso i parametri. La gestione dell'input/output deve avvenire tramite apposite procedure. 9 ESERCITAZIONE N. 7 (Funzioni) 1. Scrivere una funzione che visualizzi sullo schermo: CORSO DI LAUREA IN INFORMATICA ESAMI 1. Analisi 2. Algebra 3. Programmazione 4. Fondamenti Scegliere un esame: e quindi renda al programma chiamante la scelta effettuata. Visualizzare dal main la scelta. 2. Scrivere un programma che legge due numeri interi a e b diversi da zero. Inserire in esso una funzione che riceva i due interi letti e renda 0 se a e b hanno lo stesso segno 1 se a e b hanno segno differente Visualizzare dal main il risultato. 3. Inserire nel programma precedente una seconda funzione che riceva i due interi a e b ed un altro intero x e renda: a+b per x=1 a-b per x=2 a*b per x=3 a/b per x=4 0 in tutti gli altri casi. Visualizzare dal main i risultati resi. 4. Scrivere un programma che legge due numeri maggiori di zero. Inserire in esso una funzione che riceva i due interi letti e renda il valore maggiore. Visualizzare dal main il risultato. 5. Un polinomio di grado 2 in x e’ espresso da ax 2+bx+c. I coefficienti a, b, c assumono valori interi. Scrivere una funzione che riceve a, b e c ed un valore x e calcola il valore del polinomio in x. Modificare la funzione in modo da tabulare (calcolare e visualizzare) i valori dell’incognita nell’intervallo [0, 2] con passo 0.1. Per migliorare la precisione potete usare il tipo double per x e per il valore calcolato dalla funzione. 6. Scrivere un programma che genera casualmente un intero a compreso tra 2 e 6, e un intero b compreso tra 1000 e 2000. Costruire una funzione che riceve a e b e calcola e visualizza o Il valore delle potenze di a con esponente intero > 1 che sono inferiori o uguali a b o Quante sono queste potenze. Il numero delle potenze dovrà essere restituito e visualizzato dal main. 7. Scrivere un programma che definisce tramite una define la costante P=3.141 . Costruire una funzione che calcoli il valore di Pigreco tramite approssimazione della seguente somma Pigreco = 4 - 4/3 + 4/5 - 4/7 + 4/9 – 4/11 … e restituisca al main quanti termini della serie sono necessari per raggiungere il valore P. 8. Due classi (corso A e B) hanno 20 alunni. Si vuole saper quale classe ha la media dei voti in Informatica più alta. Creare un programma contenente le seguenti funzioni: o una funzione che non riceve nessun valore e restituisce la media di una classe o nuna funzione che ricevute le due medie restituisca il nome del corso che ha la media più alta. Visualizzare dal main il nome del corso che ha la media più alta. 9. Una società ha tre filiali (A, B, C) ognuna con un numero di dipendenti diverso, ognuno con uno stipendio diverso. Si vuole sapere il totale degli stipendi di ogni singola filiale e il totale degli stipendi di tutte le filiali. Scrivere un programma utilizzando o Una funzione che riceve il nome della filiale e il numero dei dipendenti e restituisca la somma degli stipendi di quella filiale. Inoltre la funzione stampi il nome della filiale e la somma degli stipendi. O Una funzione che ricevute tutte le somme restituisca la somma totale degli stipendi. Visualizzare dal main la somma totale degli stipendi. 10 ESERCITAZIONE N. 9 (Procedure, passaggio per indirizzo) 1. Scrivere una funzione che riceve il valore del raggio di un cerchio e calcola restituendolo al main il valore della circonferenza e dell’area del cerchio. Visualizzare dal main i valori resi. 2. Scrivere un programma contenente una funzione che generi casualmente 50 numeri positivi e restituisca al main il numero dei pari e dei dispari. 3. Scrivere un programma contenente una funzione che riceve la misura delle due basi e dell’altezza di un trapezio isoscele e restituisce il perimetro e l’area del trapezio. Costruire anche una seconda funzione che calcoli la misura del lato obliquo. 4. In una pizzeria si vendono pizze, bibite e dolci. Il listino è: • Pizze semplici 2,5 € • Pizze farcite 3 € • Bibite 3 € • Dolci 6 € Scrivere un programma che acquisito nel main il numero dei prodotti acquistati da tre clienti (inserire le quantità per singolo cliente, utilizzare un ciclo per calcolare le quantità totali), calcoli il costo totale e quello singolo dei dolci. Inoltre si calcoli quale sarebbe il costo totale se fosse attiva seguente promozione: • • se il numero delle pizze e delle bibite è uguale e si è speso più di 20 € si fa uno sconto di 5 € altrimenti fa pagare tutti i dolci 4 € I risultati devono essere restituiti al main e visualizzati solo nel main. 5. Un comune vuole calcolare quali sarebbero le entrate possibili se fosse introdotta una nuova tassa sui 50 tra terreni e case accatastati. Scrivere un programma contenente una procedura che • Generi casualmente un numero 0 o 1. 0 corrisponde a casa e 1 a terreno. • Se viene generato un terreno si generi un numero casuale intero positivo minore di 400 m2 che rappresenta i metri quadri del terreno. • Conti il numero delle case, dei terreni con metratura minore di 200 m 2 e di quelli con metratura maggiore. Tali risultati vanno resi al main. Scrivere una funzione che calcoli le possibili entrate sapendo che la tassa è pari a: • 20 € per i terreni inferiori a 200 m2 • 40 € per i terreni maggiori di 200 m 2 • 50 € per ogni casa. 6. Scrivere un programma per la gestione di un conto corrente. Sul conto possono essere svolti tre tipi di operazioni: 1-versamento, 2-prelievo, 3-emissione assegni. Dopo aver introdotto il saldo iniziale, si potranno inserire le varie operazioni digitandone il tipo e l’importo (numero positivo) su cui eseguire l’operazione. Ogni operazione sarà gestita da una funzione o procedura diversa diversa. In seguito, a richiesta dell’utente, il programma dovrà fornire: • Il numero dei versamenti effettuati e la somma totale versata; • Il numero dei prelievi effettuati e la somma totale prelevata; Il numero degli assegni emessi e la somma totale prelevata per mezzo degli stessi; • Il saldo finale. • 7. In una biblioteca si possono affittare fino a 10 libri per cliente. I maggiorenni pagano 2 € a libro, i minorenni 1 € a libro. Creare un programma che permetta la gestione della libreria. Si costruisca una funzione che permetta di inserire l’età dei clienti e il numero di libri presi e restituisca: • Il numero totale dei libri presi, • Il numero dei libri presi dai maggiorenni. • Il numero dei libri presi dai minorenni. Si costruisca una seconda funzione che calcoli il guadagno totale della libreria. I risultati devono essere visualizzati nel main. 11 Giochi 1. Ottimizzare il gioco del settemezzo utilizzando le funzioni. 2. Fra amici: Un giocatore a turno tiene banco e ha a disposizione un mazzo di carte da quaranta che deve mescolare piu’ volte e far levare a turno ai compagni partendo da sinistra. Deve essere fissata una somma. Il banchiere distribuisce una carta scoperta a tutti partendo dal compagno di destra, poi ne assegna una anche a sè. Chi ha avuto una carta di valore uguale a quella del banchiere non vince e non perde, chi invece ne ha una di valore superiore (il seme non conta) riceve una somma dal banchiere; chi ne ha una di valore inferiore paga una somma. L’asso ha il valore più alto, il due quello più basso. Nel caso venga usato un mazzo di carte da 52, dopo l’asso valgono in misura decrescente il re, la donna, il fante, il dieci, il nove, l’otto, il sette, il sei, il cinque, il quattro il tre il due.