CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 1 Laboratorio del 3 Ottobre 2011
Dott. Mirko Ravaioli e-mail:
[email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 1
1.1 Breve Storia del Linguaggio C Il Linguaggio di programmazione C è stato creato nel 1972 da Dennis Ritchie nei laboratori della Bell Telephone, con lo scopo di realizzare il sistema operativo Unix. Il C si diffuse rapidamente anche al di fuori dei laboratori della Bell e presto diverse aziende iniziarono a utilizzare versioni proprietarie di questo linguaggio, e nacquero diverse sottili differenze di implementazione che causano ancora oggi notevoli problemi agli sviluppatori. Per ovviare a questo problema nel 1983 l’istituto statunitense degli standard ANSI (American National Standards Institute) ha formato un comitato con l’obiettivo di produrre una definizione del C chiara e indipendente dalla macchina, capace però di preservare lo spirito originale, quindi in modo di definire uno standard per il C che da allora si chiamò ANSI C. Tranne poche eccezioni, tutti i compilatori moderni si conformano allo standard. Il C è considerato un linguaggio di “basso livello”, questa definizione non è peggiorativa ma significa semplicemente che il C adopera lo stesso tipo di oggetti usati da molti elaboratori, come caratteri, numeri e indirizzi, che possono essere combinati tramite gli operatori aritmetici e logici di cui si servono le macchine reali. Il C permette solo il controllo di un singolo flusso di computazione: condizioni, cicli, raggruppamenti e sottoprogrammi, ma non multiprogrammazione parallele, sincronizzazione o co-rutine.
1.2 Prima di iniziare a programmare Quando si cerca di risolvere un problema è bene seguire una cerca metodologia: o
definire precisamente il problema, altrimenti non sarà possibile individuare la soluzione
o
ideare un piano risolutivo (algoritmo risolutivo)
o
implementare l’algoritmo (metterlo in pratica)
o
controllare i risultati in modo da verificare se il problema è stato risolto o meno.
1.3 Ciclo di sviluppo del programma Il ciclo di sviluppo di un programma si divide in diverse parti: o
Creazione del Codice Sorgente: creare attraverso un editor di testo un file contenente una serie di istruzioni o comandi impiegati che combinati in una certa logica per istruire il computer in modo che esegua i compiti desiderati. Ad esempio una riga di codice sorgente C: printf(“Forza Cesena!”); Questa istruzione ordina al computer di stampare a video la scritta: “Forza Cesena!”. I file che contengono il sorgente di un programma scritto in C hanno estensione .c, ad esempio: pippo.c
o
Creazione del Programma Sorgente: il computer non è in grado di comprendere direttamente quello che l’utente indica attraverso il sorgente C, ma necessita di istruzioni binarie o digitali in un particolare formato detto Linguaggio Macchina. Quindi prima che il programma possa funzionare
Pagina 2 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 1
su un computer, deve essere tradotto nel formato sorgente al linguaggio macchina. Questa traduzione è effettuata da un programma chiamato compilatore che partendo dal sorgente produce un nuovo file su disco contenente le istruzioni in linguaggio macchina che corrispondono ai comandi impartiti in C. Queste istruzioni vengono chiamate codice oggetto e il file creato sul disco viene chiamato file oggetto. Il file oggetto ha estensione .obj.
o
Creazione del File Eseguibile con il Linking: il linguaggio C è costituito da un insieme di librerie che contengono il codice oggetto (ovvero pre-compilato) delle funzioni predefinite. Una funzione predefinita contiene codice C già scritto e fornito in forma pronta per l’uso con il compilatore stesso. Ad esempio la funzione printf() utilizzata nell’esempio precedente è una funzione di libreria. Una funzione potrebbe essere vista come un “robot da cucina” che esegue determinate operazioni ogni volta che lo si attiva chiamandolo per nome. Queste particolari funzioni svolgono compiti frequenti come la visualizzazione di informazioni sullo schermo o la lettura di dati da tastiera. Se il proprio programma contiene alcune di queste funzioni (difficilmente si può scrivere un programma di qualche utilità senza utilizzarne almeno una), il file oggetto prodotto a partire dal sorgente deve essere combinato con quello delle funzioni di libreria per generare un programma eseguibile. Questo processo è detto Linking ed è portato a termine da un programma chiamato Linker.
o
Completamento del ciclo di sviluppo: una volta compilato e linkato il proprio programma è possibile eseguirlo. Se dall’esecuzione del programma non si ottengono i risultati aspettati bisogna tornare indietro al primo passo e individuare gli errori.
1.4 Primi passi Proviamo a scrivere il primo programma che stampi a video la scritta: “Forza Cesena!”. Per risolvere questo problema dobbiamo essere in grado di redigere il testo del programma, compilarlo con successo. caricarlo, eseguirlo e scoprire dove è finito il risultato. Questi sono meccanismi essenziali per capire non solo come programmare con il linguaggio C ma con qualsiasi linguaggio di programmazione. Il programma che risolve l’esercizio è il seguente: #include
int main() { printf(“Forza Cesena!\n”); return 0; } Il programma una volta compilato ed eseguito (salvo errori!!) darà come risultato: Forza Cesena! Un programma in C di qualunque lunghezza consiste di funzioni e variabili. Una funzione consiste in un insieme di istruzioni o comandi che specificano quali operazioni devono essere effettuate, mentre le variabili memorizzano i valori usati durante l’esecuzione del programma. Nell’esempio precedente viene definita la funzione main(), di norma si è liberi di assegnare qualsiasi nome alle funzioni ma il caso di “main” è particolare in quanto tutti i programmi scritti in C inizieranno con la chiamata alla funzione main(). Quindi ogni programma dovrà avere una funzione main.
Pagina 3 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 1
Per svolgere il proprio lavoro la funzione main chiamerà altre funzioni in aiuto, alcune scritte dal programmatore altre presenti nelle librerie cui si ha accesso. La prima riga del programma: #include dice al compilatore di includere le informazioni sulla libreria standard per l’input/output. Tutte le istruzioni di inclusione delle librerie devono comparire all’inizio del programma. Tutte le funzioni hanno un inizio ed una fine, per individuare all’interno del codice dove una funzione inizia e finisce si utilizzano rispettivamente due marcatori: { (parentesi graffa aperta) e } (parentesi graffa chiusa). (premere pulsanti ALT GR + SHIFT + QUADRA APERTA per parentesi graffa aperta e premere ALT GR + SHIFT + QUADRA CHIUSA per parentesi graffa chiusa). All’interno delle graffe è presente il corpo della funzione (insieme di istruzioni eseguite alla chiamata della funzione). Nel esempio precedente all’interno del corpo della funzione main() troviamo 2 righe di codice, in paricolare la prima consiste nella chiamata della funzione printf() printf(“Forza Cesena!\n”); Una funzione è chiamata attraverso il suo nome, seguito da un elenco degli argomenti racchiuso tra parentesi tonde. Gli argomenti sono il metodo di comunicazione tra le varie funzioni in modo che la funzione chiamante riesca a fornire alla funzione chiamata un insieme di valori detti argomenti. Nel nostro esempio la funzione si chiama printf e l’argomento passato è “Forza Cesena!”. Si tratta di una funzione che produce (in gergo si dice anche stampa) a video o in uscita dei dati, in questo caso in particolare l’effetto finale è quello di visualizzare a schermo la scritta. Una successione di caratteri è comunemente detta stringa di caratteri ed è individuata da un doppio apice in apertura e in chiusura. La sequenza \n che compare nella stringa è una notazione del C per accedere alla newline, che ha effetto di spostare in avanti di una riga i dati in uscita (in poche parole di andare a capo!). Senza il \n non si va a capo. La funzione printf() non fornisce in automatico l’andata a capo. Quindi il programma di prima poteva essere scritto anche nel seguente modo: #include int main() { printf(“Forza ”); printf(“Cesena!”); printf(“\n”); return 0; } L’istruzione return è molto importante in quanto consente di restituire un parametro alla funzione chiamante. Nel nostro caso il chiamate è il sistema operativo, il nostro programma terminate le proprie operazioni restituisce il valore 0 (zero) informando il sistema operativo che tutte le operazioni si cono svolte senza errori.
1.4.1 Le istruzioni Da notare che nel programma ogni istruzione presente all’interno della funzione main() termina con un punto e virgola. Un’istruzione è un comando completo che indica al computer di eseguire un particolare compito. Le istruzioni in C terminano sempre con un punto e virgola (tranne il caso delle direttive del preprocessore #define o #include).
Pagina 4 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 1
Quindi scrivere x = 1 + 3; equivale a scrivere: x = 1 + 3; Mentre scrivere printf(“ciao a tutti”); non equivale a printf(“ Ciao a tutti”); in quanto genera un errore. Mentre scrivere printf(“ Ciao\ a tutti”); non genera errore in quanto per andare a capo con una stringa è possibile utilizzare il carattere (\) di barra inversa.
1.4.2 La funzione printf() La stringa di formato della funzione printf() specifica la modalità di formattazione dell’output e ha tre componenti possibili: o
Il testo letterale, che viene visualizzato esattamente come viene introdotto nella stringa
o
Un indicatore di conversione formato da un segno di percentuale(%) seguito da un carattere.
o
Una sequenza di escape che consente di produrre comandi di formattazione particolari. Le sequenze di escare sono formate da una barra inversa (\) seguita da un carattere. Nella sequenza precedente \n è una sequenza di escare chiamata carattere di nuova riga, che significa “a capo”. Le sequenze di escare sono utilizzate per stampare caratteri particolari. Comando
Descrizione
\t
Tabulazione
\b
blank space
\’
Apice singolo (come carattere stampato)
\\
Barra inversa (come carattere stampato)
\n
Avanzamento di riga (a capo)
\?
Punto interrogativo (come carattere stampato)
\"
I doppi apici stampati (come carattere stampato)
\a
Segnale acustico
Pagina 5 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 1
1.4.3 La funzione system() La funzione system() consente di eseguire comandi della shell del sistema operativo. Per poterla utilizzare all’interno del programma è necessario includere la libreria stdlib.h. Il testo letterale che viene passato tra parentesi tonde (argomento della funzione) individua il comando che deve essere eseguito. Ad esempio per attivare il comando “pause” del sistema operativo DOS: system(“pause”);
1.5 Come scrivere un programma La leggibilità del codice è di fondamentale importanza, anche il sorgente di un programma che esegue pochi compiti potrebbe richiedere la scrittura di diverse migliaia di righe di codice quindi esistono diverse regole o consuetudini che vale la pena rispettare fin dall’inizio con molta rigorosità e pignoleria.
1.5.1 Indentare il programma Indentare un programma significa mettere in evidenza nel testo del programma mediante opportuni incolonnamenti, i gruppi di istruzioni che svolgono un problema comune, in modo da evidenziare la funzionalità calcolata da ogni gruppo di istruzioni, rispetto al resto del programma.
1.5.2 Commentare il programma Le frasi prodotte nel corso del progetto per descrivere l’algoritmo a vari livelli di raffinamento possono comparire nella versione definitiva del sorgente sotto forma di commenti. Anche se l’algoritmo è stato progettato utilizzando direttamente il linguaggio di programmazione a disposizione è conveniente inserire in ogni caso dei commenti in punti particolari del programma che spieghino cosa il programma fa in quei punti, indipendentemente da come lo fa, cioè dalla particolare strategia scelta. I commenti non vengono interpretati dal compilatore e quindi non vanno ad incidere sull’operatività o sulla dimensione del file eseguibile finale. Un commento può essere scritto su una o più righe: /* Commento su una sola riga*/ /* Commento che occupa più di una riga*/ printf(“Forza Cesena!”); /*Commento che occupa parte di una riga*/
Esiste anche una sintassi diversa per commentare i propri programmi utilizzando una doppia barra: // questa riga è un commento printf(“Forza Cesena!”); // il commento inizia con la doppia barra
La doppia barra indica che il testo della riga è da considerarsi come un commento. Anche se molti compilatori C supportano questo tipo di sintassi è bene evitarla quando è necessario garantire aperta portabilità.
Pagina 6 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 1
1.5.3 Usare nomi di variabili autoesplicativi Per spiegare il ruolo delle variabili, oltre che usare commenti, possiamo scegliere nomi che già da soli ne spieghino il ruolo. Per esempio il nome della variabile stipendioMensile è autoesplicativo, la stessa variabile se fosse stata chiamata xMk il programma sarebbe risultato meno comprensibile ma ugualmente funzionante.
1.5.4 Produrre la documentazione nel corso stesso del progetto E’ importantissimo scrivere la documentazione durante lo sviluppo del progetto in quanto documentando le decisioni nello stesso momento in cui vengono prese, si è certi della efficacia e della completezza della documentazione. E’ storia di ogni giorno che la documentazione venga prodotta solo al termine del progetto frettolosamente e senza rispettare alcuno standard.
Pagina 7 di 7
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 2 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 2
2.1 La direttiva #include La direttiva #include ordina al compilatore C di inserire il contenuto di un file di inclusione nel programma in fase di compilazione. Un file di inclusione è un file che contiene le informazioni richieste dal programma o dal compilatore. Sono molti i file di questo tipo (detti anche file di intestazione) supportati dal compilatore. Non è mai necessario modificare le informazioni contenute in questi file, ed è per questo che vengono mantenuti separati dagli altri file. I file di inclusione dovrebbero avere tutti l’estensione .H (ad esempio stdio.h). La direttiva #include significa “aggiungi al programma il contenuto del file .h”
2.2 La funzione puts() Per visualizzare i messaggi testuali è possibile utilizzare la funzione puts(), anche se quest’ultima non può stampare valori numerici delle variabili. La funzione puts() richiede come argomento una sola stringa che viene visualizzata con l’aggiunta automatica dei un “a capo”. Ad esempio l’istruzione: puts(“Ciao a tutti”); svolge lo stesso compito di printf(“Ciao a tutti\n”); Nella stringa passata alla funzione puts() è possibile specificare le sequenze di escare che avranno lo stesso effetto di quelle relative alla funzione printf(). La funzione è inclusa nella libreria stdio.h.
2.3 La funzione exit() Questa funzione termina il programma e restituisce il controllo al sistema operativo; richiede un solo argomento di tipo intero che viene passato al sistema operativo per indicare il successo o il fallimento del programma. Sintassi: exit(status); Se status vale 0, indica che il programma è terminato normalmente, Il valore 1 indica che il programma è terminato con un errore. Il valore restituito è solitamente interrogato da sistema operativo. Per utilizzare exit() il programma deve includere il file stdlib.h che definisce anche due costanti simboliche da utilizzare come argomenti: #define EXIT_SUCCESS #define EXIT_FAILURE
0 1
Pagina 2 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 2
2.4 La memoria del Computer I computer utilizzano la memoria ad accesso veloce (RAM) per archiviare le operazioni su cui lavorano. La RAM (Random Access Memory) è composta da circuiti integrati che si trovano sulla scheda madre del computer e le informazioni in essa salvate vengono cancellate ogni volta che si spegne il computer. Da qui la definizione che la RAM è una memoria “volatile”. E’ possibile riscrivere le informazioni all’interno della RAM ogni volta che so vuole. Ogni computer ha un certo quantitativo di memoria installata, la dimensione si misura in kbyte (Kb). Il byte è l’unità di misura fondamentale delle memorie informatiche. Per farsi un idea del quantitativo di memoria necessario all’archiviazione di alcune tipologie di dati ecco alcuni esempi: Dato
Byte Richiesti
La lettera “b”
1
Il numero 450
2
il numero 14.3124
4
La frase “il mondo non crea eroi”
22
Una pagina di testo
Circa 3000
La RAM di un computer è organizzata in modo sequenziale: quindi ogni byte è messo si seguito all’altro. Ogni byte ha un indirizzo unico attraverso cui viene identificato, che lo distingue da tutti gli altri byte della memoria. Gli indirizzi vengono assegnati dalle locazioni di memoria di ordine progressivo, cominciando dall’indirizzo 0. La RAM ha diversi utilizzi in particolare per quanto ci riguarda essa permette di memorizzare i dati. I dati sono le informazioni su cui lavorano i programmi: tutte le informazioni che il programma deve eseguire vengono memorizzate all’interno della RAM durante l’esecuzione.
2.5 Le variabili Una variabile è un nome simbolico che si assegna a una allocazione di memoria utilizzata per la memorizzazione dei dati. Le variabili devono essere definite prima di essere utilizzate; la definizione di una variabile indica al compilatore il nome della variabile e il tipo di dati che è destinata a contenere.
2.5.1 I nomi delle variabili I nomi delle variabili in C devono seguire le seguenti regole: o
I nomi possono contenere lettere, numeri e il trattino di sottolineatura (_)
o
I primo carattere di un nome DEVE essere una lettera. Anche il trattino di sottolineatura può essere utilizzato come primo carattere, ma dato che in questo caso la variabile viene trattata in modo particolare dal compilatore, per il momento è il caso di non utilizzarlo come carattere iniziale.
o
Le lettere maiuscole e minuscole sono trattate come entità differenti. Quindi i nomi pippo e Pippo si riferiscono a due variabili differenti.
o
Le parole chiave del C on possono essere utilizzate come nomi di variabili. Le parole chiave sono istruzioni del linguaggio C.
Ecco degli esempi corretti e non di nomi di variabili: Nome variabile
Numero
valido
Pagina 3 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com stipendio_mensile
valido
g7t7_rfg7
valido
_1978_compleanno
valido ma sconsigliato
testo#progetto
Sbagliato: contiene il carattere #
double
Sbagliato: è una parola chiave del C
9vincitori
Sbagliato: il primo carattere è un numero
Dispensa 2
In C generalmente si utilizzano nomi di variabili scritte tutte in minuscolo (anche se non è richiesto dal linguaggio); normalmente i nomi scritti in maiuscolo sono riservati alle costanti. Fate molta attenzione in quanto la stessa lettera in maiuscolo e minuscolo viene considerata diversa. Per la maggior parte dei compilatori i nomi delle variabili non possono superare i 31 caratteri; anche se è possibile utilizzare nomi più lunghi il compilatore considera solo i primi 31. Il nome della variabile aiuta a rendere più chiaro il suo utilizzo all’interno del programma per il programmatore. Al compilatore non fa differenza avere una variabile con nome y o stipendio_mensile, nel primo caso però il significato della variabile non sarebbe stato così chiaro per chi avesse letto il codice sorgente come invece accade nel secondo caso.
2.5.2 Tipi di variabili I valori che un programma può memorizzare possono essere molto diversi e quindi richiedono spazi in memoria diversi per poterli memorizzare. Il C prevede un numero ristretto di tipi di dati fondamentali: Tipo di dato
Dimensione
Descrizione
char
1 byte
contiene un carattere dell’ambiente in cui risiede il compilatore
int
2 byte*
float
4 byte
un intero, solitamente pari alla dimensione naturale degli interi sulla macchina in cui risiede il compilatore, quindi non è detto che la dimensione sia pari a 2 byte numero con virgola mobile, con precisione singola
double
8 byte
numero con virgola mobile, con precisione doppia
Rappresentazione a virgola fissa: Dato che in un bit non è rappresentabile la virgola il metodo più semplice per rappresentare numeri frazionari è quello di scegliere arbitrariamente la posizione della virgola (ad es. se si sceglie di usare 4 bit per la parte intera e 4 per la parte frazionaria) Rappresentazione in virgola mobile: Esistono innumerevoli modi per rappresentare numeri in virgola mobile ma il sistema più utilizzato è lo standard IEEE P754; questo metodo comporta l'utilizzo della notazione scientifica, in cui ogni numero è identificato dal segno, da una mantissa e dall'esponente. In aggiunta è possibile qualificare questi tipi di dati in vari modi utilizzando i prefissi: o
short
o
long
o
unsigned
Gli attributi short e long si applicano solo agli interi. L’idea è che short e long denotino interi di diverse lunghezze. Dove è possibile int è invece di solito la grandezza naturale di riferimento per una macchina.
Pagina 4 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 2
L’attributo unsigned (privo di segno) può essere applicato a tutte le tipologie, i numeri unsigned sono sempre positivi o al più nulli. il tipo long double denota numeri con virgola mobile a precisione multipla, come per gli interi al dimensione degli oggetti è definita dall’implementazione. Tutte le variabili per default sono di tipo signed. Tipi di dati supportati: Tipo Variabile
Parola chiave
Carattere
char
1
da -128 a 127
Intero*
int
2
da -32768 a 32767
2
da -32768 a 32767
4
da -2147483648 a 2147483647
Intero corto* Intero lungo*
short int (o anche solo: short) long int (o anche solo: long)
Byte
Intervallo
Carattere senza segno
unsigned char
1
da 0 a 255
Intero senza segno
unsigned int
2
da 0 a 65535
Intero lungo senza segno
unsigned long int (o anche: unsigned long)
4
da 0 a 4294967295
Variabile mobile con precisione singola
float
4
da 1.2E-38 a 3.4E308
Variabile mobile con precisione doppia
double
8
da 2.2E-38 a 1.8E3082
*dipende dalla macchina La dimensione dei tipi di dato può variare a seconda della piattaforma utilizzata, lo standard ANSI ha definito cinque regole che ogni compilatore è tenuto a rispettare a prescindere dalla macchina su cui opera: 1. La dimensione di un char è sempre un byte 2. La dimensione di un short è minore o uguale a quella di un int 3. La dimensione di un int è minore o uguale ad un long 4. La dimensione di un unsigned è uguale a quella di un int 5. La dimensione di un float è minore o uguale a quella di un double
2.5.3 Dichiarazione di una variabile Prima di poter utilizzare una variabile in un programma C è necessario dichiararla. La dichiarazione indica al compilatore il nome e il tipo di una variabile e opzionalmente il valore di inizializzazione da assegnare. Se il programma tenta di utilizzare una variabile non dichiarata il compilatore genera un errore. La sintassi per dichiarare una variabile: tipoDato nomeVariabile; Dove con tipoDato si specifica il tipo di variabile e deve essere una delle parole chiave definite nel paragrafo precedente. Il nomeVariabile indica il nome della variabile e deve rispettare le regole descritte in precedenza. Alcuni esempi di dichiarazione di variabili: int annoNascita;
Pagina 5 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 2
char iniziale; int numeroScarpa, anni; double percentuale; float valore; int conta, numero, inizio; Alcuni esempi di dichiarazione con gli attributi: unsigned unsigned unsigned long int unsigned
int valoreNumerico; char carattere; double percentualePositiva; valore; long int valoreIntero;
2.5.4 Assegnare un valore ad una variabile Quando si dichiara una variabile si chiede al compilatore di riservare dello spazio in memoria per la stessa. Il valore presente in questo spazio non è prevedibile, potrebbe essere qualsiasi cosa. Quindi è bene associare alla variabile un valore certo prima di utilizzarla. Questa operazione può essere fatta in diversi modi: dopo la dichiarazione o contemporaneamente alla dichiarazione. int annoNascita; annoNascita = 1978 L’operatore uguale (=) serve per associare un valore alla variabile. La variabile è sempre posta a sinistra dall’operatore. In C il significato di annoNascita = 1978 significa: assegnare il valore 1978 alla variabile di nome annoNascita e NON “annoNascita è uguale a 1978”. Per dichiarare una variabile durante la dichiarazione: int annoNascita = 1978; double percentuale = 0.01; double tasso = 22.4; Bisogna fare attenzione a non inizializzare una variabile con un valore al di fuori dell’intervallo consentito dal tipo. Ecco per esempio due dichiarazioni non valide: int peso = 1000000000000; unsigned int valore = -2132; Attenzione perchè il compilatore C non individua questo tipo di errori, il programma potrebbe essere compilato e linkato senza errori ma potrebbe dare risultati inaspettati in fase di esecuzione.
2.6 La funzione sizeof() La funzione restituisce il numero di byte necessari per il tipo di dato passato come argomento alla funzione. La sintassi della funzione:
Pagina 6 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 2
sizeof(tipoDato); Un esempio di utilizzo: #include int main() { printf("\n printf("\n printf("\n printf("\n printf("\n
numero intero (int) byte: %d", sizeof(int)); numero intero lungo (long int) byte: %d", sizeof(long int)); carattere (char) byte: %d", sizeof(char)); numero con virgola (float) byte: %d", sizeof(float)); numero con virgola (double) byte: %d", sizeof(double));
return 0; } Il programma una volta compilato ed eseguito darà come risultato: numero intero (int) byte: 4 numero intero lungo (long int) byte: 4 carattere (char) byte: 1 numero con virgola (float) byte: 4 numero con virgola (double) byte: 8
Pagina 7 di 7
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 3 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 3
3.2 Output formattato: printf() Per visualizzare un numero, occorre utilizzare le funzioni per l’output formattato. La funzione printf() accetta un numero variabile di argomenti, come minimo uno. Il primo e unico argomento richiesto è la stringa di formato, che indica come formattare l’output. Gli argomenti opzionali sono variabili ed espressioni di cui visualizzare i valori Ad esempio:
L’istruzione printf(“Ciao a tutti!”); visualizza il messaggio: “Ciao a tutti” sullo schermo. In questo caso è utilizzato un unico argomento: la stringa di formato che contiene la stringa letterale da visualizzare sullo schermo
L’istruzione printf(“%d”,i); visualizza il valore della variabile intera i. La stringa di formato contiene soltanto l’indicatore %d, che indica di mostrare un singolo intero decimale. Il secondo argomento è il nome della variabile
L’istruzione printf(“%d più %d uguale %d”,a, b, a+b); visualizza (supponendo che le variabili ‘a’ e ‘b’ contengano rispettivamente 2 e 3) “2 più 3 uguale 5” sullo schermo. In questo caso vi sono quattro argomenti: una stringa di formato contenente il testo letterale e indicatori di formato, due variabili ed un’espressione di cui visualizzare i valori.
La stringa di formato di printf() può contenere quanto segue:
Zero, uno o più comandi di conversione che indicano come visualizzare un valore dell’elenco degli argomenti. Un comando di conversione è costituito da % seguito da uno o più caratteri
I caratteri che non fanno parte di un comando di conversione sono visualizzati così come sono.
Di seguito sono descritti i componenti del comando di conversione. Quelli tra parentesi sono opzionali: %[modificatore][campoMinimo][precisione][modificatoreLunghezza]specificaConversione dove:
modificatore può essere una combinazione (in qualsiasi ordine) dei seguenti simboli (tra parentesi i simboli): o
(-) il risultato della conversione e' allineato a sinistra all' interno del campo definito da campoMinimo, il default e' l' allineamento a destra
o
(+) specifica che il segno davanti al numero verrà sempre stampato. Il risultato di conversioni di tipi con segno inizia con + (se positivi) o - (se negativi); il default e' che il segno (-) appare solo davanti ai negativi
o
(spazio) inserisce uno spazio davanti al valore se il primo carattere non e' un segno
o
(#) formato alternativo (con piccole variazioni) per la maggior parte delle specifiche di conversione Se il carattere di conversione e' o (ottale) e se il valore da convertire e' diverso da zero, il primo carattere stampato e' 0. Se il carattere di conversione e' x o X (esadecimale) e se il valore da convertire non e' nullo, i primi caratteri stampati saranno 0x o 0X a seconda che la direttiva sia x o X.
o
0 (zero) il campo viene riempito con zeri invece che con spazi (il default)
campoMinimo: se il valore convertito ha meno caratteri del campo questo viene riempito da spazi, il valore di campoMinimo puo' essere un intero o *. In quest' ultimo caso il valore deve essere un intero inserito nella lista degli argomenti della printf, subito prima dell' espressione interessata da questo formato.
Pagina 2 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 3
precisione: un valore nella forma .n (con n intero oppure * se viene letto nella lista degli argomenti) dove n e' il : o o o
minimo numero di cifre per le specifiche i,d,o,u,x,X minimo numero di cifre dopo il punto decimale per le specifiche a,A,e,E,f,F massimo numero di cifre significative per le specifiche g,G
modificatoreLunghezza: dichiara che la successiva specifica di conversione deve essere applicata a un sottotipo intero particolare modificatore. La lettera h seguita da uno dei caratteri di conversione d, i, u, o, x o X indica che l'argomento e' uno short int o uno unsigned short int. Il modificatore l (elle) con gli stessi caratteri di conversione, indica che l'argomento e' un long int o uno unsigned long int. Il modificatore L seguito da uno dei caratteri di conversione e, E, f, g o G indica che l'argomento e' un long double il successivo specificatore (i o d) si applica ad una espressione o signed o unsigned char o signed o unsigned short int o signed o unsigned long int o signed o unsigned long long int
specificaConversione: è l’unico obbligatorio (a parte %) Sotto sono elencati i caratteri di conversione ed il loro significato: d, i
Visualizza un intero con segno in notazione decimale
u
Visualizza un intero senza segno di notazione decimale
o
Visualizza un intero in notazione ottale senza segno
x, X
Visualizza un intero in notazione esadecimale senza segno. Si utilizza ‘x’ minuscolo per l’uotput minuscolo e ‘X’ maiuscolo per l’output maiuscolo
c
Visualizza un singolo carattere (indicato dal codice ASCII)
e, E
f
Visualizza un float o un double in notazione scientifica ( ad esempio 123.45 è visualizzato come 1.234500e+002) sei cifre sono visualizzate a destra del punto decimale a meno che non sia specificata un’altra precisione con l’indicatore f. si utilizzano ‘e’ o ‘E’ per l’output minuscolo o maiuscolo Visualizza un float o un double in notazione decimale (ad esempio 123.45 è visualizzato come 123.450000). Sei cifre sono visualizzate a destra.
s
Visualizza una stringa
%
Visualizza il carattere %
La funzione printf() restituisce un valore che individua il numero di caratteri stampati, se il valore è negativo segnala un errore.
3.3 Le Istruzioni Un’ istruzione è un comando completo che indica al computer di eseguire un particolare compito. In generale in C le istruzioni vengono scritte una per ogni riga anche se alcune possono occupare anche più righe. Tutte le istruzioni in C (eccetto le direttive #define, #include...) devono terminare con un punto e virgola (;).
Pagina 3 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 3
3.3.1 Spazi bianchi e istruzioni Con spazi bianchi ci si riferisce ai veri e propri spazi, ai caratteri di tabulazione e alle righe vuote presenti nel codice sorgente. Il compilatore C non considera gli spazi bianchi e quando legge un istruzione dal codice sorgente cerca il carattere terminatore (il punto e virgola) ignorando tutti gli spazi bianchi presenti. Quindi l’istruzione: y=4+3; è equivalente a quella seguente: y = 4 + 3;
che a sua volta è equivalente a: y = 4 + 3; In questo modo viene lasciata al programmatore la scelta sulla modalità di formattazione del proprio codice. Comunque non è consigliato utilizzare una formattazione come nell’ultimo esempio: le istruzioni dovrebbero essere introdotte una per riga seguendo uno schema standard per la spaziatura delle variabili e degli operatori. Guardare i vari esempi inseriti nelle dispense o i sorgenti in allegato per rendersi conto di come poter formattare il proprio codice. Il C ignora gli spazi bianchi eccetto quando questi si trovano all’interno di costanti stringa letterali: infatti in questo caso gli spazi vengono considerati come parti delle stringhe (quindi come caratteri della stringa). Una stringa è una sequenza di caratteri e in particolare le costanti stringhe letterali sono stringhe racchiuse tra doppi apici che vengono interpretate dal compilatore in maniera assolutamente letterale, spazi compresi. Quindi per esempio l’istruzione (anche se solitamente non usata): printf( “Forza Cesena!” ); è corretta, mentre quella che segue genera un errore in fase di compilazione: printf(“Forza Cesena”); Per andare a capo in un’istruzione, quando ci troviamo in corrispondenza di una costante stringa letterale occorre utilizzare il carattere barra inversa (\) prima dell’interruzione. La forma corretta per l’esempio sopra riportato quindi sarà: printf(“Forza\ Cesena”);
3.3.2 Istruzioni nulle Se si inserisce un punto e virgola da solo su una riga si ottiene istruzione nulla cioè un’istruzione che non esegue alcuna operazione.
3.3.3 Istruzioni composte Un’istruzione composta, solitamente chiamata blocco, è un gruppo di due o più istruzioni C racchiuse tra parentesi graffe. Ad esempio la porzione di codice che segue è un blocco: { printf(“Forza Cesena!”);
Pagina 4 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 3
printf(“Devi vincere”); } In C i blocchi possono essere utilizzati in qualsiasi punto in cui sia possibile utilizzare un’istruzione singola. Le parentesi graffe possono essere posizionate in qualsiasi punto ad esempio: {printf(“Forza Cesena!”); printf(“Devi vincere”);} comunque si consiglia di posizionare le graffe su righe diverse, mettendo così in evidenza l’inizio e la fine del blocco. In questo modo, oltre che rendere il codice più leggibile, è possibile accorgersi se ne è stata dimenticata qualcuna.
3.4 Le Espressioni In C un’espressione è una qualsiasi cosa che deve essere valutata come un valore numerico. Le espressioni in C possono avere qualsiasi livello di complessità.
3.4.1 Espressioni Semplici Le espressioni più semplici consistono di un unico oggetto, ad esempio una variabile, una costante letterale o simbolica. Le costanti letterali vengono valutate secondo il loro valore, le costanti simboliche invece con il valore assegnato loro dalla direttiva #define, le variabili vengono valutate con il valore assegnate loro dal programma. Espressione
Descrizione
TASSO
Una costante simbolica
28
Una costante letterale
stipendio
Una variabile
3.678
Una costante letterale
3.4.2 Espressioni Complesse Le espressioni complesse consistono di più espressioni semplici combinate tra di loro attraverso degli operatori. Per esempio: 3 + 9 è un espressione formata dalle due costanti letterali 3 e 9 e dall’operatore somma +. L’espressione 3 + 9 viene valutata come 10. Si possono scrivere anche espressioni molto più complesse: 3.78 + 56 – stipendio * mesi / giorni Quando un’espressione contiene più operatori, come nell’esempio sopra riportato, il risultato dipende dalla precedenza degli operatori. Consideriamo la seguente espressione: y = b + 17; in questo caso nell’istruzione viene calcolato il valore dell’espressione b + 17 e viene assegnato il risultato alla variabile y. A sua volta l’istruzione y = b + 17 è un’altra espressione che ha il valore nella variabile a sinistra dell’uguale, quindi è possibile anche la scrittura: k = y = b + 17;
Pagina 5 di 9
Corso di Programmazione A.A. 2011-12
Dispensa 3
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
in questo caso il risultato viene assegnato sia a y che a k, in particolare prima viene valutata l’espressione b + 17, il suo valore assegnato alla variabile y, ed in fine il valore di y assegnato alla variabile k. Quindi alla fine dell’espressione k e y avranno lo stesso valore. In C sono possibili anche espressioni di questo tipo: k = 8 + (y = 3 + 4); in questo caso dopo l’esecuzione dell’istruzione la variabile y avrà il valore 7, mentre la variabile x il valore 15. In questo caso però le parentesi sono essenziali per la corretta compilazione dell’istruzione.
3.5 Gli Operatori Un operatore è un simbolo che indica al linguaggio di eseguire un’operazione, o un’azione su uno o più operandi. In C tutti gli operatori sono visti come delle espressioni.
3.5.1 Operatore di Assegnamento L’operatore di assegnamento è il simbolo uguale (=). Il suo significato e utilizzo all’interno di un programma è diverso dal suo consueto utilizzo in matematica. Scrivendo: y = k; NON significa che y è uguale a k ma invece assegna il valore di k a y. In un’istruzione di assegnamento la parte a destra del segno uguale può essere una qualsiasi espressione, mentre la parte di sinistra deve essere il nome di una variabile, quindi la sintassi risulta la seguente: variabile = espressione; Quando l’istruzione viene eseguita prima viene valutata l’espressione poi il risultato viene assegnato alla variabile. Quindi per esempio: y = 8 + 9; prima viene calcolata la somma di 8 + 9 poi il risultato viene assegnato alla variabile y. Alla fine dell’istruzione avremo che y avrà il valore 17. Considerando invece: y = 11; k = y + 7; prima viene assegnato a y il valore 11 poi, nell’istruzione successiva, viene calcolato y + 7 e il risultato assegnato a k. Quindi alla fine delle 2 righe di codice sopra riportate avremo che y sarà uguale a 11 e k a 18.
3.5.2 Operatori matematici Gli operatori matematici effettuano operazioni aritmetiche e si dividono in due categorie: operatori unari e operatori binari. Gli operatori matematici binari operano su due operandi. Questi operatori che comprendono anche le principali operazioni aritmetiche sono elencati nella tabella sottostante: Operatore
Simbolo
Azione
Esempio
Addizione
+
Somma i due operandi
x+y
Sottrazione
-
Sottrae il secondo operando dal primo
x-y
Moltiplicazione
*
Moltiplica i due operandi
x*y
Pagina 6 di 9
Corso di Programmazione A.A. 2011-12
Dispensa 3
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Divisione
/
Resto (modulo)
%
Divide il primo operando per il secondo Fornisce il resto della divisione del primo operando per il secondo
x/y x%y
Alcuni esempi di utilizzo: /* Primo esempio di utilizzo degli operatori matematici */ #include int main() { int a, b, ris; a = 10; b = 5; ris = a – b; printf(“risultato di %d-%d=%d\n”,a,b,ris); a = 7; b = 3; ris = a * b; printf(“risultato di %d*%d=%d\n”,a,b,ris); a = 21; b = 7; ris = a / b; printf(“risultato di %d/%d=%d\n”,a,b,ris); a = 21; b = 10; ris = a / b; printf(“risultato di %d/%d=%d\n”,a,b,ris); a = 13; b = 5; ris = a % b; printf(“risultato di %d %% %d=%d\n”,a,b,ris); return 0; } Gli operatori matematici unari hanno questo nome in quanto richiedono un unico operando. Il C dispone di due operatori unari: incremento e decremento e possono essere utilizzati solo con le variabili e mai con le costanti. Il loro scopo è quello di aggiungere o sottrarre un’unità dall’operando specificato. Operatore
Simbolo
Azione
Esempio
Incrementa
++
Incrementa l’operando di una unità
y++
++y
Decrementa
--
Decrementa l’operando di una unità
y--
--y
Ad esempio le istruzioni: ++x; --y; equivalgono a: x = x + 1;
Pagina 7 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 3
y = y – y; Gli operatori possono essere postfissi o prefissi. Queste due sintassi non sono equivalenti, ma differiscono per quanto riguarda il momento in cui viene effettuata l’operazione:
Gli operatori prefissi modificano il proprio operando prima che ne venga utilizzato il valore
Gli operatori postfissi modificano il proprio operando dopo avere utilizzato il valore
Vediamo un esempio: x = 7; y = x++; Dopo l’esecuzione di queste due istruzioni x vale 8 e y vale 7. Prima il valore di x è stato assegnato a y e solo allora x è stato incrementato. Se consideriamo invece le seguenti istruzioni: x = 7; y = ++x; Dopo l’esecuzione di queste due istruzioni x vale 8 e y vale 8. L’operatore = è l’operatore di assegnamento e non un operatore di confronto. Eventuali successive modifiche al valore di x non hanno effetto su y. Consideriamo un esempio: #include int main() { int a, b; a = 7; b = 7; printf(“%d printf(“%d printf(“%d printf(“%d printf(“%d
%d\n”,a--,--b); %d\n”,a--,--b); %d\n”,a--,--b); %d\n”,a--,--b); %d\n”,a--,--b);
return 0; } Il risultato del programma: 7 6 5 4 3
6 5 4 3 2
La precedenza degli operatori segue il seguente ordine:
incrementi e decrementi unari
moltiplicazioni, divisioni e resti
somme e sottrazioni
Se un’espressione contiene più di un operatore con lo stesso valore di precedenza le relative operazioni vengono eseguite da sinistra a destra. E’ possibile modificare la precedenza attraverso l’utilizzo di parentesi tonde. Ad esempio:
Pagina 8 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 3
y = 4 + 2 * 3; Dopo l’istruzione y vale 10. Prima viene eseguito 2*3 al risultato viene aggiunto 4 e il risultato assegnato a y. Consideriamo: y = 12 % 5 * 2; L’operatore % (modulo) e * (moltiplicazione) hanno la stessa precedenza quindi le istruzioni vengono eseguite da sinistra a destra: prima viene calcolato 12%5 che viene 2 che moltiplicato per 2 fa 4 quindi alla fine a y viene assegnato il valore 4. Se utilizziamo le parentesi: y = 12 % (5 * 2); Prima viene eseguita la sotto espressione individuata dalle parentesi: 5*2 poi il calcolo del modulo. Quindi alla fine dell’istruzione a y viene assegnato il valore 2.
Pagina 9 di 9
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 4 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 4
4.2 Input formattato: scanf() La maggior parte dei programmi necessita di dati forniti dall’utente, in particolare da tastiera. Il modo più flessibile per leggere dati numerici è attraverso la funzione di libreria scanf(). La funzione scanf() accetta un numero variabile di argomenti, come minimo due. Il primo è una stringa di formato che indica come interpretare l’input mediante dei caratteri speciali. Il secondo e li altri sono gli indirizzi delle variabili a cui assegnare i dati di input. Ecco un esempio: scanf(“%d”,&numero); Il primo argomento “%d”, è la stringa di formato. In questo caso “%d” indica a scanf() di cercare un valore intero con segno. Il secondo argomento utilizza l’operatore di indirizzo (&) per indicare a scanf() di assegnare il valore di input alla variabile intera numero. La stringa di formato può contenere quanto segue:
Spazi e tabulazioni che sono ignorati (possono essere utilizzati per migliorare la lettura della stringa di formato)
Caratteri (tranne %), che sono messi in corrispondenza con caratteri diversi da spazi dell’input.
Una o più specifiche di conversione, costituite dal carattere % seguito da caratteri speciali. In genere la stringa di formato contiene un’unica specifica di conversione per ogni variabile.
L’unica parte obbligatoria della stringa di formato è rappresentata dalle specifiche di conversione, che iniziano con % e contengono componenti opzionali e richiesti in un certo ordine. Scanf() applica specifiche in ordine ai campi di input. Un campo di input è una sequenza di caratteri diversi da spazi che termina con il successivo spazio bianco o quando l’ampiezza del campo, se specificata, viene raggiunta. Modificatori di precisione: Tipo Argomento
Significato
d
int *
Intero decimale
i
int *
Intero notazione decimale, ottale (inizia con O) o esadecimale (inizia con 0x o 0X)
o
int*
Intero in notazione ottale
u
unsigned int *
Intero decimale senza segno
c
char *
Uno o più caratteri sono letti sequenzialmente nella locazione di memoria indicata dall’argomento, senza aggiungere il caarattere di terminazione \0
s
char *
Una stringa di caratteri diversi da spazi viene letta nella locazione di memoria specificata
e,f,g
float *
Un numero in virgola mobile, in notazione decimale o scientifica
[…]
char *
Una stringa, soltanto I caratteri elencati tra le parentese sono accettati. L’input termina non appena si incontra un carattere non corrispondente, oppure viene raggiunta l’ampiezza specificata o viene premuto il tasto invio, Per accettare il carattere ] è necessario elencarlo per primo: []…]. Un terminatore \0 viene aggiunto alla fine della stringa.
Pagina 2 di 4
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 4
[^…]
char *
Equivale a […] salvo che sono accettati soltanto i caratteri non elencati tra parentesi.
%
nessuno
Letterale %: legge il carattere %. Non viene effettuato alcun assegnamento.
Modificatori di precisione: h
Quando inserito prima dell’indicatore di tipo d,i,o,u,x indica che l’argomento è un puntatore di tipo short invece che un tipo int. Sui PC i due tipi coincidono perciò non serve
l
Quando inserito prima dell’indicatore di tipo d,i,o,u,x indica che l’argomento è un puntatore di tipo long. Quando inserito prima dell’indicatore e,f,g indica che l’argomento è un puntatore di tipo double
L
Quando inserito prima dell’indicatore di tipo e, f, g, indica che l’argomento è un puntatore al tipo long double
L’input di scanf() è bufferizzato, perciò non vengono ricevuti effettivamente dei caratteri finché l’utente non preme INVIO. L’intera riga di caratteri arriva poi a stdin ed è elaborata in ordine da scanf(). Il controllo torna da scanf() soltanto quando è stato ricevuto input sufficienza per soddisfare le specifiche della stringa di formato. I caratteri in più se esistono rimangono in stdin e possono causare problemi. Quando viene eseguita una chiamata scanf() e l’utente ha inserito una singola riga, si possono avere tre situazioni: per questi esempi si assume che sia eseguita scanf(“%d %d”, &x, &y); per cui scanf() è in attesa di 2 decimali. Ecco le possibilità: La riga inserita dall’utente corrisponde alla stringa di formato. Ad esempio si supponga che l’utente immetta 12 14 e prema INVIO. In questo caso non ci sono problemi, scanf() è soddisfatta e non rimangono caratteri in stdin.
La riga inserita dall’utente ha troppo pochi elementi per corrispondere alla stringa di formato. As esempio si supponga che l’utente immetta 12 e prema INVIO. In questo caso scanf() continua ad attendere l’input mancante e quando lo riceve l’esecuzione continua senza che rimangano dei caratteri in stdin.
La riga inserita dall’utente ha più caratteri di quelli richiesti dalla stringa di formato. Ad esempio si supponga che l’utente immetta 12 14 16 e prema INVIO. In questo caso scanf() legge 12 e 14 e restituisce il controllo lasciando gli altri caratteri (1 e 6) in stdin.
Nella terza situazione possono verificarsi dei problemi, poiché i caratteri rimangono in attesa fino alla successiva lettura da stdin. Poi i caratteri rimasti sono i primi ad essere letti, davanti anche all’input dell’utente effettuato al momento e questo provoca errori. Per evitare questo problema si utilizza la funzione gets() che legge caratteri rimanenti da stdin, fino al termine della riga incluso. Per eliminare qualsiasi carattere indesiderato è possibile utilizzare la funzione fflush() passandogli come parametro stdin.
Pagina 3 di 4
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 4
Esempio utilizzo della funzione scanf(): #include int main() { int i1, i2; long l1; double d1; char buf1[80], buf2[80]; /*uso del modificatore
l per interi long e double*/
puts(“Inserire un intero e un numero in virgola mobile.”); scanf(“%ld %lf”,&l1, &d1); printf(“Hai inserito %ld e %lf.\n”, l1, d1); /* La stringa di formato di scanf() ha utilizzato il modificatore l per memorizzare l’input di un tipo long e un tipo double */ fflush(stdin); /*Utilizza la dimensione del campo per suddividere l’input*/ puts(“inserire un numero di 5 cifre (ad esempio 54321)”); scanf(“%2d%3d”, &i1, &i2); /* Si noti che l’indicatore dell’ampiezza di campo nella stringa di formato di scanf() suddivide l’input in due valori */ fflush(stdin); puts(“inserire il nome e il cognome separati da uno spazio. ”); scanf(“%[^ ]%s”, buf1, buf2); printf(“\n il nome è: %s\n”,buf1); printf(“\n il cognome è: %s\n”,buf2); /* Si noti che [^ ] nella stringa scanf() escludendo il carattere spazio ha causato la suddivisione dell’input */ return 0; } La funzione fflush() viene utilizzata per eliminare qualsiasi carattere indesiderato dal flusso di input. Poiché non ci sono indicatori d’ampiezza l’intero a cinque cifre è suddiviso in due interi, uno con due caratteri ed uno con tre. L’ultimo esempio utilizza il carattere di esclusione. La stringa %[^ ]%s indica a scanf() di prelevare una stringa fermandosi in corrispondenza di uno spazio, in questo modo l’input viene suddiviso in maniera efficace.
Pagina 4 di 4
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 5 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 5
5.2 Operatori compatti Gli operatori di assegnamento compatti del C forniscono un metodo veloce di combinare operazioni matematiche binarie a operazioni di assegnamento. Ad esempio, si supponga di voler incrementare di 5 il valore di x, o in altre parole voler sommare 5 a x e assegnare il risultato a x. Si può scrivere: x = x + 5; Utilizzando un operatore di assegnamento compatto, che si può considerare come un metodo compatto di assegnamento, si scrive: x += 5; In generale gli operatori di assegnamento compatti hanno la sintassi seguente (dove op sta per operatore binario): exp1 op= exp2; che equivale a scrivere: exp1 = exp1 op exp2; Si possono creare degli operatori di assegnamento compatti utilizzando I cinque operatori matematici binari. Alcuni esempi: Scrittura Compatta
Scrittura equivalente
X *= Y
X=X*Y
Y -= Z * 1
Y=Y–Z*1
A /= B
A=A/B
X += Y / 8
X=X+Y/8
Y %= 3
Y=Y%3
5.3 Operatore virgola La virgola viene utilizzata nel C come semplice carattere di iterputazione che separa le dichiarazioni delle variabili, gli argomenti delle funzioni e così via. In alcune situazioni però la virgola diventa un vero e proprio operatore: è possibile formare un’espressione separando due sottoespressioni con una virgola. Il risultato è che:
entrambe le espressioni vengono valutate, l’espressione a sinistra viene valutata per prima
l’intera espressione assume il valore dell’espressione di destra.
Ad esempio, l’istruzione seguente assegna a x il valore di b, quindi incrementa a e successivamente incrementa b: x = (a++, b++); Dato che l’operatore ++ è impiegato in modalità postfissa, a x viene assegnato il valore di b prima che questo venga incrementato. L’uso delle parentesi è necessario dato che l’operatore virgola ha una precedenza molto bassa.
Pagina 2 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 5
5.4 Costanti 5.4.1 Direttiva #define Una costante simbolica è una costante rappresentata da un nome o simbolo all’interno del programma. La direttiva #define è una direttiva del preprocessore e ha la seguente sintassi: #define NOMECOSTATNE letterale In questo modo si definisce una costante con il nome NOMECOSTANTE il cui valore è letterale. Il funzionamento della direttiva #define è quello di istruire il compilatore in modo da comportarsi nel modo seguente: “nel codice sorgente, sostituisci tutte le occorrenze di NOMECOSTANTE con letterale”.
5.4.2 Parola chiave const Il secondo modo di definire costanti simboliche consiste nell’utilizzo della parola chiave const. Const è un modificatore che può essere utilizzato in aggiunta a qualsiasi dichiarazione di variabile. Una variabile dichiarata const non può venire modificata durante l’esecuzione dei programmi, ma può solo essere inizializzata durante la sua dichiarazione, ad esempio: const int conta = 100; const float pi = 3.14159; const long debito = 1200000, float tasso = 0.21; L’effetto di const si propaga su tutte le variabili della riga di dichiarazione. Nell’ultima riga debito e tasso sono due costanti simboliche. Se il programma cerca di modificare una variabile const, il compilatore genera un messaggio di errore, come nel caso seguente: const int count = 100; count = 200; /*Il programma non viene compilato! E’ impossibile rassegnare il valore di una costante!*/ Quali sono le differenze pratiche tra le costanti simboliche create con la direttiva #define e quelle create con il modificatore const? Le differenze riguardano l’uso dei puntatori e l’ambito delle variabili.
Pagina 3 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 5
5.5 Esempi utilizzo scanf(), printf() Esempio 1 #include #define SECONDI_PER_MINUTO 60 #define SECONDI_PER_ORA 3600 int secondi, ris_ore, ris_minuti, ris_secondi; main() { /*Richiedere il numero di secondi*/ printf(“\n Inserire il numero di secondi”); scanf(“%d”,secondi); ris_ore = seconds / SECONDI_PER_ORA; ris_minuti = (seconds / SECONDI_PER_MINUTO) ris_secondi = seconds % SECONDI_PER_MINUTO;
% SECONDI_PER_MINUTO;
printf(“%d secondi equivale a:”, secondi); printf(“%d h, %d m e %d s”, ris_ore, ris_minuti, ris_secondi); return 0; } Esempio 2 #include int main() { int numero1, numero2; printf("Inserire 2 numeri separati da uno spazio: "); scanf("%d-%d",&numero1,&numero2); printf("\n numero 1: %d", numero1); printf("\n numero 2: %d", numero2); return 0; } In questo secondo esempio i numeri per essere letti devono essere separati dal carattere “-“. Esempio 3 #include int main()
Pagina 4 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 5
{ int stringa[80]; printf("Inserire del testo e premere invio: "); scanf("%[^abd]",stringa); printf("\n testo inserito: %s", stringa); return 0; } In questo secondo esempio la funzione scanf() rimane in attesa fino a quando uno dei caratteri inseriti tra le parentesi quadre non viene digitato (seguito ovviamente dal tasto invio) Esempio 4 #include int main() { int stringa[80]; printf("Inserire del testo e premere invio: "); scanf("%[^\n]",stringa); printf("\n testo inserito: %s", stringa); return 0; } In questo secondo esempio la funzione scanf() legge tutti i caratteri, incluso lo spazio, fino alla pressione del tasto invio.
Pagina 5 di 5
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 6 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 6
6.2 Operatori relazionali Gli Gli operatori relazionali del C vengono utilizzati per confrontare espressioni e rispondere a comande come “x è più grande di 100” o “y è più piccola di x” Un’espressione contenente un operatore relazionale viene valutata secondo un valore di verità cioè veto (valore 1 o false) o falso (valore 0 o false).
Operatore Uguale
Simbolo Domanda sottointesa == L’operando x è uguale all’operando y?
Esempio X ==y
Maggiore di
>
L’operando x è maggiore dell’operando y?
X>y
Minore di
<
L’operando x è minore dell’operando y?
X
Maggiore o uguale a >=
L’operando x è maggiore o uguale dell’operando y?
X >= y
Minore o uguale a
<=
L’operando x è minore o uguale dell’operando y?
X<= y
Diverso
!=
L’operando x è diverso dall’operando y?
X != y
6.3 Istruzione if L’istruzione if valuta un’espressione e dirige conseguentemente il flusso di esecuzione del programma. La sintassi (ridotta) dell’istruzione if è la seguente: if(espressione) { istruzioni } Se il valore di verità dell’espressione è vero viene conseguita l’istruzione presente nel corpo dell’istruzione (tra parentesi graffe). In caso contrario il flusso di esecuzione salta all’istruzione che segue quella contenuta nell’if. Si potrebbe dire che l’esecuzione dell’istruzione dipende dal risultato dell’espressione. #include int x, y; int main() { /*Input dei valori da tastiera*/ printf(“\n Inserire un valore per x: ”); scanf(“%d”,&x); printf(“\n Inserire un valore per y: ”); scanf(“%d”,&y); /*Confronta i valori e stampa i risultati*/ if (x == y) { printf(“x è uguale a y”); } if (x > y)
Pagina 2 di 2
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 6
{ printf(“x è maggiore di y”); } if (x < y) { printf(“x è minore di y”); } return 0; } La clausola else L’istruzione if supporta la clausola else, che ha la seguente sintassi: if (espressione) { istruzione1; } else { istruzione2; } Dove la clausole else è facoltativa. L’espressione è valutata; se è vera (se cioè espressione ha un valore diverso da zero), è eseguita istruzione1. Se è falsa (espressione è zero) e se c’è una clausola else, viene invece eseguita istruzione2. Stante la natura facoltativa della clausola else in un costrutto if-else, si crea ambiguità quando un else è omesso da una sequenza di if annidiata. Per convenzione, il compilatore abbina else al più vicino if precedente che ne sia privo. Per esempio: if (n > 0) { if (a > b) { z = a; } else { z = b; } } la clausola else va con l’istruzione if più interna, come segnala la rientranza. Se si vuole un risultato diverso, è necessario ricorrere alle parentesi graffe per imporre il giusto annidiamento: if (n > 0) { if (a > b) { z = a; } } else { z = b; }
Pagina 3 di 3
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 6
Vediamo alcuni esempi, schematizziamo con le lettere maiuscole A, B, C, D… dei blocchi di istruzioni di codice:
Esempio 1: A if (exp) { B } C • •
Se exp restituisce un valore vero (o un numero diverso da zero) la sequenza delle istruzioni è A, B, C Se exp restituisce un valore falso (o un numero numero uguale a zero) la sequenza delle istruzioni è A, C. Questo perché solo se la condizione è vera si accede al corpo dell’istruzione if.
Esempio 2: A if (exp) { B } else { C } D • •
Se exp restituisce un valore vero (o un numero diverso da zero) la sequenza delle istruzioni è A, B, D. Se exp restituisce un valore falso (o un numero numero uguale a zero) la sequenza delle istruzioni è A, C, D.
Esempio 3: A if (exp1) { B if (exp2) { C } else { D } E } else { F if (exp3) { G } H } I
Pagina 4 di 4
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com EXP1
EXP2
EXP3
SEQUENZA ISTRUZIONI
V
V
V
A B C E I
V
V
F
A B C E I
V
F
F
A B D E I
F
F
F
A F H I
V
F
V
A B D E I
F
F
V
A F G H I
F
V
F
A F G H I
F
V
V
A F G H I
Dispensa 6
Consideriamo il seguente costrutto: if (espresione) istruzione else if (espresione) istruzione else if (espresione) istruzione else if (espresione) istruzione else istruzione Questa sequenza di istruzioni if è il modo più generale di analizzare un ventaglio di possibilità in C. Le espressioni sono sempre valutate nell’ordine, se una di esse è vera, l’istruzione con cui è associata viene eseguita, e ciò conclude l’esecuzione dell’intero costrutto. Come sempre il codice per ogni istruzione può essere un’istruzione singola o un blocco (più istruzioni tra graffe). L’ultima clausola else si occupa del caso “nessuno dei precedenti” in cui, non essendo soddisfatta nessuna delle condizioni, occorre procedere d’ufficio. Può succedere che nessuna azione esplicita sia prevista per questo caso e allora il passo: else istruzione Può essere tralasciato, oppure lo si può riservare alla gestione degli errori, per rilevare una condizione “impossibile”. Vediamo un esempio: presi in ingresso 3 numeri stamparli in ordine crescente: #include int main() { int num1, num2, num3; int a, b, b; printf(“\n Inserire primo valore: ”); scanf(“%d”,&a); printf(“\n Inserire secondo valore: ”); scanf(“%d”,&b); printf(“\n Inserire terzo valore: ”); scanf(“%d”,&c);
Pagina 5 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com if (a < b) { if (a < c) { num1 = a; if (c < b) { num2 num3 } else { num2 num3 } } else { num1 = c; num2 = a; num3 = b; } } else { if (b < c) { num1 = b; if (c < a) { num2 num3 } else { num2 num3 } } else { num1 = c; num2 = b; num3 = a; } }
Dispensa 6
= c; = b;
= b; = c;
= c; = a;
= a; = c;
printf(“numeri in ordine crescente: %d, %d, %d”,num1,num2,num3); return 0; }
Pagina 6 di 6
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 6
6.4 Operatore condizionale L’operatore condizionale è l’unico operatore ternario disponibile in C. Questo significa che è l’unico a lavorare su tre operandi senapati. La sua sintassi è la seguente: exp1 ? exp2 : exp3; Se exp1 è vera (cioè non nulla), l’intera espressione assume il valore di exp2. Se exp1 è falsa (ovvero il suo valore è zero), l’intera espressione assume il valore di exp3. Ad esempio l’istruzione seguente assegna a x il valore 1 se y è vera, mentre se y è falsa assegna ad x il valore 100: x = y ? 1 : 100; Analogamente, per assegnare a x il valore più grande tra quelli contenuti in x e in y: z = (x > y) ? x : y ; L’operatore condizionale funziona in maniera analoga a l’istruzione if. L’istruzione precedente si sarebbe potuta scrivere anche così: if (x > y) z = x; else z = y; L’istruzione condizionale non può essere utilizzata al posto di qualsiasi istruzione if…else, ma quando è possibile il codice risultante è più conciso. L’operatore condizionale può essere utilizzato in alcune situazioni in cui l’istruzione if non è utilizzabile, come all’interno di un ‘istruzione printf(): printf(“il valore superiore è %d: ” ((x > y) ? x : y));
6.5 Istruzione switch L’istruzione switch esamina un ventaglio di possibilità. E devia di conseguenza il flusso dell’esecuzione. Ogni condizione esaminata esprime la coincidenza del valore di un’espressione con una costante intera. switch (espressione) { case espressione-costante: istruzioni; break; case espressione-costante: istruzioni; break; case espressione-costante: istruzioni; break; default: istruzioni } Ogni caso (clausola case) è etichettato da una o più costanti (o espressioni costanti) intere. Se uno di tali casi coincide con il valore dell’espressione, l’esecuzione prosegue dal caso in questione. Tutte le espressioni delle clausole case devono essere diverse. La clausola default è eseguita se nessuna delle altre è soddisfatta, ed è facoltativa; in sua assenza, se gli altri casi non sono soddisfatti non accade nulla: l’istruzione switch non ha alcun effetto. I casi e la clausola default possono trovarsi in un qualunque ordine. L’istruzione break provoca l’uscita immediata dal costrutto switch. Poiché i casi fungono solo da etichette, quando l’esecuzione del codice relativo a un caso è finita, essa passa al successivo, a meno che non si decida esplicitamente di fare altrimenti. Questa gestione del flusso è detta a cascata. Le istruzioni break e
Pagina 7 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 6
return sono le più comuni vie d’uscita da switch. Un istruzione break impone anche l’uscita immediata dai cicli while, for e do, come si vedrà nel seguito delle lezioni. L’esecuzione a cascata presenta vantaggi e svantaggi. Il lato positivo è la possibilità di associare molti casi una singola azione, ma implica anche che ogni caso termini con un break per evirare il passaggio in cascata al successivo: tale meccanismo, infatti, non è robusto ed espone il brano switch a una possibile disintegrazione in caso di modifica del programma. Fatto salvo l’uso di più etichette in una singola computazione, l’esecuzione a cascata va usata con parsimonia e corredata con appositi commenti.
Pagina 8 di 8
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 7 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 7
7.2 Istruzione for L’ordine di esecuzione delle istruzioni nei programmi in C è per default dall’alto verso il basso. L’esecuzione comincia con la prima istruzione di main() e va avanti istruzione per istruzione, fino a quando non viene raggiunta la fine di tale funzione. Nei programmi in C reali, raramente quest’ordine viene rispettato. Il linguaggio prevede infatti una gamma di istruzioni di controllo che permettono di gestire il flusso di esecuzione delle istruzioni. L’istruzione for è un costrutto di programmazione che esegue un blocco di istruzioni per un certo numero di volte. Spesso il costrutto viene detto “ciclo” perchè il flusso di esecuzione ripete le esecuzioni più di una volta. Un costrutto for ha la struttura seguente: for (inizio; condizione; incremento) { istruzione; }
inizio, condizione, e istruzione sono espressioni del C, mentre istruzione è un blocco di istruzioni. Quando durante l’esecuzione viene incontrata l’istruzione for si verifica quanto segue: 1. Viene valutata l’espressione inizio. Solitamente questa è un istruzione di assegnamento che imposta una variabile a un particolare valore. 2. Viene valutata l’espressione condizione. Questa solitamente è una espressione relazionale. 3. Se la condizione è falsa (cioè se vale zero) l’istruzione for si conclude e l’esecuzione passa alla prima istruzione dopo il fot 4. Se la condizione è vera (cioè se vale uno) vengono eseguite le istruzioni del blocco istruzione 5. Viene valutata l’espressione incremento e l’esecuzione torna al punto 2 Vediamo schematicamente una rappresentazione di un’istruzione for: Inizio
Valuta inizio
Valuta incremento
Valuta condizione
VERA
FALSA
Fine
Pagina 2 di 6
Esegue istruzioni
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 7
Proviamo a scrivere un programma che stampi tutti i numeri che vanno da 1 a 10. Potremo scrivere un listato con 10 istruzioni printf() oppure potremo scrivere un codice più compatto utilizzando un istruzione for: #include int conta; int main() { /*Stampa i numeri da 1 a 20 */ for (conta = 1; conta <= 20; conta++) { printf(“%d\n”,conta); } return 0; } L’output del programma: 1 2 3 4 5 6 7 8 9 10 La figura sottostante illustra la modalità operativa del ciclo for per il programma descritto sopra: Inizio
Assegna 1 a conta
incrementa conta di 1
conta è <= di 20?
VERA
FALSA
Fine
Pagina 3 di 6
Esegue printf()
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 7
Un istruzione for può essere eseguita tranquillamente all’interno di un’altra istruzione for, questo comportamento viene detto annidamento. Ad esempio proviamo a scrivere un programma che stampi a video un area di 5 righe e 20 colonne di caratteri x. #include int righa, colonna; int main() { for (riga = 1; riga <= 5; riga++) { for (colonna = 1; colonna <= 20; colonna ++) { printf(“x”); } printf(“\n”); } return 0; } ecco l’output del programma: xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx
7.3 Break e continue Torna a volte conveniente interrompere un’iterazione in corrispondenza di punti del codice diversi dall’inizio o dalla fine del corpo. L’istruzione break serve allo scopo del caso di istruzioni for, while e do, in analogia con il suo uso del costrutto switch. Essa provoca l’immediata terminazione del ciclo o dell’istruzione switch. L’istruzione continue è legata a break, ma d’uso meno comune; causa l’esecuzione dell’iterazione successiva del ciclo for. Nel caso for, l’esecuzione prosegue con la parte relativa all’incremento. L’istruzione continue non si applica all’istruzione switch. La presenza di un istruzione continue all’interno di un istruzione switch che sia a sua volta contenuta in un ciclo causa l’esecuzione dell’iterazione successiva del ciclo. Per capire la sottile, quanto importante, differenza tra le due istruzioni, presentiamo un semplice codice in cui si legge un numero da tastiera che volgiamo sia compreso tra 0 e 100, se tale valore risulta essere negativo, si esce dal ciclo, mentre se è maggiore di cento si richiede di inserire un valore valido; se il valore è tra 1 e 100, si stampa a video il suo quadrato, se è zero si esce: int valore, quadrato; for (i = 0; i < 20; i++) { scanf("%d", &valore)
Pagina 4 di 6
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com if (valore < 0) { printf("Valore non consentito\n"); break; /* esce dal ciclo */ } if (valore > 100) { printf("Valore non consentito\n"); continue; } quadrato = valore * valore; printf("%d \n", quadrato); }
Pagina 5 di 6
Dispensa 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 7
Esercizio 1 Scrivere un programma che calcoli la media di 20 numeri inseriti dall’utente Esercizio 2 Scrivere un programma che calcoli la somma di n numeri inseriti dall’utente Esercizio 3 Scrivere un programma che permetta all’utente di inserire 20 numeri, per ogni numero il programma deve indicare se si tratta di un numero pari o dispari. Esercizio 4 Scrivere un programma che estratto un numero a caso chieda all’utente di indovinarlo concedendo 3 tentativi. Esercizio 5 Scrivere un programma che stampi tutti i numeri primi compresi nell’intervallo da 1 a 100
La soluzione degli esercizi è nei file allegati
Pagina 6 di 6
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 8 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 8
Istruzione while I cicli permettono di ripetere delle operazioni fino a quando una condizione risulta vera. Consideriamo la sintassi del ciclo while: while (espressione) { Istruzione } l’espressione viene valutata, qualora sia diversa da zero, istruzione è eseguita ad espressione rivalutata. Questo ciclo prosegue finché espressione diventa zero, momento in cui l’esecuzione riprende dal punto seguente a istruzione. Consideriamo il seguente esempio: A while (espressione) { B } C Dove A, B e C sono blocchi di codice. Viene eseguita l’istruzione A, valutata l’espressione e se risulta vera (diversa da zero), si accede al corpo del ciclo while eseguendo l’istruzione B, si procede rivalutando l’espressione, se risulta vera si procede nuovamente eseguento l’istruzione B, diversamente si esegue l’istruzione C. Per stampare 7 volte il nome “Cesena” si potrebbe eseguire la seguente porzione di codice: i = 0; while (i < 7) { printf(“Cesena\n”); i++; //equivale a fare i = i + 1; } Consideriamo ora la sintassi del ciclo for: for (espressione1; espressione2; espressione3) { istruzione; } È equivalente a: espressione1; while (espressione2) { istruzione; espressione3; } Ad eccezione del comportamento dell’istruzione continue, descritto successivamente. Sul piano grammaticale le tre componenti di un ciclo for sono espressioni. Nel caso più comune espressione1 e espressione3 sono assegnamenti o chiamate di funzioni, mentre espressione2 è un espressione relazionale. Si può omettere una delle tre, ma i punti e virgola devono rimanere. Omettere espressione1 o espressione3 nell’istruzione for corrisponde a ometterle nell’equivalente ciclo while. Se però la condizione, cioè espressione2, non è presente, esse è considerata sempre vera:
Pagina 2 di 3
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 8
for (;;) { istruzione; } Il ciclo è “infinito”, presumibilmente da interrompere, prima o poi, con altri strumenti come break o return. Se sia meglio usare while o for è sostanzialmente una questione di preferenza personale. Per stampare 7 volte il nome “Cesena” si potrebbe eseguire la seguente porzione di codice: for (i = 0; i < 7; i++) { printf(“Cesena\n”); }
Istruzione do-while Come visto nelle parti precedenti, i cicli while e for valutano la condizione di terminazione fin dall’inizio. Il terzo ciclo del C, l’istruzione do-while, esamina invece la condizione in coda, dopo ogni iterazione: il corpo è quindi sempre eseguito almeno una volta. La sintassi del costrutto è: do { istruzione; } while (espressione); Si esegue dapprima istruzione, e si valuta poi espressione: se è vera, si torna a eseguire istruzione, e così via. Il ciclo termina quando espressione diventa falsa.
Pagina 3 di 3
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 9 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
Funzioni per i flussi Un flusso è una sequenza di caratteri o meglio di byte di dati. Una sequenza di byte che entra in un programma è un flusso di input mentre una sequenza di byte che esce dal programma è un flusso di output. Concentrandosi sui flussi, si può fare a meno di preoccuparsi dell’origine o della destinazione dei dati; il vantaggio principale è che in questo modo la programmazione è indipendente dai dispositivi. I flussi in C si dividono in due categorie: di testo e binari. Un flusso di testo è costituito soltanto da caratteri, come i dati inviati allo schermo. I flussi di testo sono strutturati in righe che possono essere lunghe fino a 255 caratteri e sono concluse da un carattere di nuova riga. I flussi binari possono gestire dati di qualsiasi tipo, tra cui anche quelli di testo. I byte in un flusso binario non sono tradotti o interpretati in modo particolare ma letti semplicemente così come sono. I flussi binari vengono utilizzati principalmente per i file su disco. L’ANSI C ha tre flussi predefiniti chiamate anche file di input/output standard. Se si programma per un computer PC compatibile con DOS, sono disponibili due flussi in più, aperti automaticamente quando il programma viene avviato e chiusi autonomamente al termine. Nome
Flusso
Dispositivo
stdin
Input standard
Tastiera
stdout
Output standard
Schermo
stderr
Errore standard
Schermo
stdprn *
Stampante standard
Stampante (LPT1:)
stdaux *
Ausiliario standard
Porta seriale (COM1:)
*supportati soltanto in DOS Ogni volta che si utilizzano le funzioni printf() o puts() per visualizzare del testo sullo schermo si utilizza stdout. Analogamente con la funzione scanf() si utilizza il flusso stdin. I flussi standard sono aperti automaticamente, ma altri, come quelli utilizzati per manipolare i dati memorizzati su disco devono essere aperti esplicitamente. La libreria standard del C ha molte funzioni per la gestione dei flussi di input e di output. La maggior parte di tali funzioni è disponibile in due varietà: una che utilizza sempre uno dei flussi standard e l’altra che richiede al programmatore di specificare il flusso. Utilizza uno dei flussi standard
Richiede un nome di flusso
Descrizione
printf()
fprintf()
Output formattato
vprintf()
vfprintf()
Output formattato con un elenco di argomenti variabile
puts()
fputs()
Output stringa
putchar()
putc(), fputc()
Output carattere
scanf()
fscanf()
Input formattato
gets()
fgets()
Input stringa
getchar()
getc(), fgetc()
Input carattere
perror()
Output stringa solo su stderr
Pagina 2 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
Tutte queste funzioni richiedono l’inclusione del file “stdlib.h”. Le funzioni vprintf() e vfprintf() richiedono anche “stdargs.h”.
Input da tastiera La maggior parte dei programmi in C richiede qualche forma di input dalla tastiera (ovvero da stdin). Le funzioni di input si dividono in tre livelli gerarchici: input di carattere, di riga e formattato.
Funzioni per l’input di carattere Le funzioni per l’input di carattere leggono l’input da un flusso un carattere per volta e restituiscono il carattere nel flusso o EOF se è stata raggiunta la fine del file p si è verificato un errore. EOF è una costante simbolica definita in “stdio.h” come –1- Questi funzioni differiscono per buffer e visualizzazione dei caratteri. •
•
Alcune funzioni dispongono di un buffer dove il sistema operativo memorizza tutti i caratteri fincheè l’utente non preme INVIO per inviarli al flusso stdin. Altre sono prive di buffer, quindi ogni carattere è inviato a stdin appena viene premuto il tasto corrispondente. Alcune funzioni di input eseguono l’eco di ogni carattere su stdout non appena viene ricevuto, altri no, perciè il carattere viene inviato a stdin e non a stout.
La funzione getchar() Questa funzione ottiene il carattere successivo dal flusso stdin. Fornisce un input di carattere bufferizzato con eco e il suo prototipo è: int getchar(void) L’uso di getchar() è mostrato nel listato sotto: #include main() { char ch; while ((ch = getchar()) != ‘\n’) putchar(ch); return 0; }
L’output del programma: Questo è ciò che è stato digitato. Questo è ciò che è stato digitato.
La funzione getchar() attende la ricezione di un carattere da stdin. Poichè getchar() è una funzione di input con buffer, non viene ricevuto alcun carattere finchè l’utente non preme INVIO. Tuttavia ogni tasto premuto viene immediatamente mostrato sullo schermo. Quando si preme INVIO, tutti i caratteri immessi, incluso quello di nuova riga vengono inviati a stdin dal sistema operativo. La funzione getchar() restituisce i caratteri uno alla volta, assegnando ognuno a ch. Ogni carattere è confrontato con il carattere di nuova riga ‘\n’ e se diverso, visualizzato su schermo con putchar(). Altro esempio dell’utilizzo della funzione getchar() per l’input di un’intera stringa:
Pagina 3 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
#include #define MAX 80 main() { char ch, buffer[MAX+1]; int x = 0; while (((ch = getchar()) != ‘\n’) && x < MAX) buffer[x++] = ch; buffer[x] = ‘\0’;
//carattere di fine stringa
printf(“%s\n”,buffer); return 0; }
Ecco input e output del programma: Questo è ciò che è stato digitato. Questo è ciò che è stato digitato. Il programma è simile a quello visto in precedenza, per quanto riguarda l’utilizzo di getchar(). Il ciclo presenta una condizione in più: questa volta accetta i caratteri da getchar() finchè viene riconosciuta una nuova riga o dopo 80 caratteri. L’array buffer ha dimensione MAX+1 e non MAX perché con la dimensione MAX+1 la stringa può contenere 80 caratteri più il terminatore ‘\0’. La funzione getch() Questa funzione ottiene il carattere successivo dal flusso stdin. Fornisce un input di carattere bufferizzato senza eco e il suo prototipo è: int getch(void) Questa funzione non fa parte dello standard ANSI quindi potrebbe non essere disponibile su tutti i sistemi; in più potrebbe richiedere file d’inclusione diversi. In generale il prototipo di getch() è definito in “conio.h”. L’uso di getch() è mostrato nel listato sotto: #include main() { char ch; while ((ch = getch()) != ‘\n’) putchar(ch); return 0; }
Input e output del programma:
Pagina 4 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
Questo è ciò che è stato digitato.
Questo è quello che si visualizza in quanto la funzione non ha buffer e getch() restituisce ogni carattere senza attendere la pressione di INVIO. Poiché non viene eseguito l’eco dell’input i caratteri non sono visualizzati sullo schermo. La funzione getche() Questa funzione è del tutto simile a getch(), a parte il fatto che esegue l’eco dei caratteri su stdout. Anche questa non è una funzione ANSI, ma molti compilatori la supportano.
Funzioni per l’input di riga La funzione gets() Questa funzione si limita a leggere una riga da stdin e la memorizza in una stringa (array di caratteri). LA funzione legge caratteri finchè non incontra un carattere di nuova riga (\n) o la fine della riga. Prima di utilizzare gets() è necessario allocare uno spazio in memoria sufficiente per la stringa. #include main() { char input[81]; puts(“scrivere il testo e premere INVIO:”); gets(input); printf(“E\’ stato inserito: %s\n”,input); return 0; }
Ecco input e output del programma: Scrivere il testo e premere INVIO: Questo è il testo E’ stato inserito: Questo è il testo
In questo esempio l’argomento di gets() è l’espressione input, che rappresenta il nome di un array di tipo char.
Funzioni per l’input formattato La funzione scanf() Funzione vista nelle lezioni precedenti, utilizzata soprattutto per manipolare i numeri.
Pagina 5 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
Output su schermo Le funzioni di output sullo schermo si dividono in tre categorie principali come quelle di input: output di caratteri, di riga e di formato.
Output di caratteri Le funzioni di outpur di caratteri del C inviamo un singolo carattere a un flusso. La funzione putchar() Il prototipo della funzione si trova su “stdio.h” ed è il seguente: int putchar(int c); questa funzione scrive il carattere memorizzato nel parametro su stdout. Anche se nel prototipo è specificato un argomento int, in realtà si passa un char. Si può passare un int, purché abbia valore appropriato per un carattere (cioè sia compreso tra 0 e 255). La funzione restituisce il carattere scritto, o EOF in caso di errore.
Output di riga La funzione puts() Funzione vista nelle lezioni precedenti.
Output formattato La funzione printf() Funzione vista nelle lezioni precedenti.
Pagina 6 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
Funzioni matematiche La libreria standard del C contiene numerose funzioni che svolgono calcoli matematici. I loro prototipi di trovano nel file di intestazione “math.h”. Tutte le funzioni matematiche restituiscono un valore double. Nelle funzioni trigonometriche gli angoli sono espressi in radianti (un radiante equivale a 57,96 gradi; l’angono giro di 360 gradi misura 2π radianti) Funzioni trigonometriche Funzione Prototipo
Descrizione
acos()
double acos(double x)
Restituisce l’arcocoseno dell’argomento. L’argomento deve essere compreso tra –1 e 1. Il valore restituito è compreso tra 0 e π.
asin()
double asin(double x)
Restituisce l’arcoseno dell’argomento. L’argomento deve essere compreso tra –1 e 1. Il valore restituito è compreso tra π/2 e π/2.
atan()
double atan(double x)
Restituisce l’arcotangente dell’argomento. Il valore restituito è compreso tra -π/2 e π/2.
atan2()
double atan2(double x, double y)
Restituisce l’arcotangente di x/y. Il valore restituito è compreso tra -π e π.
cos()
double cos(double x)
Restituisce il coseno dell’argomento.
sin()
double sin(double x)
Restituisce il seno dell’argomento.
tan()
double tan(double x)
Restituisce la tangente dell’argomento.
Funzioni esponenziali e logaritmiche Funzione Prototipo
Descrizione
exp()
double exp(double x)
Restituisce l’esponente naturale dell’argomento cioè e^x (e elevato alla x) dove e è uguale a: 2,7182818284590452354
log()
double log(double x)
Restituisce il logaritmo naturale dell’argomento. L’argomento deve essere maggiore di zero.
log10()
double log10(double x)
Restituisce il logaritmo in base 10 dell’argomento. L’argomento deve essere maggiore di zero
Pagina 7 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
frexp()
double frexp(double x, int* y)
Calcola un numero decimale normalizzato che rappresenta il valore di x. Il valore restituito r è compreso tra 0,5 e 1. La funzione assegna ad y un esponente intero tale che c = r * 2 ^ y. Se l’argomento x vale 0 anche r ed y verranno posti a 0.
ldexp()
double ldexp(double x, int y)
Restituisce x * 2 ^ y
Funzioni ipergoliche Funzione
Prototipo
Descrizione
cosh()
double cosh(double x)
Restituisce il coseno iperbolico dell’argomento
cosh ()
double cosh(double x)
Restituisce il seno iperbolico dell’argomento
tanh()
double tanh(double x)
Restituisce la tangente iperbolica dell’argomento
Altre funzioni matematiche Funzione Prototipo
Descrizione
sqrt()
double sqrt(double x)
Restituisce la radice quadrata dell’argomento che deve essere positivo e non nullo.
ceil()
double ceil(double x)
Restituisce il più piccolo intero non inferiore all’argomento. Ad esempio ceil(4.5) restituisce 5.0; ceil(-4.5) restituisce -4.0 .
floor()
double floor(double x)
Restituisce il più grande numero interno non superiore all’argomento. Ad esempio floor(4.5) restituisce 4.0 mentre floor(-4,5) restituisce 5.0.
abs()
int abs(int x)
Restituisce il valore assoluto dell’argomento.
labs()
long labs(long x)
Restituisce il valore assoluto dell’argomento.
modf()
double modf(double x, double *y)
Separa x in una parte intera e una decimale ciascuna con lo stesso segno di x. La parte decimale è il valore restituito; la parte intera è assegnata a *y.
Pagina 8 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 9
pow()
double pow(double x, double y)
Restituisce x ^ y (x elevato alla y); si genera errore se x = 0 e y <= 0 o se x < 0 e y non è intero.
fmod()
double fmod(double x, double y)
Restituisce il resto in formato decimale di x/y. Con lo stesso segno di x. Restituisce 0 se x è uguale a 0.
Pagina 9 di 9
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 10 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 10
Array Un array è un insieme di celle di archiviazione, ciascuna dello stesso tipo di dati e con lo stesso nome. Le celle di un array prendono il nome di elementi. Un array si dice monodimensionale quando ha in solo indice di riferimento. In indice è un numero tra parentesi quadre che segue il nome di un array e identifica ciascun elemento. Un esempio: nel programma di gestione delle spese mensili si utilizzerebbe la riga che segue per dichiarare un array di tipo float: float spese[12]; Tale array di nome spese, può contenere 12 elementi, ciascuno dei quali è assolutamente equivalente ad una variabile di tipo float. E’ possibile creare array di qualsiasi tipo riconosciuto dal C. In C gli indici vengono numerato a partire da 0, per cui i 12 elementi di spese sono indicizzati da 0 a 11. Nell’esempio precedente le spese relative al mese di gennaio potrebbero essere memorizzate nell’elemento spese[0], quello di febbraio in spese[1] e così via. Quando viene dichiarato un array, il compilatore riserva un blocco di memoria abbastanza grande da contenerlo interamente. Gli elementi dell’array vengono memorizzati all’interno di locazioni di memoria contigue. Il punto in cui vengono dichiarati gli array all’interno del codice sorgente è molto importante. Così come accade per le variabili semplici, il punto dove avviene la dichiarazione influisce sull’ambito dell’array. Gli elementi dell’array possono essere utilizzati ovunque possa esserlo una variabile dello stesso tipo. Si accede ai singoli elementi riportando il nome dell’array seguito tra parentesi quadre. Ad esempio l’istruzione seguente memorizza il valore 78.16 all’interno del secondo elemento dell’array (bisogna ricordarsi che il promo elemento è spese[0] e non spese[1]): spese[1] = 78.16; Allo stesso modo, l’istruzione: spese[10] = spese[7]; assegna all’elemento dell’array spese[10] una copia del valore contenuto nell’elemento spese[7]. Quando ci si riferisce ad un elemento di un array, l’indice può essere una costante letterale, come in questo esempio oppure un’espressione (compreso il valore di un altro elemento dell’array). Ecco alcuni esempi: float spese[12]; int a[10]; ... spese[i] = 100; /* i è una variabile intera */ spese[2 + 3] = 100; /* equivale a spese[5] */ spese[a[2]] = 100; /* a[] è un array intero */ Considerando l’ultimo esempio, prendendo un array di interi denominato a[], se all’interno dell’elemento a[2] è contenuto il valore 8 l’istruzione: spese[a[2]] = 100; ha lo stesso significato di quella seguente: spese[8] = 100; Quando si utilizzano gli array si tenga ben presente lo standard di numerazione: in un array di n elementi gli indici possibili vanno da 0 a n – 1. Se si cerca di utilizzare l’indice n, il programma da errore. Vediamo un esempio: programma che legga 5 valori interi inseriti da un utenti e successivamente li stampi nello stesso ordine. #include int main()
Pagina 2 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 10
{ int elenco[5]; int i,; /*lettura dei valori*/ for (i = 0; i < 5; i++) { printf(“\n Inserire numero %d: ”,i); scanf(“%d”,&elenco[i]); } /*stampa dei valori inseriti*/ for (i = 0; i < 5; i++) printf(“\n %d”,elenco[i]); return 0; } Vediamo una variazione del programma precedente: la stampa dei valori inseriti deve essere in senso inverso. #include int main() { int elenco[5]; int i,; /*lettura dei valori*/ for (i = 0; i < 5; i++) { printf(“\n Inserire numero %d: ”,i); scanf(“%d”,&elenco[i]); } /*stampa dei valori in senso inverso*/ for (i = 4; i >= 0; i--) printf(“\n %d”,elenco[i]); return 0; } Vediamo un altro un esempio: programma che permetta ad un utente di inserire 10 numeri e successivamente ne calcoli la somma. #include int main() { int elenco[10]; int i, somma; /*lettura dei valori*/ for (i = 0; i < 10; i++) { printf(“\n Inserire numero %d: ”,i);
Pagina 3 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 10
scanf(“%d”,&elenco[i]); } /*calcolo della somma*/ somma = 0; for (i = 0; i < 10; i++) somma += elenco[i]; printf(“\n la somma e\’ %d”,somma); return 0; } Quando viene dichiarato un array, è possibile inizializzarlo in totalmente o in parte facendo seguire la dichiarazione da un segno di uguale e da un elenco di valori racchiusi tra parentesi graffe e separati da virgole. I valori in elenco vengono assegnati in ordine agli elementi dell’array a cominciare da quello con indice 0. Ad esempio l’istruzione seguente dichiara un array di dimensione 4 e associa all’elemento di indice 0 il valore 100, all’elemento di indice 1 il valore 200, all’elemento di indice 2 il valore 300 e all’elemento di indice 3 il valore 400: int array[4] = {100, 200, 300, 400}; oppure int array[] = {100, 200, 300, 400}; Nel secondo caso la dimensione viene omessa e il compilatore crea un array della dimensione sufficiente per contenere i valori con cui lo si inizializza. E’ comunque possibile specificare un numero di valori di inizializzazione interiore alla capacità: int array[10] = {1, 2, 3}; Scrivere un programma che permetta di cercare un numero all’interno del vettore. In particolar il programma deve restituire l’indice della cella in cui il valore cercato è memorizzato. #include int main() { int elenco[10] = {13,6,98,2,31,5,88,64,26,91}; int i, cerca, trovato; printf(“inserire il numero da cercare: ”); scanf(“%d”,&cerca); trovato = 0; for (i = 0; i < 10; i++) { if (elenco[i] == cerca) { trovato = 1; break; } } if (trovato) printf(“\n Il numero e\’ nella cella %d”,i); else printf(“\n Il numero cercato non e\’ stato trovato“);
Pagina 4 di 5
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 10
return 0; } Scrivere un programma che conti tutte le occorrenze di un numero all’interno di un vettore di interi.
#include int main() { int elenco[10] = {13,6,98,2,31,5,88,64,26,91}; int i, cerca, conta; printf(“inserire il numero da cercare: ”); scanf(“%d”,&cerca); conta = 0; for (i = 0; i < 10; i++) { if (elenco[i] == cerca) conta++; } printf(“\n Il numero compare %d volte”,conta); return 0; }
Esercizio 1 Scrivere un programma che inverta l’ordine degli elementi di un array Esercizio 2 Scrivere un programma che inverta di posto due elementi di un array Esercizio 3 Scrivere un programma che confronti 2 array di interi.
Pagina 5 di 5
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 11 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
Array multidimensionali Un array si dice multidimensionale sono quelli i cui elementi sono distinti da più di un indice. Un array bidimensionale avrà 2 indici, uno tridimensionale 3 e così via. Non esiste alcun limite al numero di dimensioni di un array in C, tuttavia esiste un limite per la grandezza totale dell’array. Si supponga di voler scrivere un programma che giochi a scacchi. La scacchiera contiene 64 caselle disposte su otto righe e otto colonne. Il programma può rappresentare la scacchiera sotto forma di un array bidimensionale nel modo seguente: int scacchi[8][8]; L’array risultante contiene 64 elementi: scacchi[0][0], scacchi[0][1], scacchi[0][2], ... scacchi[7][6] e scacchi[7][7]. Analogamente un array tridimensionale può essere considerato come un cubo. A prescindere dal numero di dimensioni, gli array vengono disposti in memoria sequenziale. Stampa di una matrice #include int main() { int matrice[3][4]; int i, j; for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) { printf(“matrice[%d][%d] = %d \n”,i,j,matrice[i][j]); } } return 0; }
Tipo di dato char Per memorizzare dei caratteri, il linguaggio C utilizza il tipo char, il quale rappresenta uno dei tipi di dati interi numerici del C. Se char è un tipo numerico, come può essere utilizzato per memorizzare caratteri? La risposta sta nel modo in cui il C memorizza i caratteri. La memoria del computer registra tutti i dati in un formato numerico, non esiste un modo diretto per memorizzare i caratteri. Tuttavia, per ciascun carattere esiste un codice numerico, chiamato codice ASCII, o set di caratteri ASCII (l’acronimo ASCII significa American Standard Code for Information Interchange). Tramite questo codice vengono assegnati dei valori compresi tra 0 e 255 per le lettere maiuscole e minuscole, per le cifre numeriche, per i segni di punteggiatura e altri simboli. La tabella ASCII è riportata in fondo a questa dispensa. Ad esempio 97 rappresenta il codice ASCII per la lettera ‘a’. Quando si registra il carattere ‘a’ in una variabile di tipo char in realtà viene memorizzato il numero 97. Poiché la sequenza dei valori ammessi per il tipo char corrisponde esattamente al set di caratteri ASCII standard, il tipo char si adatta perfettamente a contenere i caratteri.
Pagina 2 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
Tutto ciò può risultare motivo di confusione. Se il C registra i caratteri come numeri, come fa il programma a sapere se una variabile di tipo char rappresenta un carattere o un numero? Come verrà chiarito in seguito, dichiarare una variabile di tipo char non è sufficiente, è necessario eseguire altre operazioni. • •
Se una variabile char viene utilizzata in un pinto del programma C dove ci si aspetta un carattere, viene interpretata come carattere. Se la variabile char viene utilizzata in un punto del programma C dove ci si aspetta un numero, viene interpretata come un numero.
Come per altre variabili, prima di utilizzare le variabili di tipo char è necessario dichiararle, ed è possibile inizializzarle nel momento stesso in cui vengono dichiarate. Di seguito vengono riportati alcuni esempi: char a, b, c; /*dichiarazione di ter variabili di tipo char non inizializzate*/ char codice = ‘x’; /*Dichiarazione delle variabile char di nome codice e memorizzazione del carattere x al suo interno*/ codice = ‘!’ /*memorizzazione del carattere nella variabile codice*/ Per creare costanti di carattere di tipo letterake, è necessario racchiudere un unico carattere tra una coppia di apici singoli. Per creare costanti composte da simboli si può utilizzare sia la direttiva #define sia la parola chiave const: #define EX ‘x’ char codice = EX;
/* imposta codice uguale a x*/
const char A = ‘z’; Nel listato sotto, viene illustrata la natura numerica delle operazioni di memorizzazione dei caratteri, tramite l’utilizzo della funzione printf(). La funzione printf() può essere utilizzata per stampare sia caratteri che numeri. La stringa di formato con il comando di conversione %c comunica a printf() di stampare un carattere, mentre %d indica di stampare un intero decimale. La sequenza dei valori ammessi per una variabile di tipo char si estende solo fino a 127, mentre i codici ASCII raggiungono il valore 255, I codici ASCII sono in realtà divisi in due parti: quelli standard si estendono solo fino a 127; questa sequenza include tutte le lettere, i numeri, i segni di punteggiatura e altri simboli della tastiera. I codici compresi tra 128 e 255 sono dei codici ASCII estesi e rappresentano caratteri speciali, quali lettere straniere e simboli grafici. Quindi per i dati di testo standard, si possono utilizzare variabili di tipo char; se si vogliono stampare caratteri del codice ASCII esteso occorre utilizzare unsigned char. Nel listato sottostante vengono stampati i caratteri del codice ASCII esteso: #include unsigned char x; int main() { for (x = 128; x <= 255; x++) { printf(“Codice ASCII %d è il carattere %c \n”,x,x); } return 0; } Le variabili di tipo char possono contenere un unico carattere, perciò hanno un utilizzo limitato, Più uyille risulta la memorizzazione di Stringhe, rappresentate da sequenze di caratteri. Il mome e l’indirizzo di una persona rappresenta un esempio di stringa. Anche se non esiste un tipo di dato speciale per le stringhe, il C gestisce questo tipo di informazioni tramite gli array di caratteri.
Pagina 3 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
Per registrare una stringa di sei caratteri, ad esempio è necessario dichiarare un array di tipo char composto da sette elementi. La dichiarazione degli array di tipo char è identica a quella degli altri tipi. Ad esempio l’istruzione: char stringa[10]; dichiara un array di tipo char composto da 10 elementi, utilizzabile per contenere una stringa di un numero di caratteri pari o inferiore a nove. Qualcuno potrebbe chiedersi perché un array di 10 elementi possa contenere solo nove caratteri. Nel linguaggio C, una stringa è definita come una sequenza di caratteri che termina con il carattere null, un carattere speciale rappresentato da \0. Nonostante sembri composto da due caratteri (barra inversa e zero), il carattere null viene interpretato come un unico carattere e possiede il valore ASCII 0 (zero). Quando un programma C, ad esempio, memorizza la stringa Alabama, registra i sette caratteri A, l, a, b, a, m, a, seguiti dal carattere null \0, per un totale di otto caratteri. In tal modo, un array di caratteri può contenere una stringa composta da un numero di caratteri inferiore di un’unità al numero di elementi che compongono l’array. Una variabile di tipo char occupa lo spazio di un byte, perciò il numero fi byte che compongono un array di variabili di tipo char è pari al numero di elementi dell’array. Analogamente agli altri tipi di dati del C, gli array di caratteri possono essere inizializzati al momento della dichiarazione. A esse è possibile assegnare un valore, elemento per elemento, come viene mostrato in seguito: char stringa[10] = {‘A’, ‘l’, ‘a’, ‘b’, ‘a’, ‘m’, ‘a’, ‘\0’}; Conviene tuttavia utilizzare una stringa letterale, rappresentata da una sequenza di caratteri racchiusi tra doppi apici: char stringa[10] = “Alabama”; Quando all’interno del programma si utilizza una stringa letterale, il compilatore aggiunge automaticamente il carattere null finale. Se al momento della dichiarazione di un array non viene specificato il numero di componenti, il compilatore calcola automaticamente la dimensione dell’array. Quindi l’istruzione che segue crea e inizializza un array di otto elementi: char stringa[] = “Alabama”; Si tenga presente che le stringhe richiedono il carattere null finale. Le funzioni del C per la gestione delle stringhe stabiliscono la lunghezza delle stringa in base alla posizione del carattere null. Queste funzioni non anno altro modo per riconoscere la fine di una stringa. Se il carattere null viene omesso, il programma suppone che la stringa si estenda fino al successivo carattere null presente in memoria, e questo causa spiacevoli errori di programmazione.
Funzioni per le stringhe Tutti i prototipi delle funzioni descritte successivamente si trovano all’interno della libreria string.h. La funzione strLen() Per determinare la lunghezza della stringa, cioè il numero di caratteri che la compongono, si ricorre alla funzione di libreria strlen() il cui prototipo, in string.h, è il seguente: size_t strlen(char *str);
Pagina 4 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
Il tipo di ritorno size_t è definito in string.h come unsigned. L’argomento passato è un puntatore alla stringa di cui si vuole sapere la lunghezza. La funzione restituisce il numero di caratteri compresi fra str e il carattere nullo, quest’ultimo escluso. La funzione strcpy() La funzione di libreria strcpy() copia un’intera stringa in una nuova posizione di memoria. Il suo prototipo è il seguente: char * strcpy(char *destinatin, char *source); La funzione strcpy() copia la stringa a cui punta source (compreso il carattere nullo terminale) nella posizione in cui punta destination. Il valore restituito è il puntatore alla stringa di destinazione. Per utilizzare strcpy() occorre prima allocare lo spazio in memoria per la stringa di destinazione. La funzione strncpy() La funzione di libreria strncpy() è simile a strcpy(), ma richiede di specificare quanti caratteri devono essere copiati. Il suo prototipo è il seguente: char * strncpy(char *destinazione, char *origine, size_t n); Gli argomenti destinazione e origine sono puntatori alle stringhe di destinazione e d’origine. La funzione copia al più i primi n caratteri della stringa d’origine in quella di destinazione. Se la stringa origine contiene meno di n caratteri vengono aggiunti caratteri nulli fino a giungere a n. Se origine contiene più di n caratteri, nella destinazione non viene inserito il carattere nullo terminale. La funzione restituisce un puntatore alla destinazione. La funzione strdup() La funzione di libreria strdup() è simile a strcpy(), ma effettua da se l’allocazione di memoria per la destinazione. Il suo prototipo è il seguente: char * strdump(char *origine); L’argomento origine è un puntatore alla stringa d’origine. La funzione restituisce un puntatore alla stringa di destinazione, cioè allo spazio allocato, oppure NULL se l’allocazione è fallita. La funzione strcat() Il prototipo di strcat() è il seguente: char *strcat(char *str1, char *str2); La funzione di libreria strcat() appende una copia di str2 in coda a str1, spostando il carattere di terminazione alla fine della nuova stringa. Si deve allocare per str1 uno spazio sufficiente a contenere il risultato. La funzione restituisce un puntatore a str1. La funzione strncat() Il prototipo di strncat() è il seguente: char *strncat(char *str1, char *str2, size_t n); La funzione di libreria strncat() concatena due stringhe ma permette di specificare quanti caratteri della seconda stringa devono essere concatenati alla prima. Se str2 contiene più di n caratteri, solo i primi n vengono copiati a str1. Se str2_ contiene meno di n caratteri, l’intera str2 è appesa a str1. In entrambi i casi il carattere nullo viene appeso alla fine della stringa risultante, Si deve allocare per str1 spaizo sufficiente a contenere il risultato. La funzione restituisce un puntatore a str1.
Pagina 5 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
La funzione strcmp() La funzione strcmp() confronta due stringhe, carattere per carattere. Il suo prototipo è il seguente: int strcmp(char *str1, char *str2); Gli argomenti str1 e str2 sono puntatori alle stringhe da confrontare. La funzione restituisce rispettivamente: • • •
Un valore minore di 0 se str1 è minore di str2 Un valore uguale a 0 se str1 è uguale a str2 Un valore maggiore di 0 se str1 è maggiore di str2
La funzione strncmp() La funzione strncmp() confronta un numero specificato di caratteri di una stringa con una seconda stringa. Il suo prototipo è il seguente: int strncmp(char *str1, char *str2, size_t n); La funzione confronta i primi n caratteri di str2 in str1. Il confronto procede finché non sono stati confrontati n caratteri oppure è stata raggiunta la fine di str1. Il criterio di confronto e il valore restituito sono gli stessi di strcmp(). La funzione strchr() La funzione strchr() cerca la prima occorrenza di un carattere dato in una stringa. Il suo prototipo è il seguente: char *strchr(char *str1, int ch); La funzione esplora str da sinistra a destra finché trova il carattere ch oppure fino a incontrare il carattere nullo terminare. Se ch è stato trovato, la funzione restituisce un puntatore alla posizione di ch, altrimenti restituisce NULL. La funzione strrchr() La funzione strrchr() è simile a strrchr(), ma cerca l’ultima occorrenza del carattere dato nella stringa. Il suo prototipo è il seguente: char *strrchr(char *str1, int ch); La funzione strrchr() restituisce un puntatore all’ultima occorrenza di ch in str oppure NULL se ch con compare in str. La funzione strcspn() La funzione strcspnr() cerca la prima occorrenza in una stringa di uno dei qualsiasi dei caratteri che compongono una seconda stringa. Il suo prototipo è il seguente: size_t strcspn(char *str1, char *str2); La funzione comincia confrontando il primo carattere di str1 con tutti i caratteri di str2. Si sottolinea che la funzione non cerca l’occorrenza di tutta la stringa Str2, ma solo dei singoli caratteri che la compongono. Se viene trovato uno di tali caratteri la funzione restituisce la sua distanza dall’inizio di str1.Se non ci sono corrispondenze, la funzione restituisce il valore di strlen(str1) o in altre parole la posizione nella stringa del carattere nullo terminale. La funzione strstr()
Pagina 6 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
La funzione strstr() cerca la prima occorrenza in una stringa entro una seconda stringa. Il suo prototipo è il seguente: char *strstr(char *str1, char *str2); La funzione restituisce un puntatore alla prima occorrenza di str2 in str1. Se str2 non compare in str1 la funzione restituisce NULL. Se la lunghezza di str1 è 0, la funzione restituisce str1. Le funzioni strlwr() e strupr() Molte librerie per il C contengono due funzioni che convertono le maiuscole in minuscole e viceversa. Queste funzioni non rientrano nello standard ANSI e possono quindi mancare o essere lievemente diverse in un particolare compilatore. Per i compilatori C della Microsoft, i prototipi delle funzioni sono: char *strlwp(char *str); char *strupr(char *str); La funzione strlwp() pone in minuscolo tutte le lettere di str, mentre la funzione strupr() converte in maiuscolo tutte le lettere. I caratteri diversi dalle lettere dell’alfabeto restano invariati. Entrambe le funzioni restituiscono un puntatore a str. La funzione strrev() La funzione strrev() rovescia l’ordine dei caratteri in una stringa. Il suo prototipo è il seguente: char *strrev(char *str); L’ordine dei caratteri di str viene rovesciato. Il carattere nullo rimane nell’ultima posizione. La funzione restituisce un puntatore a str. La funzione atoi() La funzione atoi() trasforma una stringa in un numero. Il suo prototipo è il seguente: int atoi(char *str); La funzione converte la stringa a cui punta str in un numero intero. Se la funzione non contiene caratteri convertibili restituisce 0. La funzione atol() La funzione atol() seguente:
opera come atoi() solo che restituisce un valore di tipo long. Il suo prototipo è il
long atol(char *str); La funzione atof() La funzione atof() rasforma una stringa in un valore double. Il suo prototipo è il seguente: double atof(char *str); Funzioni che esaminano caratteri Il file di librerie ctype.h contiene i prototipi di numerose funzioni che esaminano un carattere e restituiscono TRUE o FALSE a seconda che il carattere verifichi o meno una data definizione. I prototipi di queste funzioni sono del tipo seguente: int isxxxx(int ch);
Pagina 7 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
L’argomento ch è il carattere da esaminare. La funzione restituisce TRUE (un valore diverso da zero) se ch soddisfa la condizione o FALSE (zero).
Funzione
Descrizione
isalnum()
Restituisce TRUE se ch è una lettera o una cifra
isalpha()
Restituisce TRUE se ch è una lettera
isscii()
Restituisce TRUE se ch è un carattere ASCII standard (compreso tra 0 e 127)
iscntrl()
Restituisce TRUE se ch è un carattere di controllo
isdigit()
Restituisce TRUE se ch è una cifra
isgraph()
Restituisce TRUE se ch è un carattere stampabile diverso da uno spazio
islower()
Restituisce TRUE se ch è una lettera minuscola
isprint()
Restituisce TRUE se ch è un carattere stampabile compreso lo spazio
ispunct()
Restituisce TRUE se ch è un segno di interputazione
isspace()
Restituisce TRUE se ch è un carattere “bianco”,, cioè spazio o tabulazione
isupper()
Restituisce TRUE se ch è una lettera maiuscola
isxdigit()
Restituisce TRUE se ch è una cifra esadecimale
Pagina 8 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 11
Esercizio 1 Scrivere un programma che calcoli la somma di tutti gli elementi di una matrice. Esercizio 2 Scrivere un programma che determini quale è la riga di una matrice cui la somma degli elementi è massima. Esercizio 3 Presa una matrice quadrata a valori interi determinare se si tratta di una matrice magica. Una matrice si dice magica se la somma di ogni riga è uguale alla somma di ogni colonna e questo valore è anche uguale alla somma degli elementi della diagonale principale e alla somma degli elementi della diagonale. Esercizio 4 Scrivere un programma che prese in ingresso 10 parole da un utente dica quali parole si ripetono almeno 2 volte nell’elenco inserito.
Pagina 9 di 9
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 12 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
Esempio: Gioco dell’Impiccato Il gioco L'impiccato è un gioco per due giocatori. Uno dei giocatori sceglie segretamente una parola o una frase; l'altro deve indovinarla entro un certo numero di tentativi. La frase viene mostrata al secondo giocatore mascherata: mostrando al posto delle vocali il simbolo ‘+’ e al posto delle consonanti il simbolo ‘-’, gli altri caratteri (spazi o punteggiatura) rimangono invariati. Il gioco termina quando la parola viene indovinata, o si finiscono i tentativi a disposizione. L’algoritmo risolutivo per il gioco potrebbe essere: 1. Chiedere al primo giocatore di inserire la frase da indovinare 2. Mascherare la frase inserita, sostituendo al posto delle vocali il simbolo ‘+’ e al posto delle consonanti il simbolo ‘-’ 3. Chiedere al secondo giocatore di inserire una lettera 4. Verificare che la lettera inserita sia presente all’interno della frase, se la lettera è presente sostituire il simbolo con la lettera indovinata 5. Stampare la frase mascherata con le eventuali lettere indovinate 6. Se la frase non è stata indovinata vai al punto 3, altrimenti comunica all’utente che ha indovinato la frase 7. Esci dal gioco L’algoritmo sopra descritto non risolve a pieno il gioco in quanto non tiene traccia del numero massimo di tentativi che un giocatore può fare, in particolare quando si supera il numero di tentativi bisognerebbe interrompere il gioco in quanto l’utente ha perso. Per tenere traccia del numero di tentativi si potrebbe utilizzare un contatore incrementato ogni volta che l’utente inserisce una nuova lettera. L’algoritmo potrebbe diventare quindi: 1. Chiedere al primo giocatore di inserire la frase da indovinare 2. Mascherare la frase inserita, sostituendo al posto delle vocali il simbolo ‘+’ e al posto delle consonanti il simbolo ‘-’ 3. Chiedere al secondo giocatore di inserire una lettera 4. Incrementare il contatore di un unità 5. Verificare che la lettera inserita sia presente all’interno della frase, se la lettera è presente sostituire il simbolo con la lettera indovinata 6. Stampare la frase mascherata con le eventuali lettere indovinate 7. Se la frase è stata indovinata comunicare la vittoria e andare al punto 10 8. Se il numero di tentativi (e quindi il valore del contatore) è uguale al massimo dei tentativi disponibili comunica all’utente che ha perso e vai al punto 10 9. Se la frase non è stata indovinata vai al punto 3 10. Esci dal gioco Questo secondo algoritmo però permette di indovinare la frase inserendo esclusivamente lettera per lettera, potrebbe capitare che il numero di lettere diverse in una frase sia superiore al numero di tentativi e quindi il gioco non consentirebbe all’utente di arrivare in fondo e quindi di vincere. La cosa migliore da fare potrebbe essere quella di dare la possibilità all’utente di indovinare la frase ad un certo punto del gioco o meglio di
Pagina 2 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
poter scegliere se inserire una nuova lettera o indovinare la frase. In questo modo è possibile assegnare anche un punteggio sulla base dei tentativi fatti. Il nuovo algoritmo potrebbe essere: 1. Chiedere al primo giocatore di inserire la frase da indovinare 2. Mascherare la frase inserita, sostituendo al posto delle vocali il simbolo ‘+’ e al posto delle consonanti il simbolo ‘-’ 3. Chiedere al secondo giocatore se vuole indovinare la frase o inserire una lettera 4. Se il giocatore vuole indovinare la frase vai al punto 5 altrimenti al punto 7 5. Chiedere all’utente di inserire la frase 6. Se la frase è stata indovinata comunicare la vittoria e andare al punto 14 altrimenti vai al punto 3 7. Chiedere all’utente di inserire una lettera 8. Incrementare il contatore delle lettere inserite di un unità 9. Verificare che la lettera inserita sia presente all’interno della frase, se la lettera è presente sostituire il simbolo con la lettera indovinata 10. Stampare la frase mascherata con le eventuali lettere indovinate 11. Se le lettere sono state tutte scoperte comunicare la vittoria e andare al punto 14 12. Se il numero di tentativi (e quindi il valore del contatore) è uguale al massimo dei tentativi disponibili comunica all’utente che ha perso e vai al punto 14 13. Se la frase non è stata indovinata vai al punto 3 14. Esci dal gioco Questo algoritmo però consentirebbe ad un utente di provare all’infinito di indovinare la frase, senza mai inserire una lettera, potremo quindi tenere traccia anche del numero di tentativi di indovinare la frase tutto in un colpo. L’algoritmo finale potrebbe essere: 1. Chiedere al primo giocatore di inserire la frase da indovinare 2. Mascherare la frase inserita, sostituendo al posto delle vocali il simbolo ‘+’ e al posto delle consonanti il simbolo ‘-’ 3. Chiedere al secondo giocatore se vuole indovinare la frase o inserire una lettera 4. Se il giocatore vuole indovinare la frase vai al punto 5 altrimenti al punto 8 5. Chiedere all’utente di inserire la frase 6. Se la frase è stata indovinata comunicare la vittoria e andare al punto 15 altrimenti vai al punto 7 7. Dato che la frase non è stata indovinata incrementare il contatore che tiene traccia dei tentativi, se tale numero supera il massimo consentito comunicare all’utente che ha perso e andare al punto 15 altrimenti andare al punto 3 8. Chiedere all’utente di inserire una lettera 9. Incrementare il contatore delle lettere inserite di un unità 10. Verificare che la lettera inserita sia presente all’interno della frase, se la lettera è presente sostituire il simbolo con la lettera indovinata 11. Stampare la frase mascherata con le eventuali lettere indovinate 12. Se le lettere sono state tutte scoperte comunicare la vittoria e andare al punto 15 13. Se il numero di tentativi (e quindi il valore del contatore) è uguale al massimo dei tentativi disponibili comunica all’utente che ha perso e vai al punto 15 14. Se la frase non è stata indovinata vai al punto 3
Pagina 3 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
15. Esci dal gioco Sicuramente potrebbero essere fatte molte altre migliorie, però per implementare il gioco di base questo ultimo algoritmo dovrebbe rispondere a tutte le esigenze.
Implementazione l’algoritmo Suddivisione in sottoproblemi L’ultimo algoritmo potrebbe essere scomposto in 3 sotto problemi: 1. Richiesta e lettura della frase da indovinare 2. Sostituzione delle vocali con il carattere ‘+’ e delle consonanti con il carattere ‘-’ 3. Ricerca di una lettera all’interno di una frase Richiesta e lettura della frase da indovinare Il linguaggio C non ha un tipo di dato primitivo per la gestione delle stringhe, infatti esse vengono considerate come una sequenza di caratteri con l’aggiunta di un carattere speciale di terminazione \0 per individuarne la fine. Quindi per esempio volendo memorizzare la frase “ciao mondo” in un array di 15 caratteri il risultato che otterremo sarà: c
i
a
o
0
1
2
3
4
m
o
n
d
o
\0
5
6
7
8
9
10
11
12
13
14
Ipotizzando che la frase da indovinare non superi i 20 caratteri (incluso spazi, caratteri di punteggiatura...) per la memorizzazione della stringa dovremo dichiarare un array di caratteri di dimensione 21: 20 per memorizzare la stringa e 1 per il carattere terminatore: char frase[21]; L’iterazione con l’utente è molto importante e quindi bisogna comunicare attraverso delle stampe a video le istruzioni che dovranno essere eseguite, quindi attraverso una funzione printf(): printf(“Inserire la frase da indovinare e premere INVIO: ”); La lettura della frase inserita dall’utente potrebbe essere fatta attraverso la funzione scanf() utilizzando il carattere di conversione %s apposito per le stringhe: scanf(“%s”,frase); Da notare che all’interno della chiamata alla funzione scanf() la variabile “frase” non viene preceduta dall’operatore d’indirizzo & (e-commerciale) in quanto tale operatore va utilizzato esclusivamente per le variabili ti tipo primitivo.
Pagina 4 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
#include int main() { char frase[21]; printf(“Inserire la frase da indovinare e premere INVIO: ”); scanf(“%s”,frase); return 0; } Questa soluzione non è ottimale in quanto utilizzando il carattere di conversione %s la funzione scanf() considera come fine della frase NON solo la pressione del tasto INVIO ma anche il carattere spazio. Quindi inserendo la frase: “ciao mondo”, il programma memorizzerà all’interno dell’array “frase” solamente la parola “ciao”. Per ovviare a questo problema potremo utilizzare il carattere di conversione %[^...] il quale ci consente di specificare quali caratteri non sono ammessi nella lettura e quindi quelli che verranno considerati come fine stringa (al posto dei puntini vanno inseriti i caratteri non ammessi). In questo caso l’unico carattere non ammesso è l’INVIO (o meglio “nuova linea” o ritorno a carrello) individuato dal carattere speciale \n: scanf(“%[^\n]”,frase); La funzione scanf() non è l’unica funzione che consente la lettura da tastiera, infatti esistono altre funzioni per l’input di riga, ad esempio gets(). Tale funzione si limita a leggere una riga da stdin e la memorizza in una stringa (array di caratteri). La funzione legge caratteri finché non incontra un carattere di nuova riga (\n) o la fine della riga: gets(frase); In questo modo tutti i caratteri inseriti, spazi inclusi verranno letti e memorizzati all’interno dell’array frase. Potemmo ottimizzare il nostro programma inserendo una costante per gestire la dimensione per la stringa dato che tale dimensione sicuramente dovrà essere utilizzata in altri punti del nostro codice. Quindi il sorgente potrebbe essere: #include #define LUNGHEZZA_FRASE 20 int main() { char frase[LUNGHEZZA_FRASE+1]; printf(“Inserire la frase da indovinare e premere INVIO: ”); gets(frase); return 0; } Potrebbe capitare che un utente inserisca una frase con un numero di caratteri maggiore di 20, per evitare tale problema potremo stampare a video il limite massimo consentito ma questo non ci garantirebbe al 100% che l’inserimento da parte dell’utente risulti corretto. Sia la funzione scanf() che la funzione gets() entrano in azione SOLO dopo la pressione del tasto INVIO, accedendo al buffer di lettura fino a trovare il carattere terminatore. Per ovviare al problema potremo utilizzare la funzione getchar() la quale ci consente di leggere un carattere alla volta dal buffer. La funzione getchar() attende la ricezione di un carattere da stdin. Poichè getchar() è una funzione di input con buffer, non viene ricevuto alcun carattere finché l’utente non preme INVIO. Tuttavia ogni tasto premuto viene immediatamente mostrato sullo schermo. Quando si preme INVIO, tutti i caratteri immessi, incluso
Pagina 5 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
quello di nuova riga vengono inviati a stdin dal sistema operativo. La funzione getchar() restituisce i caratteri uno alla volta: i = 0; while ((ch = getchar()) != frase[i++] = ch;
13 && i < LUNGHEZZA_FRASE)
frase[i] = ‘\0’; I caratteri di volta in volta letti vengono inseriti all’interno dell’array fino ad arrivare al carattere terminatore o alla dimensione massima dell’array. In questo modo riusciamo ad evitare che all’interno dell’array “frase” che ci siano più caratteri di quelli consentiti ma non ad impedire che l’utente scriva più di 20 caratteri. Per evitare questo ultimo problema potremo utilizzare le funzioni getch() e getche() le quali consentono di leggere direttamente il carattere successivo dal flusso stdin. In poche parole man mano che l’utente inserisce i caratteri questi vengono letti dalla funzione. Utilizziamo la funzione getche() in quanto funzione con eco cioè mostra i caratteri letti nel flusso stdout: i = 0; while ((ch = getche()) != 13 && i < LUNGHEZZA_FRASE) frase[i++] = ch; frase[i] = ‘\0’; L’utente potrebbe inserire una frase vuota cioè quindi premere direttamente INVIO, ovviamente questa cosa non dovrebbe essere consentita. Potremo risolvere anche quest’ultima problematica controllando il valore della variabile “i” dopo il ciclo while: se la variabile vale 0 allora non sono stati inseriti caratteri utili: i = 0; do { while ((ch=getche()) != 13 && i < LUNGHEZZA_FRASE) frase[i++] = ch; if (i == 0) printf(“Hai inserito una frase vuota! \n”); } while(i == 0); frase[i] = ‘\0’; Il tutto viene inserito all’interno di un ciclo do while per ripetere l’operazione fino a quando l’utente non inserirà un valore corretto. Per fare in modo che l’utente possa decidere ad un certo punto dell’inserimento della frase di uscire dal programma potremo considerare per esempio che alla pressione del tasto - (meno) il programma termini. Per ottenere questo effetto dovremo controllare di volta in volta il carattere inserito dall’utente: i = 0; do { while ((ch=getche()) != ‘\n’ && ch != ‘\r’ && i < LUNGHEZZA_FRASE) { if (ch == ‘-’) exit(0); frase[i++] = ch; } if (i == 0) printf(“Hai inserito una frase vuota! \n”);
Pagina 6 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
} while(i == 0); frase[i] = ‘\0’; Quindi il programma per l’inserimento della frase diventa: #include #include #include #define LUNGHEZZA_FRASE 20 int main() { char frase[LUNGHEZZA_FRASE+1]; int i; char ch; printf(“Inserire la frase da indovinare e premere INVIO [- esci]: ”); i = 0; do { while ((ch=getche()) != 13 && i < LUNGHEZZA_FRASE) { if (ch == ‘-’) exit(0); frase[i++] = ch; } if (i == 0) printf(“Hai inserito una frase vuota! \n”); } while(i == 0); frase[i] = ‘\0’; return 0; } Sostituzione delle vocali e delle consonanti Per poter sostituire tutte le vocali con il carattere ‘+’ e tutte le consonanti con il carattere ‘-’, dovremo accedere singolarmente ad ogni cella dell’array contenente la stringa per valutarne ogni singolo carattere: for (i = 0; i < LUNGHEZZA_FRASE; i++) { if (frase[i] == ‘a’ || frase[i] == ‘e’ || frase[i] == ‘i’ || frase[i] == ‘o’ || frase[i] == ‘u’) frase[i] = ‘+’; else frase[i] = ‘-’; } Invece di utilizzare un’istruzione if per i controlli potremo utilizzare un’istruzione switch: for (i = 0; i < LUNGHEZZA_FRASE; i++)
Pagina 7 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
{ switch(frase[i]) { case ‘a’: case ‘e’: case ‘i’: case ‘o’: case ‘u’: frase[i] = ‘+’; break; default: frase[i] = ‘-’; } Le soluzioni presentate in precedenza non sono ottimali in quanto vengono considerate indipendentemente dalla lunghezza della frase tutte le celle presenti all’interno dell’array. Per ovviare al problema potremo inserire un controllo ulteriore all’interno della condizione del for per valutare che la cella considerata non contenga il carattere terminatore della stringa: for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { if (frase[i] == ‘a’ || frase[i] == ‘e’ || frase[i] == ‘i’ || frase[i] == ‘o’ || frase[i] == ‘u’) frase[i] = ‘+’; else frase[i] = ‘-’; } Se l’utente inserisce lettere maiuscole, dato che il linguaggio C è case sensitive e quindi il carattere ‘a’ minuscolo risulterà diverso dal carattere ‘A’ maiuscolo, il programma potrebbe rispondere in maniera errata. Potremo quindi all’interno dell’if o dello switch considerare tutti i caratteri (in questo caso le vocali) anche in maiuscolo oppure utilizzando la funzione tolower() potremo trasformare per la verifica tutti i caratteri in minuscolo: for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { carattere = tolower(frase[i]); if (carattere == ‘a’ || carattere == ‘e’ || carattere == ‘i’ || carattere == ‘o’ || carattere == ‘u’) frase[i] = ‘+’; else frase[i] = ‘-’; } La nuova soluzione presenta comunque ancora degli errori in termini di algoritmo: inserendo una frase con spazi o caratteri speciali questi verranno sostituiti con il simbolo ‘-’ in quanto l’unico controllo è quello che valuta se il carattere corrisponde ad una vocale. Le soluzioni a questo secondo problema possono essere varie: potremo inserire altri controlli all’interno dell’istruzione else per valutare se il carattere è una costante oppure potremo creare all’interno dello switch tanti case uno per ogni costante (allo stesso modo che è stato fatto per le vocali). Dato che i caratteri vengono memorizzati attraverso dei numeri descritti attraverso la tabella ASCII, e dato che le lettere dell’alfabeto (in minuscolo) all’interno di tale tabella hanno codici che variano da 97 a 122 il programma potrebbe essere ottimizzato nel seguente modo:
Pagina 8 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { carattere = tolower(frase[i]); if (frase[i] >= 97 && frase[i] <= 122) { if (carattere == ‘a’ || carattere == ‘e’ || carattere == ‘i’ || carattere == ‘o’ || carattere == ‘u’) frase[i] = ‘+’; else frase[i] = ‘-’; } } In questo modo verranno sostituite solamente le lettere appartenenti all’alfabeto. Rimangono però escluse tutte le lettere accentate. Per gestire anche questa problematica dovremmo utilizzare variabili di tipo “unsigned char” in quanto tali caratteri fanno parte della tabella ASCII estesa. Comunque per evitare di complicare troppo il programma non considereremo le lettere accentate. Ricerca di una lettera all’interno di una frase Per trovare tutte le occorrenze di una lettera all’interno di una frase bisogna eseguire dei confronti singolarmente con tutti i caratteri della stringa data. Considerando quanto detto nei paragrafi precedenti, la stringa inserita dall’utente viene modificata sostituendo le varie lettere dell’alfabeto con dei caratteri speciali, quindi non sarà più possibile per il programma conoscere quali lettere effettivamente saranno presenti all’interno della frase dopo le varie modifiche a meno che non esita una copia della frase iniziale o la gestione della stringa crittografata, con i simboli ‘+’ e ‘-’, non venga fatta con un altro array: for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { carattere = tolower(frase[i]); if (frase[i] >= 97 && frase[i] <= 122) { if (carattere == ‘a’ || carattere == ‘e’ || carattere == ‘i’ || carattere == ‘o’ || carattere == ‘u’) fraseCrittografata[i] = ‘+’; else fraseCrittografata [i] = ‘-’; } else fraseCrittografata[i] = frase[i]; } fraseCrittografata[i] = ‘\0’; La variabile “fraseCrittografata” è un array della stessa dimensione e tipologia dell’array “frase”. Ora con questa modifica, presa una lettera da cercare dall’utente potremo individuare tutte le occorrenze e sostituire ogni volta nella frase crittografata la lettera indovinata: printf(“Iserire una lettera e premere INVIO: ”); scanf(“%c”,&lettera); for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { if (tolower(frase[i]) == tolower(lettera))
Pagina 9 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
fraseCrittografata[i] = frase[i]; } Da notare che nella funzione scanf() la variabile “lettera” viene preceduta dall’operatore indirizzo ecommericale (&) e che all’interno del for i confronti delle lettere vengono fatti considerando le lettere in minuscolo sempre per evitare il problema della gestione case sensitive. Per evitare che l’utente debba premere INVIO dopo ogni inserimento potremo utilizzare la funzione getche() descritta in precedenza. Inoltre dato che possono essere inseriti caratteri speciali diversi dalle lettere dell’alfabeto potremo eseguire un controllo per verificare la correttezza dell’inserimento: do { printf(“Iserire una lettera: ”); fflush(stdin); lettera = tolower(getche()); if (lettera < 97 || lettera > 122) printf(“Errore di inserimento: carattere non valido\n”); } while(lettera < 97 || lettera > 122); for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { if (tolower(frase[i]) == lettera) fraseCrittografata[i] = frase[i]; } Per poter comunicare all’utente se la lettera inserita è presente o meno all’interno della frase nascosta bisogna valutare al di fuori del corpo del for se si è entrati almeno una volta all’interno del if che confronta la lettera inserita con l’i-esima della frase. Questo tipo di controllo potrebbe essere fatto con una variabile impostata ad un certo valore prima del for e modificata esclusivamente all’interno del corpo dell’istruzione if. Se si accede al corpo dell’if, e quindi se esiste almeno un occorrenza della lettera inserita dall’utente all’interno della frase, allora la variabile verrà modificata altrimenti rimarrà con il suo valore iniziale: trovato = 0; for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { if (tolower(frase[i]) == lettera) { fraseCrittografata[i] = frase[i]; trovato = 1; } } if (trovato) printf(“Lettera trovata!”); else printf(“La lettera non è presente nella frase!”);
Pagina 10 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
Ottimizzazione dei sottoproblemi in relazione al programma Dato che bisogna dare la possibilità ad un utente di indovinare la frase oltre che inserire una lettera o eventualmente di uscire dal gioco, potremo associare queste funzionalità alla pressione di tasti speciali quindi considerare che se premendo il tasto ‘-’ (meno) si esce dal gioco, premendo il tasto ‘?’ è possibile dare la soluzione, altrimenti viene considerata la lettera inserita e fatta una ricerca all’interno della frase: do { printf(“Iserire una lettera [- esci; ? indovina]: ”); fflush(stdin); lettera = tolower(getche()); if (lettera == ‘-’) exit(0); if (lettera != ‘?’ && (lettera < 97 || lettera > 122)) printf(“Errore di inserimento: carattere non valido\n”); } while(lettera != ‘?’ && (lettera < 97 || lettera > 122)); if (lettera == ‘?’) { printf(“Inserisci la frase nascosta e premere INVIO: ”); i = 0; while ((ch =getche()) != 13 && i < LUNGHEZZA_FRASE) fraseUtente[i++] = ch; fraseUtente[i] = '\0'; if (strcmp(strlwr(frase), strlwr(fraseUtente)) == 0) { printf(“Hai indovinato!”); exit(0); } else printf(“Frase non corretta!”); } else { /*parte dedicata alla ricerca della lettera*/ .... } La funzione strlwr() consente di trasformare una stringa tutta con caratteri in minuscolo, mentre la funzione strcmp() consente di confrontare due stringhe. L’inserimento della frase da parte dell’utente viene gestito come all’inizio del programma per inserire la frase da indovinare, per gli stessi motivi descritti in precedenza. Volendo limitare il numero di tentativi per indovinare la frase e un numero massimo di lettere da poter inserire dovranno essere utilizzare due contatori rispettivamente per il numero di lettere inserite e il numero di tentativi di indovinare. Tali contatori verranno di volta in volta confrontati con i massimi consentiti (tutta la parte del gioco viene inserita all’interno di un ciclo per ripetere gli inserimenti fino a quando l’utente non raggiunge i limiti dei tentativi o indovina la frase o decide di uscire dal gioco): numeroLettere = 0; numeroTentativi = 0; do {
Pagina 11 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
do { printf(“Iserire una lettera [- esci; ? indovina]: ”); fflush(stdin); lettera = tolower(getche()); if (lettera == ‘-’) exit(0); if (lettera != ‘?’ && (lettera < 97 || lettera > 122)) printf(“Errore di inserimento: carattere non valido\n”); } while(lettera != ‘?’ && (lettera < 97 || lettera > 122)); if (lettera == ‘?’) { printf(“Inserisci la frase nascosta e premere INVIO: ”); while ((ch =getche()) != 13 && i < LUNGHEZZA_FRASE) fraseUtente[i++] = ch; fraseUtente[i] = '\0'; numeroTentativi++; if (strcmp(strlwr(frase), strlwr(fraseUtente)) == 0) { printf(“Hai indovinato!”); exit(0); } else printf(“Frase non corretta!”); if (numeroTentativi >= MASSIMO_TENTATIVI) { printf(“Hai esaurito i tentativi a tua disposizione!”); exit(0); } } else { numeroLettere++; trovato = 0; for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { if (tolower(frase[i]) == lettera) { fraseCrittografata[i] = frase[i]; trovato = 1; } } if (trovato) printf(“Lettera trovata!”); else printf(“La lettera non è presente nella frase!”); printf(“\n%s”, fraseCrittografata); if (numeroLettere >= MASSIMO_INSERIMENTI) { printf(“Hai raggiunto il numero massimo di inserimenti!”); printf(“Prova a indovinare la frase nascosta: ”); i = 0; while ((ch=getche())!=‘\n’ && ch!=‘\r’ && i
Pagina 12 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
fraseUtente[i++] = ch; if (strcmp(strlwr(frase), strlwr(fraseUtente)) == 0) printf(“Hai indovinato!”); else printf(“Frase non corretta! Hai perso!”); exit(0); } } } while(1); Volendo fare le cose a modo, bisognerebbe che il numero massimo di inserimenti possibili risulti uguale al massimo al numero di lettere differenti presenti all’interno della frase: int elencoLettereAlfabeto[26]; int numeroMassimoInserimentiLettere; .../*altro codice*/ for (i = 0; i < 26; i++) elencoLettereAlfabeto[i] = 0; numeroMassimoInserimentiLettere = 0; for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != ‘\0’; i++) { lettera = tolower(frase[i]); if (lettera >= 97 && lettera <= 122) { if (elencoLettereAlfabeto[lettera-97] != 1) numeroMassimoInserimentiLettere++; elencoLettereAlfabeto[lettera-97] = 1; } } dove “elendoLettereAlfabeto” è un array di interi, ogni cella associata logicamente ad una lettera dell’alfabeto. Tale array verrà utilizzato per tenere traccia delle lettere della frase già considerate nel conteggio.
Unione dei sottoproblemi per ottenere il programma Sicuramente potrebbero essere fatte molte altre migliorie al programma, comunque quanto detto risulta più che sufficiente per creare una buona soluzione: #include #include #include #include #include
#define LUNGHEZZA_FRASE 20 #define MASSIMO_TENTATIVI 3 int main() { char fraseCrittografata[LUNGHEZZA_FRASE+1]; char frase[LUNGHEZZA_FRASE+1]; char fraseUtente[LUNGHEZZA_FRASE+1];
Pagina 13 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com int i, trovato; char carattere, lettera, ch; int numeroMassimoInserimentiLettere; int numeroLettere, numeroTentativi; int elencoLettereAlfabeto[26]; system("cls"); printf("Inserire la frase da indovinare e premere INVIO [- esci]:\n"); i = 0; do { fflush(stdin); while ((ch=getche()) != 13 && i < LUNGHEZZA_FRASE) { if (ch == '-') { exit(0); system("pause"); } frase[i++] = ch; } if (i == 0) printf("\nHai inserito una frase vuota! \n"); } while(i == 0); frase[i] = '\0'; for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != '\0'; i++) { carattere = tolower(frase[i]); if (frase[i] >= 97 && frase[i] <= 122) { if (carattere == 'a' || carattere == 'e' || carattere == 'i' || carattere == 'o' || carattere == 'u') fraseCrittografata[i] = '+'; else fraseCrittografata [i] = '-'; } else fraseCrittografata[i] = frase[i]; } fraseCrittografata[i] = '\0'; for (i = 0; i < 26; i++) elencoLettereAlfabeto[i] = 0; numeroMassimoInserimentiLettere = 0; for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != '\0'; i++) { lettera = tolower(frase[i]); if (lettera >= 97 && lettera <= 122) { if (elencoLettereAlfabeto[lettera-97] != 1) numeroMassimoInserimentiLettere++; elencoLettereAlfabeto[lettera-97] = 1; } } numeroLettere = 0; numeroTentativi = 0; system("cls"); do {
Pagina 14 di 16
Dispensa 12
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 12
do { printf("\n\nFrase da indovinare: \n%s\n\n", fraseCrittografata); printf("Iserire una lettera [- esci; ? indovina]: "); fflush(stdin); lettera = tolower(getche()); if (lettera == '-') { exit(0); system("pause"); } if (lettera != '?' && (lettera < 97 || lettera > 122)) printf("\nErrore di inserimento: carattere non valido\n"); } while(lettera != '?' && (lettera < 97 || lettera > 122)); if (lettera == '?') { printf("\nInserisci la frase nascosta e premere INVIO: "); i = 0; while ((ch =getche()) != 13 && i < LUNGHEZZA_FRASE) fraseUtente[i++] = ch; fraseUtente[i] = '\0'; numeroTentativi++; if (strcmp(strlwr(frase), strlwr(fraseUtente)) == 0) { printf("\nHai indovinato!"); system("pause"); exit(0); } else printf("\nFrase non corretta!"); if (numeroTentativi >= MASSIMO_TENTATIVI) { printf("\nHai esaurito i tentativi a tua disposizione!"); system("pause"); exit(0); } } else { numeroLettere++; trovato = 0; for (i = 0; i < LUNGHEZZA_FRASE && frase[i] != '\0'; i++) { if (tolower(frase[i]) == lettera) { fraseCrittografata[i] = frase[i]; trovato = 1; } } if (trovato) printf("\nLettera trovata!"); else printf("\na lettera non è presente nella frase!"); if (numeroLettere >= numeroMassimoInserimentiLettere) { printf("\nHai raggiunto il numero massimo di inserimenti!"); printf("\nProva a indovinare la frase nascosta: "); i = 0; while ((ch=getche())!=13 && i
Pagina 15 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com if (strcmp(strlwr(frase), strlwr(fraseUtente)) == 0) printf("\nHai indovinato!"); else printf("\nFrase non corretta! Hai perso!"); system("pause"); exit(0); } } } while(1); return 0; }
Pagina 16 di 16
Dispensa 12
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 13 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
13.1
I Puntatori
13.1.1
La memoria del computer
Dispensa 13
La memoria RAM di un PC consiste di molte migliaia di locazioni di memoria sequenziali, ciascuna identificata da un indirizzo unico. Questi indirizzi sono compresi tra 0 e un massimo che dipende dalla quantità di memoria installata. Quando si utilizza un computer, il sistema operativo occupa una parte di questa memoria. Quando si avvia un programma, il suo codice (ovvero le sue istruzioni in linguaggio macchina) e i suoi dati (ovvero le informazioni che il programma sta elaborando) occupano delle altre zone di questa memoria. Quando si dichiara una variabile in un programma in C, il compilatore riserva una locazione di memoria puntata da un indirizzo in modo da immagazzinarvi in seguito un valore di una variabile. Quindi, in effetti, il compilatore associa un indirizzo al nome della variabile stessa. Quando il programma utilizza il nome di quella variabile, accede automaticamente alla locazione di memoria relativa. In questa operazione viene utilizzato l’indirizzo della locazione di memoria, ma esso viene nascosto all’utente perché in genere non lo interessa. La figura riportata sotto mostra questo procedimento in maniera schematica: viene dichiarata e inizializzata al valore 100 una variabile di nome “rate”. Il compilatore ha riservato per tale variabile una locazione di memoria che parte dall’indirizzo 1004 e ha associato al nome “rate” l’indirizzo 1004. 1001
1002
1003
1004
1005
100
rate
13.1.2
Creazione di un puntatore
Si sarà notato che l’indirizzo della variabile “rate”(e qualsiasi altra variabile) è un numero che, in quanto tale può essere gestito dalle istruzioni C. Se si conosce l’indirizzo di una variabile è possibile creare una nuova variabile che lo contenga. Il primo passo è quello di dichiarare la variabile che conterrà l’indirizzo di “rate”. Le si da il nome “p_rate” ad esempio. All’inizio questa variabile non è inizializzata; le è stato riservato uno spazio in memoria, ma il valore al suo interno è indeterminato. 1001
1002
1003
?
1004
1005
100
rate
p_rate
Il passo successivo è quello di memorizzare l’indirizzo della variabile “rate” nella variabile “p_rate”. Dato che a quel punto la variabile “p_rate” contiene l’indirizzo di “rate”. La prima variabile indicherà la posizione di memoria dov’è stata dislocata “rate”. 1001
1002
1003
1004
1004 100
rate
p_rate Pagina 2 di 11
1005
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
In C si adotta una terminologia particolare e si dice che “p_rate punta a rate”, oppure che “p_rate” è un puntatore a “rate”. In fine un puntatore è una variabile che contiene l’indirizzo di un’altra variabile.
13.1.3
Dichiarazioni di puntatori
Nell’esempio prima descritto, il puntatore puntava ad una variabile scalare (non ad un array). I puntatori sono variabili numeriche, e come tali, devono essere dichiarati prima dell’uso, I nomi dei puntatori seguono le stesse regole di quelli delle altre variabili e devono essere unici. Come convenzione negli esercizi che vedremo e a lezione, adotteremo che un puntatore a una variabile di nome “nome” verrà chiamato “p_nome”. Questo non è assolutamente richiesto dal C; per il compilatore i puntatori possono chiamarsi in qualsiasi modo (rispettando comunque le regole del linguaggio). La dichiarazione di un puntatore ha il formato seguente: tipovariabile
*numepuntatore;
“tipovariabile” è un qualsiasi tipo di variabile riconosciuto dal C e indica il tipo della variabile cui punta il puntatore. L’asterisco (*) è detto operatore di rinvio e indica che “nomepuntatore” è un puntatore di tipo “tipovariabile”. I puntatori possono essere dichiarati assieme alle altre variabili. Ecco alcuni esempi: char *ch1, *ch2; /*ch1 e ch2 sono entrambi puntatori al tipo char float *valore, percento; /* valore è un puntatore al tipo float, e percento è una normale variabile float*/ Il simbolo * viene utilizzato sia come operatore di rinvio sia come operatore aritmetico per la moltiplicazione. Non c’è da preoccuparsi che il compilatore si confonda, poiché il contesto in cui si trova il simbolo fornisce abbastanza informazioni da distinguere i due diversi tipi di utilizzo.
13.1.4
Inizializzazione dei puntatori
I puntatori sono inutili finché non li si fa puntare a qualche cosa. Come per le variabli normali, è possibile utilizzare puntatori non inizializzati ottenendo risultati imprevedibili e potenzialmente disastrosi. Gli indirizzi non vengono inseriti nei puntatori per magia, è il programma che si deve occupare di inserirveli utilizzando questa volta l’operatore di indirizzo di “e” commerciale (&). Questo viene posto prima del nome di una variabile, questo operatore ne restituisce l’indirizzo. Perciò è possibile inizializzare un puntatore con un’istruzione del tipo: puntatore = &variabile; Se torniamo all’esempio di prima della variabile “rate”; l’istruzione che inizializzava la variabile “p_rate” in modo che punti a “rate” potrebbe essere scritta come: p_rate = &rate;
13.1.5
Uso dei puntatori
L’operatore di rinvio (*) torna in azione quando questo precede il nome di un puntatore, in questo caso il programma di riferisce alla variabile puntata. Si prenda l’esempio precedete, in cui il puntatore “p_rate” è stato impostato in modo da puntare alla variabile “rate”. Scrivendo *p_rate ci si riferisce alla variabile “rate”. Se si vuole stampare il valore contenuto in “rate” (che nell’esempio era 100) si ha la possibilità di scrivere: printf(“%d”, rate);
Pagina 3 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
oppure printf(“%d”, *p_rate); In C queste due istruzioni sono equivalenti. L’accesso al contenuto di una variabile utilizzando il suo nome si chiama accesso diretto, mentre quando si utilizza un puntatore si parla di accesso indiretto o rinvio. 1001
1002
1003
1004
1004
1005
100
rate
p_rate
*p_rate
Se si ha un puntatore di nome ptr inizializzato in modo da puntare alla variabile var, le frasi seguenti sono vere: • •
*ptr e var si riferiscono entrambe al contenuto do var (cioè a qualsiasi valore il programma abbia memorizzato in quella locazione) ptr e &var si riferiscono entrambe all’indirizzo di var
Un puntatore non preceduto dall’operatore di rinvio viene valutato per il suo contenuto, che è logicamente l’indirizzo della variabile puntata. Vediamo un esempio di un programma: #include /*dichiara e inizializza una variabile di tipo int*/ int var = 1; /*dichiara un puntatore a int*/ int *ptr; main() { /*inizializza ptr in modo che punti a var*/ ptr = &var; /*si accede a var in modo diretto e indiretto*/ printf(“\nAccesso diretto, var = %d”,var); printf(“\nAccesso indiretto, var = %d”,*ptr); /*mostriamo l’indirizzo di var in due modi*/ printf(“\n\nIndirizzo di var = %d”,&var); printf(“\nIndirizzo di var = %d”,ptr); return 0; }
Ecco l’output del programma : Accesso diretto, var = 1 Accesso indiretto, var = 1 Indirizzo di var = 4264228 Indirizzo di var = 4264228
Pagina 4 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
E’ molto probabile che sul sistema del lettore l’indirizzo di var non sia 4264228!!
13.1.6
Puntatori e tipi di variabili
La trattazione di prima ha ignorato volutamente il fatto che diversi tipi di variabili occupano diversi quantitativi di memoria. Nella maggior parte dei sistemi operativi per PC, un int occupa 2 byte, un float 4 byte e così via. Ogni singolo byte di memoria ha un proprio indirizzo, per cui una variabile che impiega più byte occupa diversi indirizzi. Come si comportano i puntatori nel caso di variabili di più byte? L’indirizzo di una variabile è l’indirizzo del primo (o più basso) byte che essa occupa. Questo può essere illustrato con un esempio; si supponga di dichiarare e inizializzare le tre variabili seguenti: int vint = 12252; char vchar = ‘a’;
/*ovviamente in memoria troveremo il codice ASCII del carattere*/ float vfloat = 1200.156004; Queste variabili vengono memorizzate come nella figura riportata sotto: la variabile int occupa 2 byte, la char ne occupa uno solo e la float quattro. vint 1000
1001
vchar 1002
12252
1003
vfloat 1004
1005
1006
97
1007
1008
1009
1010
1011
1200.156004
Ora dichiariamo e inizializziamo i puntatori a queste variabili: int *p_vint; char *p_vchar; float *p_vfloat; p_vint = &vint; p_vchar = &vchar; p_vfloat = &vfloat; Ogni puntatore contiene l’indirizzo del primo byte della variabile puntata, perciò “p_vint” vale 1000, “p_char” vale 1003 e “p_float” vale 1006. Si ricordi però che ogni puntatore è stato dichiarato in modo da puntare un certo tipo di variabile. Il compilatore sa che un puntatore a un int contiene l’indirizzo del primo di due byte, che un puntatore a un float contiene l’indirizzo del rimo di quattro byte e così via. vint 1000
1001
12252
vchar 1002
1003
vfloat 1004
1005
1006
97
1007
1008
1200.156004
p_vint
p_char
p_float
(2 byte a partire da 1000)
(1 byte a partire da 1003)
(4 byte a partire da 1006)
Pagina 5 di 11
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
13.1.7
Dispensa 13
Puntatori e array
I puntatori possono essere molto utili quando si lavora con variabili scalari, ma sono di sicuro più utili con gli array. C’è una relazione particolare tra puntatori e array in C, infatti quando si utilizza la notazione con gli indici degli array tra parentesi quadre si stanno utilizzando dei puntatori senza neanche saperlo. Il nome di un array senza parentesi quadre è un puntatore al primo elemento dell’array. Perciò se si è dichiarato un array di nome data[], “data” è l’indirizzo del primo elemento dell’array. Ma non si era detto che per avere un indirizzo è necessario l’operatore &? In effetti è possibile utilizzare anche l’espressione &data[0] per ottenere l’indirizzo del primo elemento dell’array. In C la relazione (data == &data[0]) è sempre vera. In pratica il nome di un array non è altro che un puntatore all’array stesso. Si tenga però presente che viene visto come una costante: non può essere modificato e rimane fisso per tutta la durata del programma. Eè tuttavia possibile dichiarare un altro puntatore e d inizializzarlo in maniera che punti all’array. Ad esempio il codice seguente inizializza il puntatore “p_array” all’indirizzo del primo elemento dell’array: int array[1000], *p_array; p_array = array; Dato che “p_array” è un puntatore variabile, può essere modificato e può puntare ovunque. A differenza di “array”, “p_array” non è costretto a puntare al primo elemento di array[].
13.1.8
Aritmetica dei puntatori
Quando si ha un puntatore al primo elemento di un array, questo deve essere incrementato del numero di byte necessario per il tipo di dato contenuto nell’array. Per fare questo tipo di operazione viene utilizzata l’aritmetica dei puntatori. Incrementando un puntatore, si incrementa il suo indice. Ad esempio, quando si incrementa un puntatore di un unità, l’aritmetica dei puntatori incrementa automaticamente l’indirizzo contenuto nel puntatore in modo che punti all’elemento successivo dell’array considerato. In altre parole il C conosce (dalla dichiarazione) il tipo di dato puntato dal puntatore e incrementa l’indirizzo in base alla sua dimensione. Si supponga che “ptr_to_int” sia un puntatore a un elemento di un array di dtipo int, Con l’istruzione: ptr_to_int++; il valore di “ptr_to_int” viene incrementato della dimensione del tipo int, generalmente 2 byte, per cui ptr_to_int nel momento immediatamente successivo all’istruzione punta all’elemento seguente. Aggiungendo n ad un puntatore, il C incrementa l’indirizzo contenuto al suo interno del numero di byte necessari per puntare all’elemento dell’array che segue di n posizioni. Perciò: ptr_to_int += 4; incrementa il valore contenuto in “ptr_to_int” di 8 (sempre supponendo che un int sia lungo 2 byte). Gli stessi concetti appenda descritti per l’incremento valgono anche per il decremento di puntatori, che equivale all’aggiunta di un valore negativo e, in quanto tale, ricade nell’ambito degli incrementi. Vediamo un esempio di utilizzo dell’aritmetica dei puntatori per accedere agli elementi di un array:
#include #define MAX 10 /*dichiaro e inizializzo un array di interi*/
Pagina 6 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
int i_array[MAX] = {0,1,2,3,4,5,6,7,8,9}; /*dichiaro un puntatore a int e una variabile int*/ int *i_ptr, count; /*dichiaro e inizializzo un puntatore a float*/ float f_array[MAX] = {0.0,0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9}; /*dichiaro un puntatore a float */ float *f_ptr; main() { /*inizializzo i puntatori*/ i_ptr = i_array; f_ptr = f_array; /*Stampo gli elementi dell’array*/ for (count = 0; count < MAX; count++) printf(“%d\t%f\n”,*i_ptr++,*f_ptr++) return 0; }
Ecco l’output del programma 0 1 2 3 4 5 6 7 8 9
0.000000 0.100000 0.200000 0.300000 0.400000 0.500000 0.600000 0.700000 0.800000 0.900000
L’ultima operazione consentita dall’aritmetica dei puntatori è la cosiddetta differenziazione ovvero la sottrazione tra due puntatori. Se si hanno due puntatori che puntano a due differenti elementi dello stesso array, è possibile sottrarli per sapere quanto distano l’uno dall’altro. Ancora un avolta l’aritmetica dei puntatori adatta la risposta in maniera automatica in modo da riferirsi agli elementi dell’array. Quindi se ptr1 e ptr2 puntano a due elementi dello stesso array (di qualsiasi tipo), l’espressione seguente fornisce la distanza tra gli e elementi stessi: ptr1 – ptr2; I confronti tra i puntatori sono validi solo quando tutti puntano allo stesso array. Sono queste condizioni, gli operatori relazionali ==, !=, <, >, >=, <= funzionano correttamente. Gli elementi inferiori degli array (cioè quelli con indice minore) hanno sempre indirizzi inferiori; perciò se ptr1 e ptr2 puntano a elementi dello stesso array il confronto: ptr1 < ptr2 è vero se ptr1 punta a un elemento dell’array che precede quello puntato da ptr2.
Pagina 7 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
Molte operazioni aritmetiche tra variabili normali non avrebbero senso con i puntatori e il compilatore non li permette. Ad esempio se ptr è un puntatore l’istruzione seguente: ptr *= 2; genera un messaggio di errore.
13.1.9
Precauzioni per i puntatori
Nella scrittura di un programma che impiega i puntatori è necessario evitare un errore molto serio: l’utilizzo di un puntatore non inizializzato sul lato sinistro di un’istruzione di assegnamento. Ad esempio l’istruzione che segue dichiara un puntatore di tipo int: int *ptr; Questo puntatore non è ancora inizializzato, quindi non punta a nulla. Per essere precisi non punta a nulla di conosciuto. Un puntatore non inizializzato punta a un certo valore che tuttavia non è dato a conoscere a priori. In molti casi questo valore è zero. Se si utilizza un puntatore non inizializzato in un’istruzionedi assegnamento, ad esempio: *ptr = 12; il valore 12 viene assegnato alla locazione di memoria puntata da ptr, qualunque essa sia. Questo indirizzo può essere in qualsiasi punto della memoria, nella parte relativa al sistema operativo o dove è stato caricato lo stesso programma. Il valore 12 può aver sovrascritto qualche dato importante e può provocare strani errori o blocchi della macchina. Il lato sinistro di un’istruzione è il posto più pericoloso dove utilizzare puntatori non inizializzati. Quindi assicurarsi sempre che i puntatori siano inizializzati prima di utilizzarli.
13.2
Stringhe e puntatori
Nelle lezioni precedenti si è spiegato che una stringa viene definita dal nome dell’array di caratteri che la contiene e da un carattere null. Il nome dell’array rappresenta un puntatore di tipo char all’inizio della stringa. Il carattere null segna la fine della stringa. Lo spazio reale occupato dalla stringa nell’array è casuale. Infatti l’unica funzione dell’array è quella di fornire uno spazio in cui collocare la strigna. Che cosa accade se si riesce a disporre di uno spazio di memoria senza allocare un array? Si potrebbe memorizzare lì una stringa con il suo carattere null finale. Per specificare l’inizio della strigna sarebbe sufficiente un puntatore, come se la stringa stessa si trovasse in un array. Come si può ottenere dello spazio utilizzabile all’interno della memoria? Esistono due modi: uno prevede l’allocazione di spazio per una stringa letterale in fase di compilazione del programma e l’altro utilizza la funzione malloc() per allocare dello spazio in famedi esecuzione del programma, processo noto come allocazione dinamica.
13.2.1
Allocazione di memoria per le stringhe
L’inizio di una stringa, come si è detto precedentemente, è definito da un puntatore a una variabile di tipo char. Di seguito viene mostrato come dichiarare un puntatore: char *messaggio;
Pagina 8 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
Tramite questa istruzione viene dichiarato un puntatore a una cariabile di tipo char di nome messaggio. Per il momento non punta a nulla, ma se si modifica la dichiarazione del puntatore nel modo seguente: char *messaggio = “Evviva!”; La stringa “Evviva!” (con il carattere null finale) viene memorizzata in una porzione di memoria e il puntatore messaggio viene inizializzato in modo che punti al primo carattere della stringa. Non ha importanza dove venga memorizzata la stringa, questo aspetto viene gestito automaticamente dal compilatore. Una volta che è stato definito, messaggio è un puntatore alla stringa e come tale può essere riutilizzato. La dichiarazione/inizializzazione precedente è equivalente a quella che segue e le due notazioni messaggio e messaggio[] sono tra loro intercambiabili; entrambi significano “puntatore a”. char messaggio[] = “Evviva!”; Questo modo di allocare lo spazio per le stringhe è adatto nel momento in cui il programmatore sa esattamente di che cosa ha bisogno già in fase di stesura del programma. Altrimenti se le esigenze variano in base all’input dell’utente bisogna utilizzare la funzione malloc() che consente di allocare dello spazio in memoria “al volo”. La funzione malloc()
malloc() è una delle funzioni per l’allocazione di memoria del linguaggio C. Quando viene richiamata, passandole come parametro il numero di byte di memoria necessari, malloc() trova e riserva un blocco di memoria della dimensione richiesta e restituisce l’indirizzo del primo byte del blocco. Il programmatore non si deve preoccupare della posizione in cui sia stato recuperato il blocco di memoria, poiché questo aspetto viene gestito automaticamente. La funzione malloc() restituisce un puntatore a void in quanto il tipo void è compatibile con tutti i tipi di dati. Poiché la memoria riservata da malloc() può essere utilizzata per memorizzare qualsiasi tipo di dato previsto dal linguaggio C, il tipo void è il più appropriato. La sintassi della funzione malloc() è la seguente: void *malloc(dim_t dim) ;
malloc() alloca un blocco di memoria corrispondente al numero di byte definito da dim. malloc() consente di riservare la memoria nel momento in cui se ne presenta la necessita, anziché bloccarla tutta contemporaneamente quando il programma viene avviato. Per utilizzare malloc() è necessario includere il file d’intestazione stdlib.h. Se la funzione non riesce a riservare la quantità di memoria richiesta restituisce un valore nullo. Ogni volta che si prova ad allocare della memoria è opportuno controllare il valore restituito dalla funzione, anche se la quantità di memoria da riservare è limitata. Esempio 1
#include #include main() { /*allocazione di memoria per una stringa di 100 caratteri*/ char *str; str = (char *) malloc(100); if (str == NULL) { printf(“Memoria insufficiente\n”); exit(1);
Pagina 9 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com } printf(“Operazione riuscita!\n”); return 0; }
Esempio 2 /*allocazione di memoria per un array di 50 interi*/ int *numeri; numeri = (int *) malloc (50 * sizeof(int));
Esempio 3 /*allocazione di memoria per un array di 50 valori float*/ float *numeri; numeri = (float *) malloc (50 * sizeof(float));
Esempio 4
#include #include main() { char conta, *ptr, p; /*alloca un blocco di 3 byte. Controlla se ha avuto successo*/ /*la funzione di libreria exit() termina il programma*/ ptr = (char *) malloc(35); if (ptr == NULL) { printf(“Memoria insufficiente\n”); exit(1); } /*Riempie la stringa con valori da 65 a 90*/ /*che sono i codici ASCII delle lettere dalla A alla Z*/ /*p è un puntatore utilizzato per spostarsi lungo la stringa*/ /*Vogliamo che ptr resti nella posizione che indica*/ /*l’inizio della stringa*/ p = ptr; for (conta = 65; conta < 91; conta++) *p++ = conta;
Pagina 10 di 11
Dispensa 13
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13
/*aggiungo il carattere null finale*/ *p = ‘\0’; /*visualizza la stringa sullo schermo*/ puts(ptr); return 0; }
Ecco l’output del programma: ABCDEFGHIJKLMNOPQRSTUVWXYZ
La funzione free() La memoria allocata mediante malloc() è presa da un “serbatoio” a disposizione del programma. Il serbatoio talvolta detto heap (lett. “mucchio”) è a dimensione dinita. Quando un programma finisce di utilizzare un blocco di memoria allocato dinamicamente esso dovrevve liberarlo, così da rimetterlo a disposizione per eventuali impieghi futuri. Per liberare in blocco di memoria allocato dinamicamente, si richiama la funzione free(). Il suo prototipo è il seguente: void free(void *ptr); La funzione free() rilascia il blocco di memoria al quale punta ptr. Questo blocco dev’essere stato allocato mediante malloc(). Se ptr è NULL, free() non ha alcun effetto.
Pagina 11 di 11
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 13 bis Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13 bis
Esempi utilizzo puntatori Esempio 1 #include #include int main() { int A, B; int *ptr; A = 10; B = A; ptr = &A; printf("Prima di modificare A:\n"); printf("A = %d \n",A); printf("B = %d \n",B); printf("*ptr = %d \n",*ptr); A = 35; printf("Dopo la modifica di A:\n"); printf("A = %d \n",A); printf("B = %d \n",B); printf("*ptr = %d \n",*ptr); *ptr = 17; printf("Dopo la modifica di *ptr:\n"); printf("A = %d \n",A); printf("B = %d \n",B); printf("*ptr = %d \n",*ptr); return 0; } Output del programma: Prima di modificare A: A = 10 B = 10 *ptr = 10 Dopo la modifica di A: A = 35 B = 10 *ptr = 35 Dopo la modifica di *ptr: A = 17 B = 10 *ptr = 17 Scrivere l’istruzione B = A significa associare alla variabile B lo stesso valore della variabile A, quindi creare una copia del valore presente in A e salvarlo all’interno della variabile B. Tutte le modifiche fatte alla variabile
Pagina 2 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13 bis
A non verranno sentite dalla variabile B la quale manterrà il valore invariato e viceversa. Scrivere ptr = &A significa associare al puntatore ptr l’indirizzo di memoria della variabile A, quindi attraverso il puntatore possiamo accedere all’area di memoria in cui è memorizzata la variabile A e modificarne il valore. In questo secondo caso non si creano copie. Esempio 2 #include #include int main() { int A; int *ptr; A = 17; ptr = &A; printf("%d \n",A); //valore presente dentro alla variabile A printf("%d \n",&A); //indirizzo di memoria della variabile A printf("%d \n",ptr); //contenuto della variabile ptr, (indirizzo di A) printf("%d \n",*ptr); /*valore presente all’interno della cella con indirizzo in ptr, quindi il valore di A*/ printf("%d \n",&ptr); //indirizzo di memoria della variabile ptr return 0; } Considerando il seguente stato della memoria (supponendo che un intero occupi 2 byte in memoria): 2006 2000
2001
10 2002
2003
2004
2005
ptr
2006
2007
A *ptr
L’output del programma è: 10 2006 2006 10 2001 Esempio 3 #include #include int main() {
Pagina 3 di 7
2008
2009
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13 bis
int elenco[5] = {3,14,7,89,10}; printf("%d \n",elenco); //indirizzo di memoria della prima cella printf("%d \n",elenco[0]); //valore della prima cella, quindi 3 printf("%d \n",*elenco); //valore della prima cella, quindi 3 return 0; } Il nome di un array senza parentesi quadre corrisponde al puntatore alla prima cella di memoria allocata per memorizzare l’array stesso. Quindi considerando l’esempio riportato sopra, stampando il valore di elenco (senza le parentesi quadre) si ottiene l’indirizzo di memoria della prima cella allocata, stampando invece il valore di *elenco si ottiene lo stesso valore stampato con elenco[0], quindi utilizzando l’aritmetica dei puntatori possiamo accedere alle varie celle dell’array: #include #include int main() { int elenco[5] = {3,14,7,89,10}; int i; for (i = 0; i < 5 i++) printf("\t%d \t%d\n",elenco[i],*(elenco+i)); return 0; } Questo è possibile in quanto le celle riservate in memoria per un array sono tutte consecutive. Lo stesso discorso potrebbe essere fatto per una matrice o array multidimensionale: #include #include int main() { int matrice[3][2] = {1,2,3,4,5,6}; int i, j; for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) printf("\t%d\t %d\n",matrice[i][j],*(*(matrice+i)+j)); return 0; }
Esempio 4 #include #include
Pagina 4 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com int main() { int elenco[5] = {1,1,1,1,1}; int valori[5] = {2,2,2,2,2}; int numeri[5] = {3,3,3,3,3}; int i; int *ptr; printf("Prima di modificare:\n"); for (i = 0; i < 5; i++) printf("\t%d \t%d \t%d",elenco[i],valori[i],numeri[i]); ptr = valori; for (i = 0; i < 5; i++) *(ptr + i) = i*2; printf("Dopo la modifica:\n"); for (i = 0; i < 5; i++) printf("\t%d \t%d \t%d",elenco[i],valori[i],numeri[i]); return 0; } Output del programma: Prima di modificare: 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 Dopo la modifica: 1 0 3 1 2 3 1 4 3 1 6 3 1 8 3 Attraverso ptr andiamo a modificare i valori del secondo vettore, il blocco di istruzioni: for (i = 0; i < 5; i++) *(ptr + i) = i*2; funziona per qualsiasi vettore associato a ptr
Pagina 5 di 7
Dispensa 13 bis
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13 bis
Esempi allocazione dinamica della memoria Esempio 1 #include #include int main() { int numeroValori, i; int *ptr; printf("Quanti valori vuoi utilizzare:"); scanf("%d",&numeroValori); ptr = (int *)malloc(numeroValori*sizeof(int)); if (ptr == NULL) printf("Errore nell'allocare la memoria!"); else { for (i = 0; i < numeroValori; i++) { printf("Inserisci valore %d:",i + 1); scanf("%d",(ptr + i)); } printf("\nValori inseriti:"); for (i = 0; i < numeroValori; i++) printf("\n%d",*(ptr + i)); } free(ptr); return 0; }
Esempio 2 #include #include #define DIMENSIONE_BUFFER 10 int main() { int dimStringa, i; unsigned char *stringa, *temp, *buffer; unsigned char ch; buffer=(unsigned char *)malloc(DIMENSIONE_BUFFER*sizeof(unsigned char)); stringa = (unsigned char *)malloc(sizeof(unsigned char)); dimStringa = 1; //incluso carattere \0 finale
Pagina 6 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 13 bis
printf("Inserire testo:\n"); do { i = 0; do { ch = getchar(); *(buffer+i) = ch; i++; } while (ch != '\n' && (i < DIMENSIONE_BUFFER-1)); dimStringa += i; temp = stringa; stringa = (char *)malloc(dimStringa*sizeof(unsigned char)); *stringa = '\0'; strcat(stringa,temp); strcat(stringa,buffer); free(temp); } while(ch != '\n'); printf("\n\nTesto inserito: %s",stringa); free(stringa); free(buffer); return 0; }
Pagina 7 di 7
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 14 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
14.1
Dispensa 14
Le strutture
Una struttura rappresenta l’insieme di una o più variabili raggruppate sotto un unico nome per una più facile gestione. Le variabili contenute in una struttura, contrariamente a quelle di un array, possono essere di tipi differenti. Una struttura può contenere qualsiasi tipo di dato ammesso dal C, inclusi gli array e altre strutture. Le variabili contenute in una struttura vengono chiamate elementi.
14.1.1
Definizione e dichiarazione di strutture
Il codice di un programma di grafica deve gestire le coordinate dei vari punti sullo schermo, rappresentate da un valore x, la posizione orizzontale, e da un valore y, quella verticale. E’ possibile definire una struttura di nome coord che contenga sia il valore x che y di un punto sullo schermo, nel modo seguente: struct coord { int x; int y; }; La parola chiave struct, che identifica l’inizio della definizione di struttura, deve essere immediatamente seguita dal nome della struttura stessa, o etichetta (che segue le stesse regole degli altri nomi di variabili del C). Tra le parentesi graffe che seguono il nome della struttura sono elencate le variabili da inserire nella struttura stessa. E’ necessario assegnare a ciascun elemento un nome e un tipo. Le istruzioni precedenti definiscono un tipo di struttura di nome coord che contiene due variabili di tipo intero, x e y. In realtà, in questo modo non viene creata alcuna istanza della struttura coord; in altre parole le istruzioni precedenti non dichiarano (assegnando lo spazio in memoria) alcuna struttura. Esistono due modi per dichiarare una struttura: uno consiste nel far seguire la definizione da un elenco di uno o più nomi di variabili, come viene mostrato di seguito: struct coord { int x; int y; }prima, seconda; Queste istruzioni definiscono il tipo di struttura coord e dichiarano due strutture prima e seconda, di tipo coord. Prima e seconda sono istanze del tipo coord; prima contiene due membri interi di nome x e y e così pure seconda. Questo metodo di dichiarazione delle strutture combina la dichiarazione con la definizione. Il secondo metodo consiste nel dichiarare le variabili istanza della struttura in un punto differente del codice, separato dalla definizione della struttura stessa. Anche le istruzioni seguenti dichiarano due istanze del tipo coord: struct coord { int x; int y; }; struct coord prima, secnoda;
14.1.2
Accesso agli elementi di una struttura
Ciascun elemento di una struttura può essere utilizzato come qualsiasi altra variabile del tipo assegnato. Per accedere ai singoli componenti di una struttura, si utilizza l’operatore di elemento della struttura (.), anche chiamato operatore punto, inserendolo tra il nome della struttura e il nome del componente. Quindi se si ha una struttura di nome prima con i riferimenti a un punto dello schermo di coordinate x=50, y=100, si può scrivere:
Pagina 2 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 14
prima.x = 50; prima.y = 100; Di seguito è mostrato come visualizzare le posizioni dello schermo memorizzate nella struttura prima: printf(“%d, %d”, prima.x, prima.y); Uno dei vantaggi principali nell’utilizzare strutture consiste nel fatto che si possono copiare le informazioni tra strutture dello stesso tipo utilizzando una semplice istruzione di eguaglianza. Proseguendo nell’esempio precedente, l’istruzione: prima = seconda; equivale a: prima.x = seconda.x; prima.y = seconda.y; In generale le strutture risultano utili ogni volta che si devono gestire gruppi di informazioni contenuti in tipi di variabili differenti.
14.1.3
Strutture che contengono altre strutture
Come si è già detto in precedenza, una struttura del C può contenere qualsiasi tipo di dato ammesso dal linguaggio, quindi anche altre strutture. Quindi è possibile definire una struttura come segue (supponendo ovviamente che sia già stato definito il tipo di struttura coord): struct rettangolo{ struct coord supsin; struct coord infdes; }; Questa istruzione definisce una struttura di tipo rettangolo che contiene a sua colta due strutture di tipo coord. Queste due strutture di tipo coord sono chiamate supsin e infdes. L’istruzione precedente definisce soltanto la struttura di tipo rettangolo. Per dichiarare una struttura, è necessario specificare un’istruzione del tipo: struct rettangolo riq; Si possono combinare tra loro la definizione e la dichiarazione, come si è già visto precedentemente per il tipo coord: struct rettangolo{ struct coord supsin; struct coord infdes; } riq; Per accedere alle locazioni reali dei dati (gli elementi di tipo int), è sufficiente applicare due volte l’operatore punto (.). Quindi, l’espressione: riq.supsin.x si definisce al componente x del membro supsin della struttura di tipo rettangolo di nome riq. Per definire un rettangolo di coordinate (0,10), (100,200), si può scrivere: riq.supsin.x riq.supsin.y riq.infdes.x riq.infdes.y
= = = =
0; 10; 100; 200;
Vediamo ora un esempio di utilizzo di strutture che contengono altre strutture:
Pagina 3 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 14
Riceve in input le coordinate angolari di un rettangolo e ne calcola l’area. Si suppone che la coordinata y dell’angolo superiore sinistro sia maggiore della coordinata y dell’angolo inferiore destro, che la coordinata x dell’angolo inferiore destro sia maggiore della coordinata x dell’angolo superiore sinistro e che tutte le coordinate siano positive. #include struct coord{ int x; int y; }; struct rettangolo{ struct coord supsin; struct coord infdes; }; struct rettangolo riq; main() { int lung, larg; long area ; printf(“\nInserisci la coordinata x superiore sinistra:”); scanf(“%d”,&riq.supsin.x); printf(“\nInserisci la coordinata y superiore sinistra:”); scanf(“%d”,&riq.supsin.y); printf(“\nInserisci la coordinata x inferiore destra:”); scanf(“%d”,&riq.infdes.x); printf(“\nInserisci la coordinata y inferiore destra:”); scanf(“%d”,&riq.infdes.y); /*calcola la lunghezza e la larghezza*/ larg = riq.infdes.x – riq.supsin.x; lung = riq.infdes.y – riq.supsin.y; /*Calcola e visualizza l’area*/ area = lung*larg; printf(“\nL’area è: %d”, area); return 0; } Ecco input e output del programma: Inserisci la Inserisci la Inserisci la Inserisci la L’area è 81
coordinata coordinata coordinata coordinata
x y x y
superiore superiore inferiore inferiore
sinistra: 1 sinistra: 1 destra: 10 destra: 10
Pagina 4 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
14.1.4
Dispensa 14
Strutture che contengono altre strutture
E’ possibile definire una struttura che abbia come componenti uno o più array. L’array può essere di qualsiasi tipo di dato ammesso in C. Ad esempio la definizione: struct dati{ int x[4]; char y[10]; }; definisce una struttura di tipo dati che contiene un array x di quattro elementi interi e un array y di 10 caratteri. Successivamente è possibile dichiarare una struttura record di tipo dati nel modo seguente: struct dati record; Per accedere a ciascun elemento dell’array contenuto nella struttura si utilizza una combinazione dell’operatore punto con gli indici dell’array: record.x[2] = 100; record.y[1] = ‘x’;
14.1.5
Array di strutture
E’ possibile definire un array di strutture, ad esempio in un programma per la gestione di un elenco di numeri telefonici, è possibile definire una struttura che contenga il nome e il numero dell’utente: struct utente{ char nome[20]; char cognome[30]; char telefono[25]; }; Un elenco telefonico è costituito però da molte voci, perciò un'unica istanza di questa struttura non è di molta utilità. Quello che serve è un array di strutture di tipo utente. Dopo aver definito la struttura, si dichiara un array come segue: struct utente elenco[1000]; Questa istruzione dichiara un array elenco di 1000 elementi. Ciascun elemento rappresenta una struttura di tipo utente ed è identificato da un indice.
14.1.6
Puntatori a strutture
E’ possibile dichiarare puntatori a strutture, per prima cosa occorre definire una struttura: struct parte{ int numero; char nome[10]; }; A questo punto, si dichiara un puntatore al tipo parte. struct parte *p_parte; Si rammenti che l’opeartore di rinvio (*) all’interno della dichiarazione indica che p_parte è un puntatore al tipo parte, non un’istanza della struttura di tipo parte. Si ricordi che è la dichiarazione non la definizione che predispone lo spazio in memoria per la memorizzazione dell’oggetto di dati. Poiché un puntatore necessita di un indirizzo di memoria a cui puntare, è necessario dichiarare un’istanza di tipo parte alla quale possa essere indirizzato il puntatore. Di seguito è riportata la dichiarazione: struct parte gz;
Pagina 5 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 14
A questo punto è possibile inizializzare il puntatore: p_parte = &gz; Questa istruzione assegna l’indirizzo di gz a p_parte. Ora che si dispone di un puntatore alla struttura gz, si può utilizzare l’operatore di rinvio (*). Applicando questo concetto all’esempio corrente, poiché p_parte rappresenta un puntatore alla struttura gz, *p_parte si riferisce a gz. Si può applicare l’operatore (.) per accedere ai singoli componenti di gz. Per assegnare il valore 100 a gz.numero, si può scrivere: (*p_parte).numero = 100;
*p_parte deve essere racchiuso tra parentesi perché l’operatore (.) ha la precedenza rispetto l’operatore (*). Un altro modo per accedere agli elementi di una struttura tramite un puntatore a struttura consiste nell’utilizzare l’operatore puntatore di membro indiretto, rappresentato dai caratteri -> (un tratino seguito dal simbolo maffiore). Quindi l’esempio di prima lo si poteva scrivere: p_parte->numero = 100; Altri esempi, se p_str è un puntatore alla struttura str, le espressioni seguenti si equivalgono: str.elem (*p_str).elem è_str->elem
Pagina 6 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 14
Esercizio 1 - Studenti/voti: materie insufficienti Utilizzando un array di strutture, si vogliono memorizzare i seguenti dati relativi agli studenti: Nome, Cognome, Matricola, Anno di corso, Elenco dei voti dove l’elenco dei voti è a sua volta memorizzato all’interno di un vettore. Si utilizza, poi, un ulteriore vettore di strutture per memorizzare le informazioni relative alle materie: codice materia, descrizione e docente. L’indice in cui sono memorizzati i voti all’interno dell’array di voti corrisponde alla posizione in cui sono memorizzate le materie nel rispettivo vettore delle materie. Sia data la matricola oppure il cognome di uno studente. Calcolare la sua media e visualizzare le materie in cui risulta insufficiente, il docente della materia e il voto corrispondente.
Pagina 7 di 7
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 15 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
15.1
Dispensa 15
Le funzioni
Una funzione è una sezione con nome indipendente di codice C che segue un compito specifico e restituisce opzionalmente un valore al programma chiamante. Analizziamo un esempio con di codice con una funzione:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
/*Esempio di una semplice funzione*/ #include long cubo(long x); long input, risp; main() { printf(“Immettere un valore intero: ”); scanf(“%d”, &input); /*Nota: %ld è l’indicatore di convers. per un intero lungo*/ risp = cubo(input); printf(“\nIl cubo di %ld è %ld.\n”,input, risp); return 0; } /*funzione cubo() – Calcola il valore al cubo della variabile*/ long cubo(long x) { long x_cubo; x_cubo = x * x * x; return x_cubo; }
Ecco l’output del programma: Esempio 1: Immettere un valore intero: 100 Il cubo di 100 è 1000000 Esempio 2: Immettere un valore intero: 9 Il cubo di 9 è 729 La riga 4 contiene il prototipo della funzione, il modello che apparirà nella parte seguente del programma. Un prototipo contiene il nome della funzione, un elenco di variabili che verranno passate ed eventualmente il tipo della variabile restituita. Osservando la riga 4 si può vedere che la funzione si chiama “cubo”, che richiede una variabile di tipo “long” e che restituisce un valore di tipo “long”. Le variabili passate ad una funzione sono dette argomenti e sono racchiuse tra le parentesi tonde che seguono il nome della funzione stessa. In questo esempio l’unico argomento della funzione è long x. La parola chiave che precede il nome della funzione indica il tipo di variabile restituito. In questo caso viene restituito una variabile di tipo long. La riga 13 richiama la funzione cubo e le passa il valore introdotto da tastiera come argomento. Il valore restituito dalla funzione viene assegnato alla variabile risp.
Pagina 2 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
La definizione della funzione in questo caso è contenuta nelle riche dalla 20 alla 26. Come il prototipo anche la definizione della funzione è composta da più parti. La funzione comincia con un’intestazione alla riga 20, contenente il nome della funzione, il tipo e la descrizione degli eventuali argomenti. Si noti che l’istruzione della funzione è identica al prototipo (a parte il punto e virgola). Il corpo della funzione, nelle righe dalla 21 alla 26.è racchiuso all’interno di parentesi graffe e contiene istruzioni. Le variabili locali sono quelle dichiarate all’interno del corpo della funzione. Infine, la funzione termina con un’istruzione return alla riga 25 che indica la conclusione della routine. L’istruzione return serve anche per passare il valore restituito al programma chiamante. I programmi in C eseguono le istruzione contenute all’interno delle funzioni solo al momento in cui avviene la chiamata; in questo istante, il programma ha la possibilità di inviare delle informazioni alla funzione sotto forma di argomenti, per specificare i dati richiesti dalla procedura per eseguire il proprio compito. In seguito vengono eseguite le istruzioni interne fino alla conclusione della procedura, momento in cui il flusso di esecuzione torna al programma principale nell’istruzione successiva alla chiamata. Le istruzioni possono passare al programma chiamante le informazioni elaborate attraverso una gestione particolare del valore restituito. Una funzione può essere chiamata tutte le volte che serve; inoltre le funzioni cono richiamabili in qualsiasi ordine. Il prototipo di una funzione fornisce al compilatore un’anteprima della funzione che verrà definita in una parte seguente del codice. Esempi di prototipi: double quadrato(double numero); void stampa_relazione(int numero_relazione); int get_scelta_menu(void); Esempi di definizione: double quadrato(double numero) { return numero * numero; }
/*intestazione*/ /*parentesi aperta*/ /*corpo della funzione*/ /*parentesi chiusa*/
void stampa_relazione(int numero_relazione) { if (numero_relazione == 1) printf(“Stampo relazione 1”); else printf(“Non stampo relazione 1”); } Attraverso l’impiego delle funzioni all’interno dei propri programmi in C, è possibile fare uso della programmazione strutturata, dove qualsiasi compito di un programma viene eseguito da una sezione indipendente.
15.1.1
Come si scrive una funzione
Il primo passo dello sviluppo di una funzione è la definizione del suo obiettivo.
Pagina 3 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com •
Intestazione: la prima riga di qualsiasi funzione è l’intestazione, la quale si compone di tre parti ciascuna asservita ad uno scopo preciso.
Tipo del valore restituito dalla funzione tipo
•
Dispensa 15
Nome della funzione Elenco dei parametri passati alla funzione nomefunzione (param1,...)
Tipo del valore restituito dalla funzione: corrisponde al tipo di dato che la funzione restituisce al programma chiamante.
Nome della funzione: ad una funzione può essere assegnato un nome qualsiasi, a patto che segua le regole del C riguardanti i nomi di variabile. Il nome della funzione deve essere unico.
L’elenco dei parametri: molte funzioni utilizzano degli argomenti passati nella chiamata. Una funzione deve sapere che tipo di argomenti aspettarsi, cioè il tipo di dato di ciascuno. E’ possibile passare a una funzione C qualsiasi tipo di dati. Per ciascun argomento passato alla funzione, l’elenco dei parametri deve contenere un oggetto distinto.
Corpo della funzione: è contenuto all’interno di una coppia di parentesi graffe e segue immediatamente l’intestazione. All’interno del corpo delle funzioni è possibile dichiarare delle variabili locali, così chiamate in quanto sono disponibili solo alla funzione di appartenenza e sono distinte dalle altre variabili con lo stesso nome dichiarate altrove. Per restituire un valore da una funzione si deve utilizzare la parola chiave return seguita da un’espressione del C. Una funzione può contenere più di una istruzione return, solo la prima ad essere eseguita è quella che conta.
#include int maggiore(int a, int b); main() { int x, y, z; puts(“Inserire due valori interi diversi”); scanf(“%d%d”,&x, %y) ; z = maggiore(x,y); printf(“\nIl valore maggiore è %d.”,z); return 0; } int maggiore(int a, int b) { if (a > b) return a;
Pagina 4 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
else return b; }
•
I prototipi delle funzioni: i prototipi di una funzione sono identici alle intestazioni, ma sono seguiti da un punto e virgola. I prototipi forniscono al compilatore informazioni sul tipo di valore restituito, sul nome e sui parametri.
15.1.2
Passaggio degli argomenti
Per passare degli argomenti a una funzione è necessario elencarli all’interno delle parentesi che seguono il nome della funzione stessa. Il numero, il tipo e l’ordine degli argomenti devono coincidere con quelli del prototipo e dell’intestazione. Ad esempio, se una funzione viene definita con due parametri di tipo int, occorre passarle esattamente due argomenti int, ne uno di più ne uno di meno, e del tipo corretto. Se si cerca di passare a una funzione un numero o un tipo sbagliato di argomenti, il compilatore se ne accorge sulla base delle informazioni del prototipo. Chiamata della funzione
Intestazione della funzione
15.1.3
funzione1 (a, b, c) ;
void
funzione1 (int x, int y, int z)
Chiamata alle funzioni
Esistono due modi di chiamare una funzione. Qualsiasi funzione può essere richiamata specificandone il nome e l’elenco di argomenti come un'unica istruzione. Ad esempio: areaRettangolo(5, 10); Se la funzione restituisce un valore, questo viene ignorato. Il secondo metodo può essere utilizzato con le funzioni che restituiscono un valore. Dato che queste funzioni equivalgono a un valore (quello restituito), sono a tutti gli effetti delle espressioni del C e possono essere utilizzate ovunque possa essere impiegata un’espressione. Nell’esempio seguente, areaRettangolo() è utilizzato come parametro di una funzione: printf(“L’area del rettangolo di base %d e altezza %d è: %d”, b, h, areaRettangolo(b,h)); Viene chiamata per prima la funzione areaRettangolo() con i parametri b e h, quindi viene richiamata la funzione printf() utilizzando i valori di b, h w areaRettangolo().
Pagina 5 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
15.1.4
Dispensa 15
Chiamata alle funzioni
Il termine ricorsione si riferisce alle situazioni in cui una funzione richiama se stessa in maniera sia diretta che indiretta. La ricorsione indiretta avviene quando una funzione ne richiama un’altra che al suo interno richiama la funzione primaria. Il programma presentato nell’esempio sotto, presenta un esempio di chiamata ricorsiva. In particolare viene calcolato il fattoriale di un numero. Il fattoriale di un numero x, che ha per simbolo x! si calcola nel modo seguente: x! = x *(x - 1) * (x - 2) * (x - 3) * . . . * (2) * 1 E’ possibile calcolare x! anche nella maniera seguente: x! = x * (x - 1)! Avanzando di un altro passo, (x - 1)! Può essere calcolato utilizzando la stessa procedura: (x - 1)! = (x - 1) * (x - 2)! #include unsigned int fattoriale(unsigned int a); main() { unsigned int f, x; puts(“inserire un valore intero compreso tra 1 e 8:”); scanf(“%d”,&x); if (x > 8 || x < 1) printf(“Solo I valori compresi tra 1 e 8 sono ammessi!!”); else { f = fattoriale(x); printf(“%u fattoriale valr %u”,x,f); } return 0; } unsigned int fattoriale(unsigned int a) { if (a == 1) return 1; else { a *= fattoriale(a - 1); return a; } }
15.1.5
Ambito delle variabili
L’ambito di una variabile si riferisce all’estensione entro cui parti differenti di n programma hanno accesso a una determinata variabile; in altre parole, da dove la variabile risulta visibile. Quando si parla di variabili del C, i termini accessibilità e visibilità vengono utilizzati in modo intercambiabile. In riferimento all’ambito, con il
Pagina 6 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
termine variabile si intende qualsiasi tipo di dato previsto dal linguaggio C; variabili scalari, array, strutture, puntatori e così via, oltre che costanti simboliche definite tramite la parola chiave const. Inoltre, l’ambito influisce sul tempo di vita di una variabile: quanto tempo la variabile persiste nella memoria, oppure, quando viene allocato o deallocato dello spazio in memoria per la variabile stessa. Esempio di ambito: /*Versione 1 del listato*/ #include intx = 999; void stampa_valore(void); main() { printf(“%d\n”,x); stampa_valore(); return 0; } void stampa_valore(void) { printf(“%d\n”,x); }
Ecco l’output del programma: 999 999 /*Versione 2 del listato*/ #include void stampa_valore(void); main() { intx = 999; printf(“%d\n”,x); stampa_valore(); return 0; } void stampa_valore(void) { printf(“%d\n”,x); }
Pagina 7 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
Se si prova a compilare il listato 2, viene generato un messaggio di errore del tipo: Error: undefined identifer ‘x’. L’unica differenza tra I due listati consiste nella posizione in cui la variabile x viene definite. Spostando la definizione di x in punti differenti del codice, se ne modifica l’ambito. Nel listato 1, x rappresenta una variabile esterna e il suo ambito si estende all’intero programma. Nel listato 2, x rappresenta una variabile locale e come tale il suo ambito è limitato all’interno della funfione main().
15.1.5.1
Variabili esterne
Una variabile esterna è definita esternamente da qualsiasi funzione compresa la funzione main(). Le variabili esterne vengono anche definite come variabili globali. Se una variabile esterna no viene inizializzata al momento della definizione, il compilatore le inizializza automaticamente al valore 0. L’ambito di una variabile esterna è rappresentata dall’intero programma. Ciò significa che una variabile esterna è visibile dalla funzione main() e da qualsiasi altra funzione all’interno del programma.
15.1.5.2
Variabili locali
Una variabile locale è una variabile definita all’interno di una funzione. L’ambito di una variabile locale risulta limitato alla funzione in cui è definita. Le variabili locali non vengono automaticamente inizializzata ad alcun valore. Se al momento della definizione non vengono inizializzate, contengono un valore indefinito.
15.1.5.3
Variabili statiche e variabili automatiche
Le cariabili locali sono di default automatiche. Ciò significa che vengono ricreate da zero ogni volta che viene richiamata la funzione in cui sono dichiarate e distrutte ogni volta che l’esecuzione del programma abbandona quella funzione. Ai fini pratici, ciò significa che una variabile automatica non mantiene il proprio calore nell’intervallo di tempo che intercorre tra due chiamate successive alla funzione in cui è definita. E’ possibile fare in modo che una variabile locale mantenga il proprio valore tra ciascuna chiamata alla funzione in cui è definita, e per fare ciò è necessario definirla come statica utilizzando la parola chiave static. Ad esempio: void funzione(int x) { static int a; /*codice della funzione*/ } Esempio tra variabili locali statiche e automatiche: #include void funzione1(void); main() { int conta; for (conta = 0; conta < 10; conta++) { printf(“Alla iterazione %d: ”, conta); funzione1(); } return 0;
Pagina 8 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
} void funzione1() { static int x = 0; int y = 0; printf(“x = %d, t = &d\n”, x++, y++) ; }
Ecco l’output del programma : Alla iterazione 0: x = 0, y = 0 Alla iterazione 1: x = 1, y = 0 Alla iterazione 2: x = 2, y = 0 Alla iterazione 3: x = 3, y = 0 Alla iterazione 4: x = 4, y = 0 Alla iterazione 5: x = 5, y = 0 Alla iterazione 6: x = 6, y = 0 Alla iterazione 7: x = 7, y = 0 Alla iterazione 8: x = 8, y = 0 Alla iterazione 9: x = 9, y = 0
15.1.5.4
Variabili di registro
La parola chiave register viene utilizzata per chiedere al compilatore di memorizzare una variabile locale automatica in un registro del processore, anziché in memoria. La parola chiave register è una richiesta e non un ordine. La parola chiave _register può essere utilizzata solo con variabile numeriche semplici e non è possibile definire un puntatore alle variabili di registro.
15.1.6
Passaggio di variabili a funzioni
Il modo normale di passare un argomento a una funzione è detto passaggio per valore. Con ciò si intende che la funzione riceve una copia dell’argomento.Quando una variabile è passata per valore, la funzione può accedere al valore della variabile, ma non alla variabile vera e propria. Di conseguenza, la funzione non può modificare il valore della variabile originale. Il passaggio per valore è ammesso per i tipi di dati base (char, int, long, float e double) e per le strutture. Esiste tuttavia un secondo modo di passare argomenti a una funzione: passare un puntatore alla variabile argomento, anziché il suo valore. Questo è detto passaggio per riferimento. Le modifiche alle variabili passate per riferimento vengono applicate alla variabile di origine. #include void per_valore(int a, int b, int c); void per_riferimento(int *a, int *b, int *c); main()
Pagina 9 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
{ int x = 2, y = 4, z = 6; printf(‘’ \nPrima della chiamata di per_valore(), x= %d, y = %d, z = %d.‘’,x,y,z); per_valore(x,y,z); printf(‘’ \nDopo la chiamata di per_valore(), x= %d, y = %d, z = %d.‘’,x,y,z); per_riferimento(&x, &y, &z); printf(‘’ \nDopo la chiamata di per_riferimento(), x= %d, y = %d, z = %d.‘’,x,y,z); return 0; } void per_valore(int a, int b, int c) { a = 0; b = 0; c = 0; } void per_riferimento(int *a, int *b, int *c) { *a = 0; *b = 0; *c = 0; }
Pagina 10 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
Esercizio 1. Passaggio per valore/per indirizzo Scrivere un programma che esegue lo swap di due variabili intere x e y. Prevedere due funzioni di swap, la prima (swap_val) che utilizzi un passaggio per valore e la seconda (swap_rif) che utilizzi, invece, un passaggio per indirizzo. Visualizzare il valore delle variabili x e y in seguito alla chiamata di ciascuna funzione e commentare il risultato…
Esercizio 2. Ricerca di un pattern in una stringa Scrivere un programma che cerca le occorrenze di un pattern all’interno di un insieme di stringhe. Il programma prevede che l’utente possa inserire più stringhe; si considera la terminazione di una stringa con l’andata a capo (si legge cioè una linea per volta). L’input termina quando viene inserita una stringa vuota (nessun carattere+invio). Si implementino e utilizzino due funzioni: int getline(char s[], int lim): legge una linea di input nel vettore s, fino a un massimo di lim caratteri, e ritorna la lunghezza della stringa int ricerca_pattern(char s[]) cerca il pattern dentro la stringa s e ritorna -1 se non ha trovato il pattern, un numero >=0 altrimenti. Per ogni input, il programma stampa il numero di occorrenze trovate fino a quel momento e, nel caso in cui sia stato trovato il pattern, anche la stringa.
Pagina 11 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
Soluzione esercizio 1 #include void swap_val(int,int); void swap_rif(int*,int*); main() { int x,y; printf("Inserisci x e y:\n"); scanf("%d %d",&x,&y); swap_val(x,y); printf("Dopo lo swap con passaggio per valore: x vale %d, y vale %d\n",x,y); swap_rif(&x,&y); printf("Dopo lo swap con passaggio per indirizzo: x vale %d, y vale %d\n",x,y); } void swap_val(int x, int y) // swap con passaggio per valore { int tmp; tmp = x; x = y; y = tmp; } // swap con passaggio per indirizzo void swap_rif(int *px, int *py) { int tmp; tmp = *px; *px = *py; *py = tmp; }
Soluzione esercizio 2 #include #define LIM 100 int getline(char[], int); int ricerca_pattern(char[]); char pattern[] = "ando"; int trovato=0; // inizializzazione ridondante main() { // cerca le occorrenze di un pattern dentro una stringa char line[LIM]; printf("Inserisci il testo in cui ricercare il pattern “); printf(“una linea per volta).\nStringa vuota per terminare.\n"); while (getline(line,LIM) > 0)
Pagina 12 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com { if (ricerca_pattern(line) >= 0) printf("%s\n", line); printf("\n Finora ho trovato”); printf(“%d occorrenze di %s\n",trovato,pattern); } } int getline(char s[], int lim) { /* legge una linea di input nel vettore s, fino a un massimo di lim caratteri; ritorna la lunghezza della stringa */ int c, i = 0; while(lim > 1 && (c=getchar()) != '\n') { s[i] = c; ++i; --lim; } s[i] = '\0'; return i; } int ricerca_pattern(char s[]) { /* cerca la stringa pattern dentro s */ int i, j, k, ret=-1; for (i = 0; s[i] != '\0'; ++i) { for (j = i, k = 0; pattern[k] != '\0'; ++j, ++k) if (s[j] != pattern[k]) break; if (pattern[k] == '\0') { ++trovato; ret=i; } } return ret; }
ESEMPI DI SCOPE
#include int counter = 0;
/* variabile globale */
void f(void); void f2(void);
Pagina 13 di 15
Dispensa 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
int main() { ++counter; printf("counter is %2d before the call to f\n", counter); f(); printf("counter is %2d after the call to f\n", counter); f2(); return 0; } void f(void) { int counter = 10;
/* variabile locale
*/
printf("counter is %2d within f\n", counter); } void f2(void) { printf("counter is %2d within f2\n", counter); }
Occorre sempre fare attenzione ai nomi e alla visibilità delle variabili: la variabile globale counter, definita all’inizio del programma, è visibile e modificabile in tutto il programma, eccetto per la funzione f, dove il nome counter è usato per identificare una variabile locale. Nota bene: quello visto sopra non è un buon esempio di programmazione, in quanto genera confusione per il lettore. #include void f1(int); int f2(void); void f1(int i) { extern int pippo; pippo = pippo + i; } int f2() { static int pluto; // pluto è inizializzato solo alla prima chiamata a f2 pluto = pluto + 1; return pluto; } int pippo; int main() { int j; int i = 2; printf("%d\n",pippo); f1(i); printf("%d\n",pippo);
Pagina 14 di 15
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15
j = f2(); printf("%d\n",j); j = f2(); printf("%d\n",j); return 0; }
• • •
• • •
Lo scope di una variabile locale (automatica) è la funzione dove è stata definita. Lo scope di una variabile globale (esterna) va dal punto in cui essa è definita al termine del file sorgente in cui si trova. Se è necessario riferire una variabile esterna prima che essa sia stata definita, oppure se essa è definita in un file sorgente diverso da quello in cui viene utilizzata, allora è necessaria una dichiarazione di extern. La dichiarazione static, applicata ad una variabile esterna, ne limita lo scope al file sorgente nel quale essa si trova. La dichiarazione static, applicata ad una variabile non esterna, consente alla variabile di mantenere il proprio valore anche fra due chiamate successive. Le variabili esterne e static vengono inizializzate a zero, mentre le variabili automatiche hanno valori iniziali indefiniti.
Pagina 15 di 15
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 1 Step 1 ordine
Ordine memoria
#include
alloc.
void stampa(int i); void incrementa(int k);
1
in memoria viene allocato lo spazio necessario per mantenere la variabile “numero” (variabile locale della funzione main())
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
deal.
All’avvio del programma la prima istruzione ad essere eseguita è la dichiarazione della variabile “numero” con assegnamento del valore 7
main() variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
Pagina 2 di 52
numero: 7
1
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
deal.
Quando viene chiamata una funzione, l’esecuzione del programma si blocca nel punto della chiamata, il controllo viene ceduto alla funzione chiamata
void stampa(int i); void incrementa(int k);
1 2
3 4
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
La seconda istruzione prevede una chiamata ad una funzione stampa()
In memoria vengono allocate tutte le variabili locali alla funzione incluse quelle indicate nei parametri stampa()
i: 7
2
main()
numero: 7
1
variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
3
I parametri vengono passati alla funzione copiando, in base alla posizione, il valore nella nuova variabile locale alla funzione chiamata, quindi nel nostro esempio la variabile “i” (della funzione stampa()) avrà una copia del valore della variabile “numero” (punto 3) Si procede poi con l’esecuzione delle varie istruzioni presenti all’interno del corpo della funzione stampa() (punto 4) e quindi con la stampa a video del valore della variabile “i”, cioè del valore 7.
Pagina 3 di 52
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 5
3 4
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
Quando una funzione termina tutte le variabili locali vengono rimosse dalla memoria, quindi nella funzione stampa() presa in esame la variabile “i” viene rimossa dalla memoria. Il controllo viene ceduto al chiamante, quindi alla funzione main() ripartendo dall’istruzione immediatamente dopo la chiamata (punto 5)
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
Pagina 4 di 52
3
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
deal.
La funzione main() si blocca e il controllo viene ceduto alla funzione chiamata.
void stampa(int i); void incrementa(int k);
1 2 5 6
3 4 7 8
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
Si procede con il punto 6 e quindi con la chiamata della funzione incrementa()
incrementa()
val: 5
5
incrementa()
k: 7
4
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
Pagina 5 di 52
In memoria vengono allocate tutte le variabili locali (punto 8) alla funzione incluse quelle indicate nei parametri
3
I parametri vengono passati alla funzione copiando, in base alla posizione, il valore nella nuova variabile locale alla funzione chiamata, quindi nel nostro esempio la variabile “k” (della funzione incrementa()) avrà una copia del valore della variabile “numero” (punto 7)
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 5 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 5 6
3 4 7 8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
incrementa()
val: 6
5
incrementa()
k: 8
4
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
Pagina 6 di 52
vengono eseguite tutte le istruzioni all’interno della funzione, in particolare nel nostro esempio vengono incrementate le variabili “val” e “k” (“val” diventa 6 e “k” diventa 8). Vedi punti 9 e 10 Modificando la variabile “k” non viene modificata la variabile “numero” in quanto “k” è una copia della variabile “numero”
3
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 6 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 5 6 11
3 4 7 8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
incrementa()
val: 6
5
6
incrementa()
k: 8
4
6
stampa()
i: 7
2
3
main()
numero: 7
1
variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
Pagina 7 di 52
Quando una funzione termina tutte le variabili locali vengono rimosse dalla memoria, quindi nella funzione incrementa() presa in esame le variabili “k” e “val” vengono rimosse dalla memoria. Il controllo viene ceduto al chiamante, quindi alla funzione main() ripartendo dall’istruzione immediatamente dopo la chiamata (punto 11)
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 7 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 5 6 11 12 13 3 4 7 8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { printf(“%d”,i); }
incrementa()
val: 6
5
6
incrementa()
k: 8
4
6
stampa()
i: 7
2
3
main()
numero: 7
1
7
variabili globali
void incrementa(int k) { int val = 5; val++; k++; }
Pagina 8 di 52
Quando il programma termina tutte le variabili allocate all’interno del main() vengono rimosse dalla memoria
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 2 Step 1 ordine
Ordine memoria
#include
alloc.
void stampa(int i); void incrementa(int k);
1
in memoria viene allocato lo spazio necessario per mantenere la variabile “numero” (variabile locale della funzione main())
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
deal.
All’avvio del programma la prima istruzione ad essere eseguita è la dichiarazione della variabile “numero” con assegnamento del valore 7
main() variabili globali
Pagina 9 di 52
numero: 7
1
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
Quando viene chiamata una funzione, l’esecuzione del programma si blocca nel punto della chiamata, il controllo viene ceduto alla funzione chiamata
void stampa(int i); void incrementa(int k);
1 2
3 4
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
deal.
La seconda istruzione prevede una chiamata ad una funzione stampa()
In memoria vengono allocate tutte le variabili locali alla funzione incluse quelle indicate nei parametri
stampa()
val: 10
3
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
Pagina 10 di 52
I parametri vengono passati alla funzione copiando, in base alla posizione, il valore nella nuova variabile locale alla funzione chiamata, quindi nel nostro esempio la variabile “i” (della funzione stampa()) avrà una copia del valore della variabile “numero” (punto 3)
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include
alloc.
La seconda istruzione prevede una chiamata ad una funzione incrementa() l’esecuzione della funzione stamoa() si blocca nel punto della chiamata, il controllo viene ceduto alla funzione chiamata
void stampa(int i); void incrementa(int k);
1 2
3 4 5 6
7 8
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
deal.
Si procede con il punto 5 e quindi con la stampa del valore della variabile i.
In memoria vengono allocate tutte le variabili locali alla funzione incluse quelle indicate nei parametri incrementa()
val: 5
5
incrementa()
k: 10
4
stampa()
val: 10
3
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
Pagina 11 di 52
I parametri vengono passati alla funzione copiando, in base alla posizione, il valore nella nuova variabile locale alla funzione chiamata, quindi nel nostro esempio la variabile “k” (della funzione incrementa()) avrà una copia del valore della variabile “val” (punto 7). La variabile “val” della funzione incrementa() non ha nulla a che fare con l’omonima variabile all’interno della funzione stampa()
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
void stampa(int i); void incrementa(int k);
1 2
3 4 5 6
7 8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
incrementa()
val: 6
5
incrementa()
k: 11
4
stampa()
val: 10
3
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
Pagina 12 di 52
deal.
Si procede eseguendo le istruzioni presenti all’interno del corpo della funzione incrementa() quindi con i punti 9 e 10
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 5 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2
3 4 5 6 11 7 8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
incrementa()
val: 6
5
6
incrementa()
k: 11
4
6
stampa()
val: 10
3
stampa()
i: 7
2
main()
numero: 7
1
variabili globali
Pagina 13 di 52
Quando una funzione termina tutte le variabili locali vengono rimosse dalla memoria, quindi nella funzione incrementa() presa in esame le variabili “k” e “val” vengono rimosse dalla memoria. Il controllo viene ceduto al chiamante, quindi alla funzione stampa() ripartendo dall’istruzione immediatamente dopo la chiamata (punto 11) quindi avremo la stampa del valore 10 (associato alla variabile val)
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 6 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 12 13
3 4 5 6 11 14
7
15
8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
Il controllo viene ceduto al chiamante, quindi alla funzione main() ripartendo dall’istruzione immediatamente dopo la chiamata (punto 12) quindi avremo la stampa della stringa “fine stampa”.
incrementa()
val: 5
9
incrementa()
k: 7
8
incrementa()
val: 6
5
6
incrementa()
k: 11
4
6
stampa()
val: 10
3
7
stampa()
i: 7
2
7
main()
numero: 7
1
variabili globali
Pagina 14 di 52
Quando la funzione stampa() termina le proprie istruzioni tutte le variabili locali vengono rimosse dalla memoria, quindi le variabili “i” e “val” vengono rimosse dalla memoria.
Si procede eseguendo il punto 13 con la chiamata alla funzione incrementa() In memoria vengono allocate tutte le variabili locali alla funzione incluse quelle indicate nei parametri I parametri vengono passati alla funzione copiando, in base alla posizione, il valore nella nuova variabile locale alla funzione chiamata, quindi nel nostro esempio la variabile “k” (della funzione incrementa()) avrà una copia del valore della variabile “numero” (punto 14)
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 7 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 12 13
3 4 5 6 11 14
7
15 16 17
8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
incrementa()
val: 6
9
incrementa()
k: 8
8
incrementa()
val: 6
5
6
incrementa()
k: 11
4
6
stampa()
val: 10
3
7
stampa()
i: 7
2
7
main()
numero: 7
1
variabili globali
Pagina 15 di 52
Si procede eseguendo le istruzioni presenti all’interno del corpo della funzione incrementa() quindi con i punti 9 e 10
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 8 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 12 13 18
3 4 5 6 11 14
7
15 16 17
8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
incrementa()
val: 6
9
10
incrementa()
k: 8
8
10
incrementa()
val: 6
5
6
incrementa()
k: 11
4
6
stampa()
val: 10
3
7
stampa()
i: 7
2
7
main()
numero: 7
1
variabili globali
Pagina 16 di 52
Quando una funzione termina tutte le variabili locali vengono rimosse dalla memoria, quindi nella funzione incrementa() presa in esame le variabili “k” e “val” vengono rimosse dalla memoria. Il controllo viene ceduto al chiamante, quindi alla funzione main() ripartendo dall’istruzione immediatamente dopo la chiamata (punto 18) quindi avremo la stampa del valore 7 (associato alla variabile numero)
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 9 ordine
Ordine memoria
#include
alloc.
deal.
void stampa(int i); void incrementa(int k);
1 2 12 13 18 19 20 3 4 5 6 11 14
7
15 16 17
8 9 10
int main() { int numero = 7; stampa(numero); printf(“fine stampa”); incrementa(numero); printf(“%d”,numero); return 0; } void stampa(int i) { int val = 10; printf(“%d”,i); incrementa(val); printf(“%d”,val); } void incrementa(int k) { int val = 5; val++; k++; }
incrementa()
val: 6
9
10
incrementa()
k: 8
8
10
incrementa()
val: 6
5
6
incrementa()
k: 11
4
6
stampa()
val: 10
3
7
stampa()
i: 7
2
7
main()
numero: 7
1
11
variabili globali
restituzione di un parametro
Pagina 17 di 52
Quando il programma termina tutte le variabili allocate all’interno del main() vengono rimosse dalla memoria
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 3 Step 1 ordine
Ordine memoria
#include
alloc.
deal.
void modifica(int i, int *k);
1 2 3 4
All’avvio del programma vengono dichiarate le variabili “numero” e “valore” assegnando rispettivamente i valori 7 e 5 Si procede con la stampa a video delle variabili dichiarate precedenti. Quindi a video verranno stampati i valori 7 e 5
int main() { int numero = 7; int valore = 5; printf(“%d”,numero); printf(“%d”,valore); modifica(numero,&valore); printf(“%d”,numero); printf(“%d”,valore); return 0; } void modifica(int i, int *k) { i = 10; *k = 16; }
main()
valore: 5
2
main()
numero: 7
1
variabili globali
frammento di memoria:
7 1000
numero
1001
5 1002
1003
1004
1005
1006
valore
Pagina 18 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
deal.
In memoria vengono allocate tutte le variabili locali alla funzione incluse quelle indicate nei parametri
void modifica(int i, int *k);
1 2 3 4 5
6
Si procede eseguendo il punto 5 con la chiamata alla funzione modifica()
int main() { int numero = 7; int valore = 5; printf(“%d”,numero); printf(“%d”,valore); modifica(numero,&valore); printf(“%d”,numero); printf(“%d”,valore); return 0; } void modifica(int i, int *k) { i = 10; *k = 16; }
modifica()
k: 1003
3
modifica()
i: 7
3
main()
valore: 5
2
main()
numero: 7
1
I parametri vengono passati alla funzione copiando, in base alla posizione, il valore nella nuova variabile locale alla funzione chiamata, quindi nel nostro esempio la variabile “i” (della funzione modifica()) avrà una copia del valore della variabile “numero” (punto 6) e la variabile “k” avrà l’indirizzo di memoria della variabile valore (quindi 1001)
variabili globali
frammento di memoria:
7 1000
1001
5 1002
1003
7 1004
1005
1003
1006 i
numero
1007
1008
1009 k
valore *k
Pagina 19 di 52
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include
alloc.
deal.
void modifica(int i, int *k);
1 2 3 4 5
6 7 8
int main() { int numero = 7; int valore = 5; printf(“%d”,numero); printf(“%d”,valore); modifica(numero,&valore); printf(“%d”,numero); printf(“%d”,valore); return 0; } void modifica(int i, int *k) { i = 10; *k = 16; }
modifica()
k: 1003
3
modifica()
i: 10
3
main()
valore: 16
2
main()
numero: 7
1
Vengono eseguite le istruzioni presenti all’interno della funzione: alla variabile “i” viene associato il valore 10, mentre, attraverso l’operatore di rinvio (*) si accede alla cella di memoria con indirizzo presente all’interno della variabile k (1001) inserendo il valore 16. Modificando il *k viene modificata anche la variabile “valore”
variabili globali
frammento di memoria:
7 1000
1001
16 1002
1003
10 1004
1005
1006 i
numero
1003 1007
1008
1009 k
valore *k
Pagina 20 di 52
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
deal.
void modifica(int i, int *k);
1 2 3 4 5 9
6 7 8
int main() { int numero = 7; int valore = 5; printf(“%d”,numero); printf(“%d”,valore); modifica(numero,&valore); printf(“%d”,numero); printf(“%d”,valore); return 0; } void modifica(int i, int *k) { i = 10; *k = 16; }
modifica()
k: 1001
3
4
modifica()
i: 10
3
4
main()
valore: 16
2
main()
numero: 7
1
Quando la funzione termina tutte le variabili locali vengono eliminate dalla memoria Il controllo viene restituito alla funzione chiamante riprendendo dall’istruzione immediatamente successiva alla chiamata della funzione (punto 9)
variabili globali
frammento di memoria:
7 1000
1001
16 1002
1003
10 1004
1005
1006 i
numero
valore
Pagina 21 di 52
1003 1007
1008
1009 k
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 5 ordine
Ordine memoria
#include
alloc.
deal.
void modifica(int i, int *k);
1 2 3 4 5 9 10
6 7 8
int main() { int numero = 7; int valore = 5; printf(“%d”,numero); printf(“%d”,valore); modifica(numero,&valore); printf(“%d”,numero); printf(“%d”,valore); return 0; } void modifica(int i, int *k) { i = 10; *k = 16; }
modifica()
k: 1001
3
4
modifica()
i: 10
3
4
main()
valore: 16
2
main()
numero: 7
1
Con le funzioni printf() nei punti 9 e 10 vengono stampati i valori delle variabili “numero” e “valore”, quindi rispettivamente 7 e 16
variabili globali
frammento di memoria:
7 1000
numero
1001
16 1002
1003
1004
1005
1006
valore
Pagina 22 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 6 ordine
Ordine memoria
#include
alloc.
deal.
Quando la funzione main() termina tutte le variabili locali vengono rimosse dalla memoria.
void modifica(int i, int *k);
1 2 3 4 5 9 10 11 6 7 8
int main() { int numero = 7; int valore = 5; printf(“%d”,numero); printf(“%d”,valore); modifica(numero,&valore); printf(“%d”,numero); printf(“%d”,valore); return 0; } void modifica(int i, int *k) { i = 10; *k = 16; }
modifica()
k: 1001
3
4
modifica()
i: 10
3
4
main()
valore: 16
2
5
main()
numero: 7
1
5
variabili globali
frammento di memoria:
1000
1001
1002
1003
1004
1005
1006
Pagina 23 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 4 Step 1 ordine
Ordine memoria
#include 1
alloc.
deal.
All’avvio del programma tutte le variabili globali vengono allocate in memoria. Le variabili globali sono visibili (modificabili) in tutto il programma
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
variabili globali
i: 0
1
frammento di memoria:
0 1000
1001
1002
1003
1004
1005
1006
i
Pagina 24 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include 1
2
3 4
alloc.
deal.
Si procede eseguendo il punto 2 con la chiamata alla funzione elabora() In memoria vengono allocate tutte le variabili locali alla funzione
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
cont: 0
2
elabora()
k: 0
2
variabili globali
i: 0
1
frammento di memoria:
0 1000
i
1001
0 1002
1003
1004
1005
0 1006
cont
1007
1008
k
Pagina 25 di 52
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include 1
2
3 4 5 6 7
alloc.
deal.
Vengono eseguite le istruzioni presenti nel corpo della funzione chiamata (punti 5, 6 e 7)
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
cont: 1
2
elabora()
k: 1
2
variabili globali
i: 1
1
frammento di memoria:
1 1000
i
1001
1 1002
1003
1004
1005
1 1006
cont
1007
1008
k
Pagina 26 di 52
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include 1
2 9
3 4 5 6 7 8
alloc.
deal.
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
cont: 1
2
elabora()
k: 1
2
variabili globali
i: 1
1
La funzione elabora() termina e restituisce il controllo alla funzione main() ripartendo dall’istruzione immediatamente successiva alla chiamata (punto 9). Tutte le variabili locali vengono rimosse dalla memoria eccetto le variabili statiche (le variabili statiche sono variabili visibili esclusivamente nella funzione in cui vengono dichiarate)
3
frammento di memoria:
1 1000
i
1001
1 1002
1003
1004
1005
1006
cont
Pagina 27 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 5 ordine
Ordine memoria
#include 1
2 9
10 11
3 4 5 6 7 8
alloc.
deal.
Si procede eseguendo il punto 9 con la chiamata alla funzione elabora() In memoria vengono allocate tutte le variabili locali alla funzione
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
k: 0
4
elabora()
cont: 1
2
elabora()
k: 1
2
variabili globali
i: 1
1
3
frammento di memoria:
1 1000
i
1001
1 1002
1003
1004
1005
0 1006
cont
1007
k
Pagina 28 di 52
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 6 ordine
Ordine memoria
#include 1
2 9
10 11 12 13 14
3 4 5 6 7 8
alloc.
deal.
Vengono eseguite le istruzioni presenti nel corpo della funzione chiamata (punti 12, 13 e 14)
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
k: 1
4
elabora()
cont: 2
2
elabora()
k: 1
2
variabili globali
i: 2
1
3
frammento di memoria:
2 1000
i
1001
2 1002
1003
1004
1005
1 1006
cont
1007
k
Pagina 29 di 52
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 7 ordine
Ordine memoria
#include 1
2 9 16
10 11 12 13 14 15
3 4 5 6 7 8
alloc.
deal.
5
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
k: 1
4
elabora()
cont: 2
2
elabora()
k: 1
2
variabili globali
i: 2
1
La funzione elabora() termina e restituisce il controllo alla funzione main() ripartendo dall’istruzione immediatamente successiva alla chiamata (punto 16). Tutte le variabili locali vengono rimosse dalla memoria eccetto le variabili statiche
3
frammento di memoria:
2 1000
i
1001
2 1002
1003
1004
1005
1006
cont
Pagina 30 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 8 ordine
Ordine memoria
#include 1
2 9 16
17 18
10 11 12 13 14 15
3 4 5 6 7 8
alloc.
Si procede eseguendo il punto 9 con la chiamata alla funzione elabora()
deal.
In memoria vengono allocate tutte le variabili locali alla funzione
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
k: 0
6
elabora()
k: 1
4
elabora()
cont: 2
2
elabora()
k: 1
2
variabili globali
i: 2
1
5
3
frammento di memoria:
2 1000
i
1001
2 1002
1003
1004
1005
0 1006
cont
1007
1008
1009
1010
1011
k
Pagina 31 di 52
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 9 ordine
Ordine memoria
#include 1
2 9 16
17 18 19 20 21
10 11 12 13 14 15
3 4 5 6 7 8
alloc.
Vengono eseguite le istruzioni presenti nel corpo della funzione chiamata (punti 19, 20 e 21)
deal.
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
k: 1
6
elabora()
k: 1
4
elabora()
cont: 3
2
elabora()
k: 1
2
variabili globali
i: 3
1
5
3
frammento di memoria:
3 1000
i
1001
3 1002
1003
1004
1005
1 1006
cont
1007
1008
1009
1010
1011
k
Pagina 32 di 52
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 10 ordine
Ordine memoria
#include 1
2 9 16 23
17 18 19 20 21 22
10 11 12 13 14 15
3 4 5 6 7 8
alloc.
deal.
void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
elabora()
k: 1
6
7
elabora()
k: 1
4
5
elabora()
cont: 3
2
elabora()
k: 1
2
variabili globali
i: 3
1
3
La funzione elabora() termina e restituisce il controllo alla funzione main() ripartendo dall’istruzione immediatamente successiva alla chiamata (punto 23). Tutte le variabili locali vengono rimosse dalla memoria eccetto le variabili statiche All’interno della funzione main() è possibile stampare il valore della variabile “i” in quanto variabile globale, non può essere fatta alcuna operazione sulla variabile cont in quanto variabile locale alla funzione elabora()
frammento di memoria:
3 1000
i
1001
3 1002
1003
1004
1005
1006
cont
Pagina 33 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 11 ordine
Ordine memoria
#include
17 18 19 20 21 22
10 11 12 13 14 15
1
void elabora(); int i = 0;
2 9 16 23 24
int main() { elabora(); elabora(); elabora(); return 0; }
3 4 5 6 7 8
void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
alloc.
deal.
elabora()
k: 1
6
7
elabora()
k: 1
4
5
elabora()
cont: 3
2
8
elabora()
k: 1
2
3
variabili globali
i: 3
1
9
Il programma termina e tutte le variabili allocate all’interno del main(), quelle locali e quelle statiche vengono rimosse dalla memoria
frammento di memoria:
3 1000
i
1001
3 1002
1003
1004
1005
1006
cont
Pagina 34 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 5 Step 1 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int vett[5]);
1
All’avvio del programma tutte le variabili locali alla funzione main() vengono allocate in memoria La variabile elenco tiene l’indirizzo di memoria della prima cella allocata per mantenere in memoria l’array
int main() { int elenco[5]={2,4,6,8,10}; elabora(elenco); return 0; } void elabora(int vett[5]) { int i; for (i = 0; i < 5; i++) { vett[i] = 1; } }
main()
elenco: 1001
1
variabili globali
frammento di memoria:
1000
2
4
6
8
10
1001
1002
1003
1004
1005
1006
elenco
Pagina 35 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int vett[5]);
1
int main() { int elenco[5]={2,4,6,8,10};
2
All’interno della variabile “vett” della funzione elabora() viene copiato il valore presente all’interno della variabile “elenco” passata come parametro alla funzione (quindi viene copiato l’indirizzo di memoria della prima cella allocata per l’array elenco)
elabora(elenco); return 0; }
3 4
void elabora(int vett[5]) { int i; for (i = 0; i < 5; i++) { vett[i] = 1; } }
elabora()
i: 0
3
elabora()
vett: 1001
2
main()
elenco: 1001
1
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nell’intestazione della funzione vengono allocate in memoria (punto 3 e 4)
La variabile “vett” è quindi un puntatore!
variabili globali
frammento di memoria:
1000
2
4
6
8
10
1001
1002
1003
1004
1005
1001 1006
1007
1008
1009
1010
vett
elenco
Pagina 36 di 52
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
alloc.
elabora()
i: 0, 1, 2, 3, 4
3
elabora()
vett: 1001
2
main()
elenco: 1001
1
#include
deal.
Si procede con l’esecuzione delle istruzioni presenti all’interno della funzione elabora()
void elabora(int vett[5]);
1
int main() { int elenco[5]={2,4,6,8,10};
2
elabora(elenco); return 0; }
3 4 5 6 7 8
void elabora(int vett[5]) { int i; for (i = 0; i < 5; i++) { vett[i] = 1; } }
variabili globali
frammento di memoria:
1000
1
1
1
1
1
1001
1002
1003
1004
1005
1001 1006
1007
1008
1009
1010
vett
elenco
Pagina 37 di 52
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int vett[5]);
1
int main() { int elenco[5]={2,4,6,8,10};
2 10
Da notare che le modifiche fatte a partire da “vett” vanno a modificare il vettore “elenco”
elabora(elenco); return 0; }
3 4 5 6 7 8 9
Terminata la funzione elabora il controllo passa alla funzione chiamante main() ripartendo dall’istruzione immediatamente successiva alla chiamata (punto 10). Tutte le variabili locali alla funzione elabora() vengono rimosse dalla memoria.
void elabora(int vett[5]) { int i; for (i = 0; i < 5; i++) { vett[i] = 1; } }
elabora()
i: 0, 1, 2, 3, 4
3
4
elabora()
vett: 1001
2
4
main()
elenco: 1001
1
variabili globali
frammento di memoria:
1000
1
1
1
1
1
1001
1002
1003
1004
1005
1001 1006
1007
1008
1009
1010
vett
elenco
Pagina 38 di 52
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 5 ordine
Ordine memoria
alloc.
deal.
elabora()
i: 0, 1, 2, 3, 4
3
4
elabora()
vett: 1001
2
4
main()
elenco: 1001
1
5
#include
Il programma termina e tutte le variabili locali al main() vengono rimosse dalla memoria.
void elabora(int vett[5]);
1 2 10 11 3 4 5 6 7 8 9
int main() { int elenco[5]={2,4,6,8,10}; elabora(elenco); return 0; } void elabora(int vett[5]) { int i; for (i = 0; i < 5; i++) { vett[i] = 1; } }
variabili globali
frammento di memoria:
1000
1
1
1
1
1
1001
1002
1003
1004
1005
1006
elenco
Pagina 39 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 6 Step 1 ordine
Ordine memoria
#include
alloc.
deal.
int* elabora();
1
All’avvio del programma tutte le variabili locali alla funzione main() vengono allocate in memoria La variabile “elenco” è un puntatore, quindi una variabile in grado di tenere in memoria l’indirizzo di un’altra variabile (di tipo intero)
int main() { int *elenco; elenco = elabora(); return 0; } int* elabora() { int *tmp;
main()
tmp=(int*)malloc(sizeof(int)*5); return tmp; }
elenco:
1
variabili globali
frammento di memoria:
1000
1001
1002
1003
1004
1005
1006
elenco
Pagina 40 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
deal.
int* elabora();
1
int main() { int *elenco;
2
La variabile “tmp” è un puntatore, quindi una variabile in grado di tenere in memoria l’indirizzo di un’altra variabile (di tipo intero)
elenco = elabora(); return 0; }
3 4
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nell’intestazione della funzione vengono allocate in memoria (punto 4)
int* elabora() { int *tmp; tmp=(int*)malloc(sizeof(int)*5); return tmp; }
elabora()
tmp:
2
main()
elenco:
1
variabili globali
frammento di memoria:
1000 elenco
1001
1002
1003
1004
1005
1006
tmp
Pagina 41 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include
alloc.
deal.
La funzione malloc() alloca in memoria un numero di byte pari al parametro passato come argomento. Malloc() restituisce l’indirizzo di memoria della prima cella di memoria allocata.
int* elabora();
1
int main() { int *elenco;
2
elenco = elabora(); return 0;
Quindi la variabile “tmp” dopo l’esecuzione della funzione malloc() avrà il valore 1007 (considerando il frammento di memoria preso in esempio)
} 3 4
int* elabora() { int *tmp;
5
Si procede con l’eseguire le istruzioni presenti all’interno della funzione elabora().
tmp=(int*)malloc(sizeof(int)*5); return tmp; }
elabora()
tmp: 1007
2
main()
elenco:
1
variabili globali
frammento di memoria:
1007 1000 elenco
1001
1002
1003
1004
1005
1006
tmp
Pagina 42 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
deal.
int* elabora();
1
Attraverso l’istruzione return la funzione elabora() restituisce al chiamante il valore presente nella variabile tmp quindi l’indirizzo di memoria 1007
int main() { int *elenco;
2
elenco = elabora(); return 0; }
3 4 5 6 7
Quando la funzione elabora() termina le proprie istruzioni tutte le variabili locali alla funzione vengono eliminate dalla memoria.
int* elabora() { int *tmp; tmp=(int*)malloc(sizeof(int)*5); return tmp; }
elabora()
tmp: 1007
2
main()
elenco:
1
3
La variabile “tmp” viene quindi rimossa dalla memoria, ma non viene deallocato lo spazio riservato dalla funzione malloc()
variabili globali
frammento di memoria:
1007 1000 elenco
1001
1002
1003
1004
1005
1006
tmp
Pagina 43 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 5 ordine
Ordine memoria
#include
alloc.
deal.
int* elabora();
1
int main() { int *elenco;
2
Quindi attraverso la variabile “elenco” è possibile accedere all’area di memoria allocata attraverso la funzione elabora()
elenco = elabora(); return 0; }
3 4 5 6 7
Quando la funzione elabora() termina le proprie operazioni all’interno della variabile “elenco” troveremo l’indirizzo di memoria allocato attraverso la funzione malloc() richiamata all’interno della funzione elabora()
int* elabora() { int *tmp; tmp=(int*)malloc(sizeof(int)*5); return tmp; }
elabora()
tmp: 1007
2
main()
elenco: 1007
1
3
variabili globali
frammento di memoria:
1007 1000
1001
1002
1003
1004
1005
1006
elenco
Pagina 44 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 7 Step 1 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int* v);
1
All’avvio del programma tutte le variabili locali alla funzione main() vengono allocate in memoria La variabile “elenco” è un puntatore, quindi una variabile in grado di tenere in memoria l’indirizzo di un’altra variabile (di tipo intero)
int main() { int *elenco; elabora(elenco); return 0; } void elabora(int* v) { v = (int*)malloc(sizeof(int)*5); }
main()
elenco:
1
variabili globali
frammento di memoria:
1000
1001
1002
1003
1004
1005
1006
elenco
Pagina 45 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int* v);
1
int main() { int *elenco;
2
elabora(elenco); return 0; }
3
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nell’intestazione della funzione vengono allocate in memoria (punto 3)
void elabora(int* v) { v = (int*)malloc(sizeof(int)*5); }
elabora()
v:
2
main()
elenco:
1
variabili globali
frammento di memoria:
1000
elenco
1001
1002
1003
1004
1005
1006
v
Pagina 46 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include
alloc.
deal.
La funzione malloc() alloca in memoria un numero di byte pari al parametro passato come argomento. Malloc() restituisce l’indirizzo di memoria della prima cella di memoria allocata.
void elabora(int* v);
1
int main() { int *elenco;
2
elabora(elenco); return 0; }
3 4
Si procede con l’eseguire le istruzioni presenti all’interno della funzione elabora().
void elabora(int* v) { v = (int*)malloc(sizeof(int)*5); }
elabora()
v: 1007
2
main()
elenco:
1
Quindi la variabile “v” dopo l’esecuzione della funzione malloc() avrà il valore 1007 (considerando il frammento di memoria preso in esempio)
variabili globali
frammento di memoria:
1007 1000
elenco
1001
1002
1003
1004
1005
1006
v
Pagina 47 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int* v);
1
La variabile “v” viene quindi rimossa dalla memoria, ma non viene deallocato lo spazio riservato dalla funzione malloc()
int main() { int *elenco;
2
elabora(elenco); return 0; }
3 4 5
Quando la funzione elabora() termina le proprie istruzioni tutte le variabili locali alla funzione vengono eliminate dalla memoria.
void elabora(int* v) { v = (int*)malloc(sizeof(int)*5); }
elabora()
v: 1007
2
main()
elenco:
1
3
Dopo la chiamata della funzione elabora() la variabile “elenco” NON contiene valori!
variabili globali
frammento di memoria:
1007 1000
elenco
1001
1002
1003
1004
1005
1006
v
Pagina 48 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
ESEMPIO 7-BIS Step 1 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int** v);
1
All’avvio del programma tutte le variabili locali alla funzione main() vengono allocate in memoria La variabile “elenco” è un puntatore, quindi una variabile in grado di tenere in memoria l’indirizzo di un’altra variabile (di tipo intero)
int main() { int *elenco; elabora(&elenco); return 0; } void elabora(int** v) { *v=(int*)malloc(sizeof(int)*5); }
main()
elenco:
1
variabili globali
frammento di memoria:
1000
1001
1002
1003
1004
1005
1006
elenco
Pagina 49 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 2 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int** v);
1
int main() { int *elenco;
2
3
All’interno della variabile “v” viene memorizzato l’indirizzo di memoria del puntatore “elenco”
elabora(&elenco); return 0; } void elabora(int** v) { *v=(int*)malloc(sizeof(int)*5); }
elabora()
v: 1001
2
main()
elenco:
1
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nell’intestazione della funzione vengono allocate in memoria (punto 3)
variabili globali
frammento di memoria:
1001 1000
elenco
1001
1002
1003
1004
1005
1006
v
Pagina 50 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 3 ordine
Ordine memoria
#include
alloc.
deal.
La funzione malloc() alloca in memoria un numero di byte pari al parametro passato come argomento. Malloc() restituisce l’indirizzo di memoria della prima cella di memoria allocata.
void elabora(int** v);
1
int main() { int *elenco;
2
elabora(&elenco); return 0; }
3 4
Si procede con l’eseguire le istruzioni presenti all’interno della funzione elabora().
void elabora(int** v) { *v=(int*)malloc(sizeof(int)*5); }
elabora()
v: 1001
2
main()
elenco: 1007
1
Attraverso *v accediamo alla variabile elenco, il valore restituito dalla funzione malloc() verrà quindi scritto dentro la variabile “elenco”
variabili globali
frammento di memoria:
1007 1000
elenco
1001
1001 1002
*v
1003
1004
1005
1006
v
Pagina 51 di 52
1007
1008
1009
1010
1011
Corso di Programmazione A.A. 2011-12
Dispensa 15b
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Step 4 ordine
Ordine memoria
#include
alloc.
deal.
void elabora(int** v);
1
La variabile “v” viene quindi rimossa dalla memoria, ma non viene deallocato lo spazio riservato dalla funzione malloc()
int main() { int *elenco;
2
elabora(&elenco); return 0; }
3 4 5
Quando la funzione elabora() termina le proprie istruzioni tutte le variabili locali alla funzione vengono eliminate dalla memoria.
void elabora(int** v) { *v=(int*)malloc(sizeof(int)*5); }
elabora()
v: 1001
2
main()
elenco: 1007
1
3
variabili globali
Dopo la chiamata della funzione elabora() la variabile “elenco” contiene l’indirizzo di memoria della prima cella allocata dalla funzione malloc()
frammento di memoria:
1007 1000
elenco
1001
1001 1002
1003
1004
1005
1006
v
Pagina 52 di 52
1007
1008
1009
1010
1011
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 15c Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15c
Funzioni ricorsive Le funzioni ricorsive hanno fatto la loro comparsa sulla scena molto tempo fa, praticamente con il primo linguaggio che era dotato di funzioni. Una funzione ricorsiva in sostanza è una funzione che, per svolgere il proprio lavoro, richiama sè stessa. Ad ogni richiamo la "profondità" dell'elaborazione aumenta, finché ad un certo punto, lo scopo viene raggiunto e la funzione ritorna. Una funzione ricorsiva "salva" il suo stato nel momento in cui richiama sé stessa, solitamente nello stack. Ogni volta che la ricorsione viene invocata, tutte le variabili presenti vengono inserite nello stack ed una nuova serie di variabili viene creata (sempre dallo stack). Questo significa un elevato consumo dello stack del sistema, se stiamo lavorando con uno stack limitato (come e' il caso con certi sistemi o con certe architetture di programma), rischiamo di superare i limiti dello stack e di avere un bel crash del programma. Aggiungiamo poi che, solitamente, il numero di chiamate ricorsive della funzione non e' ipotizzabile a priori ed abbiamo un possibile problema. Un altro possibile problema e' se la nostra funzione utilizza altre risorse oltre alla memoria del sistema, e tali risorse sono in quantita' limitata (connessioni a database per esempio). In questo caso, se il numero di richiami e' superiore ad un certo livello, possiamo avere un fallimento di chiamata. Non sempre le funzioni ricorsive sono una risposta efficiente. Consideriamo che ogni chiamata ricorsiva consuma memoria ed utilizza un salto ad una funzione. Ridisegnando la funzione come non-ricorsiva, si risparmia un salto e (forse) un po' di quella preziosa memoria.
Esempi Stampa di un elenco di numeri Il seguente programma stampa l’elenco dei numeri all’interno di un intervallo (da 1 a 3) in modo ricorsivo: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
#include void stampa(int mun); int main() { int x = 1; stampa(x); return 0; } void stampa (int num) { if (num > 3) return; printf(“%d\n”,num); stampa(num+1); }
(chiamata A) Dopo la riga 5 la memoria si configura nel seguente modo: nel main
x: 1
Pagina 2 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15c
dopo la chiamata alla funzione stampa() alla riga 8 nel main, la memoria è la seguente: stampa()
num: 1
nel main
x: 1
A
(chiamata B) Dato che num non è maggiore di 3 si procede con la stampa a video del numero 1 e alla riga 17 viene eseguita la chiamata ricorsiva passando il valore num+1 (cioè il valore 2), la memoria diventa: stampa()
num: 2
B
stampa()
num: 1
A
nel main
x: 1
(chiamata C) Dato che num non è maggiore di 3 si procede con la stampa a video del numero 2 e alla riga 17 viene eseguita la chiamata ricorsiva passando il valore num+1 (cioè il valore 3), la memoria diventa: stampa()
num: 3
C
stampa()
num: 2
B
stampa()
num: 1
A
nel main
x: 1
(chiamata D) Dato che num non è maggiore di 3 si procede con la stampa a video del numero 3 e alla riga 17 viene eseguita la chiamata ricorsiva passando il valore num+1 (cioè il valore 4), la memoria diventa: stampa()
num: 4
D
stampa()
num: 3
C
stampa()
num: 2
B
stampa()
num: 1
A
nel main
x: 1
dato che la variabile num è maggiore di 3 viene eseguita l’istruzione return alla riga 15, quindi la chiamata-D termina e il controllo passa alla funzione ricorsiva chiamante (chiamata-C), in particolare il controllo passa all’istruzione successiva alla chiamata ricorsiva della funzione: riga 18, quindi la funzione alla chiamata 3 termina le proprie operazioni e cede il controllo al chiamante (chiamata-B)... si procede in questo modo fino alla prima chiamata. Consideriamo una variante del programma visto precedentemente, inserendo la stampa dopo la chiamata ricorsiva: 1. #include 2. 3. void stampa(int mun); 4. 5. int main()
Pagina 3 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
Dispensa 15c
{ int x = 1; stampa(x); return 0; } void stampa (int num) { if (num > 3) return; stampa(num+1); printf(“%d\n”,num); }
Dopo la riga 5 la memoria si configura nel seguente modo: nel main
x: 1
(chiamata A) dopo la chiamata alla funzione stampa() alla riga 8 nel main, la memoria è la seguente: stampa()
num: 1
nel main
x: 1
A
(chiamata B) Dato che num non è maggiore di 3 viene eseguita la chiamata ricorsiva passando il valore num+1 (cioè il valore 2), la memoria diventa: stampa()
num: 2
B
stampa()
num: 1
A
nel main
x: 1
(chiamata C) Dato che num non è maggiore di 3 viene eseguita la chiamata ricorsiva passando il valore num+1 (cioè il valore 3), la memoria diventa: stampa()
num: 3
C
stampa()
num: 2
B
stampa()
num: 1
A
nel main
x: 1
(chiamata D) Dato che num non è maggiore di 3 viene eseguita la chiamata ricorsiva passando il valore num+1 (cioè il valore 4), la memoria diventa: stampa()
num: 4
D
stampa()
num: 3
C
Pagina 4 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com stampa()
num: 2
B
stampa()
num: 1
A
nel main
x: 1
Dispensa 15c
dato che la variabile num è maggiore di 3 viene eseguita l’istruzione return alla riga 15, quindi la chiamata-D termina e il controllo passa alla funzione ricorsiva chiamante (chiamata-C), in particolare il controllo passa all’istruzione successiva alla chiamata ricorsiva della funzione: riga 17 quindi alla stampa della variabile num all’interno della chiamata-C quindi la stampa del numero 3. Questo perchè all’interno della chiamata-C il valore della variabile num è pari a 3. Dopo aver stampato il numero la funzione alla chiamata-C termina e il controllo passa al chiamante e cioè chiamata-B, in particolare il controllo passa all’istruzione successiva alla chiamata ricorsiva della funzione: riga 17 quindi alla stampa della variabile num all’interno della chiamata-B quindi la stampa del numero 2... si procede in questo modo fino alla prima chiamata. Nella prima versione dell’esempio l’output sarà: 1, 2, 3. Nel secondo esempio invece avremo: 3, 2, 1 Fattoriale Il seguente programma calcola il fattoriale di un numero in modo ricorsivo: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
#include int fattoriale(int mun); int main() { int x, fat; scanf(“%d”,&x); fat = fattoriale(x); printf(“fattoriale: %d”, fat); return 0; } int fattoriale(int num) { if (n == 0) return 1; ris = num * fattoriale(num - 1); rerturn ris; }
Supponiamo che venga inserito il valore 3 come input. Dopo la lettura della variabile x la memoria si configura così: nel main
x: 3
(chiamata A) Dopo la chiamata della funzione fattoriale() passando il valore num-1 quindi 3 (riga 19), la memoria è la seguente: fattoriale()
num: 3
A
Pagina 5 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com nel main
Dispensa 15c
x: 3
(chiamata B) Dopo la prima chiamata ricorsiva della funzione fattoriale()passando il valore num-1 quindi 2 (riga 19), la memoria è la seguente: fattoriale()
num: 2
B
fattoriale()
num: 3
A
nel main
x: 3
(chiamata C) Dopo la prima chiamata ricorsiva della funzione fattoriale() passando il valore num-1 quindi 1 (riga 19), la memoria è la seguente: fattoriale()
num: 1
C
fattoriale()
num: 2
B
fattoriale()
num: 3
A
nel main
x: 3
(chiamata D) Dopo la prima chiamata ricorsiva della funzione fattoriale() passando il valore num-1 quindi 0 (riga 19), la memoria è la seguente: fattoriale()
num: 0
D
fattoriale()
num: 1
C
fattoriale()
num: 2
B
fattoriale()
num: 3
A
nel main
x: 3
alla chiamata-D il valore della variabile num è uguale a 0 quindi al controllo nella riga 16 viene eseguita l’istruzione return passando il valore 1 al chiamante, quindi alla chiamata-C. Alla chiamata-C troviamo nella riga 19 il valore restituito dalla chiamata-D e cioè 1 e il valore di num all’interno di questa chiamata e cioè 1. Dopo aver calcolato il prodotto il risultato viene restituito alla chiamata-B. Alla chiamata-B troviamo nella riga 19 il valore restituito dalla chiamata-C e cioè 1 e il valore di num all’interno di questa chiamata e cioè 2. Dopo aver calcolato il prodotto il risultato viene restituito alla chiamata-A. Alla chiamata-A troviamo nella riga 19 il valore restituito dalla chiamata-B e cioè 2 e il valore di num all’interno di questa chiamata e cioè 3. Dopo aver calcolato il prodotto il risultato viene restituito alla main e stampato a video (viene visualizzato il valore 6). Torri di Hanoi La Torre di Hanoi è un rompicapo matematico composto da tre paletti e un certo numero di dischi di grandezza decrescente, che possono essere infilati in uno qualsiasi dei paletti.
Pagina 6 di 7
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 15c
Il gioco inizia con tutti i dischi incolonnati su un paletto in ordine decrescente, in modo da formare un cono. Lo scopo del gioco è portare tutti dischi sull'ultimo paletto, potendo spostare solo un disco alla volta e potendo mettere un disco solo su un altro disco più grande, mai su uno più piccolo.
La proprietà matematica base è che il numero minimo di mosse necessarie per completare il gioco è 2n - 1, dove n è il numero di dischi. Ad esempio avendo 3 dischi, il numero di mosse minime è 7. Di conseguenza, secondo la leggenda, i monaci di Hanoi dovrebbero effettuare almeno 18.446.744.073.709.551.615 mosse prima che il mondo finisca, essendo n = 64. La soluzione generale è data dall'algoritmo seguente: •
Si etichettano i paletti con le lettere A, B e C
•
Dato n il numero dei dischi
•
Si numerano i dischi da 1 (il più piccolo, in alto) a n (il più grande, in basso)
Per spostare i dischi dal paletto A al paletto B: 1. Sposta i primi n-1 dischi da A a C. Questo lascia il disco n da solo sul paletto A 2. Sposta il disco n da A a B 3. Sposta n-1 dischi da C a B Questo è un algoritmo ricorsivo, di complessità esponenziale, quindi per risolvere il gioco con un numero n di dischi, bisogna applicare l'algoritmo prima a n-1 dischi. Dato che la procedura ha un numero finito di passi, in un qualche punto dell'algoritmo n sarà uguale a 1. Quando n è uguale a 1, la soluzione è banale: basta spostare il disco da A a B.
Pagina 7 di 7
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI
PROGRAMMAZIONE
A.A. 2011-12
Dispensa 16 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
I File su disco Flussi e file su disco Tutte le operazioni di input e output, comprese quelle dei file su disco, avvengono in C mediante i flussi. L’impiego dei flussi predefiniti, collegati alla tastiera e allo schermo operano sostanzialmente allo stesso modo dei flussi su file. Questo è il vantaggio dell’input/output mediante flussi: le tecniche sono le stesse per tutti i flussi. La caratteristica che distingue i flussi dai file su disco è la necessità di creare esplicitamente il flusso da associare a un file.
Tipi di file su disco Nel C ci sono due tipi di flussi: di testo e binari. A un file si può associare ciascuno di due tipi; è importante cogliere le differenze, per poter scegliere di volta in volta il tipo appropriato. I flussi di testo sono associati ai file in “in odo testo”. Tali file sono formati da un insieme di righe. Ogni riga contiene da zero o più caratteri speciali, che ne segnalano la fine. Una riga può essere al massimo lunga 255 caratteri. Va sottolineato che una riga in questo senso non è una stringa: non è terminata dal carattere NULL (\0). Quando si lavora su flussi in modo testo, si compie una traduzione tra il carattere di fine riga del C (\n) e i caratteri utilizzati dal sistema operativo per segnalare la fine di una riga del file su disco. Nei sistemi DOS, ad esempio, si utilizza la combinazione CR-LF (ritorno a capo, avanzamento riga). Nel copiare i dati verso un file su disco, ogni \n è tradotto in una coppia CR-LF; viceversa quando i dati vengono letti da un file su disco. Nei sistemi UNIX non occorre alcuna traduzione, perché il carattere di fine riga è lo stesso. I flussi binari sono associati a file in “modo binario”. In questo caso, i dati sono copiati così come sono e non vengono considerati logicamente divisi in righe. I caratteri NULL e di fine riga sono trattati esattamente come ogni altro carattere. Alcune funzioni di input/output su file si applicano solo in uno dei due modi, mentre altre sono universali.
I nomi dei file Ogni file su disco ha un nome che si può utilizzare nei programmi, i nomi sono memorizzati come stringhe di testo. Le regole che stabiliscono quali nomi sono accettabili variano a seconda del sistema operativo. E’ importante quindi conoscere le regole del sistema operativo su cui si opera. Il nome di un file in C può contenere anche dati relativi al percorso (path), che indica l’unità e la cartella in cui si trova il file. Quando non si specifica il percorso, si assume che il file si trova nella posizione che il sistema operativo considera corrente in quel momento. Si consiglia di specificare in ogni caso il percorso completo. Nel PC, per separatre i nomi delle directory in un percorso, si utilizza il carattere “\” (detto barra inversa). Ad esempio in DOS e Windows, il nome: a:\dati\elenco.txt denota un file di nome “elenco.txt”, nella cartella “\dati”, nell’unità “a”. Non dimentichiamo che il carattere “\” ha un significato speciale nelle stringhe del C. Per inserirlo letteralmente in una stringa, gli si deve anteporre un altro “\”. Perciò, in un programma in C, il nome dell’esempio precedente va indicato così: char *filename = “a:\\dati\\elenco.txt”; Quando però si digita il nome da tastiera, si deve inserire un solo “\”. Non tutti i sistemi operativi si conformano a questo stile. In UNIX, ad esempio, il separatore di cartelle è la barra obliqua “/”.
Pagina 2 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
Apertura del file La creazione di un flusso collegato ad un file su disco è detta apertura del file. Dopo essere stato aperto, un file è disponibile per la lettura, la scrittura o entrambi. Al termine delle operazioni, si deve chiudere il file. Per aprire un file, si utlizza la funzione di libreria fopen(), il cui prototipo, posto in stdio.h, è il seguente: FILE *fopen(const char *nome, const char *modo); Secondo il prototipo, fopen() restituisce un puntatore a FILE, una struttura dichiarate in stdio.h. I membri della struttura sono sfruttati dal programma nelle operazioni di accesso al file, ma il programmatore può disinteressarsene. Per ogni file che si intende aprire, si deve dichiarare un puntatore a FILE. La chiamata fopen() provoca la creazione di un esemplare di FILE e restituisce un puntatore a quell’esemplare. Tutte le operazioni seguenti sul file si servono di quel puntatore. La funzione fopen() segnala il proprio fallimento restituendo il valore NULL. Ciò può essere dovuto, ad esempio, a un errore hardware o al tentativo di aprire un file di un dischetto no formattato. L’argomento nome è il nome del file da aprire. Come si è già detto, nome può (e dovrebbe) contenere la specifica del percorso. L’argomento nome può consistere di una stringa letterale o di un puntatore a una variabile di tipo stringa. L’argomento modo specifica in che modo il file va aperto, cioè se il file è binario o di testo e se deve essere aperto in lettura, scrittura o entrambi. I valori riconosciuti sono elencati nella tabella sotto. Il modo di default è “testo”. Per aprire un file in modo “binario”, si deve aggiungere una b all’argomento modo. Perciò, ad esempio, se modo valre a, il file è aperto per l’aggiunta, in modo testo; se vale ab, il file è aperto per l’aggiunta in modo binario.
Modo r w
a
r+
w+
a+
Significato Apre il file in lettura. Se il file non esiste, fopen() restituisce NULL Apre il file in scrittura. Se il file non esiste un file con il nome indicato, ne viene creato uno. Se esiste, il suo contenuto viene cancellato senza preavviso. Apre il file in modo di aggiunta. Se non esiste un file con il nome indicato ne viene creato uno. Se esiste, i dati nuovi vengono aggiunti in coda al file Apre il file in lettura e scrittura. Se non esiste un file con il nome indicato ne viene creato uno. Se esiste, i dati nuovi vengono inseriti all’inizio, cancellando quelli precedenti Apre il file in modo di aggiunta. Se non esiste un file con il nome indicato ne viene creato uno. Se esiste, i dati nuovi vengono inseriti all’inizio, cancellando quelli precedenti Apre il file in modo di aggiunta. Se non esiste un file con il nome indicato ne viene creato uno. Se esiste, i dati nuovi vengono aggiunti in coda al file
Si ricorda che fopen() restituisce NULL in caso di errore. Fra le condizioni che possono provocare tale evento, sono comprese le seguenti.
Pagina 3 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com • • • •
Dispensa 16
Nome non valido Tentativo di aprire un file su un disco non pronto Tentativo di aprire un file in una cartella o unità inesistente Tentativo di aprire un file inesistente in sola lettura
E’ buona norma verificare se c’è stato un errore a ogni chiamata di fopen(). Anche se non è possibile stabilire quale sia stato l’errore, si può almeno avvisare l’utente e ripetere il tentativo o terminare il programma, secondo i casi. Spesso i compilatori del C offrono estensioni non ANSI che permettono di stabilire il tipo di errore, si consulti la documentazione del compilatore. Vediamo un esempio di apertura di file su disco in vari modi mediante fopen(). #include #include main() { FILE *fp; Char chm filename[40], mode[4]; while(1) { /*legge il nome del file e il modo*/ printf(“\n Inserire il nome del file: ”); gets(filename); printf(“\n Inserire il modo (max 3 caratteri): ”); gets(mode); /*Apertura del file*/ if ((fp = fopen(filename,mode)) != NULL) { printf(“\n Il file %s aperto in modo %s ”,filename,mode); fclose(fp); puts(“Premere x per uscire, un altro tasto per proseguire”); if ((ch = getc(stdin)) == ‘x’) break; } } return 0; }
Scrittura e lettura dei dati su file Un programma può utilizzare un file su disco scrivendovi o leggendovi dei fati, anche in combinazione. Ci sono tre modi di scrivere dati in un file:
•
Per mezzo dell’output formattato, si possono salvare in un file in un formato specifico. Ciò si applica a file in modo testo. L’impiego principale di questa forma è la creazione del file che contengono testo o valori numerici, destinati a essere trattati da altri programmi come fogli di calcolo o basi di dati.
Pagina 4 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com •
•
Dispensa 16
Per mezzo dell’output a caratteri, si salvano in un file caratteri singoli o righe. Si può applicare questa tecnica ai file in modo binario, ma si consiglia di limitarle ai file in modo testo. L’impiego principale di questa forma è il salvataggio di testi (dati non numerici) in file destinati a essere letti in programmi come un elaboratore di testi. Per mezzo dell’output diretto, si salva in un file su disco il contenuto de un’area di memoria. Questa forma si applica solo a file binari ed è la via migliore per salvare dati destinati a un uso successivo in un programma C.
Nella lettura dei fati da un file, si hanno le possibilità analoghe: input formattato, input a caratteri e input diretto. La scelta dipende quasi esclusivamente dal tipo del file su cui si opera. In genere, i dati vengono letti nello stesso modo in cui sono stati salvati, ma ciò non è indispensabile. Peraltro, la lettura in un modo diverso dalla precedente scrittura richiede la conoscenza approfondita del C e dei formati dei file.
Input e output formattato Input e output formattato si riferiscono a dati, testuali o numerici, in un formato specifico. Essi sono strettamente legati all’input formattato da tastiera e all’output formattato su schermo, mediante le funzioni scanf() e printf(). Funzione fprintf() L’output formattato su file si compie per mezzo delfa funzione di libreria fprintf(), il cui prototipo, posto nel file di intestazione stdio.h, è qui riportato: int fprintf(FILE *fp, char *fmt, …); Il primo argomento è un puntatore a FILE. Per scrivere dati su un file, si passa alla funzione il puntatore restituito da fopen() all’apertura del file. Il secondo argomento è la stringa di formato, segue le stesse regole della funzione printf(). L’ultimo argomento è indicato dai punti di sospensione, essi rappresentano un numero variabile di argomenti. In altre parole, oltre al puntatore al file e alla stringa di formato, fprintf() accetta zero, uno o più argomenti supplementari, proprio come printf(). Tali argomenti sono i nomi delle variabili di cui valori vanno copiati nel flusso specificato. Riassumento, fprintf() opera come printf(), tranne che l’output è inviato al flusso specificato come primo argomento. Se tale argomento è stdout, l’efetto di fprintf() è identico a quello di printf(). Vediamo un esempio: #include #include main() { FILE *fp; float data; int count; Char chm filename[40]; puts(“inserire cinque numeri decimali.”);
Pagina 5 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
for (count = 0; count < 5; count++) scanf(“%f”,&data[count]); fflush(stdin); puts(“inserire il nome del file:”); gets(filename); if ((fp = fopen(filename,”w”)) != NULL) { fprintf(stderr,“Errore nell’apertura del file %s”, filename); exit(1); } /*scrive i dati su file e su stdout*/ for (count = 0; count < 5; count++) { fprintf(fp, “\ndata[%d] = %f”, count, data[count]); fprintf(stdout, “\ndata[%d] = %f”, count, data[count]); } fclose(fp); return 0; }
Per l’input formattato da file, si utilizza la funzione di libreria fscanf(), simile a scanf(), tranne che l’input di fscanf() proviene da un flusso specificato, anziché stdin. Funzione fscanf() Ecco il prototipo di fscanf(): int fscanf(FILE *fp, conts char *fmt, …); L’argomento fp è un puntatore a FILE, usualmente restituito da una chiamata di fopen(); fmt è un puntatore a una stringa di formato, che indica come la funzione deve leggere il fato di ingresso. I componenti della stringa di formato sono gli stessi di scanf(). Infine, i punti di sospensione denotano gli eventuali argomenti supplementari, cioè gli indirizzi delle variabili alle quali assegnare i valori letti. Per mettere alla prova fscanf(), occorre un file di testo contenente numeri e parole in un formato leggibile dalla funzione, Con un editor, creare un file di nome “input.txt”, e inserirvi cinque numeri in virgola mobile, separati da spazi o “a capo”. Il contenuto del file potrebbe essere ad esempio: 123.45 100.03
87.001
0.00456 1.005
Pagina 6 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
Vediamo l’esempio:
#include #include main() { FILE *fp; float f1,f2,f3,f4,f5; if ((fp = fopen(“input.txt”,”r”)) != NULL) { fprintf(stderr,“Errore nell’apertura del file”); exit(1); } fscanf(fp, “%f %f %f %f %f” , &f1, &f2, &f3, &f4, &f5); printf(“I valori sono %f, %f, %f, %f, %f”, f1, f2, f3, f4, f5); fclose(fp); return 0; }
Input e output a caratteri Nell’ambito dei file su disco, il termine I/O a caratteri si riferisce sia a caratteri singoli sia a righe di caratteri. Come già detto, una riga è una sequenza di caratteri chiusa dal carattere di nuova riga. L’I/O a caratteri si applica ai file in modo testo. Le funzioni per l’input a caratteri: getc() e fgetc() per caratteri singoli; fgets() per righe intere. Funzione getc() Le funzioni getc() e fgetc() sono identiche e intercambiabili. Esse leggono un carattere dal flusso specificato. IL prototipo di getc() contenuto nel file stdio.h è il seguente: int getc(FILE *fp); L’argomento fp è un puntatore, tipicamente quello restituito da fopen() all’apertura di un file. La funzione restituisce il carattere letto o in caso di errore la costante EOF. La funzione getc() è già stata utilizzata per leggere un carattere battuto da tastiera. Funzione fgets() La funzione fgets() per leggere una riga di caratteri da un file, il suo prototipo è:
Pagina 7 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
char *fgets(char *str, int n, FoçE *fp); Il primo argomento è un puntatore al buffer in cui collocare l’input; n è il numero massimo di caratteri che si possono leggere; fp è un puntatore al file da cu leggere, solitamente restituito da fopen(). Alla chiamata, fgets() legge caratteri dal file puntato da fp in memoria, a partire dall’indirizzo indicato da str. La lettura prosegue fino a quando si incontra il carattere di nuova riga o qando sono stati letti n-1 caratteri, secondo quale condizione si verifica per prima. Se l’operazione non va a buon fine, fgets() restituisce str. Sono possibili due tipi di errore, segnalati da un valore di ritorno NULL:
• •
Se avviene un errore di lettura o si incontra EOF prima di aver letto almeno un carattere, la funzione restituisce NULL; il buffer a cui punta str rimane invariato Se avviene un errore di lettura o si incontra EOF dopo aver letto almeno un carattere, la funzione restituisce NULL; il contenuto del buffer a cui punta str è indefinito.
Va notato che fgets() non legge necessariamente una riga intera (cioè tutto ciò che precede il prossimo carattere di nuova riga).Se si raggiunge il numeri di n-1 caratteri letti prima di aver incontrato il carattere di nuova riga, la funzione si arresta. La successiva operazione di lettura pertirà dalla posizione in cui si è arrestata precedentemente. Si presentato due funzioni di output a caratteri: putc() e fputs(). Funzione putc() La funzione di libreria putc() scrive un carattere nel flusso specificato. IL su prototipo, contenuto di stdio.h, è il seguente: int putc(int ch, FILE *fp); L’argomento ch è il carattere da scrivere. Come nelle altre funzioni che trattano caratteri, esso è dichiarato di tipo int, ma in realtà si utilizza solo il byta “basso”. L’argomento fp è un puntatore al file su cui scrive, solitamente restituito da fopen(). La funzione putc() restituisce il carattere scritto in caso di riuscita e OEF in caso di errore. La costante simbolica di EOF, definita in stdio.h, ha il valore –1. Funzione fputs() Per scrivere una riga di caratteri in un flusso, si utilizza la funzione di libreria fputs(), che opera esattamente come puts(). La sola differenza è che in fputs() si può specificare il flusso di destinazione. Inoltre fputs() non aggiunge il carattere di nuova riga al termine della stringa, il programmatore se lo desidera, deve inserirlo esplicitamente. Il prototipo della funzione, in stdio.h, è il seguente: int fputs(char *str, FILE *fp); L’argomento str è un puntatore alla stringa da scrivere; fp è un puntatore a FILE. La stringa a cui punta str viene scritta nel file, senza il carattere terminale \0. La funzione fputs() restituisce un valore negativo in caso di succeso e EOF in caso di errore.
Pagina 8 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
Input e output diretto L’input/output diretto su file si utilizza in genere per salvare dati destinati a essere letti in seguito dallo stesso o da un altro programma in C. Esso si applica solo a file in modo binario. L’output diretto trasferisce blocchi di dati dalla memoria al disco. Ad esempio, con una sola chiamata di una funzione di output diretto, si può scrivere su disco un intero array; viceversa, una sola chiamata di una funzione di input diretto può leggere l’arrary dal disco alla memoria. Le funzioni di input/output diretto fread() e ferite(). Funzione fwrite() La funzione fwrite() scrive un blocco di dati dalla memoria a un file in modo binario. Il suo prototipo in stdlib.h, è il seguente: int fwrite(void *buf, int size, int count, FILE *fp); L’argomento buf è un puntatore all’area di memoria contente i dati da scrivere nel file. Il suo tipo è void: può puntare a qualsiasi oggetto. L’argomento size specifica la dimensione, in byte, dei singoli elementi-dato; count specifica quanti elementi devono essere scritti. Ad esempio per salvare un array di cento interi, size deve valere 2 (perché un intero occupa 2 byte) e count deve valere 100 (perché l’array contiene 100 elementi). Per stabilire il valore di size si può utilizzare la funzione sizeof(). L’argomento fp è un puntatore al file su cui scrivere, solitamente restituito da fopen(). La funzione ferite() restituisce il numero di elementi effettivamente scritti; un valore inferiore a count è indice di errore. Funzione fread() La funzione fread() legge un blocco di dati da un file in modo binario verso la memoria. Il suo prototipo in stdlib.h, è il seguente: int fread(void *buf, int size, int count, FILE *fp); Gli argomenti di fread(), sono analoghi a quelli di ferite(). L’argomento di buf è un puntatore all’area di memoria destinata a ricevere i dati letti dal file. Vediamo un esempio di utilizzo delle funzioni ferite() e fread(). #include #include #define SIZE 20 main() { FILE *fp; int count, array1[SIZE], array2[SIZE]; /*inizializza array1[]*/ for (count = 0; count < SIZE; count++)
Pagina 9 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
array1[count] = 2*count; /*apre un file in modo binario*/ if ((fp = fopen(“direct.txt”,”wb”)) != NULL) { fprintf(stderr,“Errore nell’apertura del file”); exit(1); } /*salva array1[] sul file*/ if (fwrite(array1, sizeof(int), SIZE, fp) != SIZE) { fprintf(stderr,“Errore nella scrittura su file”); exit(1); } fclose(fp); /*riapre allo stesso file per la lettuar in modo binario*/ if ((fp = fopen(“direct.txt”,”rb”)) != NULL) { fprintf(stderr,“Errore nell’apertura del file”); exit(1); } /*legge i dati in array2[]*/ if (fread(array2, sizeof(int), SIZE, fp) != SIZE) { fprintf(stderr,“Errore di lettura del file”); exit(1); } fclose(fp); for(count = 0; count < SIZE; count++) printf(“%d\t%d\n”,array1[count],array2[count]); return 0; }
File e buffer: chiusura e svuotamento Al termine delle operazioni su file, questo va chiuso con la funzione fclose(), già utilizzata in programmi di questo capitolo. Il suo prototipo è il seguente: int fclose(FILE *fp); L’argomento è un puntatore al file associato al flusso; fclose() restituisce 0 in caso di successo e –1 in caso di errore. Quando si chiude un file, il suo buffer viene “spazzato”, cioè scritto sul file. La funzione fcloseall() chiude tutti i flussi aperti, salvo quelli standard (stdin, stdout, stderr). Il suo prototipo è il seguente: int fcloseall(void);
Pagina 10 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
La funzione spezza il buffer di ogni flusso e restituisce il numeri dei flussi chiusi. Quando il programma termina (per avere raggiunto la fine di main() o per aver eseguito la funzione exit()), tutti i flussi aperti vengono automaticamente spazzati e chiusi. Tuttavia è buona norma chiudere esplicitamente i flussi, quando non sono più necessari. Il motivo è legato ai buffer dei flussi. Quando si crea un flusso associato a un file su disco, un buffer viene automaticamente creato e associato al flusso in questione. Un buffer è un’area di memoria che ospita temporaneamente i dati destinati a essere letti da un file o scritti nel file. I buffer sono necessari perché le unità a disco sono dispositivi a “blocchi”, cioè operano leggendo e scrivendo i dati in blocchi di dimensione predefinita. La dimensione ideale dei blocchi varia secondo il dispositivo. Il programmatore non deve preoccuparsi del valore esatto. Il buffer associato a un flusso e l’hardware del disco. Quando il programma scrive dati nel flusso, i dati vengono salvati nel buffer, fino a quando il buffer è pieno; a questo punto, l’intero contenuto del buffer viene copiato nel disco come blocco. Un processo simmetrico avviene in lettura. La creazione e gestione del buffer sono a cura del sistema operativo. In pratica, tutto ciò significa che, durante l’esecuzione del programma, certi dati logicamente già scritti si trovano in realtà nel buffer, e non ancora sul disco. Se il programma si blocca, se manca la corrente o se si verificano altri problemi, i dati nel buffer potrebbero perdersi. Con le funzioni di libreria fflush() e flushall() è possibile “spazzare” il buffer di un flusso senza chiuderlo. Si utilizza fflush() per trasferire su disco il contenuti di un buffer, continuando a operare sul file associato. Si utilizza flushall() par spazzare i buffer di tutti i flussi aperti. I prototipi delle due funzioni sono i seguenti: int fflush(FILE *fp); int flushall(void); L’argomento fp è un puntatore a FILE.
Accessi sequenziale e casuale A ogni file aperto è associato un indicatore di posizione (che chiameremo segnaposto), che determina il punto in cui si compiono le operazioni di lettura e scrittura. La posizione è espressa in byte dall’inizio del file. Quando si apre un file nuovo, il segnaposto è all’inizio, cioè alla posizione 0 (il file essendo nuovo è di lunghezza 0). Quando si apre un file preesistente, il segnaposto si trova alla fine del file se questo è stato aperto in modo di apprendimento e all’inizio se è stato aperto in un altro modo. Tutte le funzioni di input/output descritte in precedenza sfruttano il segnaposto, anche se “di nascosto”. Le operazioni di lettura e scrittura avvengono nella posizione segnata dal segnaposto e lo aggiornano. Se occorre controllare il segnaposto, si possono utilizzare apposite funzioni di librerie. Si può così realizzare il cosiddetto accesso casuale a un file, cioè l’accesso diretto a qualsiasi posizione del file, per leggere e scrivere dati, senza dover scorrere i precedenti. Le funzioni ftell() e rewind() Per controllare il segnaposto all’inizio del file, si utilizza la funzione di libreria rewind(), il cui prototipo, in stdio.h, è il seguente: void rewind(FILE *fp);
Pagina 11 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
L’argomento fp è un puntatore al file associato al flusso. Dopo la chiamata di rewind(), il segnaposto si trova all’inizio del file (byte 0). Per determinare la posizione del segnaposto, si utilizza la funzione ftell(), il cui prototipo, in stdio.h, è il seguente: long ftell(FILE *fp); L’argomento fp è un puntatore al file associato al flusso. La funzione restituisce un valore di tipo long, che da la posizione corrente nel file, espressa in byte dall’inizio del file (il primo byte si trova alla posizione 0). In caso di errore la funzione restituisce –1. Le funzioni fseek() La funzione fseek() consente un controllo più versatile del segnaposto di un flusso. Con fseek(), si può posse il segnaposto in qualsiasi posizione di un file. Il prototipo della funzione, in stdio.h è il seguente: int fseek(FILE *fp, long offset, int origin); L’argomento fp è il puntatore al file associato al flusso. La distanza di cui spostare il segnaposto, espressa in byte, è indicata da offset. L’argomento origin specifica il punto di partenza dello spostamento. I suoi valori possibili sono tre, le corrispondenti costanti simboliche sono descritte nella seguente tabella:
Costante
Valore
Descrizione
SEEK_SET
0
Sposta il segnaposto di offset byte dall’inizio del file
SEEK_CUR
1
Sposta il segnaposto di offset byte dalla posizione corrente
SEEK_END
2
Sposta il segnaposto di offset byte dalla fine del file
La funzione fseek() restituisce 0 se lo spostamento è stato compiuto e un valore diverso da zero se c’è stato u errore. Vediamo un esempio:
#include #include #define MAX 50 main() { FILE *fp; int count, array [MAX]; long offset; /*inizializza array*/ for (count = 0; count < MAX; count++)
Pagina 12 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com array[count] = 10*count; /*apre un file*/ if ((fp = fopen(“random.dat”,”wb”)) != NULL) { fprintf(stderr,“Errore nell’apertura del file”); exit(1); } /*ascrive l’array nel file*/ if (fwrite(array, sizeof(int), MAX, fp) != MAX) { fprintf(stderr,“Errore nella scrittura su file”); exit(1); } fclose(fp); /*riapre allo stesso file per la lettua in modo binario*/ if ((fp = fopen(“random.dat”,”rb”)) != NULL) { fprintf(stderr,“Errore nell’apertura del file”); exit(1); } /*Chiede all’utente quale elemento leggere. Viene letto l’elemento e lo visualizza; termina se l’utente inserisce -1*/ while(1) { printf(“\n Inserire elem da leggere, 0-%d, -1 per uscire: ”,MAX-1); scanf(“%ld”,offset); if (offset < 0) break; /*Pone il segnaposto alla posizione richiesta*/ if (fseek(fp, (offset * sizeof(int), SEEK_SET)) != 0) { fprintf(stderr,“Errore nell’uso di fseek()”); exit(1); } /*Legge un numero intero*/ fread(&data, sizeof(int), 1, fp); printf(“\n L’elemento in %ld ha un valore: %d”),offset,data); } fclose(fp); return 0; }
Pagina 13 di 16
Dispensa 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
Rivelazione della fine di un file Esistono due modi per rilevare la fune di un file. Nella lettura carattere per carattere di un file in modo testo, la fine del file è segnalata da un carattere speciale, individuato dalla costante speciale EOF, definita in stdio.h e pari a –1. Il valore .1 non corrisponde a nessun carattere “vero”. Quando una funzione di input a caratteri legge EOF da un file in modo testo si può essere certi che è stata raggiunta la fune del file. In un flusso binario, non si può applicare lo stesso criterio, perché un byte potrebbe avere il valore –1 ed essere un dato significativo. Si deve invece utilizzare la funzione di libreria feof(), valida sia per i file in modo binario sia per i file in modo testo. Il prototipo della funzione è: int feof(FILE *fp); L’argomento fp è un puntatore al file, solitamente restituito da fopen(). La funzione feof() restituisce un valore diverso da zero se è stata raggiunta la fine del file e 0 in caso contrario. Quando una chiamata di feof() rileva la fine del file, ogni operazione di lettura sul file è proibitafino a una chiamata di rewind() o fseek(), oppure fino alla chiusura e riapertura del file.
Pagina 14 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 16
Esercizio: Scrivere un programma che apre un file di testo e conta le occorrenze di ciascun carattere del file. Si conteggiano tutti i caratteri presenti sulla tastiera, distinguendo lettere maiuscole e minuscole, numeri, gli spazi e i segni di punteggiatura. Il risultato deve essere stampato sullo schermo. Soluzione esercizio:
#include #include int file_exists(char *filename); main() { FILE *fp; char ch, source[81]; int index; long count[127]; printf(“\n Inserire il nome del file: ”); gets(source); if (file_exists(source)) { fprintf(stderr,“\n Il file %s non esiste.”,cource); exit(1); } if ((fp = fopen(source,”rb”)) != NULL) { fprintf(stderr,“\n Errore nell’apertura del file %s”,source); exit(1); } for (index = 0; index z 127; index++) count[index] = 0; while(1) { ch = fgetc(fp); /*Termina se trova la fine del file*/ if (feof(fp)) break; /*Conteggio I caratteri fra 32 e 126*/ if (ch > 31 && ch < 127) count[ch]++; } /*visualizza i risultati*/ printf(“\nCarattere\tConteggio”);
Pagina 15 di 16
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com for (index = 32; index < 127; index++) printf(“%c\t%d\n”,index,count[index]); /*chiude il file e termina*/ fclose(fp); return 0; } int file_exists(char *filename) { /*La funzione restituisce TRUE se il file esiste, FALSE altrimenti*/ FILE *fp; if (((fp = fopen(filename,”r”)) != NULL) return 0; else { fclose(fp); return 1; } }
Pagina 16 di 16
Dispensa 16
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1
Liste
17.1.1
Inserimento in testa
Considerando le seguenti strutture dati e variabili: struct cella{ char valore; struct cella *next; }; struct cella *testa = NULL; Dove “cella” è la struttura che definisce come è fatto ogni elemento della lista, “testa” è il puntatore al primo elemento della lista (testa mantiene l’indirizzo di memoria della prima cella in lista).
A
B
C
NULL
testa Attraverso una chiamata alla funzione malloc() allochiamo in memoria lo spazio necessario per mantenere la nuova cella da inserire all’interno della lista:
A
B
C
NULL
testa
1 D
NULL
nuovo = (struct cella*)malloc(sizeof(struct cella));
nuovo “nuovo” è un puntatore ad un elemento di tipo “struct cella” allo stesso modo di “testa”, la funzione malloc() restituisce l’indirizzo di memoria della prima cella allocata, tale indirizzo viene memorizzato all’interno della variabile (puntatore) “nuovo”. Pagina 2 di 2
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
La prima operazione da compiere è collegare la nuova cella alla prima della lista, quindi fare in modo che l’elemento “next” della nuova cella prenda lo stesso valore di “testa”:
A
B
C
NULL
testa
2 nuovo->next = testa;
NULL
D nuovo
La seconda operazione consiste nell’associare la testa al nuovo elemento creato:
A
B
C
NULL
testa
3 testa = nuovo;
D nuovo In modo da ottenere l’inserimento del nuovo elemento in testa:
D
4
A
B
testa Pagina 3 di 3
C
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Se non si seguono esattamente nell’ordine indicato i passi indicati in precedenza e ad esempio come primo passo si associa la testa della lista al nuovo elemento:
A
B
C
NULL
testa ERRORE! se fatto come primo passo si perde il riferimento al resto della lista!
D nuovo Si perde il riferimento al resto della lista quindi non sarà possibile collegare il nuovo elemento con il resto della lista! Riassumendo il codice necessario per l’inserimento di un elemento in lista: nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->next = testa; testa = nuovo; Le istruzioni descritte funzionano anche considerando una lista vuota:
NULL testa
1 D
NULL
nuovo = (struct cella*)malloc(sizeof(struct cella));
nuovo Pagina 4 di 4
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com NULL testa
2 D
NULL
nuovo->next = testa;
nuovo
NULL testa
3 testa = nuovo;
D nuovo
D
4
NULL
testa
Pagina 5 di 5
Dispensa 17
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.2
Stampa degli elementi il lista
Considerando la struttura dati e le variabili definite nel punto precedente, la stampa consiste nel accedere ad ogni elemento della lista partendo dal primo elemento in testa.
1
A
B
C
NULL
con testa->valore si proprietà della prima cella
accede
alla
testa
Per poter leggere i valori nella seconda cella bisogna accedere a tale cella:
A
2
B
C
NULL testa = testa->next permette di passare alla cella successiva
testa
dopo aver eseguito testa = testa->next si ottiene:
A
B
C
NULL ora con testa->valore è possibile accede alla proprietà della seconda cella
testa si procede eseguendo nuovamente testa = testa->next per accedere alla terza cella: Pagina 6 di 6
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com A
3
B
C
NULL
B
C
NULL
testa = testa->next permette di passare alla cella successiva
testa
A
ora con testa->valore è possibile accede alla proprietà della terza cella
testa Ripetendo le operazioni viste nei punti precedenti si accede alla fine della lista:
A
B
C
NULL
C
NULL
testa
4
A
B
testa
Pagina 7 di 7
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Quindi la logica è quella di accedere a tutti gli elementi della lista fino a quando non si arriva alla fine della lista e cioè al valore NULL. Quindi riassumendo il codice potrebbe essere:
while (testa != NULL) { printf(“%d”, testa->valore); tesa = testa->next; } Procedendo in questo modo però dopo aver stampato la lista si perde il riferimento al primo elemento della lista, e quindi alla lista intera! Quindi prima di scorrere la lista bisognerebbe tener traccia del primo elemento attraverso un altro puntatore:
temp
1
A
B
C
NULL
temp = testa;
A
B
C
NULL
testa = temp->next;
A
B
C
NULL
testa
2
temp testa
temp
testa Pagina 8 di 8
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
3
temp
A
B
C
NULL testa = temp->next;
testa temp
A
B
C
NULL
testa
4
temp
A
B
C
NULL testa = temp->next;
testa
temp
A
B
C
NULL
testa Pagina 9 di 9
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 17
In questo modo arrivati alla fine della lista il riferimento al primo elemento non è andato perso. Con l’istruzione temp = testa si riesce a ripristinare lo stato iniziale.
temp = testa; while (testa != NULL) { printf(“%d”, testa->valore); testa = testa->next; } testa = temp; E’ preferibile muoversi all’interno della lista (scorrere la lista) con un puntatore diverso dalla testa e non con il riferimento alla testa (così si evita di dimenticarsi di tener traccia del primo elemento!!)
temp = testa; while (temp != NULL) { printf(“%d”, temp->valore); temp = temp ->next; } In questo modo dopo aver eseguito la stampa il riferimento alla testa non è stato modificato!
Pagina 10 di 10
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.3
Dispensa 17
Ricerca di un elemento in lista
La ricerca è simile alla stampa: bisogna scorrere la lista fino ad individuare l’elemento cercato:
temp = testa; trovato = 0; while (temp != NULL) { if (temp->valore = valoreDaCercare) { trovato = 1; break; } temp = temp ->next; } if (trovato) printf(“Il valore cercato è presente in lista!”); else printf(“valore NON trovato”); All’interno dell’analisi della lista (dentro al ciclo while) attraverso un controllo con un istruzione “if” è possibile valutare se la cella corrente è quella cercata, la variabile “trovato” ci permette di sapere al di fuori del ciclo se l’elemento cercato è presente o meno all’interno della lista. Se entriamo dentro al corpo dell’istruzione “if” la variabile “trovato” viene impostata al valore 1 altrimenti rimane settata al valore 0.
Pagina 11 di 11
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.4
Dispensa 17
Numero di occorrenze di un elemento in lista
Questa operazione è simile a quella del punto precedente (in questo caso bisogna però contare quante volte un elemento è ripetuto all’interno della lista):
temp = testa; conta = 0; while (temp != NULL) { if (temp->valore = valoreDaCercare) conta++; temp = temp ->next; } printf(“L’elemento cercato è presente %d volte!”,conta);
17.1.5
Numero di elementi in lista
temp = testa; conta = 0; while (temp != NULL) { conta++; temp = temp ->next; } printf(“La lista ha %d elementi!”,conta);
Pagina 12 di 12
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.6
Inserimento di un nuovo elemento in coda alla lista
Per poter inserire un nuovo elemento in coda alla lista prima bisogna procedere a scorrere la lista fino ad arrivare all’ultimo elemento:
1
testa
A
B
C
NULL temp = testa; while (temp->next != NULL) temp = temp ->next;
temp
Come negli esempi precedenti la lista viene scorsa attraverso un puntatore d’appoggio (cursore) in modo da non perdere il riferimento al primo elemento
testa
A
B
C
NULL
2
temp
D nuovo
attraverso la funzione malloc() viene creato il nuovo elemento: nuovo = (struct cella*)malloc(sizeof(struct cella));
Pagina 13 di 13
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
testa
A
B
C
NULL
3
temp
D Il puntatore all’elemento successivo dell’ultima cella viene fatto puntare alla nuova cella:
NULL
nuovo
temp->next = nuovo;
Bisogna fare attenzione in quanto se la lista è vuota non bisogna scorrere la lista ma semplicemente far puntare il puntatore alla testa al nuovo elemento. Il codice per questa funzionalità è:
nuovo = (struct cella*)malloc(sizeof(struct cella)); if (testa == NULL) testa = nuovo; else { temp = testa; while (temp->next != NULL) temp = temp ->next; temp->next = nuovo; }
Pagina 14 di 14
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.7
Cancellazione dell’elemento in testa alla lista
Prima di cancellare l’elemento in testa, il riferimento alla testa della lista deve passare al secondo elemento per evitare di perdere l’intera lista:
1
testa
A
B
C
NULL
Viene utilizzato un puntatore “temp” per tener traccia del primo elemento prima di far puntare a “testa” all’elemento successivo:
temp
temp = testa; testa = testa->next;
2
testa
A
B
C
NULL
C
NULL
temp a questo punto è possibile eliminare il primo elemento attraverso la funzione free():
A
3
B
temp
free(temp);
testa
Pagina 15 di 15
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.8
Cancellazione di un elemento in lista
Per cancellare un elemento dalla lista bisogna prima individuare l’elemento all’interno della lista, quindi bisogna avviare una procedura di ricerca vista in precedenza per arrivare all’elemento da eliminare.
testa
A
B
C
D
NULL
temp Dopo aver individuato l’elemento da eliminare (ad esempio la cella C considerando l’esempio precedente), bisogna rimuoverlo dalla lista. Per eliminare correttamente l’elemento senza alterare la catena che collega un elemento ad un altro, bisogna far in modo che l’elemento che precede la cella da eliminare venga collegato all’elemento che segue la cella da eliminare, quindi:
testa
A
B
C
D
NULL
temp Però per poter fare questo è necessario avere un riferimento all’elemento precedente della cella da eliminare. Quindi oltre al cursore “temp” che ci consente di scorrere la lista è necessario un ulteriore puntatore (ad esempio “prec”) che di volta in volta ci permetta di tener traccia dell’elemento precedente di quello considerato. Supponiamo di dover eliminare la cella “C”, quindi dovremo scorrere la lista fino ad arrivare a tale cella.
Pagina 16 di 16
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com prec
1
NULL testa
A
B
C
temp
NULL
D
Come prima operazione il cursore “temp” punta alla stessa cella della “testa”, ovviamente il precedente del primo elemento non esiste quindi il riferimento “prec” sarà a NULL temp = testa; ptec = NULL;
prec
2
NULL
testa
A
B
C
temp
D
NULL
Prima di accedere all’elemento successivo bisogna tener traccia dell’elemento che si sta per lasciare: prec = temp;
Pagina 17 di 17
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
prec
3
testa
A
B
C
temp
D
NULL
Si procede passando all’elemento successivo: temp = temp->next;
I punti 2 e 3 si ripetono fino ad arrivare all’elemento da eliminare “C”:
4-A
prec
4
testa
A
B
temp
C
4-B
Pagina 18 di 18
D
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com La situazione prima di eliminare l’elemento è:
prec
5 testa
A
B
C
D
NULL
D
NULL
temp Prima di eliminare l’elemento “C” bisogna collegare la cella precedente (B) a quella successiva (D):
prec
6 testa
A
B
C
temp
prec->next = temp->next;
in modo da ottenere:
7
testa
prec
A
B
C
D
NULL Con la funzione free() eliminiamo la cella puntata da “temp”
temp
free(temp); Pagina 19 di 19
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi riassumendo il codice per eliminare un elemento dalla lista:
prec = NULL; temp = testa; while (temp != NULL) { if (temp->valore == elementoDaEliminare) { prec->next = temp->next; free(temp); break; } prec = temp; temp = temp->next; } Bisogna fare attenzione in quanto se l’elemento da eliminare è il primo della lista, il codice riportato precedente genera la perdita del riferimento alla testa della lista:
prec
NULL testa
A
B
C
D
NULL
temp ERRORE! in questo modo si perde il riferimento alla testa della lista!
Pagina 20 di 20
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi il codice corretto diventa:
prec = NULL; temp = testa; while (temp != NULL) { if (temp->valore == elementoDaEliminare) { if (prec == NULL) testa = testa->next; else prec->next = temp->next; free(temp); break; } prec = temp; temp = temp->next; } prima di eliminare un elemento bisogna controllare se questo è l’elemento in testa alla lista, in tal caso bisogna muovere il riferimento alla testa.
Pagina 21 di 21
Dispensa 17
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.9
Dispensa 17
Cancellare tutte le occorrenze di un elemento in lista
Questa funzionalità è simile alla precedete solo che dopo aver eliminato la prima occorrenza dell’elemento non bisogna uscire dal ciclo ma procedere alla ricerca di altre occorrenze:
prec = NULL; temp = testa; while (temp != NULL) { if (temp->valore == elementoDaEliminare) { if (prec == NULL) testa = testa->next; else prec->next = temp->next; elimina = temp; temp = temp->next; free(elimina); } else { prec = temp; temp = temp->next; } } Dato che bisogna procedere con l’analisi della lista non è possibile eliminare a partire da “temp” in quanto verrà utilizzato all’iterazione successiva per considerare il nuovo elemento.
Pagina 22 di 22
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.10
Swap di due elementi in lista
L’operazione di swap consiste nello scambiare di posto due elementi all’interno della lista. Per eseguire questa operazione bisogna considerare diverse cose: •
individuare il primo elemento da spostare
•
individuare il secondo elemento da spostare
•
considerare cosa fare se uno dei due elementi è il primo della lista
•
considerare cosa fare se i due elementi sono consecutivi all’interno della lista (uno attaccato all’altro)
Ovviamente in base a quanto detto prima le operazioni da implementare possono essere differenti. Per individuare gli elementi da spostare bisogna implementare una ricerca all’interno della lista per ottenere i riferimenti necessari rispettivamente al primo e al secondo elemento (si ripete 2 volte la ricerca vista nei punti precedenti). Come per la cancellazione di un elemento servirà un puntatore all’elemento precedente per poter sistemare a modo tutti i riferimenti. Considerando che nessuno dei due elementi sia in testa alla lista e che gli elementi non siano consecutivi:
precPrimo testa
precSecondo
A
B
C
D
primo
E
F
NULL
secondo
i puntatori “primo” e “secondo” si riferiscono rispettivamente alla prima e alla seconda cella da invertire, mentre “precPrimo” e “precSecondo” sono puntatori agli elementi che precedono “primo” e “secondo”.
Pagina 23 di 23
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Dopo l’operazione di swap, la lista dovrebbe diventare (usando qualche puntatore d’appoggio):
4 1
precPrimo testa
precSecondo
A
B
C
D
primo
E
F
NULL
secondo
2
3 riorganizzando un po’:
precPrimo testa
precSecondo
A
E
C
D
secondo
B
primo
Vediamo ora il dettaglio dei passi per arrivare a quanto visto sopra.
Pagina 24 di 24
F
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
1
precPrimo testa
precSecondo
A
B
C
D
E
primo
NULL
F
secondo precPrimo->next = secondo;
2 precPrimo testa
precSecondo
A
B
C
primo
D
E
secondo
precSecondo->next = primo;
Pagina 25 di 25
F
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
3 temp
precPrimo testa
A
precSecondo
B
C
D
primo
E
F
NULL
secondo
Per evitare di perdere il riferimento all’elemento successivo di “primo”, prima di spostare il riferimento next di “primo” salviamo tale valore in una variabile d’appoggio “temp” temp = primo->next; primo->next = secondo->next;
temp
precPrimo testa
A
B
precSecondo
C
D
primo
E
secondo
Pagina 26 di 26
F
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
4
temp
precPrimo testa
A
precSecondo
B
C
D
primo
E
F
NULL
secondo
secondo->next = temp;
5
precPrimo testa
precSecondo
A
B
C
D
primo
E
secondo
Pagina 27 di 27
F
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Come descritto in precedenza troveremo delle differenze nel caso in cui uno dei due elementi da spostare è in testa alla lista, in quanto parte delle operazioni riguarderanno il riferimento alla testa della lista:
precPrimo
NULL
4 1
precSecondo testa
A
B
C
primo
D
NULL
E
secondo
2
3
avremo quindi:
precSecondo testa
primo
A
B
C
D
secondo
Pagina 28 di 28
E
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Un altro caso particolare lo troveremo quando gli elementi da invertire sono uno di seguito all’altro:
2
precSecondo
precPrimo testa
A
3
B
C
NULL
D
primo
secondo
1
avremo quindi:
precSecondo
precPrimo testa
A
B
C
primo
D
secondo
Pagina 29 di 29
NULL
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 17
Riassumendo il codice necessario per lo swap di due elementi in lista, considerando che “primo” sia il puntatore al primo elemento, “secondo” sia il puntatore al secondo elemento, “precPrimo” sia il puntatore all’elemento precedente del “primo” e sia impostato a NULL nel caso tale elemento sia in testa, “precSecondo” sia il puntatore all’elemento che precede “secondo” ed eventualmente sia uguale a “primo” nel caso in cui i due elementi siano consecutivi:
if (precPrimo == NULL) //il primo elemento è in testa testa = secondo; else precPrimo->next = secondo; if (precSecondo->next == primo) //se due elementi sono consecutivi temp = primo; else { temp = primo->next; precSecondo->next = primo; } primo->next = secondo->next; secondo->next = temp;
Pagina 30 di 30
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
17.1.11
Dispensa 17
Inserimento di un nuovo elemento attraverso una funzione
Proviamo a scrivere una funzione che ci consenta di inserire un nuovo elemento all’interno della lista, considerando tutte le variabili locali (anticipiamo che questo primo esempio è sbagliato! spiegheremo poi il perchè). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#include #include struct cella{ char valore; struct cella *next; }; void inserisci(struct cella *t, char val); int main() { struct cella *testa = NULL; //...altre operazioni inserisci(testa,’D’); //...altre operazioni return 0; } void inserisci(struct cella *t, char val) { struct cella *nuovo; nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->valore = v; nuovo->next = t; t = nuovo; }
Pagina 31 di 31
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Cerchiamo di capire dove si trova l’errore analizzando, tra le altre cose, quello che accade in memoria mentre le varie istruzioni vengono eseguite. Prima della chiamata all’interno del main() della funzione inserisci, la lista sarà:
A
B
NULL
C
testa in memoria quindi potremo avere la seguente situaione:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
1010
1011
1012
1013
1014
1015
testa
Quando la funzione inserisci viene chiamata (riga 17) all’interno del main(), il controllo viene ceduto dal chiamante (main) alla funzione chiamata (inserisci), tutte le variabili della funzione inserisci() vengono allocate in memoria (incluse quelle presenti all’interno dell’intestazione).
Pagina 32 di 32
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi la situazione in memoria sarà:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
1010
val
testa
1011
1012
1013
t
1014
1015
nuovo
I parametri passati alla funzione verranno copiati nelle variabili presenti nell’intestazione nell’ordine in cui sono stati inseriti, quindi “t” prenderà il valore presente all’interno di “testa” e “val” il carattere D (riga 24 e 26):
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
val
testa Pagina 33 di 33
D
1003
1010
1011
t
1012
1013
1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Attraverso la funzione malloc() verrà allocata la nuova cella in memoria, l’indirizzo del primo byte allocato verrà restituito dalla funzione malloc() e inserito (riga 28) all’interno della variabile “nuovo”:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
D
1003
1010
1011
val
testa
1015 1012
1013
t
1014
1015
nuovo
Il valore presente all’interno della variabile “val” viene copiato all’interno dell’elemento “valore” della nuova cella creata, referenziata dal puntatore “nuovo” (riga 29):
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
val
testa Pagina 34 di 34
D
1003
1010
1011
t
1015 1012
1013
D 1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi la situazione a livello logico sarà:
testa A
B
C
t
D
NULL
nuovo
Pagina 35 di 35
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Come descritto nel paragrafo 16.1.1 la prima operazione da implementare per inserire l’elemento in lista consisterà nel far puntare il puntatore next di della nuova cella a quello che punta la testa (nuovo->next = t):
testa A
B
C
NULL
t NULL
D nuovo Quindi in memoria avremo:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D
1008
val
testa
Pagina 36 di 36
D
1003
1010
1011
t
1015 1012
1013
next: 1003 1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Come descritto nel paragrafo 16.1.1 la seconda operazione da fare consiste nello spostare il riferimento della testa, nel nostro caso “t” dato che siamo all’interno della funzione (t = nuovo):
testa A
B
C
NULL
t
D nuovo Quindi in memoria avremo:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D
1008
val
testa
Pagina 37 di 37
D
1015
1010
1011
t
1015 1012
1013
next: 1003 1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Riassumendo in memoria avremo la seguente situazione:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D
1008
D
1015
1010
1011
val
testa
1015 1012
t
B
t
D nuovo
Pagina 38 di 38
C
1014
nuovo
testa A
1013
next: 1003
NULL
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Quando la funzione termina tutte le variabili locali vengono eliminate dalla memoria, il controllo viene restituito al chiamante (funzione main()) in memoria avremo la seguente situazione:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D next: 1003 1008
1010
1011
1012
1013
testa
A livello logico:
testa A
B
C
D
Quindi tutte le modifiche fatte alla testa della lista all’interno della funzione non vengono viste all’esterno! Pagina 39 di 39
NULL
1014
1015
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 17
Modifichiamo il codice descritto all’inizio paragrafo (in rosso le modifiche). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#include #include struct cella{ char valore; struct cella *next; }; void inserisci(struct cella **t, char val); int main() { struct cella *testa = NULL; //...altre operazioni inserisci(&testa,’D’); //...altre operazioni return 0; } void inserisci(struct cella **t, char val) { struct cella *nuovo; nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->valore = v; nuovo->next = *t; *t = nuovo; }
La funzione inserisci() riceve ora come primo parametro un puntatore di puntatore cioè una variabile che al suo interno mantiene l’indirizzo di memoria di un’altra variabile che a sua volta contiene un indirizzo di memoria.
Pagina 40 di 40
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Prima della chiamata all’interno del main() della funzione inserisci, la lista sarà:
A
B
NULL
C
testa
in memoria quindi potremo avere la seguente situaione:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
1010
1011
1012
1013
1014
1015
testa
Quando la funzione inserisci viene chiamata (riga 17) all’interno del main(), il controllo viene ceduto dal chiamante (main) alla funzione chiamata (inserisci), tutte le variabili della funzione inserisci() vengono allocate in memoria (incluse quelle presenti all’interno dell’intestazione).
Pagina 41 di 41
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi la situazione in memoria sarà:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
1010
val
testa
1011
1012
1013
t
1014
1015
nuovo
I parametri passati alla funzione verranno copiati nelle variabili presenti nell’intestazione nell’ordine in cui sono stati inseriti, quindi “t” prenderà l’indirizzo di memoria della prima variabile e quindi di testa “testa” e “val” il carattere D (riga 24 e 26):
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
val
testa Pagina 42 di 42
D
1001
1010
1011
t
1012
1013
1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Attraverso la funzione malloc() verrà allocata la nuova cella in memoria, l’indirizzo del primo byte allocato verrà restituito dalla funzione malloc() e inserito (riga 28) all’interno della variabile “nuovo”:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
D
1001
1010
1011
val
testa
1015 1012
1013
t
1014
1015
nuovo
Il valore presente all’interno della variabile “val” viene copiato all’interno dell’elemento “valore” della nuova cella creata, referenziata dal puntatore “nuovo” (riga 29):
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
val
testa Pagina 43 di 43
D
1001
1010
1011
t
1015 1012
1013
D 1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi la situazione a livello logico sarà:
testa A
B
C
NULL
*t
t
NULL
D nuovo
1003 1001
testa
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
1008
val
*t
D
1001
1010
1011
t
1015 1012
1013
D 1014
1015
nuovo
con “t” si ottiene il valore 1001 o meglio l’indirizzo di memoria che contiene il valore della variabile (puntatore) “testa”, mentre con “*t” si accede al contenuto dell’area di memoria referenziata dall’indirizzo 1001.
Pagina 44 di 44
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Come descritto nel paragrafo 16.1.1 la prima operazione da implementare per inserire l’elemento in lista consisterà nel far puntare il puntatore next di della nuova cella a quello che punta la testa (nuovo->next = *t):
testa A
B
C
NULL
*t
t
NULL
D nuovo
Quindi in memoria avremo:
1003 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D
1008
val
testa
Pagina 45 di 45
D
1001
1010
1011
t
1015 1012
1013
next: 1003 1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Come descritto nel paragrafo 16.1.1 la seconda operazione da fare consiste nello spostare il riferimento della testa, nel nostro caso “t” dato che siamo all’interno della funzione (*t = nuovo):
testa A
B
C
NULL
*t
t
D nuovo NB. modificando *t modifichiamo anche “testa”! Quindi in memoria avremo:
1015 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D
1008
val
testa
Pagina 46 di 46
D
1001
1010
1011
t
1015 1012
1013
next: 1003 1014
nuovo
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Riassumendo in memoria avremo la seguente situazione:
1015 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D
1008
D
1001
1010
1011
val
testa
1015 1012
t
B
t D nuovo
Pagina 47 di 47
C
1014
nuovo
testa A
1013
next: 1003
NULL
1015
Corso di Programmazione A.A. 2011-12
Dispensa 17
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Quando la funzione termina tutte le variabili locali vengono eliminate dalla memoria, il controllo viene restituito al chiamante (funzione main()) in memoria avremo la seguente situazione:
1015 1001
1002
A
C
B
next: 1008
next: NULL
next: 1005
1003
1004
1005
1006
1007
1008
D next: 1003 1008
1010
1011
1012
1013
1014
1015
testa
A livello logico:
testa
D
A
Quindi tutte le modifiche fatte alla testa della lista in questo caso rimarranno! Pagina 48 di 48
B
C
NULL
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 17
Una versione ulteriore del programma corretto potrebbe essere (in rosso le modifiche rispetto alla prima versione errata): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#include #include struct cella{ char valore; struct cella *next; }; struct cella* inserisci(struct cella *t, char val); int main() { struct cella *testa = NULL; //...altre operazioni testa = inserisci(testa,’D’); //...altre operazioni return 0; } struct cella* inserisci(struct cella *t, char val) { struct cella *nuovo; nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->valore = v; nuovo->next = t; t = nuovo; return t; }
In questo caso la funzione inserisci restituisce un valore, e cioè la testa della lista modificata (riga 32). Quando la funzione termina il valore restituito viene inserito all’interno della variabile “testa” (riga 17) in modo da mantenere aggiornate le modifiche fatte. Questa versione è equivalente alla precedente. Pagina 49 di 49
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1
Liste con doppi puntatori
In una lista con doppi puntatori ogni cella ha un riferimento all’elemento successivo e uno all’elemento precedente: struct cella{ char valore; struct cella *next; struct cella *prev; };
//puntatore all’elemento successivo //puntatore all’elemento precedente
NULL
A
B
C
NULL
testa
18.1.1
Inserimento in testa
Considerando le seguenti strutture dati e variabili: struct cella{ char valore; struct cella *next; struct cella *prev; };
//puntatore all’elemento successivo //puntatore all’elemento precedente
struct cella *testa = NULL; Dove “cella” è la struttura che definisce come è fatto ogni elemento della lista, “testa” è il puntatore al primo elemento della lista (testa mantiene l’indirizzo di memoria della prima cella in lista).
Pagina 2 di 20
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
NULL
A
B
NULL
C
testa Attraverso una chiamata alla funzione malloc() allochiamo in memoria lo spazio necessario per mantenere la nuova cella da inserire all’interno della lista:
NULL
1
B
A
C
NULL
testa nuovo = (struct cella*)malloc(sizeof(struct cella));
NULL D
NULL
nuovo “nuovo” è un puntatore ad un elemento di tipo “struct cella” allo stesso modo di “testa”, la funzione malloc() restituisce l’indirizzo di memoria della prima cella allocata, tale indirizzo viene memorizzato all’interno della variabile (puntatore) “nuovo”. La prima operazione da compiere è collegare la nuova cella alla prima della lista, quindi fare in modo che l’elemento “next” della nuova cella prenda lo stesso valore di “testa”:
NULL
2
A
B
C
NULL
testa
NULL D
nuovo->next = testa;
NULL
nuovo Pagina 3 di 20
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com La seconda operazione consiste nell’associare il puntatore alla cella precedente “prev” dell’elemento che è in testa al nuovo elemento creato:
NULL
3
A
B
NULL
C
testa
NULL
testa->prev = nuovo;
D nuovo
L’ultima operazione consiste nello spostare il riferimento della testa al nuovo elemento:
A
4
B
NULL
C
testa
NULL
testa = nuovo;
D nuovo In modo da ottenere l’inserimento del nuovo elemento in testa:
5
NULL
D
A
B
testa
Pagina 4 di 20
C
NULL
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 18
ATTENZIONE! Se la lista è vuota al primo inserimento NON sarà possibile eseguire testa->prev = nuovo in quanto NON esiste una cella in testa, quindi prima di utilizzare il puntatore all’elemento precedente bisognerà controllare che sia presente almeno un elemento all’interno della lista! Nel caso di lista vuota l’unica operazione da fare per l’inserimento risulterà:
NULL testa
1 NULL D
NULL
nuovo
NULL testa
2 NULL D
NULL
nuovo Riassumendo il codice necessario per l’inserimento di un elemento in lista: nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->prev = NULL; nuovo->next = testa; if (testa != NULL) testa->prev = nuovo; testa = nuovo; Pagina 5 di 20
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.2
Stampa degli elementi il lista con doppi puntatori
Considerando la struttura dati e le variabili definite nel punto precedente, la stampa consiste nel accedere ad ogni elemento della lista partendo dal primo elemento in testa. Le operazioni per stampare una lista con doppi puntatori sono le stesse viste con le liste semplici:
1
NULL
B
A
C
NULL
C
NULL
C
NULL
con testa->valore si proprietà della prima cella
accede
alla
testa Per poter leggere i valori nella seconda cella bisogna accedere a tale cella:
2
NULL
A
B
testa
testa = testa->next permette di passare alla cella successiva
dopo aver eseguito testa = testa->next si ottiene:
NULL
A
B
testa Pagina 6 di 20
ora con testa->valore è possibile accede alla proprietà della seconda cella
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com si procede eseguendo nuovamente testa = testa->next per accedere alla terza cella:
NULL
A
3
B
C
NULL
B
C
NULL
testa = testa->next permette di passare alla cella successiva
testa
NULL
A
ora con testa->valore è possibile accede alla proprietà della terza cella
testa Ripetendo le operazioni viste nei punti precedenti si accede alla fine della lista:
NULL
A
B
NULL
C
testa
4
NULL
A
B
NULL
C
testa Pagina 7 di 20
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 18
Quindi la logica è quella di accedere a tutti gli elementi della lista fino a quando non si arriva alla fine della lista e cioè al valore NULL. Quindi riassumendo il codice potrebbe essere:
while (testa != NULL) { printf(“%d”, testa->valore); tesa = testa->next; } Procedendo in questo modo però dopo aver stampato la lista si perde il riferimento al primo elemento della lista, e quindi alla lista intera! Quindi, come per le liste semplici prima di scorrere la lista bisognerebbe tener traccia del primo elemento attraverso un altro puntatore: temp = testa; while (testa != NULL) { printf(“%d”, testa->valore); testa = testa->next; } testa = temp; In questo modo arrivati alla fine della lista il riferimento al primo elemento non è andato perso. Con l’istruzione temp = testa si riesce a ripristinare lo stato iniziale. E’ preferibile muoversi all’interno della lista (scorrere la lista) con un puntatore diverso dalla testa e non con il riferimento alla testa (così si evita di dimenticarsi di tener traccia del primo elemento!!) temp = testa; while (temp != NULL) { printf(“%d”, temp->valore); temp = temp ->next; } In questo modo dopo aver eseguito la stampa il riferimento alla testa non è stato modificato! Pagina 8 di 20
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.3
Dispensa 18
Ricerca di un elemento in lista con doppi puntatori
La ricerca è simile alla stampa: bisogna scorrere la lista fino ad individuare l’elemento cercato:
temp = testa; trovato = 0; while (temp != NULL) { if (temp->valore = valoreDaCercare) { trovato = 1; break; } temp = temp ->next; } if (trovato) printf(“Il valore cercato è presente in lista!”); else printf(“valore NON trovato”); All’interno dell’analisi della lista (dentro al ciclo while) attraverso un controllo con un istruzione “if” è possibile valutare se la cella corrente è quella cercata, la variabile “trovato” ci permette di sapere al di fuori del ciclo se l’elemento cercato è presente o meno all’interno della lista. Se entriamo dentro al corpo dell’istruzione “if” la variabile “trovato” viene impostata al valore 1 altrimenti rimane settata al valore 0. ANCHE QUESTA OPERAZIONE E’ UGUALE ALLE LISTE SEMPLICI!
Pagina 9 di 20
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.4
Dispensa 18
Numero di occorrenze di un elemento in lista con doppi puntatori
Questa operazione è simile a quella del punto precedente (in questo caso bisogna però contare quante volte un elemento è ripetuto all’interno della lista):
temp = testa; conta = 0; while (temp != NULL) { if (temp->valore = valoreDaCercare) conta++; temp = temp ->next; } printf(“L’elemento cercato è presente %d volte!”,conta); ANCHE QUESTA OPERAZIONE E’ UGUALE ALLE LISTE SEMPLICI!
18.1.5
Numero di elementi in lista con doppi puntatori
temp = testa; conta = 0; while (temp != NULL) { conta++; temp = temp ->next; } printf(“La lista ha %d elementi!”,conta); ANCHE QUESTA OPERAZIONE E’ UGUALE ALLE LISTE SEMPLICI!
Pagina 10 di 20
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.6
Inserimento di un nuovo elemento in coda alla lista con doppi puntatori
Per poter inserire un nuovo elemento in coda alla lista prima bisogna procedere a scorrere la lista fino ad arrivare all’ultimo elemento (come con le liste semplici):
1
NULL
A
B
NULL
C
testa
temp = testa; while (temp->next != NULL) temp = temp ->next;
temp
Come negli esempi precedenti la lista viene scorsa attraverso un puntatore d’appoggio (cursore) in modo da non perdere il riferimento al primo elemento
2
NULL
A
B
C
NULL
testa temp
NULL D
attraverso la funzione malloc() viene creato il nuovo elemento: nuovo = (struct cella*)malloc(sizeof(struct cella)); Pagina 11 di 20
nuovo
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
NULL
A
B
NULL
C
testa temp
3
NULL
D
NULL
nuovo Il puntatore all’elemento successivo dell’ultima cella viene fatto puntare alla nuova cella: temp->next = nuovo;
NULL
A
B
C
testa temp
4
NULL nuovo Il puntatore all’elemento precendente della nuova cella deve puntare all’elemento riferito da temp: nuovo->prev = temp; Pagina 12 di 20
D
NULL
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 18
Bisogna fare attenzione in quanto se la lista è vuota non bisogna scorrere la lista ma semplicemente far puntare il puntatore alla testa al nuovo elemento. Il codice per questa funzionalità è:
nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->next = NULL; nuovo->prev = NULL; if (testa == NULL) testa = nuovo; else { temp = testa; while (temp->next != NULL) temp = temp ->next; temp->next = nuovo; nuovo->prev = temp; }
Pagina 13 di 20
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.7
Cancellazione dell’elemento in testa alla lista con doppi puntatori
Prima di cancellare l’elemento in testa, il riferimento alla testa della lista deve passare al secondo elemento per evitare di perdere l’intera lista:
temp
1 NULL
B
A
C
NULL
testa
2
Viene utilizzato un puntatore “temp” per tener traccia del primo elemento prima di far puntare a “testa” all’elemento successivo: temp = testa; testa = testa->next;
temp
NULL
A
B
C
NULL
testa
4
NULL
temp
NULL
La seconda cella deve essere staccata dalla prima quindi il puntatore all’elemento precedente “prev” dovrà puntare a NULL testa->prev = NULL;
A
B
C
Pagina 14 di 20
questa operazione ovviamente dovrà essere fatta NULL solo se la lista ha almeno 2 elementi e quindi se esiste la cella successiva a quella che deve essere eliminata if (testa != NULL) testa->prev = NULL;
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com testa a questo punto è possibile eliminare il primo elemento attraverso la funzione free():
NULL
temp
4 NULL
A
B
C
NULL free(temp);
testa
Riassumendo le operazioni per eliminare l’elemento in testa alla lista saranno:
if (testa != NULL) // se esiste almeno un elemento { temp = testa; testa = testa->next; if (testa != NULL) testa->prev = NULL; free(temp); }
Pagina 15 di 20
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.8
Cancellazione di un elemento in lista con doppi puntatori
Per cancellare un elemento dalla lista bisogna prima individuare l’elemento all’interno della lista, quindi bisogna avviare una procedura di ricerca vista in precedenza per arrivare all’elemento da eliminare.
NULL
A
B
C
D
NULL
testa temp Dopo aver individuato l’elemento da eliminare (ad esempio la cella C considerando l’esempio precedente), bisogna rimuoverlo dalla lista. Per eliminare correttamente l’elemento senza alterare la catena che collega un elemento ad un altro, bisogna far in modo che l’elemento che precede la cella da eliminare venga collegato all’elemento che segue la cella da eliminare, quindi:
NULL
A
B
testa
C
D
NULL
temp
Inoltre dovremo fare in modo che l’elemento che segue la cella da eliminare deve essere collegato all’elemento che la precede:
NULL testa
A
B
C temp Pagina 16 di 20
D
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Al contrario delle liste semplici, con le liste con doppi puntatori non avremo bisogno di un secondo puntatore “prec” per gestire le operazioni dette prima in quanto a partire dal riferimento “tesmp” (puntatore alla cella da eliminare) riusciremo ad accedere sia alla cella che precede sia alla cella che segue quella da eliminare Supponiamo di dover eliminare la cella “C”, quindi dovremo scorrere la lista fino ad arrivare a tale cella (come visto nei paragrafi precedenti per la ricerca d di un elemento all’interno della lista).
1
NULL
A
B
C
D
NULL
testa temp Dobbiamo fare in modo che l’elemento che precede la cella da eliminare venga collegato con l’elemento che segue la cella da eliminare, quindi considerando il disegno riportato sopra, la cella B deve essere collegata con la cella D e quindi il riferimento “next” della cella B deve puntare alla cella D:
2
NULL
A
testa
B
C
D
temp
A partire da temp riusciamo ad accedere alla cella precedente con temp->prev (cella B) e a partire da tale riferimento modifichiamo il puntatore “next” facendolo puntare alla cella che segue quella da eliminare temp->prev->next = temp->next;
Pagina 17 di 20
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 18
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
3
NULL
A
B
testa
C
D
NULL
temp
A partire da temp riusciamo ad accedere alla cella successiva con temp->next (cella D) e a partire da tale riferimento modifichiamo il puntatore “prev” facendolo puntare alla cella che precede quella da eliminare temp->next->prev = temp->prev;
in modo da ottenere:
NULL testa
A
B
C
D
temp
ATTENZIONE! Prima di eseguire: “temp->prev->next = temp->next” bisogna controllare che esista la cella precedente Stesso discorso per: “temp->next->prev = temp->prev” bisogna controllare che esista la cella che segue
Pagina 18 di 20
NULL
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 18
Quindi il codice diventa:
temp = testa; while (temp != NULL) //scorrimento della lista { if (temp->valore == elementoDaEliminare) { if (temp->prev == NULL) //l’elemento è in testa alla lista! testa = testa->next; else temp->prev->next = temp->next; if (temp->next != NULL) temp->next->prev = temp->prev; free(temp); break; } temp = temp->next; } prima di eliminare un elemento bisogna controllare se questo è l’elemento in testa alla lista, in tal caso bisogna muovere il riferimento alla testa come visto nel paragrafo precedente.
Pagina 19 di 20
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
18.1.9
Dispensa 18
Cancellare tutte le occorrenze di un elemento in lista con doppi puntatori
Questa funzionalità è simile alla precedete solo che dopo aver eliminato la prima occorrenza dell’elemento non bisogna uscire dal ciclo ma procedere alla ricerca di altre occorrenze:
temp = testa; while (temp != NULL) //scorrimento della lista { if (temp->valore == elementoDaEliminare) { if (temp->prev == NULL) //l’elemento è in testa alla lista! testa = testa->next; else temp->prev->next = temp->next; if (temp->next != NULL) temp->next->prev = temp->prev; elimina = temp; temp = temp->next; free(elimina); continue; //per ripartire dal while } temp = temp->next; } Dato che bisogna procedere con l’analisi della lista non è possibile eliminare a partire da “temp” in quanto verrà utilizzato all’iterazione successiva per considerare il nuovo elemento.
Pagina 20 di 20
CORSO DI Land AUREA SCIENZE EVersion TECNOLOGIE INFORMATICHE – CESENA Simpo PDF Merge SplitIN Unregistered - http://www.simpopdf.com
CORSO DI PROGRAMMAZIONE A.A. 2011-12
Dispensa 19 Laboratorio
Dott. Mirko Ravaioli e-mail: [email protected] http://www.programmazione.info
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
19.1
Liste con priorità o lista ordinata
Le liste con priorità (o lista ordinata) sono gestite come le liste semplici viste nella dispensa 17, solo che gli elementi all’interno della lista vengono mantenuti ordinati rispetto un criterio gestito direttamente in fase di inserimento di un nuovo elemento. Immaginiamo per esempio l’ufficio accettazione di un pronto soccorso, i vari malati vengono messi in fila per la visita in ordine di arrivo e soprattutto in ordine di gravità, quindi un malato grave sicuramente verrà visitato prima di uno meno grave anche se arrivato prima al pronto soccorso. La struttura dati necessaria per gestire la lista con priorità è la stessa vista per le liste semplici: struct cella{ int ordine; struct cella *next; };
//puntatore all’elemento successivo
struct cella *testa = NULL; Consideriamo nel nostro esempio un valore interno all’interno di ogni cella per gestire la priorità (o l’ordine). L’obiettivo è quello di mantenere la lista ordinata in maniera crescente ad ogni inserimento. Il caso più semplice si verifica quando la lista è vuota, ovviamente il nuovo elemento da inserire indipendentemente dal valore della priorità verrà inserito in testa:
1
2 testa
3 testa
NULL
testa NULL
NULL 7
NULL
nuovo
7 nuovo
nuovo = (struct cella*)malloc(sizeof(struct cella)); Pagina 2 di 10
testa = nuovo;
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Consideriamo una lista con vari elementi:
3
5 3
testa
8
10
NULL
Se il nuovo elemento da inserire ha come ordine il numero 7
NULL
7 nuovo Ovviamente dovrebbe essere inserito tra la cella con il valore 5 e la cella con il valore 8:
3 testa
5 3
7
8
10
NULL
Per inserire la nuova cella nella posizione giusta dovremo scorrere la lista fino ad arrivare alla prima cella con valore maggiore rispetto quella che deve essere inserita. Dato che la lista è ordinata in maniera crescente, il primo elemento maggiore sarà anche quello che dovrà stare immediatamente dopo al nuovo elemento dopo l’inserimento.
nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->valore = valoreDaInserire; if (testa == NULL) //lista vuota testa = nuovo; else { prec = NULL; temp = testa; while (temp != NULL) Pagina 3 di 10
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com { if (temp->valore > nuovo->valore) //trovata la cella con valore più grande { nuovo->next = temp; if (prec == NULL) // se l’elemento è da inserire in testa testa = nuovo; else prec->next = nuovo; break; } prec = temp; temp = temp->next; } }
Quando si arriva all’interno del corpo dell’istruzione if all’interno del while (quindi quando viene trovata la cella con valore più alto rispetto a quella da inserire), la situazione sarà la seguente:
1
prec
3 testa
temp
5 3
8
7
10
NULL
nuovo
Pagina 4 di 10
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 2
prec
3
temp
5 3
testa
8
10
NULL
8
10
NULL
8
10
NULL
NULL
7 nuovo
3
prec
3 testa
temp
5 3
7 nuovo
4 3 testa
5 3
7 Pagina 5 di 10
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Se l’elemento deve essere inserito in testa:
1
prec
NULL
temp
3 testa
5 3
8
10
NULL
5 3
8
10
NULL
NULL
2 nuovo
2
prec
NULL
temp
3 testa
2
NULL
nuovo
Pagina 6 di 10
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com 3
prec
NULL
temp
3
5 3
8
10
NULL
3
5 3
8
10
NULL
testa
2 nuovo
4 testa
2 Se l’elemento deve essere inserito in coda, l’algoritmo descritto sopra non funziona in quanto all’interno del ciclo while non si troverà mai una cella più grande rispetto a quella da inserire:
temp 3 testa
5 3
8
10
NULL
NULL
prec 13 nuovo Pagina 7 di 10
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Quindi dopo il ciclo bisognerà fare un controllo per capire se l’elemento non è stato inserito e quindi ci troviamo in fondo alla lista:
nuovo = (struct cella*)malloc(sizeof(struct cella)); nuovo->valore = valoreDaInserire; if (testa == NULL) //lista vuota testa = nuovo; else { prec = NULL; temp = testa; while (temp != NULL) { if (temp->valore > nuovo->valore) //trovata la cella con valore più grande { nuovo->next = temp; if (prec == NULL) // se l’elemento è da inserire in testa testa = nuovo; else prec->next = nuovo; break; } prec = temp; temp = temp->next; } if (temp == NULL) //il nuovo elemento non è stato inserito e siamo in fondo alla lista prec->next = nuovo; }
temp 3 testa
5 3
8
10
NULL
NULL
prec 13 Pagina 8 di 10
nuovo
NULL
Corso di Programmazione A.A. 2011-12
Dispensa 19
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Considerando l’ultimo algoritmo, nel caso in cui il valore della nuova cella da inserire sia già presente all’interno della lista, la nuova cella viene inserita dopo quella con il valore uguale in quanto all’interno del ciclo si cerca la cella con un valore maggiore rispetto a quella da inserire:
prec
1 3
temp
5 3
testa
8
10
NULL
NULL
5 nuovo
2
prec
3 testa
temp
5 3
8
10 NULL
5 nuovo
3 3 testa
5 3
8
10 NULL
5 Pagina 9 di 10
Corso di Programmazione A.A. 2011-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Dispensa 19
Se invece la nuova cella deve essere inserita prima di quella con il valore uguale, l’unica modifica da fare nell’algoritmo è nell’espressione presente nell’istruzione if nel corpo del ciclo while: sostituire il simbolo maggiore (>) con maggiore uguale (>=) Nel caso in cui la lista deve essere ordinata in modo decrescente basta inserire, sempre nel controllo dell’istruzione if il minore (<) o minore uguale, il resto rimane invariato! Tutte le altre operazioni che possono essere eseguite sulla lista ordinata sono fatte allo stesso modo delle liste semplici come descritto nella dispensa 17.
Pagina 10 di 10