Chiarimento sui doppi puntatori del linguaggio C.
Salve a tutti
ho un dubbio, qual'è la necessità di creare funzioni con doppi puntatori? cioè, il puntatore è l'indirizzo di memoria di una variabile (o struttura dati ecc) e ad ogni modifica, la variabile va ad essere modificata...se dovessi usare un puntatore che punta al puntatore che punta alla variabile, che cambia? il succo non è sempre quello?
mi sono posto questa domanda perchè il mio libro fa un esempio di liste chiamando due funzioni (crea il nodo e cancella) passando come argomento il doppio puntatore (quello che punta alla struttura dati). Ho provato ad eseguirlo con un solo puntatore, ma niente da fare...mi da errore, ha proprio bisogno del doppio puntatore!!! ma perchè? se volete vi posto l'esempio
Ha dimenticavo! il tipo della funzione è void, forse è per questo che passa come argomento il doppio puntatore? perche dovrebbe tornare la lista?
gRAZIE!

mi sono posto questa domanda perchè il mio libro fa un esempio di liste chiamando due funzioni (crea il nodo e cancella) passando come argomento il doppio puntatore (quello che punta alla struttura dati). Ho provato ad eseguirlo con un solo puntatore, ma niente da fare...mi da errore, ha proprio bisogno del doppio puntatore!!! ma perchè? se volete vi posto l'esempio
Ha dimenticavo! il tipo della funzione è void, forse è per questo che passa come argomento il doppio puntatore? perche dovrebbe tornare la lista?

Risposte
Ciao Eugvr93 
Domanda più che buona, è un aspetto sul quale tanti fanno molta confusione quando si approcciano al linguaggio. Il fatto è che se ho ad esempio una lista i cui elementi hanno tipo elemento* (metto il primo nome che mi è venuto in mente adesso) e voglio passarla ad una funzione che la modifichi (ad esempio inserisca un solo elemento in coda) dovrò passare alla lista un puntatore ad un oggetto di tipo elemento*, ergo il tipo dell'argomento passato sarà elemento**. Se non passi un elemento con questo tipo salvo altri errori interni alla funzione la stessa esegue le operazioni sulla lista ma una volta che termina la sua esecuzione avrai la tua lista originale senza modifiche (ergo di fatto avviene un passaggio per valore).
Comunque sì, posta pure il tuo esempio che così chiariamo per bene.

Domanda più che buona, è un aspetto sul quale tanti fanno molta confusione quando si approcciano al linguaggio. Il fatto è che se ho ad esempio una lista i cui elementi hanno tipo elemento* (metto il primo nome che mi è venuto in mente adesso) e voglio passarla ad una funzione che la modifichi (ad esempio inserisca un solo elemento in coda) dovrò passare alla lista un puntatore ad un oggetto di tipo elemento*, ergo il tipo dell'argomento passato sarà elemento**. Se non passi un elemento con questo tipo salvo altri errori interni alla funzione la stessa esegue le operazioni sulla lista ma una volta che termina la sua esecuzione avrai la tua lista originale senza modifiche (ergo di fatto avviene un passaggio per valore).
Comunque sì, posta pure il tuo esempio che così chiariamo per bene.
La sintassi di un puntatore è
[INIZIO PICCOLO COMMENTO]
È inoltre importante notare che non vi è un legame tra puntatore a puntatore e matrice paragonabile a quello dei puntatori singoli con gli array. Per capirci
D'altra parte
[FINE PICCOLO COMMENTO]
T * nome_puntatore;Doppio puntatore è un nome lievemente improprio in quanto in realtà si tratta nient'altro che un puntatore ad un puntatore. Insomma un puntatore ad una variabile di tipo puntatore.
[INIZIO PICCOLO COMMENTO]
È inoltre importante notare che non vi è un legame tra puntatore a puntatore e matrice paragonabile a quello dei puntatori singoli con gli array. Per capirci
T1 func(T2 array[], ...)è pressoché equivalente a scrivere
T1 func(T2 *array, ...)e scegli a seconda della notazione che preferisci.
D'altra parte
T1 func(T2 matrice[][N], ...)è molto diverso da
T1 func(T2 **matrice, ...)perché il primo è più simile ad un puntatore a T2 che ad un puntatore a puntatore. Seppur scrivere le cose con un puntatore a T2 possa essere un po' più frustrante. Formalmente sarebbe comunque equivalente ad un puntatore ad un array statico di N elementi di tipo T2. In questo senso è utile sapere che
T * nome[N];è un array statico di N elementi di tipo puntatore mentre
T (* nome)[N];è un puntatore ad un array di N elementi di tipo T.
[FINE PICCOLO COMMENTO]
Ne approfitto per chiedervi: quali errori non consentono la giusta esecuzione dell'esercizio? purtroppo ci sono alcuni problemi logici che non riesco a risolvere :/ comunque grazie ad entrambi per le risposte!!!
in sintesi il mio programma dovrebbe prendere le lettere da un file e indicare per quante volte ricorrono nella frase, poi stessa cosa con le parole..ovviamente è un esercizio sui nodi...
ecco il codice:
//analisi del test
#include
#include
#include
#include
struct analisi{
char caracter;
char world[20];
int value;
struct analisi *dxPtr;
struct analisi *sxPtr;
};//fine struct
typedef struct analisi anal;
//protos
void stampa_p(anal *corPtr);
void stampa_c(anal *corPtr);
void menu();
void aggiungi_c(anal **sPtr, char carattere);
void aggiungi_p(anal **sPtr, char *parola);
anal* occ_parola(anal *corPtr); //parola[20]
anal* occ_carattere(anal *corPtr);
//main
int main(){
anal *start = NULL;
int scelta;
char parola[20];
menu();
fflush(stdin);
scanf("%d", &scelta);
while (scelta != 3){
switch (scelta){
case 1:
start = occ_carattere(start);
stampa_c(start);
break;
case 2:
start = occ_parola(start);
stampa_p(start);
break;
default :
printf("Hai sbagliato.\n\n");
break;
}//fine switch
menu();
fflush(stdin);
scanf("%d", &scelta);
}//while
system("PAUSE");
}//fine main
//------------------------------------
void menu(){
printf("menu: 1) occorrenza carattere, 2) occorrenza parola, 3) esci.\n? ");
}//fine menu
//-------------------
void aggiungi_p(anal **sPtr, char *parola){
anal *corPtr;
if ( *sPtr == NULL){
*sPtr = (anal*)malloc(sizeof(struct analisi));
if ( *sPtr == NULL){
printf("Errore di memoria.\n");
}
else{
corPtr = *sPtr;
strcpy(corPtr->world, parola);
corPtr->value = 1;
corPtr->dxPtr = NULL;
corPtr->sxPtr = NULL;
}
}
else{
if ( strcmp(parola, corPtr->world) > 0){
aggiungi_p(&corPtr->dxPtr, parola);
}
else if ( strcmp(parola, corPtr->world) < 0){
aggiungi_p(&corPtr->sxPtr, parola);
}
else
corPtr->value++;
}
}
//----------------------------------------------------
void aggiungi_c(anal **sPtr, char carattere){
if ( *sPtr == NULL){
*sPtr = (anal*)malloc(sizeof(struct analisi));
if ( (*sPtr) == NULL){
printf("Errore di memoria.\n");
}
else{
printf("\n NUOVO NODO \n");
(*sPtr)->caracter= carattere;
(*sPtr)->value = 1;
(*sPtr)->dxPtr = NULL;
(*sPtr)->sxPtr = NULL;
}
}
else{printf("\nNELLE SCELTE\n");
if (carattere < (*sPtr)->caracter ){
printf("\n1 SCELTE\n");
aggiungi_c(&((*sPtr)->sxPtr), carattere);
}
else if ( carattere > (*sPtr)->caracter){
printf("\n2 SCELTE\n");
aggiungi_c(&((*sPtr)->dxPtr), carattere);
}
else{
(*sPtr)->value++;
printf("\n 3 SCELTE\n");
}
}
}
//----------------------------------------------------
anal* occ_parola(anal *corPtr){
FILE *fPtr;
char parola[20];
if ( (fPtr = fopen("analisi.txt","r")) == NULL){
printf("Errore di apertura file.\n\n");
}
else {
printf("Aperto senza problemi.\n");
while ( !feof(fPtr)){
fscanf(fPtr,"%s",parola);
parola[0] = tolower(parola[0]);
aggiungi_p(&corPtr, parola);
}
}
return corPtr;
}
//----------------------------------------
anal* occ_carattere(anal *corPtr){
FILE *fPtr;
char c;
if ( (fPtr = fopen("analisi.txt", "r")) == NULL){
printf("Errore di apertura file.\n\n");
}
else {
printf("Aperto senza problemi.\n");
while ( !feof(fPtr)){
fscanf(fPtr, "%c", &c);
c = tolower(c);
// printf("%c", c);
if (c >= 'a' && c <= 'z'){
// printf("\n%c", c);
aggiungi_c(&corPtr, c);
}
}
}
return corPtr;
}
//--------------------------------------------
void stampa_c(anal *corPtr){
printf("\n");
while (corPtr != NULL){
stampa_c(corPtr->sxPtr);
printf("%c: %d\n",corPtr->caracter, corPtr->value);
stampa_c (corPtr->dxPtr);
}
}
//---------------------------------------------
void stampa_p(anal *corPtr){
printf("\n");
while (corPtr != NULL){
stampa_p(corPtr->sxPtr);
printf("%s: %d\n",corPtr->world, corPtr->value);
stampa_p(corPtr->dxPtr);
}
}

in sintesi il mio programma dovrebbe prendere le lettere da un file e indicare per quante volte ricorrono nella frase, poi stessa cosa con le parole..ovviamente è un esercizio sui nodi...
ecco il codice:
//analisi del test
#include
#include
#include
#include
struct analisi{
char caracter;
char world[20];
int value;
struct analisi *dxPtr;
struct analisi *sxPtr;
};//fine struct
typedef struct analisi anal;
//protos
void stampa_p(anal *corPtr);
void stampa_c(anal *corPtr);
void menu();
void aggiungi_c(anal **sPtr, char carattere);
void aggiungi_p(anal **sPtr, char *parola);
anal* occ_parola(anal *corPtr); //parola[20]
anal* occ_carattere(anal *corPtr);
//main
int main(){
anal *start = NULL;
int scelta;
char parola[20];
menu();
fflush(stdin);
scanf("%d", &scelta);
while (scelta != 3){
switch (scelta){
case 1:
start = occ_carattere(start);
stampa_c(start);
break;
case 2:
start = occ_parola(start);
stampa_p(start);
break;
default :
printf("Hai sbagliato.\n\n");
break;
}//fine switch
menu();
fflush(stdin);
scanf("%d", &scelta);
}//while
system("PAUSE");
}//fine main
//------------------------------------
void menu(){
printf("menu: 1) occorrenza carattere, 2) occorrenza parola, 3) esci.\n? ");
}//fine menu
//-------------------
void aggiungi_p(anal **sPtr, char *parola){
anal *corPtr;
if ( *sPtr == NULL){
*sPtr = (anal*)malloc(sizeof(struct analisi));
if ( *sPtr == NULL){
printf("Errore di memoria.\n");
}
else{
corPtr = *sPtr;
strcpy(corPtr->world, parola);
corPtr->value = 1;
corPtr->dxPtr = NULL;
corPtr->sxPtr = NULL;
}
}
else{
if ( strcmp(parola, corPtr->world) > 0){
aggiungi_p(&corPtr->dxPtr, parola);
}
else if ( strcmp(parola, corPtr->world) < 0){
aggiungi_p(&corPtr->sxPtr, parola);
}
else
corPtr->value++;
}
}
//----------------------------------------------------
void aggiungi_c(anal **sPtr, char carattere){
if ( *sPtr == NULL){
*sPtr = (anal*)malloc(sizeof(struct analisi));
if ( (*sPtr) == NULL){
printf("Errore di memoria.\n");
}
else{
printf("\n NUOVO NODO \n");
(*sPtr)->caracter= carattere;
(*sPtr)->value = 1;
(*sPtr)->dxPtr = NULL;
(*sPtr)->sxPtr = NULL;
}
}
else{printf("\nNELLE SCELTE\n");
if (carattere < (*sPtr)->caracter ){
printf("\n1 SCELTE\n");
aggiungi_c(&((*sPtr)->sxPtr), carattere);
}
else if ( carattere > (*sPtr)->caracter){
printf("\n2 SCELTE\n");
aggiungi_c(&((*sPtr)->dxPtr), carattere);
}
else{
(*sPtr)->value++;
printf("\n 3 SCELTE\n");
}
}
}
//----------------------------------------------------
anal* occ_parola(anal *corPtr){
FILE *fPtr;
char parola[20];
if ( (fPtr = fopen("analisi.txt","r")) == NULL){
printf("Errore di apertura file.\n\n");
}
else {
printf("Aperto senza problemi.\n");
while ( !feof(fPtr)){
fscanf(fPtr,"%s",parola);
parola[0] = tolower(parola[0]);
aggiungi_p(&corPtr, parola);
}
}
return corPtr;
}
//----------------------------------------
anal* occ_carattere(anal *corPtr){
FILE *fPtr;
char c;
if ( (fPtr = fopen("analisi.txt", "r")) == NULL){
printf("Errore di apertura file.\n\n");
}
else {
printf("Aperto senza problemi.\n");
while ( !feof(fPtr)){
fscanf(fPtr, "%c", &c);
c = tolower(c);
// printf("%c", c);
if (c >= 'a' && c <= 'z'){
// printf("\n%c", c);
aggiungi_c(&corPtr, c);
}
}
}
return corPtr;
}
//--------------------------------------------
void stampa_c(anal *corPtr){
printf("\n");
while (corPtr != NULL){
stampa_c(corPtr->sxPtr);
printf("%c: %d\n",corPtr->caracter, corPtr->value);
stampa_c (corPtr->dxPtr);
}
}
//---------------------------------------------
void stampa_p(anal *corPtr){
printf("\n");
while (corPtr != NULL){
stampa_p(corPtr->sxPtr);
printf("%s: %d\n",corPtr->world, corPtr->value);
stampa_p(corPtr->dxPtr);
}
}
Sicuramente c'è
A me il compilatore dà vari warning. Per esempio non usi la variabile parola nel main e usi corPtr senza averla inizializzata in aggiungi_p. Ci sono poi cose minori come conversioni da int a char basate sul fatto che molte funzioni per char ritornano in realtà int (per gestire gli errori per esempio). Dato i warning non penso funzioni.
Trovo comunque il design discutibile. Hai una struct che ti destisce due problemi diversi ed indipendenti. Inoltre usi una struttura dati inutilmente inefficiente per i caratteri (mentre meno problematica per le parole)[nota]Un array di char usa meno spazio in memoria ed è molto più efficiente[/nota]. Chiami una funzione menu sono per chiamare un printf (quanto tra l'altro un puts andrebbe benissimo). Usi fscanf per i caratteri quando sarebbe meglio non farlo (usare getc è meglio[nota]Il metodo probabilmente ottimale è caricare i file in blocchi più grandi con fgets e leggerlo come un array[/nota]). E ovviamente lo fai anche per le stringhe, rischiando un memory overrun (leggi le note per i caratteri). Usi fflush(stdin) anche se lo standard C afferma che ha un comportamento indefinito. Usi feof come condizione per fermare il ciclo di lettura quando sarebbe meglio usare i valori di ritorno delle varie funzioni. Usi un inutile system("PAUSE") nato solo perché i prof hanno la mania di insegnare un IDE scadente.
Alcuni commenti legati alle cose che dovresti evitare in un codice C e che tu usi abitualmente (è un po' vecchio ma non è datato):
http://www.gidnetwork.com/b-57.html fflush(stdin)
http://www.gidnetwork.com/b-58.html feof()
http://www.gidnetwork.com/b-61.html system("PAUSE")
http://www.gidnetwork.com/b-59.html scanf()
http://www.gidnetwork.com/b-60.html scanf() - caratteri
http://www.gidnetwork.com/b-63.html scanf() - numeri (questo è normale e salvo casi strani dovrebbe funzionare)
http://www.gidnetwork.com/b-64.html conclusioni su scanf()
P.S.: Hanno inoltre aggiunto due standard del C dal '89 (cioè la data in cui è stato scritto lo standard ANSI che ancora molti insegnano e usano ignorando i nuovi standard).
P.S.: Usa il tag
void menu();da trasformare in
void menu(void);
A me il compilatore dà vari warning. Per esempio non usi la variabile parola nel main e usi corPtr senza averla inizializzata in aggiungi_p. Ci sono poi cose minori come conversioni da int a char basate sul fatto che molte funzioni per char ritornano in realtà int (per gestire gli errori per esempio). Dato i warning non penso funzioni.
Trovo comunque il design discutibile. Hai una struct che ti destisce due problemi diversi ed indipendenti. Inoltre usi una struttura dati inutilmente inefficiente per i caratteri (mentre meno problematica per le parole)[nota]Un array di char usa meno spazio in memoria ed è molto più efficiente[/nota]. Chiami una funzione menu sono per chiamare un printf (quanto tra l'altro un puts andrebbe benissimo). Usi fscanf per i caratteri quando sarebbe meglio non farlo (usare getc è meglio[nota]Il metodo probabilmente ottimale è caricare i file in blocchi più grandi con fgets e leggerlo come un array[/nota]). E ovviamente lo fai anche per le stringhe, rischiando un memory overrun (leggi le note per i caratteri). Usi fflush(stdin) anche se lo standard C afferma che ha un comportamento indefinito. Usi feof come condizione per fermare il ciclo di lettura quando sarebbe meglio usare i valori di ritorno delle varie funzioni. Usi un inutile system("PAUSE") nato solo perché i prof hanno la mania di insegnare un IDE scadente.
Alcuni commenti legati alle cose che dovresti evitare in un codice C e che tu usi abitualmente (è un po' vecchio ma non è datato):
http://www.gidnetwork.com/b-57.html fflush(stdin)
http://www.gidnetwork.com/b-58.html feof()
http://www.gidnetwork.com/b-61.html system("PAUSE")
http://www.gidnetwork.com/b-59.html scanf()
http://www.gidnetwork.com/b-60.html scanf() - caratteri
http://www.gidnetwork.com/b-63.html scanf() - numeri (questo è normale e salvo casi strani dovrebbe funzionare)
http://www.gidnetwork.com/b-64.html conclusioni su scanf()
P.S.: Hanno inoltre aggiunto due standard del C dal '89 (cioè la data in cui è stato scritto lo standard ANSI che ancora molti insegnano e usano ignorando i nuovi standard).
P.S.: Usa il tag
[code][/code]
Per completezza sul mio commento sull'algoritmo ti allego la risoluzione del tuo esercizio (per la parte sui caratteri) che sfrutta il metodo che ti ho detto. Sono stato attento a renderlo puramente ANSI C.
Ovviamente lavorare sulle parole è più lungo.
#include <stdio.h> #include <ctype.h> #define NUM_LETTERE ('z' - 'a' + 1) void calcola_occorrenze(FILE *, unsigned [NUM_LETTERE]); /* gli array statici sono passati per riferimento */ void stampa_risultati(unsigned const [NUM_LETTERE]); /* gli array statici sono passati per riferimento */ int main(void) { FILE * fin = fopen("analisi.txt", "r"); if ( fin == NULL ) { perror("fopen()"); } else { unsigned cont_lettere[NUM_LETTERE] = { 0 }; calcola_occorrenze(fin, cont_lettere); fclose(fin); /* non ho più bisogno del file */ stampa_risultati(cont_lettere); } } void calcola_occorrenze(FILE * f, unsigned ris[NUM_LETTERE]) { char buffer[1024]; while(fgets(buffer, sizeof buffer, f) != NULL ) { int i = 0; char c; while( (c = buffer[i++]) != '\0') { if(isalpha(c)) { c = tolower(c); ++ris[c - 'a']; } } } if( ferror(f) ) { perror("fgets()"); } } void stampa_risultati(unsigned const ris[NUM_LETTERE]) { int i = 0; for(; i != NUM_LETTERE; ++i) { char const c = i + 'a'; /* ignoro lo warning perché conosco come sono definiti NUM_LETTERE e i */ printf(" %c - %u \n", c, ris[i]); } }
Ovviamente lavorare sulle parole è più lungo.