Discussione esercizio 33:
#include <stdlib.h>
#include <string.h>
WList wl_sort(WList L) {
if (L == NULL || L->next == NULL) return L;
WElem **min = &L;
WElem *p = L;
while (p->next != NULL) {
if (strcmp(p->next->word, (*min)->word) < 0)
min = &(p->next);
p = p->next;
}
WElem *first = *min;
*min = first->next;
first->next = wl_sort(L);
return first;
}
Discussione esercizio 34:
int wl_equal(WList L1, WList L2) {
while (L1 != NULL && L2 != NULL) {
if (strcmp(L1->word, L2->word) != 0)
return 0;
if (L1->count != L2->count)
return 0;
L1 = L1->next;
L2 = L2->next;
}
return (L1 == L2);
}
Il
main con argomenti : la funzione
main
ha un'altra versione che permette di prendere degli argomenti direttamente dalla linea di comando:
int main(int argc, char *argv[]) {
...
}
argc numero argomenti
argv[i] i-esimo argomento, però argv[0] è il nome del programma
quindi gli argomenti iniziano dall'indice 1
Esempio: programma che prende come argomenti una sequenza di numeri interi e ne stampa la somma
(la funzione
sscanf()
in
stdio.h
).
#include <stdio.h>
int main(int argc, char *argv[]) {
int s = 0;
for (int i = 1 ; i < argc ; i++) {
int k;
sscanf(argv[i], "%d", &k);
s += k;
}
printf("La somma e' %d\n", s);
}
Se il programma compilato si chiama
sum
, può essere
eseguito da terminale nel seguente modo:
> sum 1 2 3 4 5
> La somma e' 15
Puntatori a funzioni . Anche le funzioni hanno un tipo. Il tipo
di una funzione è determinato dal tipo ritornato e dalla sequenza dei tipi degli argomenti.
int f(); //Tipo: funzione che ritorna int
int f2(double); //Tipo: funzione con argomento double e ritorna int
void f3(int, int); //Tipo: funzione con argomenti int, int e ritorna void
A differenza degli altri tipi non è possibile definire variabili di tipo funzione, però
si possono definire variabili di tipo
puntatore a funzione:
tipo-ritornato (*pf)(argomenti); //pf è una variabile di tipo puntatore a funzione
La sintassi per dichiarare un puntatore a funzione è del tutto simile a quella per
dichiarare il prototipo di una funzione, con la differenza che al posto del nome della funzione c'è
(*nome_puntatore_a_funzione)
. Ecco alcuni esempi:
int funz() { ... }
void funz2(double a[], int n) { ... }
int main() {
double A[10];
int (*pf)(void); //pf è una variabile di tipo puntatore a funzione senza argomenti che ritorna int
void (*pf2)(double [], int); //pf2 è una variabile di tipo puntatore a funzione con argomenti
//array di double, int e che ritorna void
pf = &funz; //&funz è il puntatore alla funzione funz
(*pf)(); //equivale alla chiamata funz();
pf(); //equivale a (*pf)();
pf2 = funz2; //equivale a pf2 = &funz2;
pf2(A, 10); //equivale alla chiamata funz2(A, 10);
}
Per i tipi
funzione vale una regola simile a quella
che vale per i tipi
array: una espressione di tipo
funzione è
automaticamente convertita al corrispondente tipo
puntatore a funzione eccetto quando è
usata in una chiamata (di funzione), come argomento dell'operatore indirizzo (
&
)
o come argomento dell'operatore
sizeof
(è invalido applicare l'operatore
sizeof
a una espressione di tipo
funzione). Così, l'assegnamento
pf2 = funz2
non necessita dell'operatore indirizzo perché
funz2
è automaticamente convertito
al corrispondente tipo puntatore.
Vale anche un'altra regola, un'espressione di tipo
puntatore a funzione può essere
usata in una chiamata di funzione senza un deferenziamento esplicito. Questa è la ragione per
cui si può scrivere, nell'esempio sopra,
pf()
in luogo di
(*pf)()
.
Le seguenti dichirazioni di variabili non sono equivalenti:
int (*pfA)();
int (*pfB)(void);
La prima,
pfA
, è una specie di puntatore generico a una funzione che ritorna
int
,
senza vincoli sul numero e tipo degli argomenti. Mentre la seconda,
pfB
, specifica un
puntatore a
funzione senza argomenti che ritorna int. Così, si ha che
int funz1() { ... }
int funz2(double a[], int n) { ... }
int main() {
int (*pfA)(), (*pfB)(void);
pfA = funz1;
pfB = funz1;
pfA = funz2; //OK, il compilatore non segnala alcun errore
pfA(); //OK, ma si possono verificare errori in esecuzione
pfA("abc"); //OK, ma si possono verificare errori in esecuzione
pfB = funz2; //ERRORE o WARNING: il compilatore segnala l'incompatibilità dei tipi
}
Grazie ai
puntatori a funzione si possono definire funzioni che prendono come
argomenti altre funzioni. Così, ad esempio, si può definire una funzione
che può ordinare un array di qualsiasi tipo (array di int, array di double, array di stringhe, ecc.).
Infatti, per ordinare un array di
T
è sufficiente sapere come confrontare
due elementi di tipo
T
e questo può essere specificato da un'opportuna funzione.
Ciò è proprio quello che fa la funzione
qsort
della libreria standard
(in
stdlib.h
):
void qsort(void *base, size_t count, size_t size, int (cmp*)(const void *e1, const void *e2));
dove il tipo
size_t
è un sinonimo di un tipo intero (generalmente
unsigned long
).
Gli argomenti hanno il seguente significato:
base
è l'indirizzo del primo elemento dell'array da ordinare;
count
è il numero di elementi;
size
è la dimensione in bytes di un elemento;
infine
cmp
è il puntatore a una funzione che confronta gli elementi e che
ritorna
-1 se e1 < e2
0 se e1 = e2
1 se e1 > e2
Se si vogliono ordinare array di interi è sufficiente definire una funzione per confrontare
interi:
int cmpInt(const void *e1, const void *e2) {
int v1 = *((int *)e1), v2 = *((int *)e2);
return (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
}
Dopo di che per ordinare un qualsiasi array
A
di
n
interi basta scrivere
qsort(A, n, sizeof(int), cmpInt);
Per ordinare array di stringhe basta definire una funzione per confrontare stringhe:
int cmpStr(const void *e1, const void *e2) {
return strcmp(*(char **)e1, *(char **)e2);
}
Un qualsiasi array
S
di
n
stringhe può essere ordinato
così:
qsort(S, n, sizeof(char *), cmpStr);
Si tenga presente che
qsort
usa un algoritmo molto più efficiente
degli algoritmi di ordinamento semplici come bubble-sort o selection-sort. Ad esempio, ordina
un array di 100000 interi almeno 1500 volte più velocemente.
Un altro esempio è una funzione che applica una funzione data a tutti gli
elementi di una lista di interi.
typedef struct Elem {
int val;
struct Elem *next;
} Elem, *List;
void apply(List L, void (*f)(Elem *e)) {
Elem *e = L;
while (e != NULL) {
f(e);
e = e->next;
}
}
Può essere usata come segue:
void square(Elem *e) {
e->val *= e->val;
}
void printE(Elem *e) {
printf("%d\n", e->val);
}
int main() {
...
apply(L, printE);
apply(L, square);
...
}
Un'altro esempio consiste nella possibilità di definire una funzione che gestisce un menu testuale
stampando le voci del menu ed eseguendo l'operazione scelta dall'utente:
#include <stdio.h>
#include "util.h"
typedef void (*OpFunc)(void);
void menu(char *mItem[], OpFunc mOp[], int n) {
int i, choice;
while (1) {
for (i = 1 ; i <= n ; i++)
printf("%2d) %s\n", i, mItem[i - 1]);
choice = input_int();
if (choice >= 1 && choice <= n) {
mOp[choice - 1]();
if (choice == n) return;
} else
printf("Scelta non valida\n");
}
}
void oper1() { printf("Operazione 1\n"); }
void oper2() { printf("Operazione 2\n"); }
void fine() { printf("Fine\n"); }
int main() {
char *m[3] = {"Operazione 1", "Operazione 2", "Fine"};
OpFunc op[3] = {oper1, oper2, fine};
menu(m, op, 3);
return 0;
}
Esercizio 35
Considerando il tipo:
typedef struct {
char cognome[40]; //stringa
char nome[20];
} Persona;
scrivere una funzione
void sort_pers(Persona P[], int n)
che ordina
l'array
P
(di
n
elementi) rispetto al campo
cognome
tramite la funzione
qsort()
.
Esercizio 36
Scrivere una funzione
List filter(List L, int (*f)(int v))
che elimina
dalla lista di interi
L
gli elementi
p
tali che
f(p->val) ≠ 0
e ritorna il puntatore alla lista modificata.