Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


SOTTOPROGRAMMI E FUNZIONI E CONSIDERAZIONI, Appunti di Fondamenti di informatica

SOTTOPROGRAMMI E FUNZIONI E CONSIDERAZIONI del prof. Porfirio

Tipologia: Appunti

2019/2020

Caricato il 11/09/2021

boom-baby
boom-baby 🇮🇹

4.5

(91)

230 documenti

1 / 8

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
SOTTOPROGRAMMI E FUNZIONI
L’idea dell’uso dei sottoprogrammi e funzioni nasce per risolvere problemi complessi .
COME?
Appunto scomponendolo in tanti problemi semplici che lo compongo.
VantaggiSe noi scomponiamo un problema in tanti sotto problemi il primo vantaggio importante è che i
singoli sotto problemi sono più semplici da risolvere. Se noi riusciamo a scomporre tale problema
complesso in sotto-problemi già risolti in precedenza, noi possiamo riutilizzare le soluzioni. Un altro
vantaggio è che se noi abbiamo un programma enorme (software, sistemi operativi) , usando tale sistema è
che è possibile far lavorare diverse persone sui singoli sotto-problemi contemporaneamente riducendo il
fattore tempo(nell’ambito di aziende) .
Svantaggi(Quello che più ci riguarda) dobbiamo modellare i nostri sotto-problemi in modo tale da far
dialogare tra di loro le funzioni. Quindi dobbiamo studiare l’integrazione tra le funzioni di un programma.
DOMANDA: che differenza c’è tra funzioni e sottoprogrammi? L’idea storica è quella di scomporre un
programma in tanti sottoprogrammi detti anche procedure, in realtà qualcuno ha osservato che questi
sottoprogrammi in realtà svolgevano ognuno un suo compito(problema) e quindi potevano essere
paragonati con funzioni matematiche più in generale in funzioni dove una funzione matematica in senso
lato è semplicemente una cosa che trasforma degli input in degli output.
Quindi una funzione sostanzialmente gestisce quanti vogliamo input e quanti vogliamo output.
Dunque non c’è una differenza evidente tra funzione, sottoprogrammi e procedure.
OSSERVAZIONE: tutto il codice del programma dovrebbe essere sempre organizzato in funzioni (ad esempio
int main)
DICHIARAZIONE
Una funzione si dichiara nella seguente maniera:
TipoRestituito_nomeDellaFunzione (listaDiParametri); //non dimentichiamo il punto e virgola
La dichiarazione "dichiara" che cosa è la funzione ma NON come lo fa
Il "valore" di una funzione è l'algoritmo che la risolve.
Quindi fare solo la dichiarazione della funzione significa dire soltanto che esiste(la medesima) ma non ci dice
come è fatta.
NomeDellaFunzione
un nome che segue le stesse regole delle variabili
NON possiamo usare come nome una parola chiave (vietato chiamare una funzione if, while, do) e non
possiamo usare liberamente main
main è riservato all'unica funzione che vale da punto di partenza del programma.
Due funzioni dello stesso programma NON possono chiamarsi allo stesso modo (regola valida in C ma NON
in C++) --> Lo scope del nome della funzione è tutto il programma (funzioni si comportano come variabili
globali (dal punto di vista dello scope)).
TipoRestituito nomeDellaFunzione (listaDiParametri);
ListaDiParametri può contenere sia le variabili di input che di output. Può contenere 0 variabili o anche un
altro qualsiasi numero.
int main() --> non ha parametri in input
TipoRestituito può rappresentare il risultato della funzione
pf3
pf4
pf5
pf8

Anteprima parziale del testo

Scarica SOTTOPROGRAMMI E FUNZIONI E CONSIDERAZIONI e più Appunti in PDF di Fondamenti di informatica solo su Docsity!

SOTTOPROGRAMMI E FUNZIONI

L’idea dell’uso dei sottoprogrammi e funzioni nasce per risolvere problemi complessi. COME? Appunto scomponendolo in tanti problemi semplici che lo compongo. VantaggiSe noi scomponiamo un problema in tanti sotto problemi il primo vantaggio importante è che i singoli sotto problemi sono più semplici da risolvere. Se noi riusciamo a scomporre tale problema complesso in sotto-problemi già risolti in precedenza, noi possiamo riutilizzare le soluzioni. Un altro vantaggio è che se noi abbiamo un programma enorme (software, sistemi operativi) , usando tale sistema è che è possibile far lavorare diverse persone sui singoli sotto-problemi contemporaneamente riducendo il fattore tempo(nell’ambito di aziende). Svantaggi(Quello che più ci riguarda) dobbiamo modellare i nostri sotto-problemi in modo tale da far dialogare tra di loro le funzioni. Quindi dobbiamo studiare l’integrazione tra le funzioni di un programma. DOMANDA: che differenza c’è tra funzioni e sottoprogrammi? L’idea storica è quella di scomporre un programma in tanti sottoprogrammi detti anche procedure, in realtà qualcuno ha osservato che questi sottoprogrammi in realtà svolgevano ognuno un suo compito(problema) e quindi potevano essere paragonati con funzioni matematiche più in generale in funzioni dove una funzione matematica in senso lato è semplicemente una cosa che trasforma degli input in degli output. Quindi una funzione sostanzialmente gestisce quanti vogliamo input e quanti vogliamo output. Dunque non c’è una differenza evidente tra funzione, sottoprogrammi e procedure. OSSERVAZIONE: tutto il codice del programma dovrebbe essere sempre organizzato in funzioni (ad esempio int main) DICHIARAZIONE Una funzione si dichiara nella seguente maniera: TipoRestituito_nomeDellaFunzione (listaDiParametri); //non dimentichiamo il punto e virgola La dichiarazione "dichiara" che cosa è la funzione ma NON come lo fa Il "valore" di una funzione è l'algoritmo che la risolve. Quindi fare solo la dichiarazione della funzione significa dire soltanto che esiste(la medesima) ma non ci dice come è fatta. NomeDellaFunzione  un nome che segue le stesse regole delle variabili NON possiamo usare come nome una parola chiave (vietato chiamare una funzione if, while, do) e non possiamo usare liberamente main main è riservato all'unica funzione che vale da punto di partenza del programma. Due funzioni dello stesso programma NON possono chiamarsi allo stesso modo (regola valida in C ma NON in C++) --> Lo scope del nome della funzione è tutto il programma (funzioni si comportano come variabili globali (dal punto di vista dello scope)). TipoRestituito nomeDellaFunzione (listaDiParametri); ListaDiParametri può contenere sia le variabili di input che di output. Può contenere 0 variabili o anche un altro qualsiasi numero. int main() --> non ha parametri in input TipoRestituito può rappresentare il risultato della funzione

ESEMPIO

int raddoppia ( int x); Potrebbe essere una funzione che dato un intero x restituisce un altro intero.  int main(); E' una funzione che non ha bisogno di nessun input e restituisce un codice di errore intero (vale 0 se non ci sono errori, un numero diverso da zero se c'è qualche problema).  int somma( int x, int y); Potrebbe ricevere due interi x e y e restituire in output la somma dei due interi Ci può essere un parametro restituito oppure anche nessun parametro.  void stampa ( int x); Potrebbe essere una funzione che stampa a video il valore di x ma non restituisce nessun risultato. I tipi possibili come parametro restituito sono soltanto:  void  Tipi scalari (ad es. Int, char, bool, ...)  Indirizzo (ad es. Char, int, struct nome, ....) Quindi abbiamo escluso almeno per il momento , di passare un’array o di passare una struct e ecc… ma ciò lo abbiamo escluso solo in parte perché noi non possiamo passare un’array però siamo liberi di passare l’indirizzo a cui l’array comincia. Se volessimo restituire un tipo strutturato dobbiamo rassegnarci a restituire soltanto l'indirizzo di quel dato strutturato. DEFINIZIONE Esempi: int raddoppia ( int x) // Dichiarazione { return 2x; // Definizione } int somma ( int x, int y){ int z = x + y; return z; } Return è l'istruzione tramite la quale viene restituito il risultato. Dopo return deve esserci il valore del tipo restituito  infatti nel nostro esempio abbiamo messo “z” che è un valore di tipo intero che coincide (per forza o meglio è una regola 😊) con il tipo restituito. Anche nell’esempio precedente si verifica la stessa cosa infatti c’è 2x che non è una variabile però se x è intero anche 2x allo stesso modo è un intero. Quindi i tipi devono coincidere. Se ciò non è possibile il compilatore o cerca di fare un casting automatico se è possibile oppure ci dice giustamente che è un errore. Return è obbligatoria se il tipo non è void altrimenti è fortemente consigliata (obbligatoria in alcuni compilatori) In una funzione ci possono essere tutte le return che vogliamo (ad esempio potremmo mettere una return su ogni ramo if)

ALTRA PROVA:

int a=42; cout<<somma(a,3); Implicitamente avviene: x=a; y=3; La funzione somma restituirà il valore 45 direttamente a “cout<<”, cioè viene stampato a video e quindi non sarà memorizzato da nessuna parte poiché uscirà direttamente a video. Prima invece che abbiamo definito int s=somma(3,4) il 7 viene memorizzato nella variabile. Quindi il passaggio per valore comporta una assegnazione implicita TUTTI I TIPI POSSONO ESSERE PASSATI PER VALORE? Non possiamo passare per valore i tipi strutturati (array, matrici, struct) perché in generale su di essi non è definita l’assegnazione. Viceversa possiamo passare tutti gli (tipi)scalari e anche gli indirizzi COME POSSIAMO PASSARE UN ARRAY? PASSIAMO ALLA FUNZIONE IL SUO INDIRIZZO Quindi se vogliamo passare un’array l’unica cosa che possiamo fare è passare il nome dell’ array perché il nome dell’array è anche l’indirizzo nel quale inizia l’array. Funzione per stampare un’array void stampa (int a[10]){ for (int i=0;i<10;i++) cout <<a[i]<<” ”; } int main(){ int v[10]={1,3,6,3,5,3,6,3,6,12}; stampa(v); } COSA AVVIENE QUANDO ESEGUIAMO STAMPA(V)? v è l'indirizzo al quale inizia l'array v[10] Nel momento in cui eseguiamo stampa(v) implicitamente viene eseguito: int a[]; a = v; quindi praticamente “a” è un alias di v; Quando stamperemo a[0] a video finirà il valore di v[0] Quindi quando noi passiamo un parametro in realtà ne stiamo passando il valore, cioè implicitamente stiamo eseguendo una assegnazione, però se facciamo un’assegnazione tra interi o meglio tra scalari stiamo ottenendo la copia del valore se invece facciamo un’assegnazione tra array allora in effetti noi stiamo creando un alias , quindi stiamo facendo la copia solo dell’indirizzo. Quindi in pratica stiamo eseguendo un PASSAGGIO PER VALORE DI UN INDIRIZZO --> PASSAGGIO PER PUNTATORE (PER INDIRIZZO) Il passaggio per puntatore in realtà rappresenta la soluzione per poter fare il passaggio di una variabile strutturata ad esempio un array.

Avrei potuto scrivere anche così: void stampa (int a[]){ for (int i=0;i<10;i++) cout<<a[i]; } int main(){ int v[10]={1,3,6,3,5,3,6,3,6,12}; stampa(v); } Siccome a è soltanto l'indirizzo di un array ma non è un nuovo array(infatti facendo int a parentesi quadra parentesi quadra stiamo dicendo che a è un indirizzo di un’array ma non è un’array in memoria), allora posso anche non conoscere la dimensione Nel caso in cui abbiamo riemp: void stampa (int a[],int r){ for (int i=0;i<r;i++) cout<<a[i]<<” ”; } int main(){ int v[10]={1,3,6,3,5}; int riemp=5; stampa(v,riemp); } Curiosità: si poteva scrivere anche così: void stampa (int v[],int riemp){ for (int i=0;i<riemp;i++) cout<<v[i]<<” “; } int main(){ int v[10]={1,3,6,3,5}; int riemp=5; stampa(v,riemp); } In realtà esistono due diverse variabili riemp, una nello scope del main e una nello scope di stampa. Queste variabili hanno lo stesso valore v nello scope di stampa è un alias di v nello scope di main In generale NON è buona norma dare gli stessi nomi alle variabili che sono in funzioni diverse (per non confondersi)

Passare per puntatore significa che anziché di passare la variabile noi passiamo il suo indirizzo, e se ci ragioniamo con gli array già è stata fatta una cosa simile perché non c’è modo di passare tutto l’array in C++ , e quindi passiamo l’indirizzo e a partire dall’indirizzo passiamo tutto l’array. Anche le struct in generale possono essere passate soltanto per indirizzo.

PASSAGGIO DI UNA STRUCT

  • E’ consigliabile passare le struct per puntatore e non per valore void visualizza (struct elemento e) {* cout<<e->valore; return; }
  • Chiamata: struct elemento s; … visualizza (&s); Nella chiamata è come se avvenisse: struct elemento e=&s;* IN C ESISTONO DUE SOLI PASSAGGI POSSIBILI: PER VALORE E PER PUNTATORE CONCETTUALMENTE PERO' SI TRATTA SEMPRE DELLO STESSO TIPO DI PASSAGGIO: PASSAGGIO DI UNO SCALARE OPPURE PASSAGGIO DI UN INDIRIZZO DI QUALCOSA IN C++ E' STATO INTRODOTTO UN TERZO TIPO DI PASSAGGIO: PASSAGGIO PER RIFERIMENTO VEDIAMOLO CON UN ESEMPIO: void dimezza (int &n){ n=n/2, return; } int main(){ int x=10; dimezza(x); cout<<x; } QUESTO NUOVO TIPO DI PASSAGGIO E’ CARATTERIZZATO DALLA PRESENZA DEL SIMBOLO & NELLA DICHIARAZIONE CHE IN QUEL PUNTO SIGNIFICA "RIFERIMENTO A" (quindi la funzione dimezza prende come parametro un riferimento alla variabile “n”(dove n è di tipo intero ma è passata per riferimento)). NEL PASSAGGIO PER RIFERIMENTO LA FUNZIONE ACCEDE ALLA VARIABILE ORIGINALE (PUO' LEGGERLA E PUO' MODIFICARLA).

VANTAGGIO: PIU' FACILE DA SCRIVERE (perché il fatto che sia stato passato per puntatore lo si deduce dall’uso di un singolo simbolo e non dobbiamo mettere asterischi nelle varie operazioni nel passaggio per puntatore) VANTAGGIO: SI PUO' PASSARE PER RIFERIMENTO QUALSIASI COSA MA GLI ARRAY PREFERIAMO SEMPRE PASSARLI PER PUNTATORE SVANTAGGIO: LEGGENDO IL MAIN NON SAPPIAMO ANCORA SE LA VARIABILE SARA' PASSATA PER VALORE O PER RIFERIMENTO RAGIONAMENTI QUANDO PASSIAMO UNO SCALARE PER VALORE, NON POSSIAMO MODIFICARLO (O MEGLIO ANDIAMO A MODIFICARE UNA SUA COPIA E NON L'ORIGINALE) QUANDO PASSIAMO UNO SCALARE PER PUNTATORE O PER RIFERIMENTO, POSSIAMO MODIFICARE IL SUO VALORE ORIGINARIO INPUT --> PASSAGGIO PER VALORE (SOLO SCALARI) OUTPUT --> CON IL RETURN (SOLO SCALARI E INDIRIZZI) INPUT E OUTPUT --> PER PUNTATORE (TUTTI I TIPI), PER RIFERIMENTO (TUTTI I TIPI) UNA FUNZIONE PUO’ AVERE TANTI INPUT E TANTI OUTPUT