Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 1
Laboratorio del 3 Ottobre 2011
Dispensa 1
Pagina 2 di 7
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. 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 luso con il compilatore stesso. Ad esempio la funzione printf() utilizzata nellesempio 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. Completamento del ciclo di sviluppo: una volta compilato e linkato il proprio programma possibile eseguirlo. Se dallesecuzione del programma non si ottengono i risultati aspettati bisogna tornare indietro al primo passo e individuare gli errori.
Pagina 3 di 7
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 <stdio.h> dice al compilatore di includere le informazioni sulla libreria standard per linput/output. Tutte le istruzioni di inclusione delle librerie devono comparire allinizio del programma. Tutte le funzioni hanno un inizio ed una fine, per individuare allinterno 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). Allinterno delle graffe presente il corpo della funzione (insieme di istruzioni eseguite alla chiamata della funzione). Nel esempio precedente allinterno 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 largomento 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 leffetto 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 landata a capo. Quindi il programma di prima poteva essere scritto anche nel seguente modo: #include <stdio.h> int main() { printf(Forza ); printf(Cesena!); printf(\n); return 0; } Listruzione 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 allinterno della funzione main() termina con un punto e virgola. Unistruzione 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
Dispensa 1
non genera errore in quanto per andare a capo con una stringa possibile utilizzare il carattere (\) di barra inversa.
\t \b \ \\ \n \? \" \a
Tabulazione blank space Apice singolo (come carattere stampato) Barra inversa (come carattere stampato) Avanzamento di riga (a capo) Punto interrogativo (come carattere stampato) I doppi apici stampati (come carattere stampato) Segnale acustico
Pagina 5 di 7
Dispensa 1
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
Dispensa 1
Pagina 7 di 7
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 2
Laboratorio
Dispensa 2
Pagina 2 di 7
Dispensa 2
La lettera b Il numero 450 il numero 14.3124 La frase il mondo non crea eroi Una pagina di testo
1 2 4 22 Circa 3000
La RAM di un computer organizzata in modo sequenziale: quindi ogni byte messo si seguito allaltro. 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 dallindirizzo 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 allinterno della RAM durante lesecuzione.
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.
o o
Pagina 3 di 7
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 allinterno 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.
contiene un carattere dellambiente in cui risiede il compilatore 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 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 o o short long unsigned
Gli attributi short e long si applicano solo agli interi. Lidea 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
Dispensa 2
Lattributo 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 dallimplementazione. Tutte le variabili per default sono di tipo signed. Tipi di dati supportati:
Tipo Variabile Parola chiave Byte Intervallo
Carattere Intero* Intero corto* Intero lungo* Carattere senza segno Intero senza segno Intero lungo senza segno Variabile mobile con precisione singola Variabile mobile con precisione doppia *dipende dalla macchina
char int short int (o anche solo: short) long int (o anche solo: long) unsigned char unsigned int unsigned long int (o anche: unsigned long) float double
1 2 2 4 1 2 4 4 8
da -128 a 127 da -32768 a 32767 da -32768 a 32767 da -2147483648 a 2147483647 da 0 a 255 da 0 a 65535 da 0 a 4294967295 da 1.2E-38 a 3.4E308 da 2.2E-38 a 1.8E3082
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
Pagina 5 di 7
Dispensa 2
Pagina 6 di 7
Dispensa 2
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));
Pagina 7 di 7
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 3
Laboratorio
Dispensa 3
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 o (-) il risultato della conversione e' allineato a sinistra all' interno del campo definito da campoMinimo, il default e' l' allineamento a destra (+) 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 (spazio) inserisce uno spazio davanti al valore se il primo carattere non e' un segno (#) 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. 0 (zero) il campo viene riempito con zeri invece che con spazi (il default)
o o
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
Dispensa 3
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: lunico obbligatorio (a parte %) Sotto sono elencati i caratteri di conversione ed il loro significato: d, i u o x, X c e, E Visualizza un intero con segno in notazione decimale Visualizza un intero senza segno di notazione decimale Visualizza un intero in notazione ottale senza segno Visualizza un intero in notazione esadecimale senza segno. Si utilizza x minuscolo per luotput minuscolo e X maiuscolo per loutput maiuscolo Visualizza un singolo carattere (indicato dal codice ASCII) 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 unaltra precisione con lindicatore f. si utilizzano e o E per loutput 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. Visualizza una stringa Visualizza il carattere %
f s %
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
Dispensa 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 nellultimo 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 allinterno 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 listruzione (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 unistruzione, quando ci troviamo in corrispondenza di una costante stringa letterale occorre utilizzare il carattere barra inversa (\) prima dellinterruzione. La forma corretta per lesempio sopra riportato quindi sar: printf(Forza\ Cesena);
Pagina 4 di 9
Dispensa 3
In C i blocchi possono essere utilizzati in qualsiasi punto in cui sia possibile utilizzare unistruzione 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 linizio 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 unespressione una qualsiasi cosa che deve essere valutata come un valore numerico. Le espressioni in C possono avere qualsiasi livello di complessit.
Una costante simbolica Una costante letterale Una variabile Una costante letterale
Pagina 5 di 9
Dispensa 3
in questo caso il risultato viene assegnato sia a y che a k, in particolare prima viene valutata lespressione b + 17, il suo valore assegnato alla variabile y, ed in fine il valore di y assegnato alla variabile k. Quindi alla fine dellespressione 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 lesecuzione dellistruzione 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 dellistruzione.
+ *
Somma i due operandi Sottrae il secondo operando dal primo Moltiplica i due operandi
Pagina 6 di 9
Dispensa 3
x%y
Alcuni esempi di utilizzo: /* Primo esempio di utilizzo degli operatori matematici */ #include <stdio.h> 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 ununit dalloperando specificato.
Operatore Simbolo Azione Esempio
++ --
y++ y--
++y --y
Pagina 7 di 9
Dispensa 3
Gli operatori possono essere postfissi o prefissi. Queste due sintassi non sono equivalenti, ma differiscono per quanto riguarda il momento in cui viene effettuata loperazione: 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 x = 7; y = x++; Dopo lesecuzione 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 lesecuzione di queste due istruzioni x vale 8 e y vale 8. Loperatore = loperatore di assegnamento e non un operatore di confronto. Eventuali successive modifiche al valore di x non hanno effetto su y. Consideriamo un esempio: #include <stdio.h> int main() { int a, b; a = 7; b = 7; printf(%d printf(%d printf(%d printf(%d printf(%d return 0; } Il risultato del programma: 7 6 5 4 3 6 5 4 3 2 %d\n,a--,--b); %d\n,a--,--b); %d\n,a--,--b); %d\n,a--,--b); %d\n,a--,--b);
Vediamo un esempio:
La precedenza degli operatori segue il seguente ordine: incrementi e decrementi unari moltiplicazioni, divisioni e resti somme e sottrazioni
Se unespressione 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 lutilizzo di parentesi tonde. Ad esempio:
Pagina 8 di 9
Dispensa 3
Dopo listruzione y vale 10. Prima viene eseguito 2*3 al risultato viene aggiunto 4 e il risultato assegnato a y. Consideriamo: y = 12 % 5 * 2; Loperatore % (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 dellistruzione a y viene assegnato il valore 2.
Pagina 9 di 9
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 4
Laboratorio
Dispensa 4
Lunica 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 lampiezza del campo, se specificata, viene raggiunta. Modificatori di precisione: Tipo Argomento d i int * int *
Significato
Intero decimale Intero notazione decimale, ottale (inizia con O) o esadecimale (inizia con 0x o 0X) Intero in notazione ottale Intero decimale senza segno Uno o pi caratteri sono letti sequenzialmente nella locazione di memoria indicata dallargomento, senza aggiungere il caarattere di terminazione \0 Una stringa di caratteri diversi da spazi viene letta nella locazione di memoria specificata Un numero in virgola mobile, in notazione decimale o scientifica Una stringa, soltanto I caratteri elencati tra le parentese sono accettati. Linput termina non appena si incontra un carattere non corrispondente, oppure viene raggiunta lampiezza specificata o viene premuto il tasto invio, Per accettare il carattere ] necessario elencarlo per primo: []]. Un terminatore \0 viene aggiunto alla fine della stringa.
o u c
char *
e,f,g []
float * char *
Pagina 2 di 4
Dispensa 4
Equivale a [] salvo che sono accettati soltanto i caratteri non elencati tra parentesi. Letterale %: legge il carattere %. Non viene effettuato alcun assegnamento.
nessuno
Modificatori di precisione: h Quando inserito prima dellindicatore di tipo d,i,o,u,x indica che largomento un puntatore di tipo short invece che un tipo int. Sui PC i due tipi coincidono perci non serve Quando inserito prima dellindicatore di tipo d,i,o,u,x indica che largomento un puntatore di tipo long. Quando inserito prima dellindicatore e,f,g indica che largomento un puntatore di tipo double Quando inserito prima dellindicatore di tipo e, f, g, indica che largomento un puntatore al tipo long double
Linput di scanf() bufferizzato, perci non vengono ricevuti effettivamente dei caratteri finch lutente non preme INVIO. Lintera 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 lutente 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 dallutente corrisponde alla stringa di formato. Ad esempio si supponga che lutente immetta 12 14 e prema INVIO. In questo caso non ci sono problemi, scanf() soddisfatta e non rimangono caratteri in stdin. La riga inserita dallutente ha troppo pochi elementi per corrispondere alla stringa di formato. As esempio si supponga che lutente immetta 12 e prema INVIO. In questo caso scanf() continua ad attendere linput mancante e quando lo riceve lesecuzione continua senza che rimangano dei caratteri in stdin. La riga inserita dallutente ha pi caratteri di quelli richiesti dalla stringa di formato. Ad esempio si supponga che lutente 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 allinput dellutente 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
Dispensa 4
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 linput di un tipo long e un tipo double */ fflush(stdin); /*Utilizza la dimensione del campo per suddividere linput*/ puts(inserire un numero di 5 cifre (ad esempio 54321)); scanf(%2d%3d, &i1, &i2); /* Si noti che lindicatore dellampiezza di campo nella stringa di formato di scanf() suddivide linput 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 dellinput */ return 0; } La funzione fflush() viene utilizzata per eliminare qualsiasi carattere indesiderato dal flusso di input. Poich non ci sono indicatori dampiezza lintero a cinque cifre suddiviso in due interi, uno con due caratteri ed uno con tre. Lultimo 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 linput viene suddiviso in maniera efficace.
Pagina 4 di 4
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 5
Laboratorio
Dispensa 5
X *= Y Y -= Z * 1 A /= B X += Y / 8 Y %= 3
Ad esempio, listruzione seguente assegna a x il valore di b, quindi incrementa a e successivamente incrementa b: x = (a++, b++); Dato che loperatore ++ impiegato in modalit postfissa, a x viene assegnato il valore di b prima che questo venga incrementato. Luso delle parentesi necessario dato che loperatore virgola ha una precedenza molto bassa.
Pagina 2 di 5
Dispensa 5
5.4 Costanti
5.4.1 Direttiva #define
Una costante simbolica una costante rappresentata da un nome o simbolo allinterno 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.
Pagina 3 di 5
Dispensa 5
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 <stdio.h> 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 <stdio.h> int main()
Pagina 4 di 5
Dispensa 5
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 <stdio.h> 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 and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 6
Laboratorio
Dispensa 6
Operatore Uguale
Maggiore di Minore di Minore o uguale a Diverso
Esempio X ==y
X>y X<y X >= y X<= y X != y
6.3 Istruzione if
Listruzione if valuta unespressione e dirige conseguentemente il flusso di esecuzione del programma. La sintassi (ridotta) dellistruzione if la seguente: if(espressione) { istruzioni } Se il valore di verit dellespressione vero viene conseguita listruzione presente nel corpo dellistruzione (tra parentesi graffe). In caso contrario il flusso di esecuzione salta allistruzione che segue quella contenuta nellif. Si potrebbe dire che lesecuzione dellistruzione dipende dal risultato dellespressione. #include <stdio.h> 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
Dispensa 6
Dove la clausole else facoltativa. Lespressione 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 listruzione 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
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 dellistruzione 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
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 nellordine, se una di esse vera, listruzione con cui associata viene eseguita, e ci conclude lesecuzione dellintero costrutto. Come sempre il codice per ogni istruzione pu essere unistruzione singola o un blocco (pi istruzioni tra graffe). Lultima clausola else si occupa del caso nessuno dei precedenti in cui, non essendo soddisfatta nessuna delle condizioni, occorre procedere dufficio. 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 <stdio.h> 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
Dispensa 6
= c; = b;
= b; = c;
= c; = a;
= a; = c;
Pagina 6 di 6
Dispensa 6
Pagina 7 di 7
Dispensa 6
return sono le pi comuni vie duscita da switch. Un istruzione break impone anche luscita immediata dai cicli while, for e do, come si vedr nel seguito delle lezioni. Lesecuzione 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 luso di pi etichette in una singola computazione, lesecuzione a cascata va usata con parsimonia e corredata con appositi commenti.
Pagina 8 di 8
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 7
Laboratorio
Dispensa 7
inizio, condizione, e istruzione sono espressioni del C, mentre istruzione un blocco di istruzioni. Quando durante lesecuzione viene incontrata listruzione for si verifica quanto segue:
1. Viene valutata lespressione inizio. Solitamente questa un istruzione di assegnamento che imposta una variabile a un particolare valore. 2. Viene valutata lespressione condizione. Questa solitamente una espressione relazionale. 3. Se la condizione falsa (cio se vale zero) listruzione for si conclude e lesecuzione 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 lespressione incremento e lesecuzione torna al punto 2 Vediamo schematicamente una rappresentazione di unistruzione for: Inizio
Valuta inizio
Valuta incremento
Valuta condizione
VERA
Esegue istruzioni
FALSA
Fine
Pagina 2 di 6
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 <stdio.h> int conta; int main() { /*Stampa i numeri da 1 a 20 */ for (conta = 1; conta <= 20; conta++) { printf(%d\n,conta); } return 0; } Loutput 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
VERA
Esegue printf()
FALSA
Fine
Pagina 3 di 6
Dispensa 7
Un istruzione for pu essere eseguita tranquillamente allinterno di unaltra 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 <stdio.h> int righa, colonna; int main() { for (riga = 1; riga <= 5; riga++) { for (colonna = 1; colonna <= 20; colonna ++) { printf(x); } printf(\n); } return 0; } ecco loutput del programma: xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx
Pagina 4 di 6
Dispensa 7
Pagina 5 di 6
Dispensa 7
Scrivere un programma che permetta allutente 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 allutente di indovinarlo concedendo 3 tentativi. Esercizio 5 Scrivere un programma che stampi tutti i numeri primi compresi nellintervallo da 1 a 100
Pagina 6 di 6
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 8
Laboratorio
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 } lespressione viene valutata, qualora sia diversa da zero, istruzione eseguita ad espressione rivalutata. Questo ciclo prosegue finch espressione diventa zero, momento in cui lesecuzione 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 listruzione A, valutata lespressione e se risulta vera (diversa da zero), si accede al corpo del ciclo while eseguendo listruzione B, si procede rivalutando lespressione, se risulta vera si procede nuovamente eseguento listruzione B, diversamente si esegue listruzione 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 dellistruzione 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 nellistruzione for corrisponde a ometterle nellequivalente ciclo while. Se per la condizione, cio espressione2, non presente, esse considerata sempre vera:
Pagina 2 di 3
Dispensa 8
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 dallinizio. Il terzo ciclo del C, listruzione 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 and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 9
Laboratorio
Dispensa 9
*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 laltra che richiede al programmatore di specificare il flusso. Utilizza uno dei flussi standard printf() vprintf() puts() putchar() scanf() gets() getchar() perror() Richiede un nome di flusso fprintf() vfprintf() fputs() putc(), fputc() fscanf() fgets() getc(), fgetc() Descrizione Output formattato Output formattato con un elenco di argomenti variabile Output stringa Output carattere Input formattato Input stringa Input carattere Output stringa solo su stderr
Pagina 2 di 9
Dispensa 9
Tutte queste funzioni richiedono linclusione 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.
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) Luso di getchar() mostrato nel listato sotto: #include <stdio.h> main() { char ch; while ((ch = getchar()) != \n) putchar(ch); return 0; }
Loutput 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 lutente 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 dellutilizzo della funzione getchar() per linput di unintera stringa:
Pagina 3 di 9
Dispensa 9
#include <stdio.h> #define MAX 80 main() { char ch, buffer[MAX+1]; int x = 0; while (((ch = getchar()) != \n) && x < MAX) buffer[x++] = ch; buffer[x] = \0; printf(%s\n,buffer); return 0; } //carattere di fine stringa
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 lutilizzo 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. Larray 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 dinclusione diversi. In generale il prototipo di getch() definito in conio.h. Luso di getch() mostrato nel listato sotto: #include <stdio.h> main() { char ch; while ((ch = getch()) != \n) putchar(ch); return 0; }
Pagina 4 di 9
Dispensa 9
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 leco dellinput i caratteri non sono visualizzati sullo schermo. La funzione getche() Questa funzione del tutto simile a getch(), a parte il fatto che esegue leco dei caratteri su stdout. Anche questa non una funzione ANSI, ma molti compilatori la supportano.
Ecco input e output del programma: Scrivere il testo e premere INVIO: Questo il testo E stato inserito: Questo il testo
In questo esempio largomento di gets() lespressione input, che rappresenta il nome di un array di tipo char.
Pagina 5 di 9
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
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; langono giro di 360 gradi misura 2 radianti) Funzioni trigonometriche Funzione Prototipo acos() double acos(double x) Descrizione Restituisce larcocoseno dellargomento. Largomento deve essere compreso tra 1 e 1. Il valore restituito compreso tra 0 e . Restituisce larcoseno dellargomento. Largomento deve essere compreso tra 1 e 1. Il valore restituito compreso tra /2 e /2. Restituisce larcotangente dellargomento. Il valore restituito compreso tra -/2 e /2. Restituisce larcotangente di x/y. Il valore restituito compreso tra - e . Restituisce il coseno dellargomento. Restituisce il seno dellargomento. Restituisce la tangente dellargomento.
asin()
double asin(double x)
atan()
double atan(double x)
atan2()
cos()
double cos(double x)
sin() tan()
Descrizione Restituisce lesponente naturale dellargomento cio e^x (e elevato alla x) dove e uguale a: 2,7182818284590452354 Restituisce il logaritmo naturale dellargomento. Largomento deve essere maggiore di zero. Restituisce il logaritmo in base 10 dellargomento. Largomento deve essere maggiore di zero
log()
double log(double x)
log10()
double log10(double x)
Pagina 7 di 9
Dispensa 9
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 largomento x vale 0 anche r ed y verranno posti a 0. Restituisce x * 2 ^ y
ldexp()
Descrizione Restituisce il coseno iperbolico dellargomento Restituisce il seno iperbolico dellargomento Restituisce la tangente iperbolica dellargomento
cosh ()
double cosh(double x)
tanh()
double tanh(double x)
Descrizione Restituisce la radice quadrata dellargomento che deve essere positivo e non nullo. Restituisce il pi piccolo intero non inferiore allargomento. Ad esempio ceil(4.5) restituisce 5.0; ceil(-4.5) restituisce -4.0 . Restituisce il pi grande numero interno non superiore allargomento. Ad esempio floor(4.5) restituisce 4.0 mentre floor(-4,5) restituisce 5.0. Restituisce il valore assoluto dellargomento. Restituisce il valore assoluto dellargomento. 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.
ceil()
double ceil(double x)
floor()
double floor(double x)
abs()
int abs(int x)
labs()
long labs(long x)
modf()
Pagina 8 di 9
Dispensa 9
Restituisce x ^ y (x elevato alla y); si genera errore se x = 0 e y <= 0 o se x < 0 e y non intero. Restituisce il resto in formato decimale di x/y. Con lo stesso segno di x. Restituisce 0 se x uguale a 0.
fmod()
Pagina 9 di 9
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 10
Laboratorio
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. Nellesempio precedente le spese relative al mese di gennaio potrebbero essere memorizzate nellelemento 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 dellarray vengono memorizzati allinterno di locazioni di memoria contigue. Il punto in cui vengono dichiarati gli array allinterno del codice sorgente molto importante. Cos come accade per le variabili semplici, il punto dove avviene la dichiarazione influisce sullambito dellarray. Gli elementi dellarray possono essere utilizzati ovunque possa esserlo una variabile dello stesso tipo. Si accede ai singoli elementi riportando il nome dellarray seguito tra parentesi quadre. Ad esempio listruzione seguente memorizza il valore 78.16 allinterno del secondo elemento dellarray (bisogna ricordarsi che il promo elemento spese[0] e non spese[1]): spese[1] = 78.16; Allo stesso modo, listruzione: spese[10] = spese[7]; assegna allelemento dellarray spese[10] una copia del valore contenuto nellelemento spese[7]. Quando ci si riferisce ad un elemento di un array, lindice pu essere una costante letterale, come in questo esempio oppure unespressione (compreso il valore di un altro elemento dellarray). 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 lultimo esempio, prendendo un array di interi denominato a[], se allinterno dellelemento a[2] contenuto il valore 8 listruzione: 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 lindice 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 <stdio.h> int main()
Pagina 2 di 5
Dispensa 10
Vediamo una variazione del programma precedente: la stampa dei valori inseriti deve essere in senso inverso. #include <stdio.h> 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 <stdio.h> 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
Dispensa 10
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 dellarray a cominciare da quello con indice 0. Ad esempio listruzione seguente dichiara un array di dimensione 4 e associa allelemento di indice 0 il valore 100, allelemento di indice 1 il valore 200, allelemento di indice 2 il valore 300 e allelemento 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 allinterno del vettore. In particolar il programma deve restituire lindice della cella in cui il valore cercato memorizzato. #include <stdio.h> 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
Dispensa 10
Scrivere un programma che conti tutte le occorrenze di un numero allinterno di un vettore di interi.
#include <stdio.h> 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 lordine 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 and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 11
Laboratorio
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 dellarray. 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]; Larray 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 <stdio.h> 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; }
Pagina 2 di 9
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; const char A = z; Nel listato sotto, viene illustrata la natura numerica delle operazioni di memorizzazione dei caratteri, tramite lutilizzo 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 <stdio.h> 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 lindirizzo 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. /* imposta codice uguale a x*/
Pagina 3 di 9
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 listruzione: 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 ununit al numero di elementi che compongono larray. 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 dellarray. 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 allinterno 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 dellarray. Quindi listruzione 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.
Pagina 4 di 9
Dispensa 11
Il tipo di ritorno size_t definito in string.h come unsigned. Largomento 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, questultimo escluso. La funzione strcpy() La funzione di libreria strcpy() copia unintera 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 dorigine. La funzione copia al pi i primi n caratteri della stringa dorigine 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 lallocazione di memoria per la destinazione. Il suo prototipo il seguente: char * strdump(char *origine); Largomento origine un puntatore alla stringa dorigine. La funzione restituisce un puntatore alla stringa di destinazione, cio allo spazio allocato, oppure NULL se lallocazione 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, lintera 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
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 lultima occorrenza del carattere dato nella stringa. Il suo prototipo il seguente: char *strrchr(char *str1, int ch); La funzione strrchr() restituisce un puntatore allultima 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 loccorrenza 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 dallinizio 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
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 dellalfabeto restano invariati. Entrambe le funzioni restituiscono un puntatore a str. La funzione strrev() La funzione strrev() rovescia lordine dei caratteri in una stringa. Il suo prototipo il seguente: char *strrev(char *str); Lordine dei caratteri di str viene rovesciato. Il carattere nullo rimane nellultima 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
Dispensa 11
Largomento ch il carattere da esaminare. La funzione restituisce TRUE (un valore diverso da zero) se ch soddisfa la condizione o FALSE (zero).
Funzione
Descrizione Restituisce TRUE se ch una lettera o una cifra Restituisce TRUE se ch una lettera Restituisce TRUE se ch un carattere ASCII standard (compreso tra 0 e 127) Restituisce TRUE se ch un carattere di controllo Restituisce TRUE se ch una cifra Restituisce TRUE se ch un carattere stampabile diverso da uno spazio Restituisce TRUE se ch una lettera minuscola Restituisce TRUE se ch un carattere stampabile compreso lo spazio Restituisce TRUE se ch un segno di interputazione Restituisce TRUE se ch un carattere bianco,, cio spazio o tabulazione Restituisce TRUE se ch una lettera maiuscola Restituisce TRUE se ch una cifra esadecimale
isalnum() isalpha() isscii() iscntrl() isdigit() isgraph() islower() isprint() ispunct() isspace() isupper() isxdigit()
Pagina 8 di 9
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 nellelenco inserito.
Pagina 9 di 9
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 12
Laboratorio
Dispensa 12
Pagina 2 di 16
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 allutente 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 allutente di inserire una lettera 8. Incrementare il contatore delle lettere inserite di un unit 9. Verificare che la lettera inserita sia presente allinterno 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 allutente 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 allinfinito 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. Lalgoritmo 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 allutente 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 allutente che ha perso e andare al punto 15 altrimenti andare al punto 3 8. Chiedere allutente di inserire una lettera 9. Incrementare il contatore delle lettere inserite di un unit 10. Verificare che la lettera inserita sia presente allinterno 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 allutente che ha perso e vai al punto 15 14. Se la frase non stata indovinata vai al punto 3
Pagina 3 di 16
Dispensa 12
Sicuramente potrebbero essere fatte molte altre migliorie, per per implementare il gioco di base questo ultimo algoritmo dovrebbe rispondere a tutte le esigenze.
Implementazione lalgoritmo
Suddivisione in sottoproblemi
Lultimo 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 allinterno 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 laggiunta 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
0
i
1
a
2
o
3 4
m
5
o
6
n
7
d
8
o
9
\0
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]; Literazione con lutente 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 dallutente potrebbe essere fatta attraverso la funzione scanf() utilizzando il carattere di conversione %s apposito per le stringhe: scanf(%s,frase); Da notare che allinterno della chiamata alla funzione scanf() la variabile frase non viene preceduta dalloperatore dindirizzo & (e-commerciale) in quanto tale operatore va utilizzato esclusivamente per le variabili ti tipo primitivo.
Pagina 4 di 16
Dispensa 12
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 allinterno dellarray 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 lunico carattere non ammesso lINVIO (o meglio nuova linea o ritorno a carrello) individuato dal carattere speciale \n: scanf(%[^\n],frase); La funzione scanf() non lunica funzione che consente la lettura da tastiera, infatti esistono altre funzioni per linput 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 allinterno dellarray 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 <stdio.h> #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 linserimento da parte dellutente 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 lutente 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
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; frase[i] = \0; I caratteri di volta in volta letti vengono inseriti allinterno dellarray fino ad arrivare al carattere terminatore o alla dimensione massima dellarray. In questo modo riusciamo ad evitare che allinterno dellarray frase che ci siano pi caratteri di quelli consentiti ma non ad impedire che lutente 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 lutente 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; Lutente potrebbe inserire una frase vuota cio quindi premere direttamente INVIO, ovviamente questa cosa non dovrebbe essere consentita. Potremo risolvere anche questultima 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 allinterno di un ciclo do while per ripetere loperazione fino a quando lutente non inserir un valore corretto. Per fare in modo che lutente possa decidere ad un certo punto dellinserimento 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 dallutente: 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
Dispensa 12
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 dellarray 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 unistruzione if per i controlli potremo utilizzare unistruzione switch: for (i = 0; i < LUNGHEZZA_FRASE; i++)
Pagina 7 di 16
Dispensa 12
Le soluzioni presentate in precedenza non sono ottimali in quanto vengono considerate indipendentemente dalla lunghezza della frase tutte le celle presenti allinterno dellarray. Per ovviare al problema potremo inserire un controllo ulteriore allinterno 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 lutente 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 allinterno dellif 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 lunico controllo quello che valuta se il carattere corrisponde ad una vocale. Le soluzioni a questo secondo problema possono essere varie: potremo inserire altri controlli allinterno dellistruzione else per valutare se il carattere una costante oppure potremo creare allinterno 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 dellalfabeto (in minuscolo) allinterno di tale tabella hanno codici che variano da 97 a 122 il programma potrebbe essere ottimizzato nel seguente modo:
Pagina 8 di 16
Dispensa 12
In questo modo verranno sostituite solamente le lettere appartenenti allalfabeto. 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 allinterno di una frase Per trovare tutte le occorrenze di una lettera allinterno di una frase bisogna eseguire dei confronti singolarmente con tutti i caratteri della stringa data. Considerando quanto detto nei paragrafi precedenti, la stringa inserita dallutente viene modificata sostituendo le varie lettere dellalfabeto con dei caratteri speciali, quindi non sar pi possibile per il programma conoscere quali lettere effettivamente saranno presenti allinterno 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 dellarray frase. Ora con questa modifica, presa una lettera da cercare dallutente 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
Dispensa 12
Da notare che nella funzione scanf() la variabile lettera viene preceduta dalloperatore indirizzo ecommericale (&) e che allinterno 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 lutente 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 dellalfabeto potremo eseguire un controllo per verificare la correttezza dellinserimento: 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 allutente se la lettera inserita presente o meno allinterno della frase nascosta bisogna valutare al di fuori del corpo del for se si entrati almeno una volta allinterno del if che confronta la lettera inserita con li-esima della frase. Questo tipo di controllo potrebbe essere fatto con una variabile impostata ad un certo valore prima del for e modificata esclusivamente allinterno del corpo dellistruzione if. Se si accede al corpo dellif, e quindi se esiste almeno un occorrenza della lettera inserita dallutente allinterno 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
Dispensa 12
Pagina 11 di 16
Dispensa 12
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<LUNGHEZZA_FRASE)
Pagina 12 di 16
Dispensa 12
Volendo fare le cose a modo, bisognerebbe che il numero massimo di inserimenti possibili risulti uguale al massimo al numero di lettere differenti presenti allinterno 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 dellalfabeto. Tale array verr utilizzato per tenere traccia delle lettere della frase gi considerate nel conteggio.
#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
Dispensa 12
Pagina 14 di 16
Dispensa 12
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 <LUNGHEZZA_FRASE) fraseUtente[i++] = ch;
Pagina 15 di 16
Dispensa 12
Pagina 16 di 16
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 13
Laboratorio
Dispensa 13
13.1
13.1.1
I Puntatori
La memoria del computer
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 lindirizzo della locazione di memoria, ma esso viene nascosto allutente 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 dallindirizzo 1004 e ha associato al nome rate lindirizzo 1004. 1001 1002 1003 1004 100 1005
rate
13.1.2
Creazione di un puntatore
Si sar notato che lindirizzo della variabile rate(e qualsiasi altra variabile) un numero che, in quanto tale pu essere gestito dalle istruzioni C. Se si conosce lindirizzo di una variabile possibile creare una nuova variabile che lo contenga. Il primo passo quello di dichiarare la variabile che conterr lindirizzo di rate. Le si da il nome p_rate ad esempio. Allinizio questa variabile non inizializzata; le stato riservato uno spazio in memoria, ma il valore al suo interno indeterminato. 1001 ? 1002 1003 1004 100 1005
p_rate
rate
Il passo successivo quello di memorizzare lindirizzo della variabile rate nella variabile p_rate. Dato che a quel punto la variabile p_rate contiene lindirizzo di rate. La prima variabile indicher la posizione di memoria dov stata dislocata rate. 1001 1004 1002 1003 1004 100 1005
p_rate
Pagina 2 di 11
rate
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 lindirizzo di unaltra variabile.
13.1.3
Dichiarazioni di puntatori
Nellesempio 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 delluso, 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. Lasterisco (*) 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
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 loperatore di indirizzo di e commerciale (&). Questo viene posto prima del nome di una variabile, questo operatore ne restituisce lindirizzo. Perci possibile inizializzare un puntatore con unistruzione del tipo: puntatore = &variabile; Se torniamo allesempio di prima della variabile rate; listruzione che inizializzava la variabile p_rate in modo che punti a rate potrebbe essere scritta come: p_rate = &rate;
13.1.5
Loperatore 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 lesempio 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 nellesempio era 100) si ha la possibilit di scrivere: printf(%d, rate);
Pagina 3 di 11
Dispensa 13
oppure printf(%d, *p_rate); In C queste due istruzioni sono equivalenti. Laccesso 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 1004 1002 1003 1004 100 *p_rate 1005
p_rate
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 allindirizzo di var
Un puntatore non preceduto dalloperatore di rinvio viene valutato per il suo contenuto, che logicamente lindirizzo della variabile puntata. Vediamo un esempio di un programma: #include <stdio.h> /*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 lindirizzo di var in due modi*/ printf(\n\nIndirizzo di var = %d,&var); printf(\nIndirizzo di var = %d,ptr); return 0; }
Ecco loutput del programma : Accesso diretto, var = 1 Accesso indiretto, var = 1 Indirizzo di var = 4264228 Indirizzo di var = 4264228
Pagina 4 di 11
Dispensa 13
E molto probabile che sul sistema del lettore lindirizzo di var non sia 4264228!!
13.1.6
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? Lindirizzo di una variabile lindirizzo 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 1002
vchar
1003 1004 1005 1006
vfloat
1007 1008 1009 1010 1011
12252
97
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 lindirizzo 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 lindirizzo del primo di due byte, che un puntatore a un float contiene lindirizzo del rimo di quattro byte e cos via. vint
1000 1001 1002
vchar
1003 1004 1005 1006
vfloat
1007 1008 1009 1010 1011
12252
97
1200.156004
p_vint
(2 byte a partire da 1000)
p_char
(1 byte a partire da 1003)
p_float
(4 byte a partire da 1006)
Pagina 5 di 11
Dispensa 13
13.1.7
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 dellarray. Perci se si dichiarato un array di nome data[], data lindirizzo del primo elemento dellarray. Ma non si era detto che per avere un indirizzo necessario loperatore &? In effetti possibile utilizzare anche lespressione &data[0] per ottenere lindirizzo del primo elemento dellarray. In C la relazione (data == &data[0]) sempre vera. In pratica il nome di un array non altro che un puntatore allarray 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 allarray. Ad esempio il codice seguente inizializza il puntatore p_array allindirizzo del primo elemento dellarray: 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
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 nellarray. Per fare questo tipo di operazione viene utilizzata laritmetica dei puntatori. Incrementando un puntatore, si incrementa il suo indice. Ad esempio, quando si incrementa un puntatore di un unit, laritmetica dei puntatori incrementa automaticamente lindirizzo contenuto nel puntatore in modo che punti allelemento successivo dellarray considerato. In altre parole il C conosce (dalla dichiarazione) il tipo di dato puntato dal puntatore e incrementa lindirizzo in base alla sua dimensione. Si supponga che ptr_to_int sia un puntatore a un elemento di un array di dtipo int, Con listruzione: 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 allistruzione punta allelemento seguente. Aggiungendo n ad un puntatore, il C incrementa lindirizzo contenuto al suo interno del numero di byte necessari per puntare allelemento dellarray 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 lincremento valgono anche per il decremento di puntatori, che equivale allaggiunta di un valore negativo e, in quanto tale, ricade nellambito degli incrementi. Vediamo un esempio di utilizzo dellaritmetica dei puntatori per accedere agli elementi di un array:
Pagina 6 di 11
Dispensa 13
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 dellarray*/ for (count = 0; count < MAX; count++) printf(%d\t%f\n,*i_ptr++,*f_ptr++) return 0; }
Ecco loutput 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
Lultima operazione consentita dallaritmetica 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 luno dallaltro. Ancora un avolta laritmetica dei puntatori adatta la risposta in maniera automatica in modo da riferirsi agli elementi dellarray. Quindi se ptr1 e ptr2 puntano a due elementi dello stesso array (di qualsiasi tipo), lespressione 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 dellarray che precede quello puntato da ptr2.
Pagina 7 di 11
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 listruzione seguente: ptr *= 2; genera un messaggio di errore.
13.1.9
Nella scrittura di un programma che impiega i puntatori necessario evitare un errore molto serio: lutilizzo di un puntatore non inizializzato sul lato sinistro di unistruzione di assegnamento. Ad esempio listruzione 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 unistruzionedi 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 unistruzione 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 dellarray di caratteri che la contiene e da un carattere null. Il nome dellarray rappresenta un puntatore di tipo char allinizio della stringa. Il carattere null segna la fine della stringa. Lo spazio reale occupato dalla stringa nellarray casuale. Infatti lunica funzione dellarray 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 linizio della strigna sarebbe sufficiente un puntatore, come se la stringa stessa si trovasse in un array. Come si pu ottenere dello spazio utilizzabile allinterno della memoria? Esistono due modi: uno prevede lallocazione di spazio per una stringa letterale in fase di compilazione del programma e laltro utilizza la funzione malloc() per allocare dello spazio in famedi esecuzione del programma, processo noto come allocazione dinamica.
13.2.1
Linizio 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
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 allinput dellutente bisogna utilizzare la funzione malloc() che consente di allocare dello spazio in memoria al volo. La funzione malloc()
malloc() una delle funzioni per lallocazione 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 lindirizzo 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 dintestazione 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 <stdlib.h> #include <stdio.h> 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
Dispensa 13
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 <stdlib.h> #include <stdio.h> 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*/ /*linizio della stringa*/ p = ptr; for (conta = 65; conta < 91; conta++) *p++ = conta;
Pagina 10 di 11
Dispensa 13
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 devessere stato allocato mediante malloc(). Se ptr NULL, free() non ha alcun effetto.
Pagina 11 di 11
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 13 bis
Laboratorio
Dispensa 13 bis
Pagina 2 di 7
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 lindirizzo di memoria della variabile A, quindi attraverso il puntatore possiamo accedere allarea di memoria in cui memorizzata la variabile A e modificarne il valore. In questo secondo caso non si creano copie. Esempio 2 #include <stdio.h> #include <stdlib.h> 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 allinterno 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 2002 2003 2004 2005 2006
10
2007 2008 2009
ptr *ptr
Loutput del programma : 10 2006 2006 10 2001 Esempio 3 #include <stdio.h> #include <stdlib.h> int main() {
Pagina 3 di 7
Dispensa 13 bis
Il nome di un array senza parentesi quadre corrisponde al puntatore alla prima cella di memoria allocata per memorizzare larray stesso. Quindi considerando lesempio riportato sopra, stampando il valore di elenco (senza le parentesi quadre) si ottiene lindirizzo di memoria della prima cella allocata, stampando invece il valore di *elenco si ottiene lo stesso valore stampato con elenco[0], quindi utilizzando laritmetica dei puntatori possiamo accedere alle varie celle dellarray: #include <stdio.h> #include <stdlib.h> 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 <stdio.h> #include <stdlib.h> 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; }
Pagina 4 di 7
Dispensa 13 bis
Pagina 5 di 7
Dispensa 13 bis
Esempio 2 #include <stdio.h> #include <stdlib.h> #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
Dispensa 13 bis
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 and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 14
Laboratorio
Dispensa 14
14.1
Le strutture
Una struttura rappresenta linsieme 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
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 linizio 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
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 loperatore 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
Dispensa 14
Di seguito mostrato come visualizzare le posizioni dello schermo memorizzate nella struttura prima: printf(%d, %d, prima.x, prima.y); Uno dei vantaggi principali nellutilizzare strutture consiste nel fatto che si possono copiare le informazioni tra strutture dello stesso tipo utilizzando una semplice istruzione di eguaglianza. Proseguendo nellesempio precedente, listruzione: 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
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. Listruzione precedente definisce soltanto la struttura di tipo rettangolo. Per dichiarare una struttura, necessario specificare unistruzione 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 loperatore punto (.). Quindi, lespressione: 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;
Pagina 3 di 7
Dispensa 14
Riceve in input le coordinate angolari di un rettangolo e ne calcola larea. Si suppone che la coordinata y dellangolo superiore sinistro sia maggiore della coordinata y dellangolo inferiore destro, che la coordinata x dellangolo inferiore destro sia maggiore della coordinata x dellangolo superiore sinistro e che tutte le coordinate siano positive. #include <stdio.h> 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 larea*/ area = lung*larg; printf(\nLarea : %d, area); return 0; } Ecco input e output del programma: Inserisci la Inserisci la Inserisci la Inserisci la Larea 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
Dispensa 14
14.1.4
E possibile definire una struttura che abbia come componenti uno o pi array. Larray 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 dellarray contenuto nella struttura si utilizza una combinazione delloperatore punto con gli indici dellarray: 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 dellutente: 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 lopeartore di rinvio (*) allinterno della dichiarazione indica che p_parte un puntatore al tipo parte, non unistanza della struttura di tipo parte. Si ricordi che la dichiarazione non la definizione che predispone lo spazio in memoria per la memorizzazione delloggetto di dati. Poich un puntatore necessita di un indirizzo di memoria a cui puntare, necessario dichiarare unistanza di tipo parte alla quale possa essere indirizzato il puntatore. Di seguito riportata la dichiarazione: struct parte gz;
Pagina 5 di 7
Dispensa 14
Questa istruzione assegna lindirizzo di gz a p_parte. Ora che si dispone di un puntatore alla struttura gz, si pu utilizzare loperatore di rinvio (*). Applicando questo concetto allesempio corrente, poich p_parte rappresenta un puntatore alla struttura gz, *p_parte si riferisce a gz. Si pu applicare loperatore (.) 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 loperatore (.) ha la precedenza rispetto loperatore (*).
Un altro modo per accedere agli elementi di una struttura tramite un puntatore a struttura consiste nellutilizzare loperatore puntatore di membro indiretto, rappresentato dai caratteri -> (un tratino seguito dal simbolo maffiore). Quindi lesempio 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
Dispensa 14
Utilizzando un array di strutture, si vogliono memorizzare i seguenti dati relativi agli studenti: Nome, Cognome, Matricola, Anno di corso, Elenco dei voti dove lelenco dei voti a sua volta memorizzato allinterno di un vettore. Si utilizza, poi, un ulteriore vettore di strutture per memorizzare le informazioni relative alle materie: codice materia, descrizione e docente. Lindice in cui sono memorizzati i voti allinterno dellarray 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 and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 15
Laboratorio
Dispensa 15
15.1
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 <stdio.h> long cubo(long x); long input, risp; main() { printf(Immettere un valore intero: ); scanf(%d, &input); /*Nota: %ld lindicatore 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 loutput 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 lunico 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
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 unintestazione alla riga 20, contenente il nome della funzione, il tipo e la descrizione degli eventuali argomenti. Si noti che listruzione della funzione identica al prototipo (a parte il punto e virgola). Il corpo della funzione, nelle righe dalla 21 alla 26. racchiuso allinterno di parentesi graffe e contiene istruzioni. Le variabili locali sono quelle dichiarate allinterno del corpo della funzione. Infine, la funzione termina con unistruzione return alla riga 25 che indica la conclusione della routine. Listruzione return serve anche per passare il valore restituito al programma chiamante. I programmi in C eseguono le istruzione contenute allinterno 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 nellistruzione 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 unanteprima 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 limpiego delle funzioni allinterno 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
Il primo passo dello sviluppo di una funzione la definizione del suo obiettivo.
Pagina 3 di 15
Dispensa 15
Intestazione: la prima riga di qualsiasi funzione lintestazione, la quale si compone di tre parti ciascuna asservita ad uno scopo preciso.
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. Lelenco 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, lelenco dei parametri deve contenere un oggetto distinto.
Corpo della funzione: contenuto allinterno di una coppia di parentesi graffe e segue immediatamente lintestazione. Allinterno 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 unespressione del C. Una funzione pu contenere pi di una istruzione return, solo la prima ad essere eseguita quella che conta.
#include <stdio.h> 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
Dispensa 15
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
Per passare degli argomenti a una funzione necessario elencarli allinterno delle parentesi che seguono il nome della funzione stessa. Il numero, il tipo e lordine degli argomenti devono coincidere con quelli del prototipo e dellintestazione. 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
funzione1 (a, b, c) ;
void
15.1.3
Esistono due modi di chiamare una funzione. Qualsiasi funzione pu essere richiamata specificandone il nome e lelenco 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 unespressione. Nellesempio seguente, areaRettangolo() utilizzato come parametro di una funzione: printf(Larea 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
Dispensa 15
15.1.4
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 unaltra che al suo interno richiama la funzione primaria. Il programma presentato nellesempio 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 <stdio.h> 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
Lambito di una variabile si riferisce allestensione 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 allambito, con il
Pagina 6 di 15
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, lambito 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 <stdio.h> intx = 999; void stampa_valore(void); main() { printf(%d\n,x); stampa_valore(); return 0; } void stampa_valore(void) { printf(%d\n,x); }
Ecco loutput del programma: 999 999 /*Versione 2 del listato*/ #include <stdio.h> 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
Dispensa 15
Lunica 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 lambito. Nel listato 1, x rappresenta una variabile esterna e il suo ambito si estende allintero programma. Nel listato 2, x rappresenta una variabile locale e come tale il suo ambito limitato allinterno 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. Lambito di una variabile esterna rappresentata dallintero programma. Ci significa che una variabile esterna visibile dalla funzione main() e da qualsiasi altra funzione allinterno del programma.
15.1.5.2
Variabili locali
Una variabile locale una variabile definita allinterno di una funzione. Lambito 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
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 lesecuzione del programma abbandona quella funzione. Ai fini pratici, ci significa che una variabile automatica non mantiene il proprio calore nellintervallo 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 <stdio.h> void funzione1(void); main() { int conta; for (conta = 0; conta < 10; conta++) { printf(Alla iterazione %d: , conta); funzione1(); } return 0;
Pagina 8 di 15
Dispensa 15
Ecco loutput 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
Il modo normale di passare un argomento a una funzione detto passaggio per valore. Con ci si intende che la funzione riceve una copia dellargomento.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 <stdio.h> void per_valore(int a, int b, int c); void per_riferimento(int *a, int *b, int *c); main()
Pagina 9 di 15
Dispensa 15
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
Dispensa 15
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 allinterno di un insieme di stringhe. Il programma prevede che lutente possa inserire pi stringhe; si considera la terminazione di una stringa con landata a capo (si legge cio una linea per volta). Linput 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
Dispensa 15
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 <stdio.h> #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
Dispensa 15
ESEMPI DI SCOPE
#include <stdio.h> int counter = 0; void f(void); void f2(void); /* variabile globale */
Pagina 13 di 15
Dispensa 15
/* variabile locale
*/
Occorre sempre fare attenzione ai nomi e alla visibilit delle variabili: la variabile globale counter, definita allinizio 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 <stdio.h> 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
Dispensa 15
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
Dispensa 15b
ESEMPIO 1
Step 1 Ordine #include <stdio.h> void stampa(int i); void incrementa(int k); int main() { int numero = 7; stampa(numero); printf(fine stampa); incrementa(numero); printf(%d,numero); return 0; } void stampa(int i) { printf(%d,i); } void incrementa(int k) { int val = 5; val++; k++; } main() variabili globali numero: 7 1 memoria ordine alloc. deal. Allavvio del programma la prima istruzione ad essere eseguita la dichiarazione della variabile numero con assegnamento del valore 7 in memoria viene allocato lo spazio necessario per mantenere la variabile numero (variabile locale della funzione main())
Pagina 2 di 52
Dispensa 15b
La seconda istruzione prevede una chiamata ad una funzione stampa() Quando viene chiamata una funzione, lesecuzione del programma si blocca nel punto della chiamata, il controllo viene ceduto alla funzione chiamata In memoria vengono allocate tutte le variabili locali alla funzione incluse quelle indicate nei parametri
1 2
i: 7 numero: 7
2 1
3 4
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 lesecuzione delle varie istruzioni presenti allinterno 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
Dispensa 15b
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 dallistruzione immediatamente dopo la chiamata (punto 5)
1 2 5
i: 7 numero: 7
2 1
3 4
Pagina 4 di 52
Dispensa 15b
Si procede con il punto 6 e quindi con la chiamata della funzione incrementa() La funzione main() si blocca e il controllo viene ceduto alla funzione chiamata. In memoria vengono allocate tutte le variabili locali (punto 8) 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 7)
1 2 5 6
val: 5 k: 7 i: 7 numero: 7
5 4 2 1 3
3 4 7 8
Pagina 5 di 52
Dispensa 15b
vengono eseguite tutte le istruzioni allinterno 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
1 2 5 6
val: 6 k: 8 i: 7 numero: 7
5 4 2 1 3
3 4 7 8 9 10
Pagina 6 di 52
Dispensa 15b
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 dallistruzione immediatamente dopo la chiamata (punto 11)
1 2 5 6 11
val: 6 k: 8 i: 7 numero: 7
5 4 2 1
6 6 3
3 4 7 8 9 10
Pagina 7 di 52
Dispensa 15b
Quando il programma termina tutte le variabili allocate allinterno del main() vengono rimosse dalla memoria
1 2 5 6 11 12 13 3 4 7 8 9 10
val: 6 k: 8 i: 7 numero: 7
5 4 2 1
6 6 3 7
Pagina 8 di 52
Dispensa 15b
ESEMPIO 2
Step 1 Ordine #include <stdio.h> void stampa(int i); void incrementa(int k); 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++; } main() variabili globali numero: 7 1 memoria ordine alloc. deal. Allavvio del programma la prima istruzione ad essere eseguita la dichiarazione della variabile numero con assegnamento del valore 7 in memoria viene allocato lo spazio necessario per mantenere la variabile numero (variabile locale della funzione main())
Pagina 9 di 52
Dispensa 15b
La seconda istruzione prevede una chiamata ad una funzione stampa() Quando viene chiamata una funzione, lesecuzione del programma si blocca nel punto della chiamata, il controllo viene ceduto alla funzione chiamata 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 i (della funzione stampa()) avr una copia del valore della variabile numero (punto 3)
1 2
3 4
val: 10 i: 7 numero: 7
3 2 1
Pagina 10 di 52
Dispensa 15b
Si procede con il punto 5 e quindi con la stampa del valore della variabile i. La seconda istruzione prevede una chiamata ad una funzione incrementa() lesecuzione della funzione stamoa() si blocca nel punto della chiamata, il controllo viene ceduto alla funzione chiamata 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 val (punto 7). La variabile val della funzione incrementa() non ha nulla a che fare con lomonima variabile allinterno della funzione stampa()
1 2
3 4 5 6
7 8
Pagina 11 di 52
Dispensa 15b
Si procede eseguendo le istruzioni presenti allinterno del corpo della funzione incrementa() quindi con i punti 9 e 10
1 2
3 4 5 6
7 8 9 10
Pagina 12 di 52
Dispensa 15b
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 dallistruzione immediatamente dopo la chiamata (punto 11) quindi avremo la stampa del valore 10 (associato alla variabile val)
1 2
3 4 5 6 11 7 8 9 10
Pagina 13 di 52
Dispensa 15b
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. Il controllo viene ceduto al chiamante, quindi alla funzione main() ripartendo dallistruzione immediatamente dopo la chiamata (punto 12) quindi avremo la stampa della stringa fine stampa.
1 2 12 13
9 8 5 4 3 2 1 6 6 7 7
3 4 5 6 11 14 15 7 8 9 10
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)
Pagina 14 di 52
Dispensa 15b
Si procede eseguendo le istruzioni presenti allinterno del corpo della funzione incrementa() quindi con i punti 9 e 10
1 2 12 13
9 8 5 4 3 2 1 6 6 7 7
3 4 5 6 11 14 15 16 17 7 8 9 10
Pagina 15 di 52
Dispensa 15b
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 dallistruzione immediatamente dopo la chiamata (punto 18) quindi avremo la stampa del valore 7 (associato alla variabile numero)
1 2 12 13 18
9 8 5 4 3 2 1
10 10 6 6 7 7
3 4 5 6 11 14 15 16 17 7 8 9 10
Pagina 16 di 52
Dispensa 15b
Quando il programma termina tutte le variabili allocate allinterno del main() vengono rimosse dalla memoria
1 2 12 13 18 19 20 3 4 5 6 11 14 15 16 17 7 8 9 10
9 8 5 4 3 2 1
10 10 6 6 7 7 11
restituzione di un parametro
Pagina 17 di 52
Dispensa 15b
ESEMPIO 3
Step 1 Ordine #include <stdio.h> void modifica(int i, int *k); 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; }
frammento di memoria:
Allavvio 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
1 2 3 4
valore: 5 numero: 7
2 1
numero
valore
Pagina 18 di 52
Dispensa 15b
Si procede eseguendo il punto 5 con la chiamata alla funzione modifica() 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 i (della funzione modifica()) avr una copia del valore della variabile numero (punto 6) e la variabile k avr lindirizzo di memoria della variabile valore (quindi 1001)
1 2 3 4 5
3 3 2 1
frammento di memoria:
numero
valore
Pagina 19 di 52
Dispensa 15b
1 2 3 4 5
Vengono eseguite le istruzioni presenti allinterno della funzione: alla variabile i viene associato il valore 10, mentre, attraverso loperatore di rinvio (*) si accede alla cella di memoria con indirizzo presente allinterno della variabile k (1001) inserendo il valore 16. Modificando il *k viene modificata anche la variabile valore
3 3 2 1
6 7 8
frammento di memoria:
numero
valore
Pagina 20 di 52
Dispensa 15b
Quando la funzione termina tutte le variabili locali vengono eliminate dalla memoria Il controllo viene restituito alla funzione chiamante riprendendo dallistruzione immediatamente successiva alla chiamata della funzione (punto 9)
1 2 3 4 5 9
3 3 2 1
4 4
6 7 8
frammento di memoria:
numero
valore
Pagina 21 di 52
Dispensa 15b
Con le funzioni printf() nei punti 9 e 10 vengono stampati i valori delle variabili numero e valore, quindi rispettivamente 7 e 16
1 2 3 4 5 9 10
3 3 2 1
4 4
6 7 8
frammento di memoria:
numero
valore
Pagina 22 di 52
Dispensa 15b
Quando la funzione main() termina tutte le variabili locali vengono rimosse dalla memoria.
1 2 3 4 5 9 10 11 6 7 8
3 3 2 1
4 4 5 5
frammento di memoria:
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
Pagina 23 di 52
Dispensa 15b
ESEMPIO 4
Step 1 Ordine #include <stdio.h> 1 void elabora(); int i = 0; int main() { elabora(); elabora(); elabora(); return 0; } void elabora() { static int cont = 0; int k = 0; cont++; k++; i++; }
frammento di memoria:
Allavvio del programma tutte le variabili globali vengono allocate in memoria. Le variabili globali sono visibili (modificabili) in tutto il programma
variabili globali
i: 0
0 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
Pagina 24 di 52
Dispensa 15b
Si procede eseguendo il punto 2 con la chiamata alla funzione elabora() In memoria vengono allocate tutte le variabili locali alla funzione
3 4
cont: 0 k: 0 i: 0
2 2 1
frammento di memoria:
cont
Pagina 25 di 52
Dispensa 15b
Vengono eseguite le istruzioni presenti nel corpo della funzione chiamata (punti 5, 6 e 7)
3 4 5 6 7
cont: 1 k: 1 i: 1
2 2 1
frammento di memoria:
cont
Pagina 26 di 52
Dispensa 15b
2 9
La funzione elabora() termina e restituisce il controllo alla funzione main() ripartendo dallistruzione 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 4 5 6 7 8
frammento di memoria:
cont: 1 k: 1 i: 1
2 2 1 3
cont
Pagina 27 di 52
Dispensa 15b
Si procede eseguendo il punto 9 con la chiamata alla funzione elabora() In memoria vengono allocate tutte le variabili locali alla funzione
2 9
10 11
3 4 5 6 7 8
frammento di memoria:
1 1005 1006
cont
Pagina 28 di 52
Dispensa 15b
Vengono eseguite le istruzioni presenti nel corpo della funzione chiamata (punti 12, 13 e 14)
2 9
10 11 12 13 14
3 4 5 6 7 8
frammento di memoria:
2 1005 1006
cont
Pagina 29 di 52
Dispensa 15b
2 9 16
La funzione elabora() termina e restituisce il controllo alla funzione main() ripartendo dallistruzione immediatamente successiva alla chiamata (punto 16). Tutte le variabili locali vengono rimosse dalla memoria eccetto le variabili statiche
10 11 12 13 14 15
3 4 5 6 7 8
frammento di memoria:
cont
Pagina 30 di 52
Dispensa 15b
Si procede eseguendo il punto 9 con la chiamata alla funzione elabora() In memoria vengono allocate tutte le variabili locali alla funzione
2 9 16
k: 0 k: 1 cont: 2 k: 1 i: 2
6 4 2 2 1 3 5
17 18
10 11 12 13 14 15
3 4 5 6 7 8
frammento di memoria:
0 1011
cont
Pagina 31 di 52
Dispensa 15b
Vengono eseguite le istruzioni presenti nel corpo della funzione chiamata (punti 19, 20 e 21)
2 9 16
k: 1 k: 1 cont: 3 k: 1 i: 3
6 4 2 2 1 3 5
17 18 19 20 21
10 11 12 13 14 15
3 4 5 6 7 8
frammento di memoria:
1 1011
cont
Pagina 32 di 52
Dispensa 15b
2 9 16 23
La funzione elabora() termina e restituisce il controllo alla funzione main() ripartendo dallistruzione immediatamente successiva alla chiamata (punto 23). Tutte le variabili locali vengono rimosse dalla memoria eccetto le variabili statiche Allinterno 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()
k: 1 k: 1 cont: 3 k: 1 i: 3
6 4 2 2 1
7 5
17 18 19 20 21 22
10 11 12 13 14 15
3 4 5 6 7 8
frammento di memoria:
cont
Pagina 33 di 52
Dispensa 15b
Il programma termina e tutte le variabili allocate allinterno del main(), quelle locali e quelle statiche vengono rimosse dalla memoria
2 9 16 23 24
k: 1 k: 1 cont: 3 k: 1 i: 3
6 4 2 2 1
7 5 8 3 9
17 18 19 20 21 22
10 11 12 13 14 15
3 4 5 6 7 8
frammento di memoria:
cont
Pagina 34 di 52
Dispensa 15b
ESEMPIO 5
Step 1 Ordine #include <stdio.h> void elabora(int vett[5]); 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; } }
frammento di memoria:
Allavvio del programma tutte le variabili locali alla funzione main() vengono allocate in memoria La variabile elenco tiene lindirizzo di memoria della prima cella allocata per mantenere in memoria larray
elenco: 1001
2 1000 1001
4 1002
6 1003
8 1004
elenco
Pagina 35 di 52
Dispensa 15b
1 2
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nellintestazione della funzione vengono allocate in memoria (punto 3 e 4) Allinterno della variabile vett della funzione elabora() viene copiato il valore presente allinterno della variabile elenco passata come parametro alla funzione (quindi viene copiato lindirizzo di memoria della prima cella allocata per larray elenco) La variabile vett quindi un puntatore!
frammento di memoria:
2 1000 1001
4 1002
6 1003
8 1004
elenco
vett
Pagina 36 di 52
Dispensa 15b
Si procede con lesecuzione delle istruzioni presenti allinterno della funzione elabora()
1 2
frammento di memoria:
1 1000 1001
1 1002
1 1003
1 1004
elenco
vett
Pagina 37 di 52
Dispensa 15b
1 2 10
Terminata la funzione elabora il controllo passa alla funzione chiamante main() ripartendo dallistruzione immediatamente successiva alla chiamata (punto 10). Tutte le variabili locali alla funzione elabora() vengono rimosse dalla memoria. Da notare che le modifiche fatte a partire da vett vanno a modificare il vettore elenco
3 2 1
4 4
1 1000 1001
1 1002
1 1003
1 1004
elenco
vett
Pagina 38 di 52
Dispensa 15b
Il programma termina e tutte le variabili locali al main() vengono rimosse dalla memoria.
1 2 10 11 3 4 5 6 7 8 9
frammento di memoria:
1 1000 1001
1 1002
1 1003
1 1004
elenco
Pagina 39 di 52
Dispensa 15b
ESEMPIO 6
Step 1 Ordine #include <stdio.h> int* elabora(); int main() { int *elenco; elenco = elabora(); return 0; } int* elabora() { int *tmp; tmp=(int*)malloc(sizeof(int)*5); return tmp; }
frammento di memoria:
Allavvio 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 lindirizzo di unaltra variabile (di tipo intero)
elenco:
1000 elenco
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
Pagina 40 di 52
Dispensa 15b
1 2
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nellintestazione della funzione vengono allocate in memoria (punto 4) La variabile tmp un puntatore, quindi una variabile in grado di tenere in memoria lindirizzo di unaltra variabile (di tipo intero)
tmp: elenco:
2 1
1000 elenco
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
tmp
Pagina 41 di 52
Dispensa 15b
Si procede con leseguire le istruzioni presenti allinterno della funzione elabora(). La funzione malloc() alloca in memoria un numero di byte pari al parametro passato come argomento. Malloc() restituisce lindirizzo di memoria della prima cella di memoria allocata. Quindi la variabile tmp dopo lesecuzione della funzione malloc() avr il valore 1007 (considerando il frammento di memoria preso in esempio)
1 2
2 1
1007 1000 elenco 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
tmp
Pagina 42 di 52
Dispensa 15b
Quando la funzione elabora() termina le proprie istruzioni tutte le variabili locali alla funzione vengono eliminate dalla memoria. Attraverso listruzione return la funzione elabora() restituisce al chiamante il valore presente nella variabile tmp quindi lindirizzo di memoria 1007 La variabile tmp viene quindi rimossa dalla memoria, ma non viene deallocato lo spazio riservato dalla funzione malloc()
1 2
frammento di memoria:
1007 1000 elenco 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
tmp
Pagina 43 di 52
Dispensa 15b
1 2
Quando la funzione elabora() termina le proprie operazioni allinterno della variabile elenco troveremo lindirizzo di memoria allocato attraverso la funzione malloc() richiamata allinterno della funzione elabora() Quindi attraverso la variabile elenco possibile accedere allarea di memoria allocata attraverso la funzione elabora()
frammento di memoria:
1007 1000 elenco 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
Pagina 44 di 52
Dispensa 15b
ESEMPIO 7
Step 1 Ordine #include <stdio.h> void elabora(int* v); int main() { int *elenco; elabora(elenco); return 0; } void elabora(int* v) { v = (int*)malloc(sizeof(int)*5); }
frammento di memoria:
Allavvio 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 lindirizzo di unaltra variabile (di tipo intero)
elenco:
1000 elenco
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
Pagina 45 di 52
Dispensa 15b
1 2
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nellintestazione della funzione vengono allocate in memoria (punto 3)
frammento di memoria:
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
elenco
Pagina 46 di 52
Dispensa 15b
Si procede con leseguire le istruzioni presenti allinterno della funzione elabora(). La funzione malloc() alloca in memoria un numero di byte pari al parametro passato come argomento. Malloc() restituisce lindirizzo di memoria della prima cella di memoria allocata. Quindi la variabile v dopo lesecuzione della funzione malloc() avr il valore 1007 (considerando il frammento di memoria preso in esempio)
1 2
frammento di memoria:
1007 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
elenco
Pagina 47 di 52
Dispensa 15b
Quando la funzione elabora() termina le proprie istruzioni tutte le variabili locali alla funzione vengono eliminate dalla memoria. La variabile v viene quindi rimossa dalla memoria, ma non viene deallocato lo spazio riservato dalla funzione malloc() Dopo la chiamata della funzione elabora() la variabile elenco NON contiene valori!
1 2
frammento di memoria:
1007 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
elenco
Pagina 48 di 52
Dispensa 15b
ESEMPIO 7-BIS
Step 1 Ordine #include <stdio.h> void elabora(int** v); int main() { int *elenco; elabora(&elenco); return 0; } void elabora(int** v) { *v=(int*)malloc(sizeof(int)*5); }
frammento di memoria:
Allavvio 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 lindirizzo di unaltra variabile (di tipo intero)
elenco:
1000 elenco
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
Pagina 49 di 52
Dispensa 15b
1 2
Alla chiamata della funzione elabora (punto 2) il controllo viene ceduto alla funzione chiamata, tutte le variabili locali e presenti nellintestazione della funzione vengono allocate in memoria (punto 3) Allinterno della variabile v viene memorizzato lindirizzo di memoria del puntatore elenco
frammento di memoria:
1001 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
elenco
Pagina 50 di 52
Dispensa 15b
Si procede con leseguire le istruzioni presenti allinterno della funzione elabora(). La funzione malloc() alloca in memoria un numero di byte pari al parametro passato come argomento. Malloc() restituisce lindirizzo di memoria della prima cella di memoria allocata. Attraverso *v accediamo alla variabile elenco, il valore restituito dalla funzione malloc() verr quindi scritto dentro la variabile elenco
1 2
frammento di memoria:
1001 1003 1004 1005 1006 1007 1008 1009 1010 1011
elenco
*v
Pagina 51 di 52
Dispensa 15b
Quando la funzione elabora() termina le proprie istruzioni tutte le variabili locali alla funzione vengono eliminate dalla memoria. La variabile v viene quindi rimossa dalla memoria, ma non viene deallocato lo spazio riservato dalla funzione malloc() Dopo la chiamata della funzione elabora() la variabile elenco contiene lindirizzo di memoria della prima cella allocata dalla funzione malloc()
1 2
frammento di memoria:
1001 1003 1004 1005 1006 1007 1008 1009 1010 1011
elenco
Pagina 52 di 52
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 15c
Laboratorio
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 lelenco dei numeri allinterno 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 <stdio.h> 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
Dispensa 15c
(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() stampa() nel main num: 2 num: 1 x: 1 B A
(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() stampa() stampa() nel main num: 3 num: 2 num: 1 x: 1 C B A
(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() stampa() stampa() stampa() nel main num: 4 num: 3 num: 2 num: 1 x: 1 D C B A
dato che la variabile num maggiore di 3 viene eseguita listruzione return alla riga 15, quindi la chiamata-D termina e il controllo passa alla funzione ricorsiva chiamante (chiamata-C), in particolare il controllo passa allistruzione 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 <stdio.h> 2. 3. void stampa(int mun); 4. 5. int main()
Pagina 3 di 7
Dispensa 15c
(chiamata A) dopo la chiamata alla funzione stampa() alla riga 8 nel main, la memoria la seguente: stampa() nel main num: 1 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() stampa() nel main num: 2 num: 1 x: 1 B A
(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() stampa() stampa() nel main num: 3 num: 2 num: 1 x: 1 C B A
(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() stampa() num: 4 num: 3 D C
Pagina 4 di 7
Dispensa 15c
dato che la variabile num maggiore di 3 viene eseguita listruzione return alla riga 15, quindi la chiamata-D termina e il controllo passa alla funzione ricorsiva chiamante (chiamata-C), in particolare il controllo passa allistruzione successiva alla chiamata ricorsiva della funzione: riga 17 quindi alla stampa della variabile num allinterno della chiamata-C quindi la stampa del numero 3. Questo perch allinterno 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 allistruzione successiva alla chiamata ricorsiva della funzione: riga 17 quindi alla stampa della variabile num allinterno della chiamata-B quindi la stampa del numero 2... si procede in questo modo fino alla prima chiamata. Nella prima versione dellesempio loutput 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 <stdio.h> 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
Dispensa 15c
(chiamata B) Dopo la prima chiamata ricorsiva della funzione fattoriale()passando il valore num-1 quindi 2 (riga 19), la memoria la seguente: fattoriale() fattoriale() nel main num: 2 num: 3 x: 3 B A
(chiamata C) Dopo la prima chiamata ricorsiva della funzione fattoriale() passando il valore num-1 quindi 1 (riga 19), la memoria la seguente: fattoriale() fattoriale() fattoriale() nel main num: 1 num: 2 num: 3 x: 3 C B A
(chiamata D) Dopo la prima chiamata ricorsiva della funzione fattoriale() passando il valore num-1 quindi 0 (riga 19), la memoria la seguente: fattoriale() fattoriale() fattoriale() fattoriale() nel main num: 0 num: 1 num: 2 num: 3 x: 3 D C B A
alla chiamata-D il valore della variabile num uguale a 0 quindi al controllo nella riga 16 viene eseguita listruzione 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 allinterno 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 allinterno 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 allinterno 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
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 and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI
A.A. 2011-12
PROGRAMMAZIONE
Dispensa 16
Laboratorio
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. Limpiego dei flussi predefiniti, collegati alla tastiera e allo schermo operano sostanzialmente allo stesso modo dei flussi su file. Questo il vantaggio dellinput/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.
Pagina 2 di 16
Dispensa 16
Modo r w
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 allinizio, 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 allinizio, 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
r+
w+
a+
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
Dispensa 16
E buona norma verificare se c stato un errore a ogni chiamata di fopen(). Anche se non possibile stabilire quale sia stato lerrore, si pu almeno avvisare lutente 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 <stdlib.h> #include <stdio.h> 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; }
Pagina 4 di 16
Dispensa 16
Per mezzo delloutput 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. Limpiego 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 delloutput diretto, si salva in un file su disco il contenuto de unarea 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.
Pagina 5 di 16
Dispensa 16
Per linput formattato da file, si utilizza la funzione di libreria fscanf(), simile a scanf(), tranne che linput di fscanf() proviene da un flusso specificato, anzich stdin. Funzione fscanf() Ecco il prototipo di fscanf(): int fscanf(FILE *fp, conts char *fmt, ); Largomento 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
Dispensa 16
Vediamo lesempio:
#include <stdlib.h> #include <stdio.h> main() { FILE *fp; float f1,f2,f3,f4,f5; if ((fp = fopen(input.txt,r)) != NULL) { fprintf(stderr,Errore nellapertura 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; }
Pagina 7 di 16
Dispensa 16
char *fgets(char *str, int n, FoE *fp); Il primo argomento un puntatore al buffer in cui collocare linput; 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 dallindirizzo 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 loperazione 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); Largomento 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. Largomento 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); Largomento 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
Dispensa 16
Pagina 9 di 16
Dispensa 16
Pagina 10 di 16
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 unarea 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 lhardware del disco. Quando il programma scrive dati nel flusso, i dati vengono salvati nel buffer, fino a quando il buffer pieno; a questo punto, lintero 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 lesecuzione 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); Largomento fp un puntatore a FILE.
Pagina 11 di 16
Dispensa 16
Largomento fp un puntatore al file associato al flusso. Dopo la chiamata di rewind(), il segnaposto si trova allinizio 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); Largomento 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 dallinizio 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); Largomento fp il puntatore al file associato al flusso. La distanza di cui spostare il segnaposto, espressa in byte, indicata da offset. Largomento origin specifica il punto di partenza dello spostamento. I suoi valori possibili sono tre, le corrispondenti costanti simboliche sono descritte nella seguente tabella:
Valore 0 1 2
Descrizione Sposta il segnaposto di offset byte dallinizio del file Sposta il segnaposto di offset byte dalla posizione corrente 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 <stdlib.h> #include <stdio.h> #define MAX 50 main() { FILE *fp; int count, array [MAX]; long offset; /*inizializza array*/ for (count = 0; count < MAX; count++)
Pagina 12 di 16
Dispensa 16
Pagina 13 di 16
Dispensa 16
Pagina 14 di 16
Dispensa 16
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 <stdlib.h> #include <stdio.h> 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 nellapertura 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
Dispensa 16
Pagina 16 di 16
Dispensa 17
17.1
17.1.1
Liste
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 lindirizzo di memoria della prima cella in lista).
A testa
NULL
Attraverso una chiamata alla funzione malloc() allochiamo in memoria lo spazio necessario per mantenere la nuova cella da inserire allinterno della lista:
A testa
NULL
1
D nuovo NULL
nuovo un puntatore ad un elemento di tipo struct cella allo stesso modo di testa, la funzione malloc() restituisce lindirizzo di memoria della prima cella allocata, tale indirizzo viene memorizzato allinterno della variabile (puntatore) nuovo.
Pagina 2 di 2
Dispensa 17
La prima operazione da compiere collegare la nuova cella alla prima della lista, quindi fare in modo che lelemento next della nuova cella prenda lo stesso valore di testa:
A testa
NULL
2
nuovo->next = testa;
D nuovo
NULL
A testa
NULL
3
testa = nuovo;
D nuovo
In modo da ottenere linserimento del nuovo elemento in testa:
4
testa
NULL
Pagina 3 di 3
Dispensa 17
Se non si seguono esattamente nellordine indicato i passi indicati in precedenza e ad esempio come primo passo si associa la testa della lista al nuovo elemento:
A testa
NULL
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 linserimento 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 nuovo NULL
Pagina 4 di 4
Dispensa 17
2
D nuovo NULL
nuovo->next = testa;
NULL testa
3
testa = nuovo;
D nuovo
4
testa
NULL
Pagina 5 di 5
Dispensa 17
17.1.2
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
testa
NULL
accede
alla
Per poter leggere i valori nella seconda cella bisogna accedere a tale cella:
NULL
testa = testa->next permette di passare alla cella successiva
testa
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
Dispensa 17
testa
NULL
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
testa
NULL
testa
Pagina 7 di 7
Dispensa 17
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
testa
NULL
temp = testa;
temp testa
NULL
testa = temp->next;
temp
NULL
testa
Pagina 8 di 8
Dispensa 17
temp
NULL
testa = temp->next;
testa temp
NULL
testa
temp
NULL
testa = temp->next;
testa
temp
NULL
testa
Pagina 9 di 9
Dispensa 17
In questo modo arrivati alla fine della lista il riferimento al primo elemento non andato perso. Con listruzione 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 allinterno 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
Dispensa 17
17.1.3
La ricerca simile alla stampa: bisogna scorrere la lista fino ad individuare lelemento 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); Allinterno dellanalisi 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 lelemento cercato presente o meno allinterno della lista. Se entriamo dentro al corpo dellistruzione if la variabile trovato viene impostata al valore 1 altrimenti rimane settata al valore 0.
Pagina 11 di 11
Dispensa 17
17.1.4
Questa operazione simile a quella del punto precedente (in questo caso bisogna per contare quante volte un elemento ripetuto allinterno della lista):
temp = testa; conta = 0; while (temp != NULL) { if (temp->valore = valoreDaCercare) conta++; temp = temp ->next; } printf(Lelemento cercato presente %d volte!,conta);
17.1.5
temp = testa; conta = 0; while (temp != NULL) { conta++; temp = temp ->next; } printf(La lista ha %d elementi!,conta);
Pagina 12 di 12
Dispensa 17
17.1.6
Per poter inserire un nuovo elemento in coda alla lista prima bisogna procedere a scorrere la lista fino ad arrivare allultimo elemento:
testa
NULL
temp = testa; while (temp->next != NULL) temp = temp ->next;
temp
Come negli esempi precedenti la lista viene scorsa attraverso un puntatore dappoggio (cursore) in modo da non perdere il riferimento al primo elemento
testa
NULL
temp NULL
D
attraverso la funzione malloc() viene creato il nuovo elemento: nuovo = (struct cella*)malloc(sizeof(struct cella));
nuovo
Pagina 13 di 13
Dispensa 17
testa
NULL
temp NULL
D
Il puntatore allelemento successivo dellultima cella viene fatto puntare alla nuova cella: temp->next = nuovo;
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
Dispensa 17
17.1.7
Prima di cancellare lelemento in testa, il riferimento alla testa della lista deve passare al secondo elemento per evitare di perdere lintera lista:
testa
NULL
temp
Viene utilizzato un puntatore temp per tener traccia del primo elemento prima di far puntare a testa allelemento successivo: temp = testa; testa = testa->next;
testa
NULL
temp
a questo punto possibile eliminare il primo elemento attraverso la funzione free():
3
temp
NULL
testa
free(temp);
Pagina 15 di 15
Dispensa 17
17.1.8
Per cancellare un elemento dalla lista bisogna prima individuare lelemento allinterno della lista, quindi bisogna avviare una procedura di ricerca vista in precedenza per arrivare allelemento da eliminare.
testa
NULL
temp
Dopo aver individuato lelemento da eliminare (ad esempio la cella C considerando lesempio precedente), bisogna rimuoverlo dalla lista. Per eliminare correttamente lelemento senza alterare la catena che collega un elemento ad un altro, bisogna far in modo che lelemento che precede la cella da eliminare venga collegato allelemento che segue la cella da eliminare, quindi:
testa
NULL
temp
Per per poter fare questo necessario avere un riferimento allelemento 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 dellelemento 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
Dispensa 17
temp
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
NULL
testa
NULL
temp
Prima di accedere allelemento successivo bisogna tener traccia dellelemento che si sta per lasciare: prec = temp;
Pagina 17 di 17
Dispensa 17
prec
testa
NULL
temp
prec
4-A
testa
NULL
temp
4-B
Pagina 18 di 18
Dispensa 17
prec
5
testa A B C
D
NULL
temp
Prima di eliminare lelemento C bisogna collegare la cella precedente (B) a quella successiva (D):
prec
6
testa A B C
D
NULL
temp
in modo da ottenere:
prec->next = temp->next;
prec
testa
NULL
Con la funzione free() eliminiamo la cella puntata da temp free(temp);
temp
Pagina 19 di 19
Dispensa 17
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 lelemento da eliminare il primo della lista, il codice riportato precedente genera la perdita del riferimento alla testa della lista:
prec testa
NULL NULL
temp
ERRORE! in questo modo si perde il riferimento alla testa della lista!
Pagina 20 di 20
Dispensa 17
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 lelemento in testa alla lista, in tal caso bisogna muovere il riferimento alla testa.
Pagina 21 di 21
Dispensa 17
17.1.9
Questa funzionalit simile alla precedete solo che dopo aver eliminato la prima occorrenza dellelemento 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 lanalisi della lista non possibile eliminare a partire da temp in quanto verr utilizzato alliterazione successiva per considerare il nuovo elemento.
Pagina 22 di 22
Dispensa 17
17.1.10
Loperazione di swap consiste nello scambiare di posto due elementi allinterno 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 allinterno della lista (uno attaccato allaltro)
Ovviamente in base a quanto detto prima le operazioni da implementare possono essere differenti. Per individuare gli elementi da spostare bisogna implementare una ricerca allinterno 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 allelemento 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 NULL
primo
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
Dispensa 17
4 1
precPrimo testa
precSecondo NULL
primo
secondo
3 riorganizzando un po:
precPrimo testa
precSecondo NULL
secondo
primo
Vediamo ora il dettaglio dei passi per arrivare a quanto visto sopra.
Pagina 24 di 24
Dispensa 17
precPrimo testa
precSecondo NULL
primo
secondo
precPrimo->next = secondo;
2
precPrimo testa precSecondo NULL
primo
secondo
precSecondo->next = primo;
Pagina 25 di 25
Dispensa 17
3
precPrimo testa temp precSecondo NULL
primo
secondo
Per evitare di perdere il riferimento allelemento successivo di primo, prima di spostare il riferimento next di primo salviamo tale valore in una variabile dappoggio temp temp = primo->next; primo->next = secondo->next;
precPrimo testa
temp
precSecondo NULL
primo
secondo
Pagina 26 di 26
Dispensa 17
precPrimo testa
temp
precSecondo NULL
primo
secondo
secondo->next = temp;
precPrimo testa
precSecondo NULL
primo
secondo
Pagina 27 di 27
Dispensa 17
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
1
primo
secondo
avremo quindi:
primo
secondo
Pagina 28 di 28
Dispensa 17
precPrimo testa
precSecondo
NULL
primo
secondo
avremo quindi:
precPrimo testa
precSecondo
NULL
primo
secondo
Pagina 29 di 29
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 allelemento precedente del primo e sia impostato a NULL nel caso tale elemento sia in testa, precSecondo sia il puntatore allelemento 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
Dispensa 17
17.1.11
Proviamo a scrivere una funzione che ci consenta di inserire un nuovo elemento allinterno 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 <stdio.h> #include <stdlib.h> 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
Dispensa 17
Cerchiamo di capire dove si trova lerrore analizzando, tra le altre cose, quello che accade in memoria mentre le varie istruzioni vengono eseguite.
A testa
in memoria quindi potremo avere la seguente situaione:
NULL
B next: 1005 1008 1008 1010 1011 1012 1013 1014 1015
testa
Quando la funzione inserisci viene chiamata (riga 17) allinterno 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 allinterno dellintestazione).
Pagina 32 di 32
Dispensa 17
B next: 1005 1008 1008 1010 1011 1012 1013 1014 1015
testa
val
nuovo
I parametri passati alla funzione verranno copiati nelle variabili presenti nellintestazione nellordine in cui sono stati inseriti, quindi t prender il valore presente allinterno di testa e val il carattere D (riga 24 e 26):
B next: 1005 1008 1008 D 1010 1003 1011 1012 1013 1014 1015
testa
Pagina 33 di 33
val
nuovo
Dispensa 17
Attraverso la funzione malloc() verr allocata la nuova cella in memoria, lindirizzo del primo byte allocato verr restituito dalla funzione malloc() e inserito (riga 28) allinterno della variabile nuovo:
B next: 1005 1008 1008 D 1010 1003 1011 1012 1015 1013 1014 1015
testa
val
nuovo
Il valore presente allinterno della variabile val viene copiato allinterno dellelemento valore della nuova cella creata, referenziata dal puntatore nuovo (riga 29):
B next: 1005 1008 1008 D 1010 1003 1011 1012 1015 1013 1014 D 1015
testa
Pagina 34 di 34
val
nuovo
Dispensa 17
D nuovo
Pagina 35 di 35
Dispensa 17
Come descritto nel paragrafo 16.1.1 la prima operazione da implementare per inserire lelemento in lista consister nel far puntare il puntatore next di della nuova cella a quello che punta la testa (nuovo->next = t):
D nuovo
Quindi in memoria avremo:
B next: 1005 1008 1008 D 1010 1003 1011 1012 1015 1013 1014
testa
val
nuovo
Pagina 36 di 36
Dispensa 17
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 allinterno della funzione (t = nuovo):
testa A t B C NULL
D nuovo
Quindi in memoria avremo:
B next: 1005 1008 1008 D 1010 1015 1011 1012 1015 1013 1014
testa
val
nuovo
Pagina 37 di 37
Dispensa 17
B next: 1005 1008 1008 D 1010 1015 1011 1012 1015 1013 1014
testa
val
nuovo
testa A t B C NULL
D nuovo
Pagina 38 di 38
Dispensa 17
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:
testa
A livello logico:
testa A B C NULL
Quindi tutte le modifiche fatte alla testa della lista allinterno della funzione non vengono viste allesterno!
Pagina 39 di 39
Dispensa 17
La funzione inserisci() riceve ora come primo parametro un puntatore di puntatore cio una variabile che al suo interno mantiene lindirizzo di memoria di unaltra variabile che a sua volta contiene un indirizzo di memoria.
Pagina 40 di 40
Dispensa 17
A testa
NULL
B next: 1005 1008 1008 1010 1011 1012 1013 1014 1015
testa
Quando la funzione inserisci viene chiamata (riga 17) allinterno 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 allinterno dellintestazione).
Pagina 41 di 41
Dispensa 17
B next: 1005 1008 1008 1010 1011 1012 1013 1014 1015
testa
val
nuovo
I parametri passati alla funzione verranno copiati nelle variabili presenti nellintestazione nellordine in cui sono stati inseriti, quindi t prender lindirizzo di memoria della prima variabile e quindi di testa testa e val il carattere D (riga 24 e 26):
B next: 1005 1008 1008 D 1010 1001 1011 1012 1013 1014 1015
testa
Pagina 42 di 42
val
nuovo
Dispensa 17
Attraverso la funzione malloc() verr allocata la nuova cella in memoria, lindirizzo del primo byte allocato verr restituito dalla funzione malloc() e inserito (riga 28) allinterno della variabile nuovo:
B next: 1005 1008 1008 D 1010 1001 1011 1012 1015 1013 1014 1015
testa
val
nuovo
Il valore presente allinterno della variabile val viene copiato allinterno dellelemento valore della nuova cella creata, referenziata dal puntatore nuovo (riga 29):
B next: 1005 1008 1008 D 1010 1001 1011 1012 1015 1013 1014 D 1015
testa
Pagina 43 di 43
val
nuovo
Dispensa 17
testa A B C NULL
*t D nuovo NULL
B next: 1005 1008 1008 D 1010 1001 1011 1012 1015 1013 1014 D 1015
testa
*t
val
nuovo
con t si ottiene il valore 1001 o meglio lindirizzo di memoria che contiene il valore della variabile (puntatore) testa, mentre con *t si accede al contenuto dellarea di memoria referenziata dallindirizzo 1001.
Pagina 44 di 44
Dispensa 17
Come descritto nel paragrafo 16.1.1 la prima operazione da implementare per inserire lelemento in lista consister nel far puntare il puntatore next di della nuova cella a quello che punta la testa (nuovo->next = *t):
B next: 1005 1008 1008 D 1010 1001 1011 1012 1015 1013 1014
testa
val
nuovo
Pagina 45 di 45
Dispensa 17
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 allinterno della funzione (*t = nuovo):
testa A *t D nuovo
NB. modificando *t modifichiamo anche testa! Quindi in memoria avremo:
NULL
B next: 1005 1008 1008 D 1010 1001 1011 1012 1015 1013 1014
testa
val
nuovo
Pagina 46 di 46
Dispensa 17
B next: 1005 1008 1008 D 1010 1001 1011 1012 1015 1013 1014
testa
val
nuovo
testa A B C NULL
t D nuovo
Pagina 47 di 47
Dispensa 17
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:
testa
A livello logico:
testa
NULL
Quindi tutte le modifiche fatte alla testa della lista in questo caso rimarranno!
Pagina 48 di 48
Dispensa 17
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 allinterno della variabile testa (riga 17) in modo da mantenere aggiornate le modifiche fatte. Questa versione equivalente alla precedente.
Pagina 49 di 49
Dispensa 18
18.1
In una lista con doppi puntatori ogni cella ha un riferimento allelemento successivo e uno allelemento precedente: struct cella{ char valore; struct cella *next; struct cella *prev; };
NULL testa
NULL
18.1.1
Inserimento in testa
Considerando le seguenti strutture dati e variabili: struct cella{ char valore; struct cella *next; struct cella *prev; };
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 lindirizzo di memoria della prima cella in lista).
Pagina 2 di 20
Dispensa 18
NULL testa
NULL
Attraverso una chiamata alla funzione malloc() allochiamo in memoria lo spazio necessario per mantenere la nuova cella da inserire allinterno della lista:
NULL
NULL
testa
nuovo = (struct cella*)malloc(sizeof(struct cella));
NULL D nuovo
nuovo un puntatore ad un elemento di tipo struct cella allo stesso modo di testa, la funzione malloc() restituisce lindirizzo di memoria della prima cella allocata, tale indirizzo viene memorizzato allinterno della variabile (puntatore) nuovo. La prima operazione da compiere collegare la nuova cella alla prima della lista, quindi fare in modo che lelemento next della nuova cella prenda lo stesso valore di testa:
NULL
NULL
NULL
testa
nuovo->next = testa;
Pagina 3 di 20
Dispensa 18
NULL
NULL
testa
NULL D nuovo
testa->prev = nuovo;
Lultima operazione consiste nello spostare il riferimento della testa al nuovo elemento:
NULL
testa
NULL D nuovo
In modo da ottenere linserimento del nuovo elemento in testa:
testa = nuovo;
NULL testa
NULL
Pagina 4 di 20
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 allelemento precedente bisogner controllare che sia presente almeno un elemento allinterno della lista! Nel caso di lista vuota lunica operazione da fare per linserimento risulter:
NULL testa
1
NULL D nuovo NULL
NULL testa
2
NULL D nuovo
Riassumendo il codice necessario per linserimento 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
NULL
Dispensa 18
18.1.2
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:
NULL testa
NULL
accede
alla
Per poter leggere i valori nella seconda cella bisogna accedere a tale cella:
NULL testa
NULL
NULL
NULL
ora con testa->valore possibile accede alla propriet della seconda cella
testa
Pagina 6 di 20
Dispensa 18
NULL
NULL
testa
NULL
NULL
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
NULL
testa
NULL
NULL
testa
Pagina 7 di 20
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 listruzione temp = testa si riesce a ripristinare lo stato iniziale. E preferibile muoversi allinterno 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
Dispensa 18
18.1.3
La ricerca simile alla stampa: bisogna scorrere la lista fino ad individuare lelemento 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); Allinterno dellanalisi 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 lelemento cercato presente o meno allinterno della lista. Se entriamo dentro al corpo dellistruzione 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
Dispensa 18
18.1.4
Questa operazione simile a quella del punto precedente (in questo caso bisogna per contare quante volte un elemento ripetuto allinterno della lista):
temp = testa; conta = 0; while (temp != NULL) { if (temp->valore = valoreDaCercare) conta++; temp = temp ->next; } printf(Lelemento cercato presente %d volte!,conta); ANCHE QUESTA OPERAZIONE E UGUALE ALLE LISTE SEMPLICI!
18.1.5
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
Dispensa 18
18.1.6
Per poter inserire un nuovo elemento in coda alla lista prima bisogna procedere a scorrere la lista fino ad arrivare allultimo elemento (come con le liste semplici):
NULL testa
NULL
temp = testa; while (temp->next != NULL) temp = temp ->next;
temp
Come negli esempi precedenti la lista viene scorsa attraverso un puntatore dappoggio (cursore) in modo da non perdere il riferimento al primo elemento
NULL testa
NULL
temp
NULL
attraverso la funzione malloc() viene creato il nuovo elemento: nuovo = (struct cella*)malloc(sizeof(struct cella));
Pagina 11 di 20
D nuovo
NULL
Dispensa 18
NULL testa
NULL
temp
NULL nuovo
Il puntatore allelemento successivo dellultima cella viene fatto puntare alla nuova cella: temp->next = nuovo;
NULL
NULL testa
temp
NULL nuovo
Il puntatore allelemento precendente della nuova cella deve puntare allelemento riferito da temp: nuovo->prev = temp;
Pagina 12 di 20
NULL
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
Dispensa 18
18.1.7
Prima di cancellare lelemento in testa, il riferimento alla testa della lista deve passare al secondo elemento per evitare di perdere lintera lista:
temp
1
NULL testa A B C NULL
Viene utilizzato un puntatore temp per tener traccia del primo elemento prima di far puntare a testa allelemento successivo: temp = testa; testa = testa->next;
temp
NULL testa
NULL
temp
NULL
La seconda cella deve essere staccata dalla prima quindi il puntatore allelemento precedente prev dovr puntare a NULL testa->prev = NULL; 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;
NULL
Pagina 14 di 20
Dispensa 18
temp
NULL
4
NULL A B C NULL
free(temp);
testa
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
Dispensa 18
18.1.8
Per cancellare un elemento dalla lista bisogna prima individuare lelemento allinterno della lista, quindi bisogna avviare una procedura di ricerca vista in precedenza per arrivare allelemento da eliminare.
NULL testa
NULL
temp
Dopo aver individuato lelemento da eliminare (ad esempio la cella C considerando lesempio precedente), bisogna rimuoverlo dalla lista. Per eliminare correttamente lelemento senza alterare la catena che collega un elemento ad un altro, bisogna far in modo che lelemento che precede la cella da eliminare venga collegato allelemento che segue la cella da eliminare, quindi:
NULL testa
B temp
NULL
Inoltre dovremo fare in modo che lelemento che segue la cella da eliminare deve essere collegato allelemento che la precede:
NULL testa
B temp
NULL
Pagina 16 di 20
Dispensa 18
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 allinterno della lista).
NULL testa
NULL
temp
Dobbiamo fare in modo che lelemento che precede la cella da eliminare venga collegato con lelemento 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:
NULL testa
B temp
NULL
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
Dispensa 18
NULL testa
B temp
NULL
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
B temp
NULL
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
Dispensa 18
temp = testa; while (temp != NULL) //scorrimento della lista { if (temp->valore == elementoDaEliminare) { if (temp->prev == NULL) //lelemento 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 lelemento in testa alla lista, in tal caso bisogna muovere il riferimento alla testa come visto nel paragrafo precedente.
Pagina 19 di 20
Dispensa 18
18.1.9
Questa funzionalit simile alla precedete solo che dopo aver eliminato la prima occorrenza dellelemento 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) //lelemento 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 lanalisi della lista non possibile eliminare a partire da temp in quanto verr utilizzato alliterazione successiva per considerare il nuovo elemento.
Pagina 20 di 20
CORSO DI and Split Unregistered TECNOLOGIE INFORMATICHE Simpo PDF MergeLAUREA IN SCIENZE EVersion - http://www.simpopdf.com CESENA
CORSO DI PROGRAMMAZIONE
A.A. 2011-12
Dispensa 19
Laboratorio
Dispensa 19
19.1
Le liste con priorit (o lista ordinata) sono gestite come le liste semplici viste nella dispensa 17, solo che gli elementi allinterno della lista vengono mantenuti ordinati rispetto un criterio gestito direttamente in fase di inserimento di un nuovo elemento. Immaginiamo per esempio lufficio 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; };
struct cella *testa = NULL; Consideriamo nel nostro esempio un valore interno allinterno di ogni cella per gestire la priorit (o lordine). Lobiettivo 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
testa NULL
2
testa NULL 7 nuovo
nuovo = (struct cella*)malloc(sizeof(struct cella));
Pagina 2 di 10
3
testa NULL NULL nuovo
testa = nuovo;
NULL
Dispensa 19
3 testa
5 3
10
NULL
7 nuovo
Ovviamente dovrebbe essere inserito tra la cella con il valore 5 e la cella con il valore 8:
NULL
3 testa
5 3
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 linserimento.
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
Dispensa 19
Quando si arriva allinterno del corpo dellistruzione if allinterno del while (quindi quando viene trovata la cella con valore pi alto rispetto a quella da inserire), la situazione sar la seguente:
prec
temp
3 testa
5 3
10
NULL
7 nuovo
NULL
Pagina 4 di 10
Dispensa 19
3 testa
5 3
10
NULL
7 nuovo
NULL
prec
temp
3 testa
5 3
10
NULL
7 nuovo
4
3 testa 5 3 8 10 NULL
7
Pagina 5 di 10
Dispensa 19
prec
NULL
temp
3 testa
5 3
10
NULL
2 nuovo
NULL
prec
NULL
temp
3 testa
5 3
10
NULL
2 nuovo
NULL
Pagina 6 di 10
Dispensa 19
prec
NULL
temp
3 testa
5 3
10
NULL
2 nuovo
4
3 testa 5 3 8 10 NULL
2
Se lelemento deve essere inserito in coda, lalgoritmo descritto sopra non funziona in quanto allinterno del ciclo while non si trover mai una cella pi grande rispetto a quella da inserire:
NULL
prec 13 nuovo
Pagina 7 di 10
NULL
Dispensa 19
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 lelemento 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; }
NULL
prec 13
Pagina 8 di 10
NULL
nuovo
Dispensa 19
Considerando lultimo algoritmo, nel caso in cui il valore della nuova cella da inserire sia gi presente allinterno della lista, la nuova cella viene inserita dopo quella con il valore uguale in quanto allinterno del ciclo si cerca la cella con un valore maggiore rispetto a quella da inserire:
1
3 testa
prec
temp
5 3
10
NULL
5 nuovo
NULL
prec
temp
3 testa
5 3
10 NULL
5 nuovo
3
3 testa 5 3 8 10 NULL
5
Pagina 9 di 10
Dispensa 19
Se invece la nuova cella deve essere inserita prima di quella con il valore uguale, lunica modifica da fare nellalgoritmo nellespressione presente nellistruzione 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 dellistruzione 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