Premi qui per scaricare il PDF
In questa lezione di programmazione in C, vedremo cosa sono i sottoprogrammi e utilizzeremo vedremo un esempio pratico di puntatori.
Motivazioni
- Modularità nello sviluppo del codice
- Affrontare il problema per raffinamenti successivi
- Riusabilità
- Scrivere una sola volta il codice e usarlo più volte;
- Astrazione
- Esprimere in modo sintetico operazioni complesse;
- Definire operazioni specifiche dei tipi di dato definiti dal programmatore: esempio: calcolo totale + iva di un ordine.
Un sottoprogramma deve essere definito rispetto a dei dati generici, e poi può essere chiamato (attivato per eseguire una determinata operazione).
Sottoprogrammi
I sottoprogrammi si dividono in:
- procedure;
- funzioni;
Le procedure sono dei sottoprogrammi che non restituiscono niente. Possiamo utilizzarle per esempio per creare un menu.
Le funzioni sono dei sottoprogrammi che restituiscono un valore. Un esempio è la strlen() che restituisce il valore “lunghezza della stringa“
Il C non ha differenze di sintassi tra le due. Esistono solo funzioni che restituiscono un valore (che viene ignorato → procedure) oppure restituiscono un valore significativo
Sintassi
Per inserire un sottoprogramma nel codice C, si deve:
- scrivere il prototipo/testata del sottoprogramma prima del main. (La testata è composta da nome e dal tipo del sottoprogramma, con i parametri formali);
- Richiamare/Attivare il sottoprogramma nel main (quando ci serve);
- Alla fine del main, scrivere il corpo del sottoprogramma, riportando la testata e le istruzioni che esso deve eseguire.
Ecco un esempio di scrittura del sottoprogramma
Procedure
void menu(); //Testata del sottoprogramma
int main()
{
menu(); //Viene richiamata la procedura menu, che esegue le istruzioni scritte in basso
return 0;
}
void menu() //Corpo del sottoprogramma (istruzioni)
{
printf("1. per inserire");
printf("2. per cancellare");
printf("3. per uscire");
}
void → valore vuoto.
Per far capire al compilatore che il “menu” è un sottoprogramma, si scrive la testata prima del main. Serve solo per aumentare la leggibilità del codice, non è obbligatorio, possiamo anche scrivere l’intero sottoprogramma prima del main.
Esempio Procedura
int main ()
{
int a, b;
scanf("%d %d", &a, &b);
somma (a,b); //Parametri attuali
return 0;
}
void somma (int x,int y)// Parametri formali
{
int c;
c = x + y;
/*All'inizio dell'esecuzione, hanno il valore
che viene passato dall'utente nelle variabili a e b*/
printf("La somma e' %d \\n", c);
}
Parametri formali: variabili generiche utilizzate all’atto della definizione del sottoprogramma. In quel momento non vi è associato alcun valore.
Parametri attuali: variabili utilizzate al momento dell’invocazione (utilizzo) del sottoprogramma. Ha un tipo e un valore.
Visibilità variabili
Le variabili nascono e muoiono con il sottoprogramma. Ogni sottoprogramma può usare solo le variabili locali+parametri+variabili globali.
Quando il sottoprogramma termina, le variabili locali vengono cancellate.
La variabile “c” è una variabile locale, cioè utilizzabile solo all’interno della procedura.
Funzione
int somma (int, int);
int main()
{
int a, b, c;
scanf("%d %d", &a, &b);
c = somma(a,b); //c è uguale al risultato della funzione somma
return 0;
}
int somma(int x, int y)
{
int c;
c = x + y;
return c; //la funzione termina e fa assumere alla funzione il valore di c, quindi la somma di a + b
}
La due variabili c hanno valori diversi → anche se hanno lo stesso nome, non hanno nulla in comune, le variabili sono invisibili al di fuori della funzione di appartenenza.
Possiamo scrivere diversi return in un sottoprogramma, ma ne verrà eseguito sempre e solo uno.
Esempio scambio valori
int main()
{
int a,b;
scanf("%d %d", &a, &b);
scambia (a,b);
printf("I valori scambiati sono %d %d", a, b);
return 0;
}
void scambia(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
NON FUNZIONA → il valore dei parametri attuali viene copiato nei parametri formali, la funzione viene eseguita correttamente, ma non vengono scambiati i valori di a e b.
Per far funzionare lo scambio dobbiamo:
int main()
{
int a,b;
scanf("%d %d", &a, &b);
scambia (&a,&b);
printf("I valori scambiati sono %d %d", a, b);
return 0;
}
void scambia(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
Esempio utilizzo sottoprogramma
Codice SENZA sottoprogramma
int main()
{
int i, n, primo = 1;
for (i = 1; i <= 100 && primo; i++)
{
primo = 1;
for (n = 2; n < numero && primo; n++)
if (i % n == 0)
primo = 0;
if (primo == 1)
printf("%d\\n", i);
}
return 0;
}
Codice CON sottoprogramma
int verifica primo (int numero); //Testata o Prototipo
int main()
{
int i, n;
for (i = 1; i <= 100; i++)
if(verifica_primo(i)) //equivale a verifica_primo(i) != 0
printf("%d\\n", i);
return 0;
}
int verifica_primo(int numero)
{
int n, primo = 1;
for (n = 2; n < numero && primo; n++) //primo equivale a primo != 0
if (numero % n == 0)
primo = 0;
return primo;
}
Calcolo del perimetro con le funzioni
#include < math.h>
typedef struct punto
{
float x, y;
}punto;
float dist (punto p1, punto p2)
{
//radice quadrata della somma del quadrato della differenza tra i due punti x e y
return sqrt(pow(p1.x - p2.x,2) + pow (p1.y - p2.y, 2));
}
float perimetro (punto poligono[], int dim)
{
int i;
float p = 0.0;
for (i = 1; i < dim; i++)
p += dist(poligono[i-1], poligono[i]);
//Distanze tra i vertici del poligono in posizione i-1 e i
return p + dist(poligono[0], poligomo[dim-1]); //Distanza tra l'ultimo vertice e il primo
}
int main()
{
punto pol[5];
//Acquisizione dei punti con scanf
printf("Il perimetro e' %2.f", perimetro(pol,5); //%2.f (float con due cifre decimali)
return 0;
}
La libreria math.h ci permette di utilizzare le funzioni:
- (radice quadrata) → double sqrt(double);
- (elevamento a potenza) → double pow (double, double);
In C, quando si utilizza un array come parametro formale, tra le parentesi quadre, non va inserito alcun dato. (vedi l’inizializzazione della funzione perimetro).
Area Poligono scomposto in Triangoli
Utilizzando il teorema di Erone per l’area dei triangoli: A = sqrt(sp(sp-a)(sp-b)(sp-c))*
sp → semi-perimetro; a,b,c → lati; dim == 6.

float erone (punto p1, punto p2, punto p3)
{
punto tri[3];
float sp; //Sempiperimetro
tri[0] = p1;
tri[1] = p2;
tri[2] = p3;
sp = 0.5 * perimetro(tri, 3);
return sqrt(sp*(sp-dist(p1,p2))*(sp-dist(p2,p3))*(sp-dist(p3,p1)));
}
//OPPURE
float erone2(punto p1, punto p2, punto p3)
{
punto tri[3];
float sp,a,b,c; //Sempiperimetro
a = dist(p1,p2);
b = dist(p2,p3);
c = dist(p1,p3)
sp = (a+b+c)/2;
return sqrt(sp*(sp-a)*(sp-b)*(sp-c));
}
//Calcolo area - SOLO con poligoni convessi
float areapoligono(punto polig[], int dim)
{
int i;
float area 0.0;
for (i = 2; i < dim; i++)
area += erone2(polig[0], polig[i-1], polig[i]);
return area;
}
Poligono convesso → Non contiene prolungamenti dei suoi lati. L’angolo interno è compreso tra 0° e 180°
Tipo del risultato e dei parametri
Può essere:
- built-in o user-defined (int, float, char o typedef);
- I sottoprogrammi lavorano sulle variabili indicate come parametri formali nella definizione della funzione, che saranno copie dei valori delle variabili indicate come parametri attuali nell’espressione che invoca la funzione.
- Un puntatore a qualsiasi tipo.
NON può essere un array (return array → NO)
- ma può essere una struct (che contiene degli array);
Perchè si passano come parametri l’indirizzo della prima cella, quindi delle copie di indirizzi di memoria. (I valori nelle celle dell’array NON sono copiati nel parametro formale, SOLO l’indirizzo della prima cella viene copiato nel parametro formale).
L’array come parametro formale quindi, rappresenta una posizione di memoria. E’ quindi equivalente ad un puntatore ad una variabile.
Esempio: somma degli elementi di un vettore
int main()
{
double ris;
double pippo[100];
ris = sum(pippo);
}
//Il tipo array rappresenta una posizione di memoria
double sum (double a[]) //Equivale a -> double sum(double *a)
{
double acc = 0.0;
int i;
for (i = 0; i < 100; i++)
acc += a[i];
return acc;
}
//Oppure se fosse stato definito
typedef double TipoArrayF[100];
//Il prototipo sarebbe potuto essere:
double sum (TipoArrayF a);
Esempio
#include <stdio.h>
void modificaInt(int);
void modificaArray(int[]);
int main()
{
int a = 0, i, v[4] = {0,1,2,3};
printf("a = %d\\n",a); //a = 0
for (i = 0; i < 4; i++)
printf("v[%d] = %d\\n", i, v[i]); //0, 1, 2, 3
modificaInt(a);
modificaArray(v);
printf"a = %d\\n", a); //a rimane 0.
for(i = 0; i < 4; i++)
printf("v[%d] = %d\\n",i,v[i]);//100, 101, 102, 103
return 0;
}
void modificaInt (int i)
{
i += 100;
}
void modificaArray (int a[])
{
int i;
for (i = 0; i < 4; i++)
a[i] += 100;
}
Esempio strcmp
\0 → carattere tappo, ha il codice ASCII che coincide con un byte che ha tutti i bit nulli.
int mystrcmp(char s1[], char s2[])
{
int i = 0;
while (s1[i] == s2[i] && s1[i] != '\\0')
++i; //oppure i++;
return ((int)s1[i] - (int)s2[i]);
}
//OPPURE
int mystrcmp2 (char *s1, char *s2) //Poichè l'array indica un indirizzo di memoria, quindi come un puntatore
{
while (*s1 == *s2 && *s1 != '\\0')
{
s1++;
s2++;
}
return *s1 - *s2;
}
Variabili collettive – confronto
ARRAY (variabili omogenee)
- Non si possono assegnare collettivamente;
- Quando sono passati come parametri a una funzione, il parametro formale è una copia dell’indirizzo della prima cella del vettore indicato come parametro attuale;
- Non possono essere restituiti tramite return.
STRUCT (variabili eterogenee)
- Si possono assegnare collettivamente;
- Quando sono passate come parametri a una funzione, il parametro formale è una copia del valore del parametro attuale;
- Possono essere restituite tramite return.
Sintassi dell’invocazione
Invocazione di una funzione:
- Le espressioni che ne costituiscono gli argomenti vengono valutate e il valore reso disponibile alla funzione;
- La funzione viene eseguita fino al comando return;
- Il valore dell’espressione-funzione è il valore dell’argomento del return che ha causato la terminazione e viene restituita a chi ha invocato il sottoprogramma. Tutte le variabili locali vengono distrutte.
Modello di esecuzione
E’ come se venga creata una nuova macchina dedicata ad eseguire ciascuna funzione, all’atto della sua chiamata.
Ogni macchina dedicata a una funzione ha una sua memoria, per le variabili locali, i valori dei parametri che ricevono, il risultato che restituiscono.
Tale memoria si dice ambiente della funzione o (Record di Attivazione della funzione).
Passaggio parametri attuali
Tutti i parametri in C sono passati per copia. Le variabili passate come parametri a una funzione (parametri attuali), non cambiano valore nell’ambiente del chiamante (parametri passati per valore).
Se si vuole che una funzioni agisca sulle variabili dell’ambiente del chiamante, occorre passare l’indirizzo di tali variabili (parametri passati per locazione o per indirizzo) → passaggio di copie di puntatori.
- Il parametro formale deve essere di tipo puntatore al tipo del parametro attuale di cui si vuole la modifica.
- Nella chiamata si deve passare l’indirizzo (usando &) del parametro attuale da modificare.
- Nel corpo della funzione si usa l’operatore * di dereferenziazione per riferirsi al parametro.
- Una funzione che abbia parametri passati per puntatore e ne modifichi il valore, si dice avere un effetto collaterale (side effect). Questo tipo di passaggio di parametri è sconsigliato.
Esempio NON funzionante
void swap(int p, int q)
{
int temp;
temp = p;
p = q;
q = temp;
}
//Nel main:
int main()
{
int i = 5, j = 7;
swap (i,j);
}
ERRORE → dopo la chiamata del sottoprogramma, i e j non sono cambiate. I valori sono sempre i = 5, j = 7. I valori dei parametri attuali, vengono copiati nei parametri del sottoprogramma (p e q). Al termine del sottoprogramma (p e q) vengono distrutti e (i e j) rimangono invariate.
Esempio Funzionante
void swap(int *p, int *q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
}
//Nel main:
int main()
{
int i = 5, j = 7;
swap (&i, &j);
}
In questo modo i valori dei parametri sono stati passati per indirizzo e alla fine dell’esecuzione del sottoprogramma, (i e j) risulteranno invertiti.
void game (int x, int *y)
{
printf("Begin game: x=%d, y=%d\\n", x, *y);
x = x + 1;
*y = *y + 1;
printf("End game: x=%d, y=%d\\n", x, *y);
}
int main()
{
int i = 0, j = 0;
printf("Begin main: i=%d, j=%d\\n", i, j);
game (i, &j); //Recordi di attivazione
printf("Ending main: i=%d, j=%d\\n", i, j);
return 0;
}
All’uscita da game il valore di i è rimasto lo stesso, mentre quello di j (passato tramite puntatore) è cambiato. Se vogliamo poter modificare il valore di una variabile in modo che resti modificato all’uscita della funzione, occorre passarla tramite un puntatore.
Parametri di tipo array e struct
Per passare a una funzione un parametro di tipo array, occorre passarne l’indirizzo della 1ma cella, perciò di fatto gli array sono sempre passati per indirizzo
- Una funzione NON può restituire un array, ma solo un puntatore a un array (cioè il puntatore al suo primo elemento).
Un parametro di tipo struct si può passare sia per indirizzo sia per valore (anche se la struct contiene campi di tipo array).
- Una funzione può restituire una struct. (Una copia di struct, anche se contiene degli array)
typedef double TipoArray[DIMENSIONE];
//Prototipi di sottoprogramma equivalenti
double sum(TipoArray a, int n);
double sum(double a[], int n);
double sum(double *a, int n);
//n rappresenta la porzione dell'array da considerare valida
//Chiamata del sottoprogramma
sum(V, 50); //v[0] + V[1] + ... + V[49]
sum(&v[5], 7); //V[5] + V[6] + ... + V[11] 7 posizioi dalla posizione iniziale (che è 5)
sum(V+5, 7); //V[5] + V[6] + ... + V[11] 7 posizioi dalla posizione iniziale (che è 5)
double sum(TipoArray a, int n)
{
int i;
double ris = 0.0;
for(i = 0; i < n; i++
ris += a[i];
return ris;
}
Non occorre specificare la dimensione statica degli array (il calcolatore può eseguire il calcolo dello spiazzamento in base al tipo puntato).
- All’interno del sottoprogramma: v[i] = *(v+i)
- Alla macchina per poter calcolare l’espressione v[i] serve conoscere solo sizeof(untipo).
Array multidimensionali
Il prototipo deve essere
void sum(int mat[][N], int lunghezza_righe, int lunghezza_colonne)
N indica il valore massimo del numero di colonne allocate nell’ambiente del chiamante. Mettere il numero di righe nel passaggio della matrice a un sottoprogramma, è superfluo. Dobbiamo specificare tutte le dimensioni, tranne la prima.
Possiamo evitare di mettere
void sum(int mat[N][N], int lunghezza_righe, int lunghezza_colonne)
All’interno del sottoprogramma: mat[i][j] = *( * (mat+i)+j) = * (&mat[0][0] + i * N + j)
Esempio
void sum (int mat[][Y]);
void subtract (int cube[][Y][Z]);
void sum (int *mat);//NON SI PUO' FARE
Copia di stringhe
Copia di un carattere alla volta sino a ‘\0’ (incluso)
void strcopia(char s1[], char s2[]) { int i = 0; if (s1 !=NULL $$ s2 != NULL) { while (s2[i] != '\\0') { s[i] = s2[i]; i++; } s1[i] = '\\0'; } } //OPPURE - Scrittura analoga void strcopia2(char s1[], char s2[]) { while(s1 && s2 && (*(s1++) = *(s2++)) != '\\0'); }
Ordinamento di array – Bubblesort
void bubbleaort(int param[], int size)
{
int i,j;
for (i = 0; i < size; i++)
for(j = i+1; j < size; j++)
if(param[j] < param[i])
swap(¶m[j], ¶m[i]);
}
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
Gestione di numeri complessi
#include <math.h> //sqrt(), fabs() valore assoluto
typedef struct
{
float re;
float im;
}nC;
void printC(nC z)
{
char sre = '+', sim = '+';
if (z.re < 0.0)
sre = '-';
if (z.im < 0.00)
sim = '-';
printf("%c %.2f %c %.2f i", sre, fabs(z.re), sim, fabs(z.im));
}
float modulo (nc z)
{
return sqrt(z.re * z.re + z.im * z.im);
}
nC costruisci(float r, float i)
{
nC temp;
temp.re = r;
temp.im = i;
return temp;
}
nC quadratoC(nC z) //versione funzionale
{
return costruisci (z.re * z.re - z.im * z.im, 2 * z.re * z.im);
}
void quadratoC2 (nC z, nC *ris) //Versione procedurale
{
ris.re = z.re * z.re - z.im * z.im;
ris.im = 2 * z.re * z.im;
}
void leggiC (nC *z)
{
printf("Parte reale : ");
scanf("%f", &(z.re));
printf("Parte immaginaria : ");
scanf("%f", &(z.im));
}
nC leggiC2()
{// versione “funzionale”
nC temp;
printf("Parte reale : ");
scanf( "%f", &(temp.re));
printf("Parte immaginaria : ");
scanf("%f", &(temp.im));
return temp;
}
nC somma( nC z, nC w )
{ // restituisce z+w
return costruisci( z.re+w.re, z.im+w.im );
}
nC prodotto( nC z, nC w )
{
return costruisci( z.re * w.re – z.im * w.im, z.re * w.im – z.im * w.re);
}
nC quoziente( nC z, nC w )
{// restituisce z/w
return costruisci((z.re*w.re + z.im*w.im)/(w.re*w.re + w.im*w.im), (z.im*w.re – z.re*w.im)/(w.re*w.re + w.im*w.im);
}
nC inverso( nC z )
{ // restituisce 1/z
return quoziente(costruisci(1.0, 0.0), z);
}
nC potenzaIntera( nC b, int e )
{ // restituisce be
nC risult = costruisci(1.0, 0.0);
if ( e < 0 )
{
e = – e;
b=inverso(b);
}
for( ; e > 0; e-- )
risult = prodotto(risult, b);
return risult;
}