)21'$0(17,',,1)250$7,&$,,
|
|
- Leonora Scotti
- 5 anni fa
- Visualizzazioni
Transcript
1 )21'$0(17,',,1)250$7,&$,, (6(5&,7$=,21(Qƒ,//,1*8$**,2& (6(5&,=,2Qƒ Definire una macro ODD(i) che restituisca TRUE o FALSE a seconda che il valore intero passato sia rispettivamente dispari o pari. Poiché un intero è dispari se ha il bit meno significativo a 1, ed è pari in caso contrario, una possibile definizione può essere la seguente: #define ODD(i) ((i) & 1) (6(5&,=,2Qƒ Definire una macro CPL2(i) che restituisca il complemento a 2 del valore passatole come argomento. Poiché il complemento a 2 si ottiene sommando 1 al complemento a 1 del numero dato, si ha semplicemente: #define CPL2(a) (1 + (~a)) (6(5&,=,2Qƒ Definire una macro AND(a,b) che restituisca il risultato dell'operazione di AND logico fra i due parametri a e b. Ricordando che nell'operazione di AND il risultato è certamente zero se uno dei due operandi è falso, mentre è uguale all'altro operando in caso contrario, si può scrivere: #define AND(a,b) ((a)? (b) : 0) (6(5&,=,2Qƒ Definire una macro DEMORGAN_AND(a,b) che restituisca il risultato dell'operazione di AND logico fra i due parametri effettuando il calcolo sulla base delle identità del Teorema di De Morgan. Poiché è noto che: si ha immediatamente: A B = ( ( A) ( B) ) 7
2 #define DEMORGAN_AND( a, b ) ( ~( (~a) (~b) ) ) (6(5&,=,2Qƒ Definire una macro GENERIC_SWAP( NAME, ELEM_TYPE ) che definisca una funzione di nome NAME che effettui lo scambio (swap) fra due elementi di tipo ELEM_TYPE: NAME deve ricevere come parametri i puntatori ai due elementi da scambiare. In altri termini: GENERIC_SWAP(int_swap, int) dovrebbe definire una funzione int_swap capace di scambiare due interi GENERIC_SWAP(float_swap, float) dovrebbe definire una funzione float_swap capace di scambiare due valori di tipo float Poiché questa macro è decisamente più complessa di quelle fin qui viste, è necessario fare una precisazione. Come regola generale, le macro devono essere scritte su un'unica riga: qualora sia indispensabile andare a capo, è necessario, per indicare che il testo continua nella riga successiva, porre al termine della riga stessa un backslash \. Chiarito ciò, una macro che effettua quanto richiesto può essere la seguente: #define GENERIC_SWAP( NAME, ELEM_TYPE ) \ void NAME( a, b ) \ ELEM_TYPE *a, *b; \ ELEM_TYPE t; \ t = *a; *a = *b; *b = t; Nel primo dei due casi indicati nel testo dell'esercizio ( GENERIC_SWAP(int_swap, int) ) il risultato sarebbe quindi una funzione int_swap così definita: void int_swap ( a, b ) int *a, *b; int t; t = *a; *a = *b; *b = t; (6(5&,=,2Qƒ Scrivere una funzione che, a ogni invocazione, restituisca il successivo numero primo. Poiché il tempo di vita di una variabile locale di una funzione (variabile DXWRPDWLFD) è quello della funzione stessa, a ogni nuova invocazione delle funzione, tutte le variabili locali sono concettualmente vuote: quelle per le quali è specificato un valore iniziale sono inizializzate a tale valore, le altre hanno un contenuto indefinito. Sebbene questo comportamento sia fortemente desiderabile e opportuno nella stragrande maggioranza dei casi, perché garantisce l'indipendenza di una invocazione da tutte quelle passate e future, esistono casi (come quello in esame) in cui, per il particolare compito svolto da una funzione, è utile che una o più variabili (a tutti gli effetti, YDULDELOL 8
3 GL VWDWR) mantengano il loro valore fra un invocazione e l altra della funzione stessa. Questa necessità insorge particolarmente quando sono richieste IXQ]LRQL GL JHQHUD]LRQH di sequenze di valori che non devono ripetersi e in cui, eventualmente, ogni valore può dipendere dai precedenti. In questi casi, è utile la possibilità, offerta dal C, di GLFKLDUDUHVWDWLFGHOOHYDULDELOLLQWHUQHDXQD IXQ]LRQH. Il significato di questa dichiarazione, del tutto diverso da quello di un'analoga dichiarazione applicata a un oggetto esterno, è che XQD YDULDELOH ORFDOH GLFKLDUDWD VWDWLF PDQWLHQHLOVXRYDORUHIUDXQDFKLDPDWDHODOWUD della stessa funzione, il che è esattamente quanto ci serve per tenere traccia dell'ultimo numero primo generato. Costruiremo quindi una funzione nextprime, senza parametri, che a sua volta si avvarrà di una funzione isprime per controllare che un dato valore sia primo. Quest'ultimo controllo, esclusi i casi banali dei numeri 1 e 2, verrà attuato col metodo di Eratostene, verificando che il numero (dispari) dato non sia divisibile per alcun numero minore della sua radice quadrata. Il programma potrà allora presentarsi come segue: #include <stdio.h> #include <math.h> /* Include la libreria matematica (sqrt) */ #define NO 0 #define YES 1 int isprime(int n) /* Ritorna 0 o 1 secondo se n è primo */ int i, max=sqrt( (double)n ); if (n>0 && n<4) return YES; else if (!(n%2)) return NO; for(i=3; i<=max; i+=2) if (!(n%i)) return NO; return YES; int nextprime(void) static n=0; /* variabile statica con inizializzazione */ if (n>=0 && n<=2) return ++n; else do n+=2; while(!isprime(n)); return n; void main() int p,max; 9
4 do printf("\ngenerare i numeri primi fino a:\t"); scanf("%d",&max); while(max<1); do printf("\nnumero primo:\t%d", p=nextprime() ); while(p<max); L'unico elemento realmente innovativo è costituito dalla funzione nextprime, che contiene al suo interno una dichiarazione di variabile statica il cui valore viene mantenuto fra una chiamata e l'altra. Una variabile statica è sempre inizializzata automaticamente dal sistema a zero, a meno che non sia espressamente indicato un diverso valore. Tale inizializzazione avviene una sola volta, concettualmente SULPD dell'inizio dell'esecuzione del programma. Perciò la variabile n viene azzerata (da codice generato ad hoc dal compilatore) ogni volta che il programma viene ULHVHJXLWR, e QRQ D RJQL LQYRFD]LRQH della funzione nextprime, come invece avverrebbe per una normale variabile locale. (6(5&,=,2Qƒ Scrivere una funzione ULFRUVLYD che sommi i primi 1 numeri naturali. In C, una funzione può, se ciò è utile e necessario, richiamare se stessa, direttamente o indirettamente (cioè attraverso altre funzioni): può, cioè, essere ULFRUVLYD. Dal punto di vista concettuale, non c'è alcuna differenza fra chiamare un'altra funzione o la stessa funzione: in entrambi i casi, infatti, vi è un FOLHQWH (main o altra funzione) che si rivolge a un VHUYLWRUH per l espletamento di un particolare compito. Come caso particolare, una funzione può utilizzare se stessa come suo servitore, qualora ciò sia utile. Conseguentemente, anche il linguaggio tratta i due casi esattamente nello stesso modo. Tecnicamente, ciò è possibile perché ogni attivazione di una nuova funzione produce immediatamente la creazione (sul momento) del relativo DPELHQWH, ossia di una zona con tutti i parametri e tutte le variabili locali della funzione stessa: ergo, ogni nuova attivazione di funzione (anche della stessa funzione) è indipendente da tutte le attivazioni precedenti. Per affrontare un problema con approccio ricorsivo, bisogna innanzitutto modificare il proprio modo di pensare, cambiando metodo rispetto al "classico" approccio iterativo. Infatti, mentre quest'ultimo richiede di cogliere l'essenza del problema ed essere in grado di esprimere il passo generico che porta alla soluzione, l'approccio ricorsivo si limita a tentare di DEEDVVDUHLOJUDGRGLGLIILFROWj del problema, esprimendone la soluzione in termini di alcuni passi elementari HLQROWUHdella soluzione dello stesso problema, in un caso più semplice. Nel caso in esame, anziché vedere il problema come accumulazione progressiva di valori con relativi risultati parziali, per evidenziare un SDVVRHOHPHQWDUHGLGHFRPSRVL]LRQH si può osservare che se 1=0 la somma è banalmente zero, mentre se 1>0 la somma vale 1 più la somma dei valori fino a 1-1. Quest'ultima somma non è altro che la soluzione del medesimo problema (sommare un certo insieme di numeri), applicato però al caso (più semplice) di 1-1 valori anziché a 1valori. 10
5 Questo porta a scrivere direttamente il codice della soluzione che si presenta come segue. #include <stdio.h> int sum_to(unsigned int n) if (n==0) return 0; else return n+sum_to(n-1); main() int n; printf("\nintrodurre N:\t"); scanf("%d",&n); printf("\nsomma fino a %d:\t%d\n", n, sum_to(n)); La funzione sum_to opera in modo concettualmente assai semplice: se Q è zero, il risultato della somma è zero; altrimenti, esso è pari alla somma di tutti gli Q numeri precedenti (il cui calcolo è delegato alla stessa sum_to), più Q stesso. Ad esempio, per Q=4 si ha: sum_to(4) = 4 + sum_to(3) = = 4 + ( 3 + sum_to(2) ) = = 4 + ( 3 + ( 2 + sum_to(1) ) ) = = 4 + ( 3 + ( 2 + (1 + sum_to(0) ) ) ) = = 4 + ( 3 + ( 2 + (1 + 0 ) ) ) = /* */ = 4 + ( 3 + ( ) ) = = 4 + ( ) = = = = 10. Concretamente, ogni espansione di sum_to corrisponde alla creazione di un ambiente per una nuova attivazione di questa funzione. Quindi, in corrispondenza del punto, vi sono cinque attivazioni di sum_to contemporaneamente attive, ognuna in attesa che la successiva produca il valore necessario per concludere la rispettiva operazione. Da in poi, avendo l ultima chiamata di sum_to fornito direttamente un risultato (0), le varie invocazioni iniziano a chiudersi, e inizia a formarsi il risultato finale. Questa successione di chiamate ricorsive può essere seguita col debugger del Turbo C, tramite l'opzione FDOOVWDFN del menù GHEXJ. (6(5&,=,2Qƒ Scrivere una funzione ricorsiva che stampi una parola rovesciata.. Per stampare a rovescio (cioè dall'ultima lettera alla prima) una parola, si può partire dall'idea di stampare innnanzitutto l'ultimo carattere, e poi stampare rovesciati (se esistono) tutti gli altri 11
6 caratteri. Con questa semplice osservazione, si è espresso il problema di stampare rovesciata una parola lunga Q tramite un'operazione elementare (la stampa del singolo carattere) e dello stesso problema in un caso più semplice (stampare rovesciati i precedenti Q-1 caratteri). La funzione richiesta può quindi agire esattamente in questo modo. Più precisamente, se la stringa passatale è lunga zero non fa nulla, se è lunga uno la stampa e termina, mentre se è lunga più di uno stampa l'ultimo carattere e richiama se stessa sulla sottostringa iniziale (privata, cioè, dell'ultimo carattere). Una possibile codifica può essere la seguente: #include <stdio.h> #include <string.h> #define MAXLEN 30 void print_rev(char word[]) int len=strlen(word); char w[maxlen]; if (len<1) return; else if (len==1) putchar(word[0]); else strncpy(w, word, len-1); w[len-1]= \0 ; putchar(word[len-1]); print_rev(w); return; main() int n; char parola[maxlen]; printf("\nintrodurre una parola:\t"); scanf("%s",&parola); print_rev(parola); 12
7 (6(5&,=,2Qƒ Realizzare una funzione che risolva ricorsivamente il problema della 7RUUHGL+DQRL. La Torre di Hanoi Si tratta di un antico gioco, in cui sono date tre torri (sinistra, centrale, e destra) e un certo numero di dischi forati. I dischi hanno diametro diverso gli uni dagli altri, e inizialmente sono infilati uno sull'altro (dal basso in alto) dal più grande al più piccolo sulla torre di sinistra. Scopo del gioco è portarli tutti sulla torre di destra, usando quella centrale come appoggio (torre di transito), e rispettando due regole: a) si può muovere un solo disco alla volta; b) un disco più grande non può mai stare sopra a un disco più piccolo. Una soluzione generale, non ricorsiva, è molto difficile da dare, soprattutto quando il numero dei dischi supera poche unità (già 4 non è banale). La soluzione ricorsiva, invece, è immediata: per spostare Q dischi dalla torre di sinistra alla torre di destra basta supporre di saper spostare gli Q dischi superiori in quella centrale, muovere quello rimasto dalla torre di sinistra a quella di destra, e infine rispostare gli Q superiori da quella centrale a quella di destra. Ciò equivale a dire che il problema di grado Q può essere ricondotto a una mossa elementare e a due problemi di grado Q, che possono essere a loro volta risolti procedendo nello stesso modo. Ovviamente, a forza di decomporre il problema si arriva al punto di spostare un solo disco, che è una mossa elementare fattibile direttamente. Questa strategia, ancorché semplice, non è comoda da gestire a mano: viceversa è adattissima a un calcolatore. Il programma che la implementa è riportato qui di seguito. #include <stdio.h> typedef enum SINISTRA, CENTRALE, DESTRA torre; void hanoi(int d, torre start, torre end, torre transit); main() int d; printf("\ntorre DI HANOI\n\n"); printf("\nquanti dischi?\t"); scanf("%d", &d); printf("\n\nsoluzione:\n"); hanoi(d, SINISTRA, DESTRA, CENTRALE); return 0; void hanoi(int d, torre iniziale, torre finale, torre ausiliaria) if (d==1) printf("muovi un disco dalla torre %d alla torre %d\n", iniziale, finale); return; 13
8 else hanoi(d-1,iniziale, ausiliaria, finale); printf("muovi un disco dalla torre %d alla torre %d\n", iniziale, finale); hanoi(d-1, ausiliaria, finale, iniziale); return; Osserviamo che per modellare la tre torri, si è introdotto, tramite la direttiva typedef, un nuovo tipo torre, che da questo momento è liberamente usabile al pari dei tipi predefiniti del C (int, char, etc). Quindi, sarà possibile, in particolare, definire e usare variabili di tipo torre. 1 Concretamente, essendo questo nuovo tipo definito come un enumerazione, i tre identificatori SINISTRA, CENTRALE e DESTRA sono associati ai tre valori interi 0, 1 e 2: perciò l'output del programma si esprime in termini di "torre 0, torre 1, torre 2". Naturalmente sarebbe semplice prevedere forme di output più chiare e leggibili (ad esempio facendo stampare sinistra in luogo di torre 0, etc.): questo miglioramento è lasciato al lettore. Piuttosto, interessa sottolineare che la funzione hanoi realizza esattamente la logica sopra riportata: infatti, nel caso di un solo disco lo muove direttamente, altrimenti prima ne sposta Q, poi sposta quello rimasto, indi risposta nuovamente i precedenti Q. Le tre torri iniziale, finale e ausiliaria naturalmente variano da una chiamata all'altra, perché i vari sottoproblemi devono in generale spostare dei dischi fra due generiche torri (p. es. dalla torre centrale a quella di destra), per la qual operazione sfruttano come transito la terza torre (ad es. quella di sinistra). (6(5&,=,2Qƒ Realizzare una funzione che copi una stringa in un'altra tramite puntatori. Il problema della copiatura di una stringa in un'altra è interessante perché evidenzia i limiti della "analogia" fra puntatori e vettori. Per questo, nel seguito sono presentate e commentate tre versioni: una errata, le altre due entrambe corrette ma con diversa semantica. Una prima idea può essere quella di assegnare direttamente un vettore di caratteri a un'altro, come nell'esempio che segue: #include <stdio.h> main() char s1[80],s2[80]; printf("\nstringa 1:\t"); scanf("%s",s1); 1 Maggiori dettagli sulla direttiva typedef si possono trovare nel commento all esercizio n
9 V V; /* ATTENZIONE: ERRATO!! */ printf("\nstringa 1:\t%s",s1); printf("\nstringa 2:\t%s",s2); printf("\nmodifica stringa 1:\t"); scanf("%s",s1); printf("\nstringa 1:\t%s",s1); printf("\nstringa 2:\t%s",s2); Tuttavia, assegnare direttamente una stringa a un altra (qui, s1 a s2) QRQ VLJQLILFD FRSLDUOD ma soltanto (tentare di) assegnare all una l indirizzo dell altra (qui, l indirizzo di s1 all indirizzo di s2). Ciò è però errato, non essendo ovviamente possibile cambiare l'indirizzo a cui si trova un vettore. Una tale operazione, DQFRUFKp OLPLWDWD DL VROL SXQWDWRUL, è invece lecita se alla sinistra dell'assegnamento compare un SXQWDWRUHDFKDU, come nell'esempio che segue. #include <stdio.h> main() char s1[80], V; printf("\nstringa 1:\t"); scanf("%s",s1); V V; /* LECITO, ma NON DUPLICA LA STRINGA! */ printf("\nstringa 1:\t%s",s1); printf("\nstringa 2:\t%s",s2); printf("\nmodifica stringa 1:\t"); scanf("%s",s1); printf("\nstringa 1:\t%s",s1); printf("\nstringa 2:\t%s",s2); Ora l'assegnamento è sintatticamente lecito, tuttavia esso SURGXFHVRORXQDFRSLDGLSXQWDWRUL, non delle variabili da essi puntate! Perciò, s1 e s2 referenziano ora entrambi ODVWHVVDVWULQJD, e dunque qualunque modifica fatta sull'una si ritrova immediatamente sull'altra. Per copiare davvero una stringa in un'altra è invece necessaria una copia fisica, uno per volta, di tutti gli elementi, che si può attuare ad esempio tramite un ciclo: #include <stdio.h> void strcpy(char *s, char *t) while((*s=*t)!= \0 ) s++; t++; 15
10 main() char s1[80], /* stavolta, s2 deve avere lo spazio */ printf("\nstringa 1:\t"); scanf("%s",s1); strcpy(s2,s1); printf("\nstringa 1:\t%s",s1); printf("\nstringa 2:\t%s",s2); printf("\nmodifica stringa 1:\t"); scanf("%s",s1); printf("\nstringa 1:\t%s",s1); printf("\nstringa 2:\t%s",s2); Osserviamo che la funzione strcpy, scritta in questo modo, è ridondante: un programmatore esperto l'avrebbe probabilmente sintetizzata così: void strcpy(char *s, char *t) while(*s++=*t++); Infatti, gli aggiornamenti dei due puntatori possono essere fatti efficacemente con due postincrementi, e il confronto col carattere '\0' è, al solito, sovrabbondante. Osserviamo anche che, per quanto sia stretta la correlazione fra i due, YHWWRULHSXQWDWRULQRQVRQR GXHFRQFHWWLLGHQWLFL. Ciò è confermato dal seguente esempio: char msg[] = "testo del messaggio"; /* msg è un VETTORE */ char *msg = "testo del messaggio"; /* msg è un PUNTATORE */ Nel primo caso, msg è il nome di un vettore, la cui lunghezza è fissata staticamente: perciò, i singoli caratteri entro il "testo del messaggio" possono cambiare, ma msg si riferisce sempre alla stessa area di memoria. Non gli si può quindi assegnare, come nel primo degli esercizi precedenti, un altro indirizzo, perché il nome msg è univocamente associato a questa zona di memoria, e non ad altre. Nel secondo caso, invece, essendo msg un puntatore, può essere fatto puntare altrove, ma il contenuto della stringa è una costante che non dovrebbe essere modificata. Tentando di farlo, il risultato è, in generale, indefinito 2. (6(5&,=,2Qƒ 2 La ragione di questo è abbastanza evidente: si pensi a cosa potrebbe accadere se più stringhe condividono, nella rappresentazione dei dati organizzata dal sistema. delle sottostringhe... 16
11 Scrivere una funzione C in grado di riconoscere identificatori, ovvero sequenze di caratteri che rispettino la seguente grammatica: <identificatore> ::= <lettera> <lettera> <caratteri> <caratteri> ::= <lettera> <caratteri> <cifra> <caratteri> <lettera> ::= _ a b c... z A B C... Z <cifra> ::= Tradotto dal formalismo, si tratta evidentemente di riconoscere sequenze di caratteri che cominciano con una lettera o un underscore, e proseguono con lettere, undescore o cifre. Quindi, ci è richiesta una funziona che riceva in ingresso una stringa e, sostanzialmente, risponda SI o NO (oppure TRUE o FALSE, o 1 o 0, eccetera) a seconda che essa rispetti tale sintassi e costituisca, perciò, un identificatore, o meno. Da notare che è opportuno, per generalità, prevedere l'eventualità che esistano spazi iniziali, che ovviamente devono essere saltati. Una funzione che fa questo può essere la seguente. #define NULL 0 char *identifier( char *s ) while ( isspace(*s) ) s++; if (!(isalpha(*s)) && *s!= _ ) return NULL; else s++ ; while ( isalpha(*s) isdigit(*s) *s== _ ) s++ ; if ( isspace(*s) *s== \0 ) return s; else return NULL; ; Questa funzione non soltanto verifica la sintassi degli identificatori: restituisce anche un puntatore al primo carattere nella stringa che non corrisponde alla sintassi (ad esempio uno spazio che separa l'identificatore stesso dalla parola successiva). Qualora la verifica dia esito negativo, per convenzione il valore ritornato è NULL. (6(5&,=,2Qƒ Scrivere una funzione C in grado di riconoscere espressioni matematiche che rispettino la seguente grammatica (che costituisce una versione semplificata di quella illustrata nella lezione n. 34): <espressione> ::= <termine> <termine> + <termine> <termine> - <termine> <termine> ::= <fattore> <fattore> * <fattore> <fattore> / <fattore> <fattore> ::= <identificatore> <numero> ( <espressione> ) In primo luogo, osserviamo che occorrerà disporre di idonee funzioni atte a riconoscere identificatori e numeri, che sono le entità di più basso livello trattate dalla grammatica. Rimandando per gli identificatori all'esercizio precedente, tralasceremo in questa sede di presentare il codice della 17
12 funzione che riconosce numeri, in quanto facilmente realizzabile sulla scorta degli esercizi precedenti; ci concentreremo invece sulla parte, più innovativa, di riconoscimento delle espressioni. Sebbene l'operazione di riconoscimento in sé sia non banale, a causa soprattutto della definizione ricorsiva dei fattori in termini di espressioni, la possibilità offerta dal C di specificare funzioni ricorsive consente di ottenere lo scopo con un codice soprendentemente breve e compatto, come mostra l'esempio sotto riportato. #define NULL 0 char *term(char *); /* dichiarazioni delle funzioni successive */ char *factor(char *); char *identifier(char *); /* vedere esercizio precedente */ char *number(char *); /* da fare a cura del lettore */ char *expression (char *s) char *t; printf("\nespressione..."); t = term(s); if ( t == NULL ) return NULL; else s = t; while ( isspace(*s) ) s++ ; if (*s!= - && s*!= + ) return s; /* è un puro termine */ else s++; while ( isspace(*s) ) s++ ; t = term(s); if ( t == NULL ) return NULL; else return t; char *term (char *s) char *f; printf("...termine..."); f = factor(s); if ( f == NULL ) return NULL; else s = f; while ( isspace(*s) ) s++ ; if (*s!= '*' && *s!= '/' ) return s; /* è un puro fattore */ else s++; while ( isspace(*s) ) s++ ; f = factor(s); if ( f == NULL ) return NULL; else return f; char *factor (char *s) char *t; 18
13 printf("...fattore..."); t = identifier(s); if ( t!= NULL ) return t; else t = number(s); if ( t!= NULL ) return t; else /* espress. fra parentesi */ printf("...(espressione)..."); while ( isspace(*s) ) s++ ; if (*s!= ( ) return NULL; else s++; while ( isspace(*s) ) s++ ; t = expression(s); /* chiamata ricorsiva!! */ if ( t == NULL ) return NULL; else s=t; while ( isspace(*s) ) s++ ; if (*s!= ) ) return NULL; else return ++s; /* un esempio di main */ main() char s[250]; char *t; printf("\nintrodurre l espressione:\t"); scanf("%s", &s); t = expression(s); if ( t==null ) printf("\nespressione errata\n"); else printf("\nespressione OK\nParte residua non riconosciuta:%s\n",t); Come si può vedere, la funzione che analizza i termini è del tutto analoga a quella che analizza le espressioni, in ossequio al fatto che la struttura sintattica delle due entità è assolutamente analoga: in effetti, tutta la differenza sta nel nome dell'entità analizzata e negli operatori (additivi in un caso, moltiplicativi nell'altro) accettati e riconosciuti. Per questo, descriveremo solo la prima. Nella funzione expression, in primo luogo si verifica la presenza di un termine: se esso non esiste, la funzione termina e fallisce restituendo NULL. Altrimenti, si cerca il primo carattere non-spazio e si controlla se si tratta di un operatore di somma ( + o - ): se così non è si termina, restituendo il puntatore a tale elemento non identificato. Se invece si riscontra la presenza di "+" o "-", si parte, tolti gli eventuali spazi, alla ricerca di un nuovo termine. Di nuovo, se esso non viene trovato la funzione fallisce restituendo NULL, diversamente ritorna il puntatore (fornito dalla funzione term) al primo carattere non identificato. La funzione factor è sostanzialmente simile, anche se formalmente diversa. In questo caso, si cerca in primo luogo un identificatore: se lo si trova si termina restituendo, al solito, il puntatore (fornito da identifier) al primo elemento non identificato; altrimenti, e qui è la sola reale differenza, si cambia strada (ovvero si tenta di applicare una diversa regola di produzione), controllando se non si tratti per caso di un numero. Di nuovo, se così è, si restituisce il puntatore al primo carattere non identificato, altrimenti si passa a tentare la terza e ultima via: quella corrispondente a un fattore realizzato racchiudendo una espressione fra parentesi. 19
14 Tolti gli spazi, si procede allora alla ricerca di una parentesi tonda aperta (fallendo se non la si trova), quindi si richiama ricorsivamente la funzione expression originaria per l analisi del contenuto delle parentesi (fallendo se da essa risulta che non si tratta di una espressione corretta), infine si cerca la parentesi chiusa, fallendo se manca. Da notare che, per come è definita la grammatica, vi sono alcune differenze rispetto al modo usuale di scrivere le espressioni: in particolare, ogni operatore di un dato livello deve agire esclusivamente su due entità ben definite, eventualmente delimitate da parentesi. Ne segue che A * 5 4 / 3 - k2 sono legali, mentre a * 5 / k + 8 sono errate; la versione corretta sarebbe: a + (3+5) 4 * (5/8) (5-k) + 8 Questa apparente complicazione è alla base della semplicità con cui è stato possibile scrivere il codice: la grammatica completa della lezione 34 sarebbe stata decisamente più complessa, essenzialmente a causa del fatto che gli operatori devono risultare associativi a sinistra (ovvero, deve essere letto come (7-4)-6, e non come 7-(4-6) ), il che richiede un'analisi della stringa più sofisticata, con capacità di "guardare avanti". Non approfondiamo oltre questo aspetto ora: chi desidera può comunque provare a riflettere su come differenze grammaticali anche piccole possano comportare notevoli complicazioni nell'analizzatore corrispondente. (6(5&,=,2Qƒ Realizzare una programma che ricopi il file dato come primo argomento nel file dato come secondo argomento, assicurandosi di non avere mai linee più lunghe di 40 caratteri. Si tratta evidentemente di operare in due fasi: in primo luogo analizzare i parametri di ingresso recuperando i nomi dei file su cui agire, aprendo tali file rispettivamente in lettura e in scrittura (ed emettendo, in mancanza, opportuni messaggi d'errore), e in secondo luogo attivare un ciclo di copiatura che, ogni 40 caratteri letti, inserisca nel nuovo file il carattere di QHZOLQH. Una possibile codifica può essere la seguente: #include <stdio.h> #define MAXCHAR 40 main(int argc, char **argv) FILE *fin, *fout; void stop(char *); int count=0, ch; if (argc==3) fin=fopen(argv[1],"r"); fout=fopen(argv[2], "w"); if (fin==null) stop("impossibile aprire file d ingresso\n"); 20
15 if (fout==null) stop("impossibile aprire file d uscita\n"); else stop("manca qualche parametro\n"); while(!feof(fin)) ch=fgetc(fin); fputc(ch, fout); if (++count>maxchar) fputc( \n, fout); count=0; fclose(fin); fclose(fout); exit(0); void stop(char *msg) fprintf(stderr,msg); exit(1); La funzione feof garantisce l uscita dal ciclo al termine del file d ingresso; la funzione stop, da noi definita, stampa invece un messaggio d errore e fa abortire il programma. Da notare il controllo sui parametri d'ingresso, che tenta di fornire una messaggistica il più possibile vicina al tipo di errore verificatosi. Osserviamo che questa versione del programma non resetta il contatore qualora il testo d'ingresso contenga già al suo interno dei newline: volendolo fare, basta cambiare la parte centrale del ciclo di copiatura come segue. while(!feof(fin)) if ((ch=fgetc(fin))== \n ) count=0; fputc(ch, fout); if (++count>maxchar) fputc( \n, fout); count=0; (6(5&,=,2Qƒ Scrivere un programma che appenda al file dato come primo argomento il file dato come secondo argomento. 21
16 Anche qui, in funzione dei parametri di ingresso occorre aprire un file in lettura, e l altro in modo append, ovviamente intercettando e segnalando opportunamente le situazioni anomale; quindi, si attiverà un ciclo di copiatura carattere per carattere. Il programma risultante è quello sotto riportato. #include <stdio.h> main(int argc, char **argv) FILE *fin, *fapp; void stop(char *); if (argc==3) fapp=fopen(argv[1],"a"); fin=fopen(argv[2], "r"); if (fin==null) stop("impossibile aprire file d ingresso\n"); if (fapp==null) stop("impossibile aprire file d uscita\n"); else stop("manca qualche parametro\n"); while(!feof(fin)) fputc(fgetc(fin), fapp); fclose(fin); fclose(fapp); exit(0); void stop(char *msg) fprintf(stderr,msg); exit(1); Osserviamo il ciclo di copiatura, che, non essendo più necessario salvare in una variabile il carattere letto, è ridotto all'osso, al punto da scriversi sostanzialmente in un'unica istruzione while. (6(5&,=,2Qƒ Scrivere un programma che sostituisca tutte le minuscole in maiuscole nel file dato come (unico) argomento. Per risolvere il problema, occorre evidentemente essere in grado, in buona sostanza, di aprire un file effettuando sia delle letture sia, ove necessario (in particolare: ove il carattere letto sia una minuscola), delle scritture (qui per sostituire quel carattere con la corrispondente maiuscola). Inoltre, è necessario potersi collocare in una ben precisa posizione sul file, onde sovrascrivere esattamente ciò che si vuole sostituire, e non un carattere qualsiasi. A questo scopo, servono alcune caratteristiche della gestione file del C che fanno parte delle cosiddette IXQ]LRQLGLDJJLRUQDPHQWR ILOHXSGDWHIXQFWLRQV. 22
17 In primo luogo, osserviamo che le modalità di lettura, o di append, o di scrittura fin qui utilizzate per aprire i file sono insufficienti, in quanto le prime due SUHVXSSRQJRQR FKH LO ILOH HVLVWD, provocando il fallimento della fopen in caso contrario, mentre la modalità di scrittura se il file non esiste lo crea, altrimenti lo apre ma FDQFHOODQGR QHO FRQWHPSR RJQL SUHFHGHQWH FRQWHQXWR. In tal caso, la fopen può fallire solo se non è fisicamente possibile creare il file. Per aprire un file esistente senza distruggerlo, ma con la possibilità di modificarne il contenuto, serve una nuova modalità di apertura, detta PRGDOLWjGLDJJLRUQDPHQWR e indicata dall'aggiunta, in coda alla normale specifica di apertura, dello specificatore. Si possono quindi avere due modalità di base "r+" e "w+". Ognuna mantiene le sue caratteristiche di base: quindi, "r+" presuppone che il file esista, lo apre in lettura ma consente anche (alternativamente) di scriverci sopra, mentre "w+", pur consentendo delle letture, mantiene la propria caratteristica base, creando il file se non esiste o cancellandolo completamente se già esiste. La modalità "a+" è analoga a "r+", con la differenza di iniziare ad agire in coda al file. Nel nostro caso, dato che si tratta di agire su un file che non si vuole distruggere o riscrivere, ma soltanto modificare VHOHWWLYDPHQWH in alcuni punti (precisamente, in presenza delle minuscole), la modalità necessaria è chiaramente la "r+". Aperto quindi il file fornito dal parametro di ingresso, e intercettate le eventuali situazioni anomale, occorrerà avviare un ciclo di letture carattere per carattere, controllando a ogni carattere letto se si tratti di una minuscola. In caso positivo, occorrerà retrocedere di una posizione (perché la testina virtuale di lettura/scrittura di un file si sposta concettualmente in avanti di una posizione subito dopo aver letto o scritto un carattere), e sovrascrivere tale carattere con la maiuscola corrispondente. Questi posizionamenti sul file possono essere effettuati con la funzione di libreria fseek. Il programma risultante è quello sotto riportato. #include <stdio.h> #include <ctype.h> main(int argc, char **argv) FILE *file; void stop(char *); int ch; if (argc==2) if ((file=fopen(argv[1], "r+"))==null) stop("impossibile aprire file d ingresso\n"); else stop("manca il nome del file da elaborare\n"); while((ch=fgetc(file))!=eof) if(islower(ch)) fseek(file, ftell(file)-1, SEEK_SET); fputc(toupper(ch), file); fseek(file, 0, SEEK_CUR); fclose(file); exit(0); 23
18 void stop(char *msg) fprintf(stderr,msg); exit(1); La funzione fseek è doppiamente essenziale: serve infatti sia per posizionarsi su una data posizione nel file, VLD D FRQVHQWLUH ODOWHUQDQ]D GL OHWWXUH H VFULWWXUH. In effetti, la semantica del modo di aggiornamento "+" (abbinato a uno qualunque dei modi "r", "w", e "a") ULFKLHGH HVSOLFLWDPHQWHche, dopo una sequenza di letture, e prima di iniziare delle scritture, venga usata una delle funzione di riposizionamento su file. Lo stesso vale dopo una sequenza di scritture, prima di passare a eseguire nuovamente delle letture. La sintassi completa di fseek (e della funzione ausiliaria ftell) è riassunta nel riquadro sottostante. 6LQWDVVLGLIVHHNHIWHOO int fseek(file *f, long offset, int origin); long ftell(file *f); dove: offset dà la posizione, rispetto a origin, a cui portarsi sul file origin dà la posizione, rispetto a cui misurare lo scostamento, e può valere: SEEK_SET per indicare l'inizio del file SEEK_CUR per indicare la posizione corrente nel file SEEK_END per indicare la fine del file PER UN FILE DI TESTO: - offset deve essere o zero, o un valore restituito da ftell. In quest'ultimo caso, origin deve obbligatoriamente valere SEEK_SET. PER UN FILE BINARIO: - la nuova posizione si trova a offset caratteri da origin. Nel nostro caso, la funzione fseek è necessaria una prima volta (con scostamento ftell(file)-1 rispetto all'inizio del file) per retrocedere di una posizione, tornando in corrispondenza del carattere appena letto (una minuscola da sostituire), e una seconda volta (con offset zero rispetto alla posizione corrente) semplicemente per consentire, alla successiva iterazione del ciclo, una lettura: in effetti, in questo secondo caso non si effettua alcun reale riposizionamento. Il ciclo che costituisce il cuore del programma avrebbe anche potuto essere riformulato utilizzando, per esprimere la condizione di controllo, la funzione feof: while(!feof(file)) ch=fgetc(file); if(islower(ch)) fseek(file, ftell(file)-1, SEEK_SET); fputc(toupper(ch), file); fseek(file, 0, SEEK_CUR); 24
19 o anche: while(!feof(file)) if(islower(ch=fgetc(file))) fseek(file, ftell(file)-1, SEEK_SET); fputc(toupper(ch), file); fseek(file, 0, SEEK_CUR); 25
20 (6(5&,=,2Qƒ Scrivere un programma che salvi in un file, dato come argomento, un vettore di interi. Finora l'unico tipo di file considerato è stato il file di caratteri, o ILOH GL WHVWR. Tuttavia, è spesso necessario salvare su file dati che QRQ sono caratteri. In questi casi, è assai utile disporre di un supporto più generale di quello costituito dai file di testo: a questo scopo, il linguaggio C offre il concetto di )LOH%LQDULR. Un file binario serve a supportare qualunque tipo di file, in particolare file che QRQ contengono caratteri. Poiché un file binario non è un file di caratteri, ODILQHGHOILOH deve essere necessariamente testata tramite la primitiva feof, non potendosi più applicare il confronto fra un carattere letto e il carattere EOF. Per leggere e scrivere su un file binario il linguaggio mette a disposizione le due primitive fread e fwrite, la cui sintassi è riassunta nel riquadro sottostante. 6LQWDVVLGLIUHDGHIZULWH size_t fread(void *vet, size_t size, size_t n, FILE *f); size_t fwrite(const void *vet, size_t size, size_t n,file *f); dove: vet n f è un (puntatore a un) vettore di elementi, ognuno di ampiezza size è il numero di elementi da leggere o scrivere è il file (binario) La prima legge dal file, collocandoli nel vettore vet, al più n oggetti, ciascuno di ampiezza pari a size, e restituisce il numero di oggetti effettivamente letti, che può essere minore di quanto richiesto (se il file conteneva meno elementi). Per determinare lo stato di terminazione si devono usare le funzioni feof e ferror. La funzione fwrite viceversa scrive sul file, prelevandoli dal vettore vet, n oggetti, ciascuno di ampiezza pari a size, e restituisce il numero di oggetti scritti, che può essere inferiore al richiesto solo in caso di errore (come ad esempio l'esaurimento di spazio su disco). Usando queste primitive, il problema dato si risolve semplicemente passando a fwrite gli opportuni parametri: il programma si potrà presentare allora come sotto illustrato. #include <stdio.h> main(int argc, char **argv) FILE *file; void stop(char *); int i, n; int tab[]=3, 6, -12, 5, -76, 3, 32, 12, 65, 1, 0, -9 ; if (argc==2) if ((file=fopen(argv[1], "wb"))==null) 26
21 stop("impossibile aprire file d uscita\n"); else stop("manca il nome del file di uscita\n"); n = sizeof(tab)/sizeof(tab[0]); fwrite(tab, sizeof(tab[0]), n, file); fclose(file); exit(0); void stop(char *msg) fprintf(stderr,msg); exit(1); Si noti il metodo, del tutto generale, usato per determinare il numero di elementi nel vettore, come rapporto fra la dimensione del vettore e quella di un suo generico elemento. Da notare che questo programma non verifica che siano stati effettivamente scritti tutti gli elementi: in una applicazione reale, il valore di ritorno di fwrite dovrebbe essere controllato, gestendo il caso in cui l operazione non abbia potuto essere completata. 27
22 (6(5&,=,2Qƒ Scrivere un programma che che legga da un file, dato come argomento, una lista di interi, ponendola in un vettore. Si tratta evidentemente del problema duale del precedente, risolvibile semplicemente usando opportunamente la primitiva fread. Una possibile codifica può quindi essere la seguente: #include <stdio.h> #define MAX 40 main(int argc, char **argv) FILE *file; void stop(char *); int i, n, tab[max]; if (argc==2) if ((file=fopen(argv[1], "rb"))==null) stop("impossibile aprire file d ingresso\n"); else stop("manca il nome del file d ingresso\n"); n=fread(tab, sizeof(tab[0]), MAX, file); fclose(file); for(i=0;i<n;i++) printf("%d%c", tab[i], (i==n-1)? \n : \t ); exit(0); void stop(char *msg) fprintf(stderr,msg); exit(1); Si noti lo sfruttamento della semantica di fread al fine di ottenere "gratis" il numero effettivo di elementi letti come valore di ritorno (salvato in n), fornendo nel contempo MAX come numero massimo di elementi da leggere in modo da evitare di acquisire più elementi di quanti il vettore ne possa contenere. (6(5&,=,2Qƒ Scrivere un programma che stampi una lista di interi su video, stampante o file a seconda del parametro passato come argomento Per ottenere lo scopo, la soluzione più semplice è adottare la primitiva fprintf, passandole come FILE* una variabile opportunamente settata, in precedenza, o uno dei file standard (stdout o stdprn rispettivamente per video e stampante) o il risultato della fopen nel caso di un file su disco. Per comodità, si può stabilire che il default sia lo schermo (e quindi la variabile FILE* valga inizialmente stdout). Il programma assume quindi un aspetto come il seguente: 28
23 #include <stdio.h> main(int argc, char **argv) FILE *out=stdout; char lastch= \n ; int diskfile=0, i, n; int tab[]= 12, 54, -34, 65, 978, 19, 32 ; if (--argc>0) if (strcmp(argv[argc], "prn:")==0) out=stdprn; lastch= \f ; else out=fopen(argv[argc], "a"); diskfile=1; n=sizeof(tab)/sizeof(tab[0]); for(i=0;i<n;i++) fprintf(out, "%d%c", tab[i], (i==n-1)? lastch : \t ); if (diskfile) fclose(out); return 0; Dal punto di vista pratico, il programma emette tutto il proprio output sul file out, indipendentemente da FRVD esso realmente sia. A sua volta, il file out, inizializzato per default a stdout, viene settato su stdprn se l argomento passato vale "prn:", o al file su disco corrispondente al nome fornito, se è presente un qualunque altro argomento. Come si è già accennato, stdprn è lo VWDQGDUG SULQWLQJ GHYLFH, aperto anch'esso automaticamente dal sistema all'inizio dell'esecuzione (come stdin, stdout e stderr). Ogni output diretto su stdprn viene, di fatto, inviato alla stampante. Analogamente, esiste anche lo stream standard stdaux, che corrisponde allo VWDQGDUGDX[LOLDU\GHYLFH, la cui funzione dipende da quale dispositivo fisico (porte seriali, o, altro) è realmente connesso al dispositivo logico "aux:". Il carattere lastch, settato per default a un QHZOLQH, viene modificato nel caso della stampante al carattere \f (form feed) per provocare un salto di pagina. Da notare, infine, ODFXUDSRVWDQHOOHYLWDUHGLFKLXGHUHSHUVHPSUHVWGRXW, dato che non c'è modo di riaprire esplicitamente uno dei file standard, una volta chiusi (le funzioni fopen e freopen, mostrata nell'esempio successivo, richiedono sempre esplicitamente, infatti, un nome di file). A questo scopo, il flag diskfile condiziona l'operazione di chiusura all'essere out un file su disco precedentemente aperto con fopen, impedendola se out risulta inizializzato a stdout (come accade ad esempio in assenza di parametri). 29
24 (6(5&,=,2Qƒ Scrivere un programma che stampi una lista di interi su video o su file a seconda del parametro passato come argomento, UHGLULJHQGRLQPRGRSHUPDQHQWHVWGRXW sul file. In questo caso, è necessaria una primitiva che riutilizzi un descrittore già attivo (predisposto in precedenza da fopen) per "agganciargli" un diverso file fisico. Questo servizio viene svolto in C dalla primitiva di libreria freopen. Il programma può quindi presentarsi come segue: #include <stdio.h> main(int argc, char **argv) int i, n, tab[]= 12, 54, -34, 65, 978, 19, 32 ; /* ridirige permanentemente stdout: il vecchio stdout è perso */ if (--argc>0) if(iuhrshqdujy>dujf@dvwgrxw==null) SHUURU("error redirecting stdout\n"); exit(1); /* tutte le prossime printf andranno sul file */ n=sizeof(tab)/sizeof(tab[0]); for(i=0;i<n;i++) printf("%d%c", tab[i], (i==n-1)? '\n' : '\t'); fclose(stdout); return 0; In effetti, la funzione freopen è simile alla fopen, da cui si distingue perché ULDSUH un file anziché aprirlo ex novo. Per questo, richiede come parametro il nome di un FILE* già esistente, che viene chiuso e riassociato al nuovo file. Da notare anche la funzione perror, che stampa un messaggio d'errore sullo standard error. Più esattamente: perror(msg) equivale a: fprintf(stderr, "%s: %s\n", msg, errmsg) dove errmsg è il messaggio d'errore standard, definito dall'implementazione, relativo all'errore attualmente indicato dalla variabile di sistema errno (error number). (6(5&,=,2Qƒ Scrivere una prima versione semplificata del preprocessore C che effettui l'intercettazione delle #define e costruisca la tabella delle sostituzioni. 30
25 In pratica, si richiede di analizzare il testo di un programma C, intercettando le righe che iniziano con #define, e catturando le due stringhe successive. Per semplicità, in questa versione non si considereranno le #define relative alle macro: quindi non saranno trattati testi da sostituire contenenti spazi o altri separatori, né le definizioni su più righe (che richiederebbero di gestire il carattere \ ). Sotto queste ipotesi, la soluzione può consistere nel leggere ULJDSHUULJD, tramite fgets, il testo del programma, analizzando poi ogni singola riga con sscanf al fine di intercettare le #define. In questo caso, la sscanf stessa può estrarre le due stringhe rappresentanti rispettivamente il testo da cercare e quello da sostituire, riempiendo una riga dell'apposita tabella. Al termine, la stampa della suddetta tabella (organizzata in forma di vettore di strutture) darà il quadro della situazione. Un esempio di programma che realizza tutto questo è riportato qui di seguito. #include <stdio.h> #include <string.h> #define MAXNAME_LEN 30 #define MAXVALUE_LEN 50 typedef struct char name[maxname_len]; char value[maxvalue_len]; tab_entry; #define MAXDEFS 100 #define MAXLINE_LEN 132 static tab_entry tab[maxdefs]; static int tabptr = 0; main(int argc, char **argv) FILE *source; char line[maxline_len]; void get_def(char *line); void print_tab(char *); if(argc<2) fprintf(stderr,"sintassi: DEFS <nomefile.c>\n"); exit(1); else if ((source=fopen(argv[1],"r"))==null) fprintf(stderr,"file sorgente non trovato\n"); exit(2); while (!feof(source)) fgets(line, MAXLINE_LEN, source); if (strncmp(line, "#define", 7)==0) get_def(line); fclose(source); print_tab(argv[1]); return 0; void get_def(char *line) 31
26 if (tabptr<maxdefs) sscanf(line, "#define %s%s", tab[tabptr].name, tab[tabptr].value); tabptr++; else fprintf(stderr, "\nmassimo numero di definizioni trattabili" " superato\n"); exit(3); void print_tab(char *file) int i; printf("\n\n File: %s", file); printf("\n " " "); printf("\n COSTANTE SIMBOLICA " " DEFINIZIONE "); printf("\n " " "); for(i=0; i<tabptr; i++) printf("\n *s *s ", MAXNAME_LEN-2, tab[i].name, MAXVALUE_LEN-10, tab[i].value ); printf("\n " " "); printf("\n\n"); return; Il vettore tab è un vettore (static, e perciò invisibile fuori da questo file) di strutture, ognuna fatta da due stringhe di caratteri denominate name e value. Esso è gestito dall'indice tabptr, pure static e inizializzato a zero. Come previsto in fase di specifica, il ciclo principale del programma legge una a una le righe del file d'ingresso, e verifica se iniziano con la sequenza "#define": se sì, chiama get_def per estrarre la relativa definizione. Alla fine di tutto, print_tab stampa (per ora sullo standard output) la tabella delle sostituzioni. Il cuore della funzione di analisi, get_def, è la funzione standard di libreria sscanf, che è del tutto identica a scanf, tranne per il fatto di prendere il proprio input non dallo standard input (o da un file come fscanf), ma dalla stringa passatale (tipicamente, come in questo caso, precedentemente letta con gets o funzioni similari). Questo approccio risulta utile ogni volta che è necessario sottoporre una linea a diverse analisi (anche consecutive), spesso senza sapere in partenza quale di queste analisi avrà esito positivo. La specifica di conversione %*s usata nella printf della funzione print_tab presenta il massimo grado di parametrizzazione del formato: significa che ODPSLH]]D GHO FDPSR disponibile per stampare s non è costante e nota a priori, ma viene fornita volta per volta nella lista degli argomenti. In questo caso, essa vale, rispettivamente per le due stringhe, MAXNAME_LEN-2 e MAXVALUE_LEN
27 La parola chiave typedef definisce un nuovo tipo, in questo caso chiamato tab_entry, sulla base del prototipo fornitole. Dal momento in cui essa compare, il nuovo tipo può essere usato in ogni luogo in cui possono essere usati i tipi predefiniti del linguaggio (come int, float, char...). La typedef differisce da una #define, in quanto la prima è una keyword del linguaggio C, mentre la seconda è soltanto una direttiva al preprocessore, i cui effetti sono certamente già completati SULPD che la compilazione vera e propria abbia inizio. Da notare che non è indifferente che il nome tab_entry sia posto prima o dopo le parentesi graffe che delimitano la struttura: nel primo caso infatti esso diviene il nome di un tipo, mentre nel secondo è soltanto un'abbreviazione per la successiva elencazione dei membri (tra parentesi graffe), ma non assurge al livello di tipo. Il riquadro sottostante illustra queste due possibilità. typedef struct char name[maxname_len]; char value[maxvalue_len]; tab_entry; struct tab_entry char name[maxname_len]; char value[maxvalue_len]; ; Pertanto, mentre nel primo caso tab_entry può essere usato come nome del tipo di una variabile in fase di dichiarazione/definizione della stessa, nel secondo il nome del tipo è struct tab_entry. (6(5&,=,2Qƒ Scrivere una programma che consenta di vedere la stessa area di memoria o come un numero intero o come sequenza di bytes, utilizzando il costrutto union. Si tratta semplicemente di definire una union avente come campi un int e un vettore di bytes (cioè di unsigned char) lungo quanto un int: la selezione dell'uno o dell'altro consentirà quindi in modo naturale di "commutare", per così dire, fra le due rappresentazioni. Un programma che fa ciò è presentato qui di seguito. 33
28 #include <stdio.h> typedef union int val; unsigned char rep[sizeof(int)]; integer; main(void) integer i; void print_internal_representation(integer); i.val=34; printf("\nvalore: %d\n", i.val); print_internal_representation(i); printf("\nindirizzo di i.val:\t%ld\n", &i.val); printf("\nindirizzo di i.rep:\t%ld\n", &i.rep); return 0; void print_internal_representation(integer i) int k; printf("\nrappresentazione interna:\n\n"); for(k=0; k<sizeof(integer); k++) printf("%4d%c", i.rep[k], (k==sizeof(integer)-1? \n : \t ) ); Sebbene printf e print_internal_representation stampino i valori di due variabili all'apparenza diverse, ciò che viene visualizzato da quest'ultima dipende dall'assegnamento precedente a i.val. In effetti, una union non è altro che una collezione di variabili aventi tutte il medesimo indirizzo di partenza, come, peraltro, si può facilmente verificare. (6(5&,=,2Qƒ Scrivere un programma che allochi dinamicamente spazio per al più 10 stringhe di al più 80 caratteri. Finora, tutte le variabili viste erano o variabili globali (definite e allocate staticamente) o variabili locali a una funzione (definite staticamente e allocate/deallocate automaticamente a run-time). Tuttavia, le variabili definite staticamente hanno dei limiti, primo fra tutti quello di dover conoscere a priori la loro dimensione massima, che le rende inadatte a certi campi di applicazione. Per ovviare a questo problema, il C mette a disposizione due primitive, malloc e free, per allocare e deallocare esplicitamente delle nuove variabili a tempo d'esecuzione. Più precisamente, la primitiva malloc chiede al sistema un'area ampia Qbytes, e ne restituisce l'indirizzo sotto forma di void* (ossia di puntatore generico), mentre la primitiva free libera l'area associata a quell'indirizzo (non è necessario specificare nuovamente la dimensione in quanto provvede il sistema a tenere traccia di queste informazioni). 34
29 Per risolvere il problema dato occorre dunque predisporre un ciclo che legga dieci stringhe da tastiera, ognuna lunga al più 80 caratteri, e le ricopi ognuna in una nuova area di memoria, chiesta da malloc al sistema volta per volta sulla base della OXQJKH]]D HIIHWWLYD della stringa letta. I puntatori a tali aree di memoria potranno essere memorizzati in un apposito YHWWRUHGLSXQWDWRUL. Un possibile programma che realizzi tutto questo è il seguente: #include <stdio.h> #include <stdlib.h> /* contiene prototipi di malloc e free */ #include <string.h> main(void) char line[81]; char *s[10]; int i; for(i=0; i<10; i++) gets(line); s[i]= (char *) malloc(strlen(line)+1); strcpy(s[i], line); for(i=0; i<10; i++) printf("\n%s", s[i]); free(s[i]); return 0; E' importante notare che malloc, quando riserva memoria, non ha alcuna informazione sul tipo di uso che ne verrà fatto; per questo, essa si limita a restituire OLQGLUL]]R di quell'area, sotto forma di void* che, com'è noto, proprio per questo non è dereferenziabile. Per utilizzare praticamente l'area riservata è quindi LQGLVSHQVDELOH usare il cast (char*)per convertire esplicitamente il puro indirizzo in un puntatore del tipo corrispondente all'uso che si fa di tale area di memoria. Dal punto di vista pratico, il programma è completato da un secondo ciclo che rilegge e visualizza le stringhe precedentemente immesse, deallocando quindi l'area di memoria corrispondente tramite free. L'area liberata potrà essere nuovamente utilizzata in successive richieste a malloc, nei tempi e modi stabiliti della politica di gestione della memoria adottata dal compilatore. Per finire, osserviamo che, se tutto si limitasse a questo, al di là della maggior generalità ottenuta nel trattamento stringhe (e in generale di vettori) non si vedrebbe la reale utilità delle primitive di allocazione dinamica, in quanto, pur potendo allocare nuove variabili dinamicamente, e quindi in modo svincolato dalle definizioni statiche, di fatto bisognerebbe però avere definito staticamente il puntatore da utilizzare con la malloc, il che riporterebbe il problema al punto di partenza. 35
FONDAMENTI DI INFORMATICA II
FONDAMENTI DI INFORMATICA II ESERCITAZIONE n 1: Linguaggio C 1. Macro ESERCIZIO n 1 Definire una macro ODD(i) che restituisca TRUE o FALSE a seconda che il valore intero passato sia rispettivamente dispari
DettagliFONDAMENTI DI INFORMATICA II
FONDAMENTI DI INFORMATICA II ESERCITAZIONE n 2: Linguaggio C 1. Filtri su file + Stringhe e Vettori (parte 2) ESERCIZIO n 13 Realizzare un programma che ricopi il file dato come primo argomento nel file
DettagliGestione dei file in C
Gestione dei file in C Fondamenti di Informatica Che cos è un file e a cosa serve? Memoria di massa vs memoria centrale q La memoria di massa (disco fisso) è un dispositivo di memorizzazione generalmente
DettagliEsercitazioni di Fondamenti di Informatica - Lez. 9 4/12/2018
Esercitazioni di Fondamenti di Informatica - Lez. 9 /1/018 Esercizi sulla gestione dei file in C Il codice degli esercizi e contenuto nella cartella parte1 1. Creare una funzione C che legga tutto cio
DettagliGestione dei file in C
Gestione dei file in C Fondamenti di Informatica Che cos è un file e a cosa serve? Memoria di massa vs memoria centrale q La memoria di massa (disco fisso) è un dispositivo di memorizzazione generalmente
DettagliDati due punti sul piano calcolare la loro distanza
Introduzione al C Primo esempio in C Dati due punti sul piano calcolare la loro distanza Soluzione: la distanza fra due punti si calcola secondo il teorema di Pitagora, con la formula: y Distanza = (lato12
DettagliLaboratorio di Programmazione
Laboratorio di Programmazione (Laurea triennale in matematica) Lezione 26 Sommario: Gestione dei file in C. File ad accesso sequenziale: apertura, chiusura, lettura e scrittura. File binari. Gestione files
DettagliFile binari e file di testo
I file File binari e file di testo distinzione tra file binari file di testo si possono usare funzioni diverse per la gestione di tipi di file diversi Programmazione Gestione dei file 2 File binari e file
DettagliIl linguaggio C. Breve panoramica su stdio.h
Il linguaggio C Breve panoramica su stdio.h 1 Input/Output: stdio.h Contiene definizioni di costanti legate all I/O es. EOF (end of file) #define EOF (-1) valore restituito alla fine di un file Contiene
DettagliI File. Consente una memorizzazione persistente dei dati, non limitata dalle dimensioni della memoria centarle.
I File Il file e l unita logica di memorizzazione dei dati su memoria di massa. Consente una memorizzazione persistente dei dati, non limitata dalle dimensioni della memoria centarle. Generalmente un file
DettagliStreams e disk files
Streams e disk files Streams Un canale è una sequenza di byte di dati Sorgente o destinazione di dati che possono essere associati ad un disco o ad altre periferiche Due tipi di stream: Testo: sequenza
DettagliInformatica per Statistica Riassunto della lezioni del 14/11/2012 e 16/11/2012
Informatica per Statistica Riassunto della lezioni del 14/11/2012 e 16/11/2012 Igor Melatti Cenni sui puntatori in C Per ogni variabile normale dichiarata, il compilatore riserva una precisa quantità di
DettagliLe strutture. Una struttura C è una collezione di variabili di uno o più tipi, raggruppate sotto un nome comune.
Le strutture Una struttura C è una collezione di variabili di uno o più tipi, raggruppate sotto un nome comune. Dichiarazione di una struttura: struct point { int x; int y; }; La dichiarazione di una struttura
DettagliInput/output da file I/O ANSI e I/O UNIX FLUSSI E FILE FLUSSI FLUSSI di TESTO FLUSSI BINARI FILE
Input/output da file Il linguaggio C non contiene istruzioni di I/O, in quanto tali operazioni vengono eseguite tramite funzioni di libreria standard. Questo approccio rende estremamente flessibile e potente
DettagliLa gestione dell'errore
La gestione dell'errore Esiste una variabile globale intera, definita nell'header errno.h che viene settata nel caso in cui una chiamata di sistema non possa eseguire correttamente il suo compito. Tale
DettagliIntroduzione al C. Stream e disk file
Introduzione al C Stream e disk file Stream Un canale è una sequenza di byte di dati Sorgente o destinazione di dati che possono essere associati ad un disco o ad altre periferiche Due tipi di stream:
Dettaglifile fisico file logico
I files Un file è una struttura di dati residente su una memoria di massa (file fisico) Un programma vede un file come una sequenza di bytes (file logico) Generalizzando, un file può comprendere qualsiasi
DettagliGestione dei File. Credits Prof. Campi
Gestione dei File Credits Prof. Campi 1 Perché i file? Sono strutture dati persistenti Sono solitamente memorizzati sui dischi Si usano dall'interno dei programmi Realizzano la persistenza dei dati cioè
DettagliCaratteri e stringhe
Caratteri e stringhe Caratteri Dato che un computer può memorizzare esclusivamente sequenze di bit, per memorizzare un carattere (e quindi testi) è necessario stabilire una convenzione che associa a un
DettagliI files in C. A cura del prof. Ghezzi
I files in C A cura del prof. Ghezzi 1 Perchè i files? Realizzano la persistenza dei dati dati che sopravvivono all esecuzione del programma Sono delle strutture di dati sequenziali 2 Files e S.O. I files
Dettaglipuntatori Lab. Calc. AA 2006/07 1
puntatori Lab. Calc. AA 2006/07 1 Attributi di un oggetto nome o identificatore; tipo; valore (o valori); indirizzo; Lab. Calc. AA 2006/07 2 Indirizzo Consideriamo la dichiarazione con inizializzazione:
DettagliLa copia di un file. contare di quanti caratteri sia composto il file (e quindi determinare la dimensione del file di origine)
La copia di un file Nell'esercizio di oggi cerchiamo di implementare un programma che permetta di effettuare la copia di un file di testo. L'operazione di copia, oltre a permettere di creare un nuovo file
DettagliCaratteri e stringhe
Caratteri e stringhe Caratteri Dato che un computer può memorizzare esclusivamente sequenze di bit, per memorizzare un carattere (e quindi testi) è necessario stabilire una convenzione che associa a un
DettagliEsercitazioni di Fondamenti di Informatica - Lez. 8 27/11/2018
Esercitazioni di Fondamenti di Informatica - Lez. 8 27/11/2018 Esercizi sull allocazione dinamica della memoria in C Il codice di questi esercizi é contenuto nella cartella parte1 1. Implementare una lista
DettagliEsercizio 2 (punti 7) Dato il seguente programma C: #include <stdio.h> int swap(int * nome, int length);
Fondamenti di Informatica L-A (A.A. 004/005) - Ingegneria Informatica Prof.ssa Mello & Prof. Bellavista I Prova Intermedia del 11/11/004 - durata h - COMPITO B Esercizio 1 (punti 1) Una associazione di
DettagliGestione dei file. Linguaggio ANSI C Input/Output - 13
Gestione dei file Necessità di persistenza dei file archiviare i file su memoria di massa. Un file è una astrazione fornita dal sistema operativo, il cui scopo è consentire la memorizzazione di informazioni
DettagliSistemi Operativi (M. Cesati)
Sistemi Operativi (M. Cesati) Compito scritto del 19 febbraio 2013 Nome: Matricola: Corso di laurea: Cognome: Crediti da conseguire: 5 6 9 Scrivere i dati richiesti in stampatello. Al termine consegnare
DettagliEsercizio 1 (15 punti)
Esercizio 1 (15 punti) Corsi di laurea in Ingegnera Elettronica e Ingegneria Gestionale Fondamenti di Programmazione / Fondamenti di Informatica I Prova scritta del 16 giugno 2017 Il noto gioco MasterMind
DettagliInput / Output. Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard
Stdio.h Input / Output Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard Sia i file che i dispositivi (tastiera, schermo...) sono visti come
DettagliGestione dei files. Prof. Francesco Accarino IIS Altiero Spinelli Sesto San Giovanni Via Leopardi 132
Gestione dei files Prof. Francesco Accarino IIS Altiero Spinelli Sesto San Giovanni Via Leopardi 132 Struttura del disco fisso Un disco fisso è composto da una serie di piatti sovrapposti Ogni piatto è
DettagliLezione 8: Stringhe ed array multidimensionali
Lezione 8: Stringhe ed array multidimensionali Vittorio Scarano Corso di Laurea in Informatica Università degli Studi di Salerno Struttura della lezione AVVISO: la lezione di laboratorio di 28/5 non si
DettagliInput/Output. Lettura e scrittura Caratteri e Stringhe: Terminale e file. Input/output. caratteri stringhe formattato ascii binari
Input/Output Lettura e scrittura Caratteri e Stringhe: Terminale e file Input/output console file caratteri stringhe formattato ascii binari Linguaggio ANSI C Input/Output - 1 La libreria standard del
DettagliFUNZIONI. attribuire un nome ad un insieme di istruzioni parametrizzare l esecuzione del codice
Funzioni FUNZIONI Spesso può essere utile avere la possibilità di costruire nuove istruzioni che risolvono parti specifiche di un problema Una funzione permette di attribuire un nome ad un insieme di istruzioni
DettagliEsercitazione 11. Liste semplici
Esercitazione 11 Liste semplici Liste semplici (o lineari) Una lista semplice (o lineare) è una successione di elementi omogenei che occupano in memoria una posizione qualsiasi. Ciascun elemento contiene
DettagliIngresso ed Uscita in C. Informatica 1 / 15
Ingresso ed Uscita in C Informatica 1 / 15 Input e Output in C Linguaggio C: progettato per essere semplice e con poche istruzioni Non esistono istruzioni di ingresso / uscita (I/O)!!! Ingresso ed uscita
DettagliLezione 11: Liste a Puntatori e Input/Output
Lezione 11: Liste a Puntatori e Input/Output Vittorio Scarano Laboratorio di Informatica I Corso di Laurea in Informatica Università degli Studi di Salerno Struttura della lezione Richiamo: strutture (struct)
DettagliPrimo programma in C
Primo programma in C Struttura minima di un file C Applicazioni C in modo console Struttura del programma Commenti Direttive #include Definizione di variabili Corpo del main 2 Struttura minima di un file
DettagliLe strutture. Una struttura C è una collezione di variabili di uno o più tipi, raggruppate sotto un nome comune.
Le strutture Una struttura C è una collezione di variabili di uno o più tipi, raggruppate sotto un nome comune. Dichiarazione di una struttura: struct point { int x; int y; }; La dichiarazione di una struttura
DettagliUn file è un astrazione di memorizzazione di dimensione potenzialmente illimitata (ma non infinita), ad accesso sequenziale.
IL CONCETTO DI FILE Un file è una astrazione fornita dal sistema operativo, il cui scopo è consentire la memorizzazione di informazioni su memoria di massa. Concettualmente, un file è una sequenza di registrazioni
DettagliElementi lessicali. Lezione 4. La parole chiave. Elementi lessicali. Elementi lessicali e espressioni logiche. Linguaggi di Programmazione I
Lezione 4 Elementi lessicali e espressioni logiche Matricole 2-3 Elementi lessicali il linguaggio C ha un suo vocabolario di base i cui elementi sono detti token esistono 6 tipi di token: parole chiave
DettagliI/O Avanzato in C: scheda riassuntiva
Funzione sscanf r = sscanf(str, "formato", &variabili ) ; str Stringa da cui acquisire i dati "formato" Sequenza dei campi da leggere ("%d", "%s",... ) &variabili Variabili nelle quali depositare i valori
DettagliIndice. La gestione dei file in C e gli stream. Apertura e chiusura di un file. Operazioni sui file. Accesso sequenziale e non sequenziale
File in C Indice La gestione dei file in C e gli stream Apertura e chiusura di un file Operazioni sui file Accesso sequenziale e non sequenziale Gestione dei file in C In C all interno della standard library
DettagliGESTIONE DEI FILE IN C. Docente: Giorgio Giacinto AA 2008/2009
Università degli Studi di Cagliari Corso di Laurea Specialistica in Ingegneria per l Ambiente ed il Territorio Corso di Laurea Specialistica in Ingegneria Civile - Strutture FONDAMENTI DI INFORMATICA 2
DettagliInput / Output. Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard
Stdio.h Input / Output Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard Sia i file che i dispositivi (tastiera, schermo...) sono visti come
DettagliIl linguaggio C. Puntatori e dintorni
Il linguaggio C Puntatori e dintorni 1 Puntatori : idea di base In C è possibile conoscere e denotare l indirizzo della cella di memoria in cui è memorizzata una variabile (il puntatore) es : int a = 50;
DettagliL'Allocazione Dinamica della Memoria nel linguaggio C
L'Allocazione Dinamica della Memoria nel linguaggio C Prof. Rio Chierego riochierego@libero.it http://www.riochierego.it/informatica.htm Sommario Questo documento tratta l'allocazione dinamica della memoria
DettagliIntroduzione alla programmazione in linguaggio C
Introduzione alla programmazione in linguaggio C Il primo programma in C commento Header della libreria Funzione principale Ogni istruzione in C va terminata con un ; Corso di Informatica AA. 2007-2008
DettagliUna stringa di caratteri in C è un array di caratteri terminato dal carattere '\0' a p e \0
STRINGHE: ARRAY DI CARATTERI Una stringa di caratteri in C è un array di caratteri terminato dal carattere '\0' s a p e \0 0 1 2 3 Un vettore di N caratteri può dunque ospitare stringhe lunghe al più N-1
DettagliCorso di Informatica A.A
Corso di Informatica A.A. 2009-2010 Lezione 13 Corso di Informatica 2009-2010 Lezione 13 1 Input/output da file Stream Si definisce stream una sequenza di bytes di dati che può essere in ingresso (input
DettagliAllocazione dinamica memoria
Allocazione dinamica memoria Marco Casazza 11/12/2017 1 Esercizio 1 1 /* 2 Creare una lista bidirezionale di interi, ovvero una lista 3 che permette lo scorrimento in entrambe le direzioni ( dal primo
DettagliGestione dei File C. Generalità
Gestione dei File C 1 Generalità Il linguaggio C non contiene alcuna istruzione di Input/Output. Tali operazioni vengono svolte mediante chiamate a funzioni definite nella libreria standard contenute nel
DettagliCompendio sottoinsieme del C++ a comune col C. (Libreria standard, Input/Output, Costanti, Dichiarazioni e typedef, Memoria Dinamica)
Compendio sottoinsieme del C++ a comune col C (Libreria standard, Input/Output, Costanti, Dichiarazioni e typedef, Memoria Dinamica) Librerie 1/2 Il solo insieme di istruzioni di un linguaggio di programmazione
DettagliAllocazione dinamica della memoria
Allocazione dinamica della memoria Allocazione statica: limiti Per quanto sappiamo finora, in C le variabili sono sempre dichiarate staticamente la loro esistenza deve essere prevista e dichiarata a priori
DettagliGestione dei file. Stefano Ferrari. Università degli Studi di Milano Programmazione. anno accademico
Gestione dei file Stefano Ferrari Università degli Studi di Milano stefano.ferrari@unimi.it Programmazione anno accademico 2016 2017 Gli stream Si dice stream qualsiasi sorgente di dati in ingresso e qualsiasi
DettagliFunzioni e Ricorsione
Funzioni e Ricorsione La ricorsione consiste nella possibilità di definire una funzione in termini di se stessa Nel codice di una funzione ricorsiva compare una (o più di una) chiamata alla funzione stessa
DettagliStruttura dei programmi e Variabili
Linguaggio C Struttura dei programmi e Variabili 1 La struttura generale dei programmi! Struttura del programma: Direttive Funzioni Dichiarazioni Istruzioni Di assegnamento direttive Di ingresso e uscita
DettagliAllocazione dinamica della memoria
Allocazione dinamica della memoria Violetta Lonati Università degli studi di Milano Dipartimento di Informatica Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica Violetta Lonati
DettagliFondamenti di Informatica T. Linguaggio C: i puntatori
Linguaggio C: i puntatori Il puntatore E` un tipo di dato, che consente di rappresentare gli indirizzi delle variabili allocate in memoria. Dominio: Il dominio di una variabile di tipo puntatore è un insieme
DettagliI file possono essere manipolati (aperti, letti, scritti ) all interno di programmi C. dischi nastri cd
Per poter mantenere disponibili i dati tra le diverse esecuzioni di un programma (persistenza dei dati) è necessario poterli archiviare su memoria di massa. dischi nastri cd GESTIONE DEI FILE I file possono
DettagliCorso di Fondamenti di Informatica Ingegneria delle Comunicazioni BCOR Ingegneria Elettronica BELR Introduzione al C Unità 9 File
Corso di Fondamenti di Informatica Ingegneria delle Comunicazioni BCOR Ingegneria Elettronica BELR Introduzione al C Unità 9 File D. Bloisi, A. Pennisi, S. Peluso, S. Salza, C. Ciccotelli Sommario Input/Output
Dettagli#include <stdio.h> /* l esecuzione comincia dalla funzione main */ int main()
Primi passi Il mio primo programma #include /* l esecuzione comincia dalla funzione main */ int main() { printf( Hello World!\n" ); return 0; /* il programma termina con successo */ } /* fine
DettagliFiles in C endofile
Files in C Il C vede i file semplicemente come un flusso (stream) sequenziale di bytes terminati da un marcatore speciale che determina la fine del file (end-of-file). 0 1 2 3 4... endofile A differenza
DettagliDichiarazioni e tipi predefiniti nel linguaggio C
Politecnico di Milano Dichiarazioni e tipi predefiniti nel linguaggio C Variabili, costanti, tipi semplici, conversioni di tipo. Premessa Programmi provati sul compilatore Borland C++ 1.0 Altri compilatori:
DettagliEsercizi. I File ed il C
Politecnico di Milano Esercizi File di testo,, file binari I File ed il C Insieme omogeneo di dati, memorizzato su disco e caratterizzato da un nome La lunghezza di un file non è fissata a priori Astrazione
DettagliLaboratorio di Informatica L-A 1
Funzioni e Ricorsione La ricorsione consiste nella possibilità di definire una funzione in termini di se stessa È basata sul principio di induzione matematica: se una proprietà P vale per n=n 0 e si può
DettagliLinguaggio C: i file
Dipartimento di Elettronica ed Informazione Politecnico di Milano Informatica A - GES Prof. Plebani A.A. 2006/2007 Linguaggio C: i file La presente dispensa e da utilizzarsi ai soli fini didattici previa
DettagliLinguaggio C. Vettori, Puntatori e Funzioni Stringhe. Università degli Studi di Brescia. Prof. Massimiliano Giacomin
Linguaggio C Vettori, Puntatori e Funzioni Stringhe Università degli Studi di Brescia Prof. Massimiliano Giacomin SCHEMA DELLA LEZIONE RELAZIONE TRA VETTORI E PUNTATORI (e le stringhe letterali come caso
Dettagli#include <stdio.h> main() { - 1 -
Un primo esempio di programma Ogni programma C deve contenere una funzione speciale chiamata main che indica il punto in cui inizia l esecuzione del programma. La funzione main è unica all interno di ogni
DettagliC: panoramica. Violetta Lonati
C: panoramica Violetta Lonati Università degli studi di Milano Dipartimento di Scienze dell Informazione Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica AA 2009/2010 Violetta Lonati
DettagliLezione 19 e Allocazione dinamica della memoria - Direttive al preprocessore - Libreria standard - Gestione delle stringhe
Lezione 19 e 20 - Allocazione dinamica della memoria - Direttive al preprocessore - Libreria standard - Gestione delle stringhe Valentina Ciriani (2005-2008) Laboratorio di programmazione Valentina Ciriani
DettagliLab 12 Allocazione dinamica della memoria
Fondamenti di Informatica e Laboratorio T-AB Ingegneria Elettronica e Telecomunicazioni Lab 12 Allocazione dinamica della memoria Lab17 1 Esercizio 0 Un file di testo di nome bambini.txt, contiene informazioni
DettagliSistemi Operativi (M. Cesati)
Sistemi Operativi (M. Cesati) Compito scritto del 17 febbraio 2014 Nome: Matricola: Corso di laurea: Cognome: Crediti da conseguire: 5 6 9 Scrivere i dati richiesti in stampatello. Al termine consegnare
Dettagligiapresente( ) leggi( ) char * strstr(char * cs, char * ct) NULL
Materiale di ausilio utilizzabile durante l appello: tutto il materiale è a disposizione, inclusi libri, lucidi, appunti, esercizi svolti e siti Web ad accesso consentito in Lab06. L utilizzo di meorie
DettagliInput / Output. Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard
Stdio.h Input / Output Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard Sia i file che i dispositivi (tastiera, schermo...) sono visti come
DettagliI/O da tastiera + Alessandra Giordani Lunedì 2 maggio
I/O da tastiera + costrutti while e if Alessandra Giordani agiordani@disi.unitn.it Lunedì 2 maggio 2011 http://disi.unitn.it/~agiordani/ Ripasso funzione printf() Usata per stampare il contenuto di una
DettagliEsercitazione di Reti degli elaboratori
Esercitazione di Prof.ssa Chiara Petrioli Christian Cardia, Gabriele Saturni Cosa vedremo in questa lezione? Gli Array Gli array multidimensionali Le stringhe I puntatori Esercizi Pagina 1 Gli Array Definizione
DettagliTipi di dati strutturati e Linguaggio C. Record o strutture Il costruttore struct in C
Tipi di dati strutturati e Linguaggio C Record o strutture Il costruttore struct in C Dati strutturati Record Un record o struttura è una struttura dati ottenuta aggregando elementi di tipo diverso che
DettagliInformatica per Statistica Riassunto della lezione del 21/10/2011
Informatica per Statistica Riassunto della lezione del 1/10/011 Igor Melatti Costrutti del linguaggio C: ripasso Si consideri il programma C alla Figura 1 ci sono due dichiarazioni di funzioni, insertion
DettagliStringhe e allocazione dinamica della memoria
Stringhe e allocazione dinamica della memoria Esercizio Scrivere un programma strings.c che legge da standard input una sequenza di parole separate da uno o più spazi, e stampa le parole lette, una per
DettagliIntroduzione al C. Lezione 4 Allocazione dinamica della memoria. Rossano Venturini. Pagina web del corso
Introduzione al C Lezione 4 Allocazione dinamica della memoria Rossano Venturini rossano@di.unipi.it Pagina web del corso http://didawiki.cli.di.unipi.it/doku.php/informatica/all-b/start Lezioni di ripasso
DettagliUniversità degli Studi di Milano
Università degli Studi di Milano Corso di Laurea in Sicurezza dei Sistemi e delle Reti Informatiche Lezione 3 Input/Output elementare. Operatori, espressioni e istruzioni FABIO SCOTTI Laboratorio di programmazione
DettagliPassaggio dei parametri
Passaggio dei parametri Per valore Il valore viene copiato dall environment esterno all environment della funzione o procedura Cambiamenti dei parametri così passati non si riflettono sull environment
DettagliIngresso ed Uscita in C. Luca Abeni
Ingresso ed Uscita in C Luca Abeni Input e Output in C Linguaggio C: progettato per essere semplice e con poche istruzioni Non esistono neanche istruzioni di ingresso / uscita (I/O)!!! Ingresso ed uscita
DettagliRicorsione. La ricorsione consiste nella possibilità di definire una funzione in termini di se stessa
Funzioni e Ricorsione La ricorsione consiste nella possibilità di definire una funzione in termini di se stessa È basata sul principio di induzione matematica: se una proprietà P vale per n=n 0 e si può
DettagliSono file di caratteri, organizzati in linee. Ogni linea è terminata da un marcatore di fine linea (newline, carattere \n ).
I file Il file è l unità logica di memorizzazione dei dati su memoria di massa. Consente una memorizzazione persistente dei dati, non limitata dalle dimensioni della memoria centrale. Generalmente un file
DettagliIntroduzione al linguaggio C Primi programmi
Introduzione al linguaggio C Primi programmi Violetta Lonati Università degli studi di Milano Dipartimento di Scienze dell Informazione Laboratorio di algoritmi e strutture dati Corso di laurea in Informatica
DettagliInput/Output su disco
Input/Output su disco In C, la gestione dei dispositivi di lettura (tastiera, file su disco,...) e scrittura (monitor, file su disco, stampante,...) viene effettuata mediante canali di comunicazione. Tali
DettagliGESTIONE DEI FILE. File come tipo di dati
GESTIONE DEI FILE File come tipo di dati Nel linguaggio C, i file vengono trattati come un tipo di dati derivato, cioè ottenuto dai tipi elementari esistenti. In pratica, quando si apre e si gestisce un
DettagliCostanti e Variabili
Parte 3 Costanti e Variabili Identificatori Un identificatore è un nome che viene associato a diverse entità (costanti, tipi, variabili, funzioni, ecc.) e serve ad identificare la particolare entità Gli
DettagliINPUT E OUTPUT DI VALORI NUMERICI
INPUT E OUTPUT DI VALORI NUMERICI FUNZIONI DI I/O PER NUMERI Per utilizzare le funzioni di Input/Output bisogna includere il file di intestazione (header file) denominato contiene la
DettagliLinguaggio C. Generalità sulle Funzioni. Variabili locali e globali. Passaggio di parametri per valore.
Linguaggio C Generalità sulle Funzioni. Variabili locali e globali. Passaggio di parametri per valore. 1 Funzioni Generalizzazione del concetto di funzione algebrica: legge che associa a valori delle variabili
DettagliLo scopo. Il primo esperimento. Soluzione informale. Le variabili
Lo scopo 2 Il primo esperimento Si vuole scrivere un programma in linguaggio C che chieda all utente di introdurre da tastiera due numeri interi e visualizzi il valore della loro somma sul video Ver. 2.4
Dettagli