Eccoci di nuovo qua!
Proseguiamo ad esplorare il mondo dei puntatori.
Sommario
- Soluzione esercizio Lezione 5
- Funzioni: variabili passate per valore e per riferimento
- scanf
- Array e funzioni
- Parametri di ingresso del main argc ed *argv[]
- Riassunto
- Esercizi
1 Soluzione esercizio Lezione 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
#include <stdio.h> #include <stdlib.h> /********************************* ** * ** i | ** * ** j \ ** *---* ** k **********************************/ int main() { int ***cubo; int contatore=0; int i,j,k; cubo = (int ***) malloc (sizeof(int **) * 3); for(i=0; i<3; i++) { cubo[i] = (int **) malloc (sizeof(int *) * 3); for(j=0; j<3; j++) cubo[i][j] = (int *) malloc (sizeof(int) * 3); } for (i=0; i<3; i++) for (j=0; j<3; j++) for (k=0; k<3; k++) cubo[i][j][k] = contatore++; for(i=0; i<3; i++) { printf("i = %d\n",i); for(j=0; j<3; j++) { for (k=0; k<3; k++) { printf("%3d ",cubo[i][j][k]); } printf("\n"); } } for(j=0; j<3; j++) { printf("j = %d\n",j); for(i=0; i<3; i++) { for (k=0; k<3; k++) { printf("%3d ",cubo[i][j][k]); } printf("\n"); } } for(k=0; k<3; k++) { printf("k = %d\n",k); for(i=0; i<3; i++) { for (j=0; j<3; j++) { printf("%3d ",cubo[i][j][k]); } printf("\n"); } } cubo = (int ***) malloc (sizeof(int **) * 3); for(i=0; i<3; i++) { cubo[i] = (int **) malloc (sizeof(int *) * 3); for(j=0; j<3; j++) cubo[i][j] = (int *) malloc (sizeof(int) * 3); } for(i=0; i<3; i++) { for(j=0; j<3; j++) { free(cubo[i][j]); } free(cubo[i]); } free(cubo); return 0; } /************************* Output: i = 0 0 1 2 3 4 5 6 7 8 i = 1 9 10 11 12 13 14 15 16 17 i = 2 18 19 20 21 22 23 24 25 26 j = 0 0 1 2 9 10 11 18 19 20 j = 1 3 4 5 12 13 14 21 22 23 j = 2 6 7 8 15 16 17 24 25 26 k = 0 0 3 6 9 12 15 18 21 24 k = 1 1 4 7 10 13 16 19 22 25 k = 2 2 5 8 11 14 17 20 23 26 ***********************/ |
2 Funzioni: variabili passate per valore e per riferimento
Ebbene, vediamo tramite un esempio in cosa consiste il passaggio per valore.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/*********************** esempio6_1.c Passaggio di una variabile ad una funzione per valore arkkimede aprile 2021 ************************/ #include <stdio.h> int quadrato(int b); // Prototipo della funzione int main() { int a = 2; int y; y = quadrato(a); printf("a = %d\n",a); printf("y = %d\n",y); } // Funzione che effettua il quadrato di un intero int quadrato(b) int b; // Notazione utile quando si cerca di comprendere i puntatori { b*=b; return b; } /************************* Output: a = 2 y = 4 *************************/ |
- la variabile passata alla funzione ritorna nel main uguale a se stessa
- la subroutine ha infatti lavorato su di copia
- notazione nella funzione valida ma utile principalmente nel caso dei puntatori
A livello di memoria abbiamo quanto rappresentato in Fig1:
Fig1 – Passaggio di una variabile per valore
le variabili sono totalmente separate.
Le cose sono differenti nel caso del passaggio per riferimento:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/*********************** esempio6_2.c Passaggio di una variabile ad una funzione per riferimento arkkimede aprile 2021 ************************/ #include <stdio.h> void valorizzatore(int *x); // Prototipo della funzione int main() { int a=0; printf("a prima della funzione = %d\n",a); valorizzatore(&a); printf("a dopo della funzione = %d\n",a); } // Funzione che valorizza una variabile void valorizzatore(x) int *x; // Notazione alternativa utile per comprendere i puntatori { *x = 5; return; } /************************* Output: a prima della funzione = 0 a dopo della funzione = 5 *************************/ |
Questa volta a livello di memoria cosa è successo?
Fig2 – Passaggio di una variabile per riferimento
Dalla Fig2 si vede che il puntatore x (notazione alternativa usata nel codice) coincide con l’indirizzo di a e di conseguenza *x sarà uguale ad a
In altre parole, come dimostra anche l’esempio, una funzione può anche restituire uscite, oltre che tramite l’istruzione return, anche tramite i parametri di ingresso, a patto di usare dei puntatori, per il meccanismo appena illustrato.
3 scanf
Arrivati a questo punto allora non stupirà vedere come funziona l’istruzione scanf (e tutte le sue analoghe) nella lettura di parametri di input.
Fino ad ora abbiamo solo stampato stringhe e grandezze numeriche, adesso è arrivato il momento di leggere i nostri input.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
/*********************** esempio6_3.c scanf arkkimede aprile 2021 ************************/ #define STR_LEN 50 #include <stdio.h> #include <stdlib.h> int main() { int a; float g; char *str; str = (char *)malloc(sizeof(char)*STR_LEN); if ( str == NULL ) { printf("Problemi di memoria\n"); exit(1); } printf("Introduci un intero: "); scanf("%d",&a); printf("Introduci un float: "); scanf("%f",&g); printf("Introduci una stringa: "); scanf("%s",str); printf("\n\n"); printf("Hai introdotto l'intero: %d\n",a); printf("Hai introdotto il float: %f\n",g); printf("Hai introdotto la stringa: %s\n",str); printf("\n\n"); for ( int i = 0; i < STR_LEN; i++ ) str[i] = '\0'; sprintf(str,"12 78.4 1289.5"); sscanf(str,"%d %*f %f",&a,&g); // %*f consente di saltare uno degli input printf("Hai letto dalla stringa l'intero %d ed il float %f\n",a,g); free(str); return 0; } /************************* Output: Introduci un intero: 10 Introduci un float: 23.15 Introduci una stringa: Milano Hai introdotto l'intero: 10 Hai introdotto il float: 23.150000 Hai introdotto la stringa: Milano Hai letto dalla stringa l'intero 12 ed il float 1289.500000 *************************/ |
- Come per il passaggio di variabili per riferimento, la scanf necessita l’indirizzo della variabile in input
- Come per la printf bisogna specificare il formato di lettura.
- Si noti come nel caso della stringa non serve & perché il nome della stringa stessa è un puntatore (linea 14)
- Come esiste la sprintf, analogamente c’è la sscanf che agisce sulle stringhe
- Le stringhe in ingresso alla scanf non devono avere spazi: la lettura infatti si arresta quando ne incontra uno, (vedremo come superare questo inconveniente con un’altra funzione)
- Con %*formato è possibile saltare la lettura di un elemento presente.
4 Array e funzioni
Una condizione molto particolare si può generare quando si usano array nelle funzioni.
Come prima configurazione consideriamo un array allocato nel main e passato ad una subroutine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/*********************** esempio6_4.c array passato a subroutine arkkimede aprile 2021 ************************/ #define STR_LEN 50 #include <stdio.h> #include <stdlib.h> void sub(int *y, int n); int main() { int *x, nElem=4; x = (int *)malloc(sizeof(int)*nElem); if ( x == NULL ) { printf("Problemi di memoria\n"); exit(1); } sub(x, nElem); for(int i=0; i<nElem; i++) printf("x[%d] = %4d\n",i,x[i]); free(x); return 0; } void sub(y, n) int *y; int n; { for(int i=0; i<n; i++) y[i] = i*10; return; } /************************* Output: x[0] = 0 x[1] = 10 x[2] = 20 x[3] = 30 *************************/ |
Quindi quello che viene allocato nel main può tranquillamente essere trattato nella funzione a patto di passare il puntatore al vettore.
A livello di memoria la situazione è quella di Fig3
Fig3 – Array passato a funzione
in cui i due puntatori si portano a coincidere.
Avevamo detto che era possibile restituire grandezze tramite i parametri di ingresso alle funzioni: vediamo allora il caso il cui l’array è allocato nella subroutine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
/*********************** esempio6_5.c array generato da subroutine arkkimede aprile 2021 ************************/ #define STR_LEN 50 #include <stdio.h> #include <stdlib.h> void MakeArray(int **y, int n); int main() { int *x, nElem=3; MakeArray(&x, nElem); for(int i=0; i<nElem; i++) printf("x[%d] = %4d\n",i,x[i]); free(x); return 0; } void MakeArray(y, n) int **y; int n; { int *tmp; tmp = (int *) malloc (sizeof(int) * n); if ( tmp == NULL ) { printf("Problemi di memoria\n"); exit(1); } tmp[0] = 89; tmp[1] = 100; tmp[2] = 12; *y = tmp; return; } /************************* Output: x[0] = 89 x[1] = 100 x[2] = 12 *************************/ |
Prima di analizzare il codice per capire come funziona, chiariamo cosa è un puntatore doppio.
Fig4 – Puntatore doppio
In Fig4 è rappresentata la situazione.
Se dichiariamo int **y, a livello di memoria, l’indirizzo sarà y che avrà come cella di memoria *y che in effetti è un altro indirizzo che punta a *yy.
Adesso esaminiamo le varie fasi in cui possiamo suddividere il nostro codice:
- Inizialmente, nel main, il puntatore x non è stato inizializzato quindi l’unica grandezza rilevante è il suo indirizzo &x linea 13
- Nella linea 15, in cui passiamo l’indirizzo di x, di fatti stiamo facendo qualcosa di simile alla Fig4, stiamo usando un puntatore doppio:
- &x ≡ y
- x ≡ *y
- *x ≡ **y
- Per il punto 2 allora, nella chiamata alla funzione effettuiamo la seguente uguaglianza: &x=y (tramite la notazione alternativa tale uguaglianza appare evidente)
- Nella funzione vi è anche il puntatore tmp che si alloca come fatto precedentemente e si valorizzano le varie posizioni.
- Ora tmp che è un puntatore singolo, devo uguagliarlo ad un puntatore singolo che consenta di ritornare nel main e questo è *y (linea 39) e si noti che in questo modo l’indirizzo di x è stato preservato (&x).
- Nel main, y=&x, *y=tmp e quindi x[0] = tmp[0], x[1] = tmp[1], etc
Tutto questo non è obbligatorio, come dimostra l’esempio6_6.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/*********************** esempio6_6.c array generato da subroutine restituito da return arkkimede aprile 2021 ************************/ #define STR_LEN 50 #include <stdio.h> #include <stdlib.h> int *MakeArray(int n); int main() { int *x, nElem=3; x = MakeArray(nElem); for(int i=0; i<nElem; i++) printf("x[%d] = %4d\n",i,x[i]); free(x); return 0; } int *MakeArray(n) int n; { int *y; y = (int *) malloc (sizeof(int) * n); if ( y == NULL ) { printf("Problemi di memoria\n"); exit(1); } y[0] = 89; y[1] = 100; y[2] = 12; return y; } /************************* Output: x[0] = 89 x[1] = 100 x[2] = 12 *************************/ |
5 Parametri di ingresso del main argc ed *argv[]
Sino ad ora la funzione main l’abbiamo scritta sempre senza parametri di ingresso.
Esistono però due ingressi per la funzione principale che consentono di passare degli input al nostro programma in fase di esecuzione.
La più generica chiamata della funzione principale è: int main(int argc, char *argv[]):
- argc indica il numero di parametri che sono stati passati eseguendo il comando
- argv[] è un vettore di stringhe che contiene tutti i parametri passati, ricordando che argv[0] rappresenta il nome dell’eseguibile
Vediamo un esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/*********************** esempio6_7.c ingressi di main arkkimede aprile 2021 ************************/ #include <stdio.h> int main(int argc, char *argv[]) { printf("argc = %d\n",argc); for(int i=0; i<argc; i++) printf("argv[%d] = %s\n",i,argv[i]); } /************************* Output eseguendo ./esempio6_7 primo secondo terzo argc = 4 argv[0] = ./esempio6_7 argv[1] = primo argv[2] = secondo argv[3] = terzo *************************/ |
Appare evidente allora come valutando il valore della variabile argc, è possibile tentare una prima verifica dei parametri di ingresso del codice.
Supponendo di voler valutare il valore dell’ordinata di una retta, di cui si conosca il coefficiente angolare (m) ed il termine noto (q), passate per il punto xP (l’equazione della retta è y=m*x + q e quindi yP = m*xP + q):
Questo tipo di test protegge dagli errori più grossolani.
Per verificare eventuali errori nei parametri di ingresso, un’analisi molto più approfondita sarebbe necessaria, ma in questa sede si voleva solo proporre un possibile uso ragionevole di argc ed argv.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/*********************** esempio6_8.c Verifica parametri di ingresso arkkimede aprile 2021 ************************/ #include <stdio.h> #include <stdlib.h> void uso(char argv[0]); int main(int argc, char *argv[]) { if(argc != 4) uso(argv[0]); float m, q, xP, yP; sscanf(argv[1],"%f",&m); sscanf(argv[2],"%f",&q); sscanf(argv[3],"%f",&xP); yP = m*xP + q; printf("yP = %f\n",yP); } void uso(char *nomeEseguibile) { printf("Uso: %s coeffiienteAngolare termineNoto xDelPunto\n",nomeEseguibile); exit(1); } /************************* Output ./esempio6_8 Uso: ./esempio6_8 coeffiienteAngolare termineNoto xDelPunto ./esempio6_8 1. 3. Uso: ./esempio6_8 coeffiienteAngolare termineNoto xDelPunto ./esempio6_8 1. 3. 2. yP = 5.000000 *************************/ |
Terminiamo qui questa carrellata sui puntatori.
Alcuni aspetti non sono stati riportati (come per esempio l’aritmetica dei puntatori) ma si spera che le principali informazioni siano presenti. Come già detto nella prima lezione, lo scopo di questo corso non è fare una trattazione a livello universitario delle caratteristiche di un linguaggio di programmazione, ma fornire i principali strumenti per poter affrontare la scrittura di un codice. L’esperienza e tutti i casi che incontrerete faranno il resto e ricordate: Per quanto originali voi siate, sicuramente il vostro problema sarà già capitato a qualcun altro. Usando le keyword appropriate allora cercate in rete. Troverete sicuramente la soluzione, ma per comprenderla, tutto quello che avrete visto in queste pagine sarà indispensabile.
6 Riassunto
- Una variabile, passata ad una funzione per valore, non sarà modificata, la funzione ne fa una copia che usa al suo interno, lasciando inalterato l’ingresso
- Una variabile invece passata per riferimento (indirizzo) sarà modificata. L’indirizzo sarà preservato e la funzione, tramite un puntatore agirà sulla cella di memoria della variabile in ingresso, modificandola. Questo è il principio su cui si basa, in generale, la restituzione di output tramite i parametri di ingresso.
- Sulla base di questi principi funziona la scanf (e sua variante che lavora sulle stringhe sscanf) che consente di leggere i parametri immessi tramite la tastiera
- Un array passato ad una funzione è un particolare caso di passaggio per riferimento, infatti il contenuto del vettore potrà essere modificato dalla funzione stessa
- Caso particolare è quello di allorare un vettore in una subroutine e restituirlo tramite i parametri di ingresso. Nel caso di un vettore di n dimensioni, sarà necessario ricorrere ad un puntatore di tipo n+1 (ad esempio vettore monodimensionale, puntatore doppio, vettore bidimensionale, puntatore triplo, etc..)
- In ogni caso questo procedere non è un obbligo, si può sempre restituire l’array tramite l’istruzione return
- La funzione main, prevede l’uso, non obbligatorio, di due parametri di ingresso argc ed *argv[]
- Il primo conta i parametri di ingresso considerando come parametro anche il nome dell’eseguibile
- il secondo è un vettore di stringhe in cui in posizione 0 c’è il nome dell’eseguibile e nelle posizioni seguenti gli eventuali parametri passati da riga di comando
- Analizzando queste due variabili è possibile preservare da eventuali errori commessi in fase di lancio del programma
7 Esercizi
- Provate a scrivere un programma con due funzioni a cui passate variabili per valore e riferimento e verificatene il funzionamento
- Provate a leggere e stampare dati immessi tramite tastiera
- Modificate array allocati nel main in subroutine (anche multidimensionali)
- Seguendo la falsa riga dell’esempio6_5, allocate e restituite tramite parametri di ingresso un array bidimensionale (la soluzione sarà fornita la prossima lezione)
- Giocate con argc ed argv, rendendo più professionali i vostri programmi.
Ed anche questa settimana è tutto.
Qui il forum di supporto al corso.
Se vuoi restare aggiornato, seguici anche sui nostri social: Facebook, Twitter, Youtube
Se vuoi anche trovare prodotti e accessori Raspberry Pi in offerta, seguici anche su Telegram !!