Professional Documents
Culture Documents
Premessa
Quanto avete sotto gli occhi non un manuale di linguaggio C, non serve per imparare a
programmare, non nemmeno un eserciziario completo ma semplicemente un insieme scelto e
annotato di esempi tratti dalle esercitazioni o proposti nei temi desame del corso di Informatica A
di Ingegneria Gestionale. Si sono cercati di evidenziare i punti che presentano le maggiori difficolt
e sono causa di errori ricorrenti per gli studenti, le cui osservazioni e domande, anche se talvolta a
loro insaputa, hanno consentito la raccolta. Si ringraziano anticipatamente quanti vorranno
contribuire a migliorarla.
Gli esercizi, suddivisi in base allargomento, sono riportati in forma completa, ad esempio nel caso
di una funzione viene inclusa anche la codifica del modulo che la richiama. Cos se da un lato non si
evita una certa ripetitivit e monotonia, dallaltro si facilita lestrazione del singolo esercizio e
quindi la modifica e il test. La suddivisione in parte arbitraria in quanto lo stesso esempio
potrebbe spesso trovarsi in capitoli diversi. Alle soluzioni compatte ed eleganti sono state preferite
quelle pi facilmente leggibili.
Per la codifica si usato il formato di carattere courier new, siete invitati ad usarla come punto
di partenza per le vostre variazioni e sperimentazioni: ogni linguaggio, naturale o artificiale che sia,
si apprende infatti soprattutto con la pratica. opinione di chi scrive che lo studio di un linguaggio
di programmazione (nella fattispecie il C) abbia anche un valore formativo nellapprendimento di
un metodo per affrontare i problemi che si presentano nella realt lavorativa.
Concludendo questo lavoro ha un duplice scopo: di fornire una serie di programmi completi come
input per esperimenti di laboratorio e di offrire alla vista un insieme di codifiche che sono pi facili
da leggersi da un foglio (o dallo schermo di un computer) che dalla lavagna durante le esercitazioni.
Dati strutturati
Uso di typedef
Il modo, a mio parere pi chiaro, per definire un tipo di dato fornito di struttura quello di usare le
parole chiave typedef struct. Il programma che segue definisce (nel file .h) alcuni tipi di
dati e alcune funzioni, che si possono pensare come modelli informatici di note entit ed operatori
della geometria elementare del piano. Si spera che la rappresentazione di un mondo geometrico che
tutti conoscono aiuti ad eliminare anche nella sua corrispondenza informatica ogni residua
ambiguit.
Per illustrare le due possibili modalit di chiamata, le funzioni che richiedono in input un triangolo
sono state implementate in modo differente e cio ad AreaT viene passato come parametro il
puntatore alla variabile triangolo, a Baricentro invece, viene passato il valore. Perch le
funzioni possano ritornare al chiamante non solo un numero ma anche un punto bisogna che questo
sia stato definito come un tipo dato.
Chi legge invitato ad accrescere questa libreria di funzioni a suo piacimento, si consiglia per
esempio di programmare il calcolo del punto di intersezione fra due rette che presenta qualche
interesse anche dal punto di vista informatico a causa dei diversi casi particolari che si possono
presentare.
Il file .c, esempio di come si pu costruire un programma di test della codifica di una funzione,
merita qualche commento, in quanto ha una struttura che ritroveremo in seguito. Esso composto
da un ciclo while che si interrompe quando viene fornita la risposta 0, le altre risposte fanno
prendere al processo diversi rami alternativi (comando switch case), ciascuno dei quali
corrisponde allesecuzione di una diversa funzione.
2
file: piano.h
/* costante booleana */
typedef enum {falso, vero} boolean;
/* punto nel piano */
typedef struct {
float x;
float y;
} punto;
/* retta (o segmento) per due punti */
typedef struct {
punto p1;
punto p2;
} segmento;
/* triangolo (individuato dai suoi vertici)
typedef struct {
punto p1;
punto p2;
punto p3;
} triangolo;
*/
if(temp1 == temp2)
all = vero;
return all;
file: piano.c
#include <stdio.h>
#include "piano.h"
void main() {
punto punto1, punto2, punto3, punto4;
segmento segmento1;
triangolo triangolo1;
boolean allineamento;
int scelta = -1;
while(scelta != 0) {
printf("\n 0: per finire, 1: allineamento, 2: punto medio,\n 3: area
triangolo, 4: baricentro\n");
scanf("%d", &scelta);
switch(scelta) {
case 1:
printf("\nAscissa e ordinata del primo punto:
");
scanf("%f %f", &punto1.x, &punto1.y);
printf("\nAscissa e ordinata del secondo punto: ");
scanf("%f %f", &punto2.x, &punto2.y);
printf("\nAscissa e ordinata del terzo punto:
");
scanf("%f %f", &punto3.x, &punto3.y);
allineamento = Ver_All(punto1, punto2, punto3);
if(allineamento == vero)
printf("\ni punti sono allineati");
else
printf("\ni punti non sono allineati");
break;
case 2:
printf("\nAscissa e ordinata del primo punto:
");
scanf("%f %f", &segmento1.p1.x, &segmento1.p1.y);
printf("\nAscissa e ordinata del secondo punto: ");
scanf("%f %f", &segmento1.p2.x, &segmento1.p2.y);
punto4 = PuntoM(segmento1);
StampaP(punto4);
break;
case 3:
printf("\nAscissa e ordinata del
scanf("%f %f", &triangolo1.p1.x,
printf("\nAscissa e ordinata del
scanf("%f %f", &triangolo1.p2.x,
printf("\nAscissa e ordinata del
scanf("%f %f", &triangolo1.p3.x,
primo punto:
");
&triangolo1.p1.y);
secondo punto: ");
&triangolo1.p2.y);
terzo punto: ");
&triangolo1.p3.y);
primo punto:
");
&triangolo1.p1.y);
secondo punto: ");
&triangolo1.p2.y);
terzo punto: ");
&triangolo1.p3.y);
punto4 = Baricentro(triangolo1);
StampaP(punto4);
break;
}
Strutture di controllo
Uso di if condizionale
Il programma usoif illustra la condizione if, in esso listruzione scanf ritorna nella variabile
cc il numero di dati letti correttamente, basta perci inserire un valore che non sia intero per uscire
dal ciclo e terminare cos il ciclo di esecuzione del programma.
file: usoif.c
/* ordinamento di tre numeri (esempio di if) */
#include<stdio.h>
void main() {
int a, b, c, cc;
printf("\nfornisci i tre interi da ordinare):
cc = scanf("%d %d %d", &a, &b, &c);
");
while(cc == 3)
{
if((a <= b) && (b <= c))
printf("\n %d %d %d", a, b, c);
else if((a <= c) && (c <= b))
printf("\n %d %d %d", a, c, b);
else if((b <= a) && (a <= c))
printf("\n %d %d %d", b, a, c);
else if((b <= c) && (c <= a))
printf("\n %d %d %d", b, c, a);
else if((c <= a) && (a <= b))
printf("\n %d %d %d", c, a, b);
else printf("\n %d %d %d", c, b, a);
printf("\nfornisci i tre interi da ordinare):
cc = scanf("%d %d %d", &a, &b, &c);
}
}
");
1
2
3
4
5
6
7
8
9
10
1
3
6
10
15
21
28
36
45
1
4
10
20
35
56
84
120
1
5
15
35
70
126
210
1
6
21
56
126
252
1
7
28
84
210
1
8
36
120
1
9
45
1
10
Cicli equivalenti
Si possono costruire strutture logiche equivalenti con cicli for, while e do-while, la scelta
dipende dalla convenienza e dal gusto personale. Nellesempio che segue viene riscritto in tre modi
lalgoritmo di calcolo della potenza di un numero (con esponente intero).
(prima di ogni insieme di istruzioni dovranno trovarsi le seguenti dichiarazioni e istruzioni:
float base, ris;
int espon;
if(espon < 0) {
espon = -espon;
base = 1.0 / base;
}
)
Vettori e puntatori
Un puntatore un insieme di celle di memoria usate per contenere lindirizzo di un dato di un
particolare tipo, la sua definizione deve fare riferimento anche al tipo di dato.
Questo un punto molto importante da cui deriva la possibilit di unalgebra dei puntatori.
Allora (vedi il programma che segue):
char *p2; definisce p2 come indirizzo di una variabile carattere,
char *p1 = ABC; definisce p1 come indirizzo di una variabile carattere che viene
inizializzato con lindirizzo della stringa di caratteri ABC.
#include <stdio.h>
void main()
{
char *p1 = "ABC";
char *p2;
p2 = p1;
*p1 = 'X';
printf("p2: %s \n", p2); /* viene stampata la stringa XBC */
SIZE
20
**
**
**
**
**
*/
typedef struct {
int Info[MAX_SZ];
int numelem;
int first;
} Coda;
int InserInPila(Pila *p, int elm) {
int ret = NULL;
if (p->numelem < MAX_SZ) {
p->Info[p->numelem++] = elm;
ret = 1;
}
return (ret);
}
int TogliDaPila(Pila *p) {
int ret = NULL;
if (p->numelem > 0)
ret = p->Info[--(p->numelem)];
return (ret);
}
void StampaPila(Pila *p) {
int i;
printf("Pila: ");
for(i = 0; i < p->numelem; i++)
printf("%4d", p->Info[i]);
printf("\n");
}
int InserInCoda(Coda *c, int elm) {
int i, ret = NULL;
if(c->numelem < MAX_SZ) {
if(c->numelem == 0)
c->first = 0;
i = (c->first + c->numelem++) % MAX_SZ;
c->Info[i] = elm; ret = 1;
}
return (ret);
}
int TogliDaCoda(Coda *c) {
int reso = NULL;
if(c->numelem != 0) {
reso = c->Info[c->first];
c->numelem--;
c->first = (c->first + 1) % MAX_SZ;
}
return (reso);
}
void StampaCoda(Coda *c) {
int i,k;
printf("Coda: ");
for(i = 0;i < c->numelem; i++) {
k = (c->first + i) % MAX_SZ;
printf("%4d", c->Info[k]);
}
printf("\n");
}
void main() {
10
Pila pila1;
Coda coda1;
pila1.numelem = 0;
coda1.numelem = 0;
InserInPila(&pila1,1);
StampaPila(&pila1);
InserInPila(&pila1,2);
StampaPila(&pila1);
InserInPila(&pila1,3);
StampaPila(&pila1);
printf("%4d\n",TogliDaPila(&pila1));
StampaPila(&pila1);
printf("\n");
InserInCoda(&coda1,10);
StampaCoda(&coda1);
InserInCoda(&coda1,11);
StampaCoda(&coda1);
InserInCoda(&coda1,12);
StampaCoda(&coda1);
printf("\n");
printf("%4d\n",TogliDaCoda(&coda1));
printf("\n");
StampaCoda(&coda1);
printf("\n");
printf("%4d\n",TogliDaCoda(&coda1));
printf("\n");
StampaCoda(&coda1);
printf("\n");
InserInCoda(&coda1,20);
InserInCoda(&coda1,21);
InserInCoda(&coda1,22);
InserInCoda(&coda1,23);
StampaCoda(&coda1);
printf("\n");
printf("%4d\n",TogliDaCoda(&coda1));
printf("\n");
printf("%4d\n",TogliDaCoda(&coda1));
printf("\n");
StampaCoda(&coda1);
}
1
1
1
2
2
Coda:
Coda:
Coda:
10
10
10
11
11
11
12
12
10
Coda:
11
Coda:
12
11
Coda:
12
20
21
21
22
23
22
23
12
20
Coda:
12
Visibilit e funzioni
Visibilit delle variabili in blocchi annidati
Le variabili definite in un blocco (insieme di comandi racchiusi da { }) sono visibili solo allinterno
di quel blocco perci, se in un blocco annidato in un altro viene definita una variabile con un nome
gi usato, questa una nuova variabile, accessibile solo allinterno di questultimo. Il programma
seguente mostra tale situazione. Attenzione quindi ai nomi delle variabili e a dove sono posizionate!
file: visib.c
#include <stdio.h>
int i = 1;
int main()
{
printf("%d\n", i);
/* stampa 1 */
{
int i = 2, j = 3;
printf("%d\n%d\n", i, j);
{
/* i e j definite a
livello di blocco */
/* stampa 2, 3 */
int i = 0;
}
printf("%d\n", i);
/* stampa
2 */
}
printf("%d\n", i);
/* stampa 1 */
return 0;
}
Potenza
Ogni funzione vede le variabili che appartengono al suo ambiente locale cio quelle dichiarate
nella sua testata e nella sua parte dichiarativa e, quando rende il controllo al programma chiamante
(nella maggior parte dei casi il main), pu ritornare un valore mediante listruzione
return(..).
Perci, se la funzione di tipo algebrico e calcola un risultato dipendente da una serie di variabili
esterne, queste ultime sono generalmente passate come valori alla funzione che ottiene la soluzione
e la ritorna.
La funzione potenza calcola la potenza (intera) di un numero attraverso un ciclo for:
file: potenza.c
/* potenza di un numero */
13
#include<stdio.h>
float potenza(float base, int espon){
float ris;
if(espon < 0) {
espon = -espon;
base = 1.0 / base;
}
ris = 1.0;
for(; espon > 0; espon--)
ris = ris * base;
return(ris);
}
void main() {
float base;
int espon;
printf("\nfornisci base (un numero negativo termina il programma): ");
scanf("%f", &base);
while(base > 0) {
printf("\nfornisci esponente (numero intero): ");
scanf("%d", &espon);
printf("\nrisultato : %f", potenza(base,espon));
printf("\nfornisci base (un numero negativo termina il
scanf("%f", &base);
}
programma): ");
14
}
}
void main() {
double A, B, C;
printf("\nvalore di A, B e C: \n");
scanf("%lf %lf %lf", &A, &B, &C);
EquazioneSecondoGrado(A, B, C);
}
15
}
void scambiacx(complex *pt1, complex *pt2) {
complex tmp;
tmp = *pt1;
*pt1 = *pt2;
*pt2 = tmp;
}
file: scambia.c
1 due: 2
2 due: 1
b: b
b: a
1.010000 flb: 2.020000
2.020000 flb: 1.010000
10.003000 dbb: 23.995000
23.995000 dbb: 10.003000
1.200000 2.100000 cx2: 91.200000 92.100000
91.200000 92.100000 cx2: 1.200000 2.100000
16
}
}
void main() {
int unoi = 111, duei = 222;
double unof = 10.10002, duef = 20.20001;
char ac = 'Y', bc = 'Z';
void *pt1, *pt2;
complex cx1, cx2;
cx1.realp = 1.2; cx1.imgp = 9.3;
cx2.realp = 8.2; cx2.imgp = 8.3;
17
222
111
f2: 20.200010
f2: 10.100020
9.300000 cx2: 8.200000 8.300000
8.300000 cx2: 1.200000 9.300000
Dati strutturati
Ricorsivit
Molti algoritmi possono essere risolti in modo ricorsivo. Secondo questo paradigma il problema di
partenza pu essere risolto attraverso la soluzione del problema stesso su un sottoinsieme dei dati e
combinando i risultati ottenuti. Lalgoritmo richiama allora se stesso per una esecuzione interna alla
precedente, finch non si verifichi una condizione di arresto.
Vengono ora riportati alcuni algoritmi codificati sia in modalit iterativa sia ricorsiva cos che il
confronto sia pi facile, pi oltre si affronteranno in modo analogo problemi che implicano strutture
dinamiche che sono ricorsive anche nella loro stessa definizione.
Massimo Comun Divisore
Il Massimo Comun Divisore fra due numeri si pu ottenere, secondo il geniale algoritmo di Euclide,
osservando che il MCD(n,m) = MCD(n-m,m), dove n > m o ancora MCD(n,m) = MCD(m, n%m)
dove n%m il resto della divisione n/m.
file: MCDiv.c
/*************************************************************
** Massimo Comun Divisore fra due numeri interi
**
18
** algoritmo di Euclide:
**
** il MCD fra due numeri e' il MCD fra il minore dei due
**
** e la loro differenza
**
*************************************************************/
#include<stdio.h>
/* codifica ricorsiva */
int MCDric(int a, int b)
{
int differenza, minore;
if(a == b)
return (b);
else {
differenza = a>b ? a-b : b-a;
minore = a>b ? b : a;
return (MCDric(differenza,minore));
};
}
/* codifica iterativa */
int MCDite(int a, int b)
{
while (a != b) {
if (a > b)
a = a - b;
else
b = b - a;
}
return (b);
}
void main ()
{
int x, y;
printf("introdurre
scanf("%d %d", &x,
printf("Il Massimo
printf("Il Massimo
}
19
Media ricorsiva
Anche la media pu essere ottenuta ricorsivamente osservando che:
X i X i 1
X i 1
, i 1, X0 0
i
file: mediar.c
#include <stdio.h>
float Media(float elementi[], int num_elementi)
/* funzione di calcolo della media in modo ricorsivo
** bisogna fornire l'indirizzo del vettore che contiene i valori
** e la sua lunghezza */
{
float med,med1;
if(num_elementi == 1)
med=*elementi;
else
{
med1=Media(elementi + 1, num_elementi - 1);
med=med1 + ((*elementi - med1) / num_elementi);
}
return med;
}
void main()
{
int cont, num_elem;
float media, elementi[50];
printf("inserisci il numero di elementi dei quali vuoi fare la media:
scanf("\n%d", &num_elem);
for(cont=0; cont<num_elem; cont++)
{
printf("Inserisci numero: \n");
scanf("%f",&elementi[cont]);
}
media = Media(elementi, num_elem);
printf ("\nLa media e`: %f", media);
}
Polinomio
Per un polinomio di grado n nella variabile x di coefficienti ci, vale la seguente eguaglianza:
Pn ( x) cn x n cn 1 x n1 c1 x1 c0 c0 x (c1 x (c2 x cn ))
*/
#include <stdio.h>
20
");
#define MAX_SZ 10
/* i parametri passati sono: il valore della variabile x, il vettore
degli n+1 coefficienti del polinomio, il suo grado n
float Poli(float x, float * c, int n) {
float Poli_val = 0.0;
if (n >= 0)
Poli_val = *c + x * Poli(x, c + 1, n - 1);
return (Poli_val);
}
*/
void main () {
float coeff[MAX_SZ], polinomio, x;
int i, n, cont;
printf("\nInserisci grado del polinomio\n");
cont = scanf("%d", &n);
/* lettura grado = lunghezza */
while(cont == 1){
printf("\nInserisci i %i coefficienti\n", n + 1);
for(i = 0; i < n + 1; i++)
scanf(" %f", &coeff[i]);
printf("\nInserisci il valore x:
scanf(" %f",&x);
");
polinomio = Poli(x,coeff,n);
printf("\n valore: %f",polinomio);
printf("\nInserisci grado del vettore\n");
cont = scanf("%d", &n);
/* lettura grado = lunghezza */
}
Algoritmi vari
Ricerca binaria
Lalgoritmo ricerca un dato in un insieme ordinato, consiste nel posizionarsi al centro della
sequenza e individuare in quale delle due met si trova eventualmente il valore cercato poi si
prosegue in modo analogo nella nuova met, fino al successo o allesaurimento dei dati. Se N il
numero degli elementi, il numero di ricerche allora proporzionale a log2(N).
La funzione ricbin ricerca un numero intero x nella porzione del vettore v compresa fra gli
indici i e j. Essa ritorna al programma chiamante 1 se la ricerca ha avuto successo, 0 in caso
contrario. Non difficile riscriverla in modo ricorsivo.
file: binr2.c
#include <stdio.h>
#define N 10
int ricbin(int x, int v[ ], int i, int j) {
int k;
if (i > j)
return (0);
do {
k = (i + j) / 2;
21
if (x < v[k])
j = k - 1;
else
i = k + 1;
} while ((i <= j) && (v[k] != x));
return (x == v[k]);
}
int main(void)
{
int vect[N], i, sx = 0, dx = 9, n, x;
printf("introdurre una sequenza ordinata di %d numeri\n", N);
for (n = 0; n < N ; n++ ) {
scanf("%d", &x);
vect[n] = x;
} /* endfor */
printf("introdurre il numero da cercare \n");
scanf("%d", &x);
i = ricbin(x,vect,sx,dx);
if (i != 0) printf("trovato %d\n", x);
else printf("%d non trovato\n", x);
return (0);
}
#include<stdio.h>
#define MAX_LN 30
int main() {
char parola[MAX_LN];
int lung;
int inverti(char parola[]);
printf("\ninserisci la parola: ");
scanf("%s", parola);
lung = inverti(parola);
printf("\n %s lung: %d", parola, lung);
return 0;
}
int inverti(char parola[]) {
char alorap[MAX_LN];
int l, i = 0;
while(parola[i] != '\0' && i < MAX_LN)
{ alorap[i] = parola[i]; i++; }
l = --i;
while(i >= 0)
{ parola[l - i] = alorap[i]; i--; }
return (l + 1);
}
22
23
Il programma conta1 legge un file di testo e conta i digit numerici e quelli alfabetici. Si noti che
i caratteri sono numeri interi perci non ci si deve meravigliare se valgono gli operatori aritmetici
e di confronto.
file: conta1.c
#include <stdio.h>
/*conta i caratteri alfabetici e numerici di un testo*/
void main()
{
int c, i, altri = 0, spazi = 0;
int ndigit[10];
int nalfab[26];
for (i = 0; i < 10; i++)
ndigit[i] = 0;
for (i = 0; i < 26; i++)
nalfab[i] = 0;
while ((c = getchar()) != EOF)
if (c >= '0' && c <= '9')
++ndigit[c - '0'];
else if(c == ' ')
++spazi;
else if((c >= 'A') && (c <= 'Z'))
++nalfab[c - 'A'];
else if((c >= 'a') && (c <= 'z'))
++nalfab[c - 'a'];
else ++altri;
printf("\nnumeri\n");
for (i = 0; i < 10; ++i)
printf(" %3d", i);
printf("\n");
for (i = 0; i < 10; ++i)
printf(" %3d", ndigit[i]);
printf("\nlettere\n");
for (i = 0; i < 26; ++i)
printf(" %3c", 'A' + i);
printf("\n");
for (i = 0; i < 26; ++i)
printf(" %3d", nalfab[i]);
printf("\n spazi = %d, altri = %d", spazi, altri);
}
6
4
7
0
8
0
9
1
70
17
34
11
13
25
10
30
24
Strutture dinamiche
Lintroduzione di una struttura dinamica permette una maggiore flessibilit nelluso della memoria
che pu essere allocata e liberata in funzione della necessit.
Liste
Una lista composta da una successione di elementi, quindi perch possa essere percorsa
interamente almeno in una direzione, bisogna che ogni elemento contenga lindirizzo di quello
successivo. La definizione dellelemento di lista ricorsiva: contiene il puntatore a un dato dello
stesso tipo. Ogni lista deve avere un inizio e una fine, lultimo elemento si distingue per il fatto che
lindirizzo del prossimo elemento la costante NULL (= fine lista); il primo elemento viene invece
raggiunto attraverso un puntatore, che si usa chiamare testa della lista, ed da considerarsi parte
integrante della lista stessa. Una funzione progettata per gestire una lista deve sempre ricevere come
parametro il puntatore ad essa. Se la funzione pu modificare il puntatore perch, per esempio,
inserisce in testa un nuovo elemento, necessario che riceva come parametro un doppio puntatore.
Infatti con questa tecnica la nostra funzione in grado di modificare la testa della lista anche se si
trova nellarea di memoria del programma chiamante.
Il programma listai che composto dal file .h che contiene la definizione dellelemento pi
semplice che si possa pensare, composto da un dato intero e da un puntatore e qualche esempio di
funzione di gestione della lista. Di alcune di queste funzioni vengono presentate implementazioni
diverse. Si osservi che, come gi notato, le funzioni che leggono la lista ma la lasciano inalterata (ad
esempio quelle di stampa) possono ricevere in input il puntatore alla lista, quelle che ne modificano
la struttura hanno invece come parametro in ingresso il puntatore al puntatore alla lista.
Le funzioni sono molto semplici, si evitato di allocare e rilasciare la memoria perci nel main
(file .c), - da usarsi per esperimenti - gli elementi sono gi predefiniti staticamente. In qualche
caso la versione ricorsiva di una funzione quella pi immediata e naturale. Si confronti, per
esempio, la codifica delle funzioni di stampa e si osservi come sia possibile, nella versione
ricorsiva, cambiare lordine di stampa semplicemente invertendo lordine di due istruzioni. Non
sarebbe altrettanto facile in quella iterativa.
file: listai.h
/* definizione dell'elemento di lista di interi */
typedef struct EL {
int Info;
struct EL *next;
} ElmLista, *Lista;
/* stampa lista (iterativa) in ordine diretto */
void StampaLista(Lista l) {
while (l != NULL) {
printf("%i\n",l->Info);
l = l->next;
}
}
/* stampa lista (ricorsiva) in ordine diretto */
void StampaDiretta(Lista l) {
if(l) {
printf("%i\n", l->Info);
StampaDiretta(l->next);
}
}
/* stampa lista (ricorsiva) in ordine inverso */
void StampaInversa(Lista l) {
25
if(l) {
StampaInversa(l->next);
printf("%i\n",l->Info);
}
}
/* conteggio (iterativo) del numero di elementi di una lista */
int LungListaI(Lista l){
int lung = 0;
while(l) {
lung++;
l = l->next;
}
return (lung);
}
/* conteggio (ricorsivo) del numero di elementi di una lista */
int LungListaR(Lista l) {
int lung = 0;
if (l != NULL) {
lung = 1 + LungListaR(l->next);
}
return (lung);
}
/* inserimento in testa */
void InserisciInTesta(Lista *lp, ElmLista *elp) {
elp->next = *lp;
*lp = elp;
}
/* inserimento in coda (iterativo) */
void InserisciInCoda(Lista *lp, ElmLista *elp) {
ElmLista *elementoCorrente;
elementoCorrente = *lp;
if (*lp == NULL)
*lp = elp; /* caso di lista vuota */
else {
while (elementoCorrente->next != NULL) {
elementoCorrente = elementoCorrente->next;
}
elementoCorrente->next = elp;
}
elp->next = NULL;
}
/* inserimento in coda (ricorsivo) */
void InserisciInCodaR(Lista *lp, ElmLista *elp) {
if (*lp == NULL) {
*lp = elp;
/* fine della lista */
elp->next = NULL;
}
else {
InserisciInCodaR(&(*lp)->next, elp);
}
}
/* inserimento in ordine (si suppone la lista in ordine crescente) */
26
file: listai.c
#include <stdio.h>
#include "listai.h"
void main() {
ElmLista Elm1, Elm2, Elm3, Elm4, Elm5, Elm6, Elm0, *PElm;
Lista Lista1;
Lista1 = &Elm1;
Elm1.Info = 1;
Elm1.next = &Elm2;
Elm2.Info = 2;
Elm2.next = &Elm3;
Elm3.Info = 3;
Elm3.next = &Elm5;
Elm5.Info = 5;
Elm5.next = NULL;
Elm4.Info = 4;
Elm6.Info = 6;
Elm0.Info = 0;
/*
StampaLista(Lista1);
printf("\n");
InserisciInOrdine(&Lista1,&Elm4);
InserisciInOrdine(&Lista1,&Elm6);
InserisciInOrdine(&Lista1,&Elm0);
StampaLista(Lista1);
printf("\n");
StampaInversa(Lista1);
printf("\n");
printf("\n %i \n ",(*TogliDaTesta(&Lista1)).Info);
printf("\n");
StampaLista(Lista1);
printf("\n");
27
printf("\n %i \n ",TogliDaTesta(&Lista1)->Info);
printf("\n");
StampaLista(Lista1);
InserisciInCoda(&Lista1,&Elm4);
StampaLista(Lista1);
printf("\n");
InserisciInCodaR(&Lista1,&Elm0);
StampaLista(Lista1);
printf("\n");
printf("\n
printf("\n
printf("\n
printf("\n
%i
%i
%i
%i
",LungListaI(Lista1));
",LungListaR(Lista1));
",LungListaI(NULL));
",LungListaR(NULL));
StampaDiretta(Lista1);
printf("\n");
StampaInversa(Lista1);
printf("\n");
*/
}
perci entrambi gli elementi interessati alla operazione e il numero di istruzioni eseguite
indipendente dalla lunghezza della coda, analogamente al caso della pila. In questo modo per
bisogna distinguere fra loro i tre casi: in cui non ci siano elementi accodati, ce ne sia uno solo e ce
ne siano due o pi.
file: pila.h
typedef struct _elPila
{
int
Info;
struct _elPila *prossimo;
}
elPila, *Pila;
Pila
{
*nuovoElemento;
nuovoElemento->Info = el;
nuovoElemento->prossimo = p;
p = nuovoElemento;
return(p);
Pila
{
Pila punt;
if (p == NULL) return (NULL);
*el = p->Info;
punt = p->prossimo;
free(p);
return (punt);
void
{
StampaPila(Pila p)
if (p == NULL) {
printf("la pila non ha elementi\n");
return;
}
while (p != NULL)
{
printf("%i\n", p->Info);
p = p->prossimo;
}
file: pila.c
#include <stdio.h>
#include <stdlib.h>
#include "pila.h"
29
void
{
main()
Pila
p = NULL;
int
el;
int scelta = -1;
while(scelta != 0) {
printf("\n 0 per uscire, 1 per inserire, 2 per togliere, 3 per
visualizzare\n");
scanf("%d", &scelta);
switch(scelta) {
case 1:
printf("inserire un elemento: ");
scanf("%d", &el);
p = Inserisci(p, el);
if (p == NULL) printf("inserimento non riuscito");
break;
case 2:
p = Togli(p, &el);
if (p != NULL) printf("%i ",el);
break;
case 3:
StampaPila(p);
break;
}
}
}
file: coda.h
*nuovoElemento;
30
}
Coda
{
}
void
{
StampaCoda(Coda pc)
Coda punt;
if (pc == NULL) {
printf("la pila non ha elementi\n");
return;
}
punt = pc;
do
{
punt = punt->prossimo;
printf("%i\n", punt->Info);
}
while (punt != pc);
file: coda.c
#include <stdio.h>
#include <stdlib.h>
#include "pila.h"
void
{
main()
Pila
p = NULL;
int
el;
int scelta = -1;
while(scelta != 0) {
printf("\n 0 per uscire, 1 per inserire, 2 per togliere, 3 per
visualizzare\n");
scanf("%d", &scelta);
switch(scelta) {
case 1:
printf("inserire un elemento: ");
scanf("%d", &el);
p = Inserisci(p, el);
if (p == NULL) printf("inserimento non riuscito");
break;
case 2:
31
p = Togli(p, &el);
if (p != NULL) printf("%i ",el);
break;
case 3:
StampaPila(p);
break;
}
}
}
Alberi
Dopo la lista la struttura dinamica pi semplice lalbero binario, struttura gerarchica in cui ogni
elemento pu avere ha due figli, sinistro e destro, a partire dalla radice fino alle foglie (che
terminano il ramo, non avendo pi figli. Un albero binario pu essere visitato in modi diversi.
Lesempio che segue (file albero2.c) mostra la costruzione (funzione: addtree) di un albero
di stringhe (elemento dellalbero: Treenode), la sua visita e stampa in ordine alfabetico (funzione:
treeprint) e la stampa della struttura (funzione: StampaAlbero).
Le funzioni fin qui elencate sono ricorsive, oltre a queste nel programma ne sono codificate altre di
servizio che servono per copiare le stringhe e allocare le aree di memoria necessarie. Essendo state
codificate dopo il modulo main, devono essere dichiarate prima del loro uso.
Viene poi mostrato un esempio di esecuzione del programma con come input la prima terzina del
Purgatorio di Dante (input in carattere corsivo).
Per aggiungere un nuovo elemento si parte dalla radice muovendosi a sinistra o a destra in funzione
del fatto che la parola si trovi in ordine alfabetico prima o dopo della radice, Se abbiamo raggiunto
la fine del ramo, il nuovo elemento viene aggiunto come foglia in quella posizione; in caso
contrario lelemento incontrato la radice del sottoalbero dal quale si riparte con lo stesso
procedimento.
Per percorrere in ordine alfabetico lalbero cos costruito bisogna raggiungere la foglia pi a sinistra
e seguirlo a ritroso stampando lelemento radice di ogni sottoalbero dopo gli elementi del ramo
sinistro e prima di quelli del destro. Come sempre il modo migliore per capire lalgoritmo quello
di operare su un caso pratico studiando la traccia del programma.
Infine, per comprendere meglio la stampa della struttura dellalbero costruito, occorre ruotare di 90
gradi in senso orario il foglio con loutput, allora si legger agevolmente che:
per la radice dellalbero,
correr e vele sono i figli della radice di sinistra e di destra rispettivamente,
acque e migliori sono figli di correr,
vele ha solo il figlio di sinistra (se),
acque ha i figli: a e alza,
ecc.
file: albero2.c
32
char *word;
/* puntatore al testo */
int count;
/* numero di occorrenze */
Treeptr left, right; /* figli di sinistra e di destra */
} Treenode;
char *strdupz(char *);
Treeptr addtree(Treeptr, char *);
void treeprint(Treeptr);
Treeptr talloc(void);
void StampaAlbero(Treeptr, int);
/* costruisce albero dinamico inserendo parole */
int main(void)
{
Treenode *root;
char word[MAXWORD];
char *fine = "END";
root = NULL;
printf("\ninserisci una parola alla volta <END termina la sequenza>\n");
scanf("%s", word);
while(*word != *fine)
{
root = addtree(root, word);
scanf("%s", word);
}
printf("\n
ALBERO ORDINATO\n");
treeprint(root);
printf("\n");
printf("\n
STRUTTURA DELL'ALBERO\n");
printf("\n");
StampaAlbero(root, 1);
return 0;
33
treeprint (p->left);
printf("%s (%d)\n", p->word, p->count);
treeprint(p->right);
}
/* stampa la forma e il contenuto dell'albero */
void StampaAlbero(Treenode *p, int l)
{
int i;
if (p != NULL)
{
StampaAlbero(p->right, l + 1);
for (i = 0; i < l; i++)
printf(" ");
printf("%s\n",p->word);
StampaAlbero(p->left, l + 1);
}
}
/* crea un tnode */
Treenode *talloc(void)
{
return (Treeptr)malloc(sizeof(Treenode));
}
/* crea una copia della stringa letta s */
char *strdupz(char *s)
{
char *p;
p = (char *)malloc(strlen(s) + 1);
if (p != NULL)
strcpy(p, s);
return p;
}
34
ormai (1)
per (1)
se' (1)
si' (1)
vele (1)
STRUTTURA DELL'ALBERO
vele
si'
se'
per
ormai
navicella
mio
migliori
mar
le
lascia
la
ingegno
dietro
del
crudele
correr
che
alza
acque
a
Input Output
Esempio di scrittura, lettura e aggiornamento di un file
I record contenuti nel file prova.txt hanno lunghezza fissa pari a 20 byte, se si prova perci a
visualizzare con un editor il file costruito, in coda ad ogni parola appaiono una serie di caratteri
sporchi non stampabili fino al completamento dei 20 byte. Il problema non si pone se si stampano le
stringhe con un formato, per esempio con listruzione printf, in questo caso infatti il carattere
\0 segnala la fine della stringa.
Il programma operio vuole illustrare le istruzioni di I/O, in particolare: fopen, fclose,
fwrite, fread, ftell, fseek. Le ultime due servono rispettivamente a memorizzare e a
posizionare lindirizzo di lettura/scrittura nel file.
file: operio.c
/* il programma richiede la successione di parole con cui viene popolato il file
prova.txt. L'elenco viene terminato quando viene inserito il carattere '#'
a inserimento ultimato vengono richieste due parole, la prima viene cercata
nel file e, se trovata, sostituita con la seconda */
#include<stdio.h>
#include<string.h>
#define LUNG 20
void main() {
char filename[] = "prova.txt";
char parola[LUNG], parolaDaTrovare[LUNG], parolaNuova[LUNG];
int n;
long pos = 0L;
35
FILE *fp;
if ((fp = fopen(filename, "w")) == NULL) exit (-1);
printf("inserisci parola\n");
scanf(" %s", parola);
while (parola[0] != '#') {
fwrite (parola, sizeof(parola),1,fp);
printf("inserisci parola\n");
scanf("%s", parola);
}
fclose(fp);
if ((fp = fopen(filename, "r+")) == NULL) exit (-1);
printf("\ninserisci la parola da sostituire e la parola nuova\n");
scanf("%s %s", parolaDaTrovare, parolaNuova);
for (;;) {
n = fread(parola, sizeof(parola),1,fp);
if (n == 0) {
printf("parola non trovata\n");
exit(-1);
}
if (strcmp(parola,parolaDaTrovare) == 0)
break;
}
pos = ftell(fp);
pos = pos - sizeof(parola);
fseek(fp,pos,0);
fwrite(parolaNuova,sizeof(parola),1,fp);
fclose(fp);
}
36