View Full Version : [C] Strutture,liste e puntatori..
Salve a tutti... ho questo codice fornito dal mio professore di fondamenti..
typedef struct{
char titolo[30];
int video;
int audio;
char trama[255];
} film ;
struct StructLista {
film info;
struct StructLista *next;
} ;
typedef struct StructLista NodoLista;
typedef NodoLista* Lista;
// Inizializza e azzera la lista passata come parametro
void InizLista(Lista *lis){
*lis==NULL;
}
// Inserisce l'elemeno passato come parametro, in testa
void InserisciInTesta(Lista *lis, film elemento){
Lista paux;
paux=malloc(sizeof(NodoLista));
if(paux==NULL) exit(1);
paux->info=elemento;
paux->next=*lis;
*lis=paux;
}
// Restituisce la testa della lista
void TestaLista(Lista lis, film *elem){
if(lis==NULL)exit(1);
*elem=lis->info;
}
Mi chiedo la seguente cosa..
definisco un nuovo tipo -> typedef NodoLista* Lista; <- quindi Lista mi punta all'indirizzo del nodo della lista...
Però nella prima funzione (ad esempio) passo come paramentro -> Lista *lis <-.. questo vuol dire che già Lista è un puntatore che associo ad un'altro puntatore (*lis), quindi il parametro non sarà un puntatore ma il suo contenuto interno, infatti poi uso tale puntatore con l'operatore * per settare il suo indirizzo in null... mi chiedo se non è possibile fare lo stesso nel seguente modo
void InizLista(Lista lis){
lis==NULL;
}
in tale modo lis dovrebbe essere già un puntatore.. potreste spiegarmi bene questa situazione??
Innanzi tutto, == è un operatore di confronto, non di assegnamento. Probabilmente quello che vuoi fare tu è: lis = NULL;
Per quanto riguarda la tua domanda: si, non solo si può fare, ma è anche la via migliore (o meno deviata :) Spesso in C le strutture dati vengono passate per indirizzo per questioni di performance. Ma questo ovviamente non è il tuo caso, quindi non vedo una ragione di chiedere un puntatore ad un puntatore, invece del puntatore stesso.
Innanzi tutto, == è un operatore di confronto, non di assegnamento. Probabilmente quello che vuoi fare tu è: lis = NULL;
Per quanto riguarda la tua domanda: si, non solo si può fare, ma è anche la via migliore (o meno deviata :) Spesso in C le strutture dati vengono passate per indirizzo per questioni di performance. Ma questo ovviamente non è il tuo caso, quindi non vedo una ragione di chiedere un puntatore ad un puntatore, invece del puntatore stesso.
Si è stato un errore di battitura.. era un'assegnamento!! Comunque, perchè il professore ha scelto quella via usando il doppio puntatore(puntatore di puntatore)??
Quello che volevo chiedere è : il suo modo di passare il parametro il mio sono uguali giusto? un puntatore di puntatore annulla il puntatore stesso, quindi tantovale passare solo un puntatore a nodo ed usarlo senza derefenziamento!
Ikon O'Cluster
02-06-2009, 02:11
A mio modo di vedere quel typedef copre il fatto che Lista sia in realtà un puntatore. Se tu usassi come argomento formale della funzione il tipo Lista qualcuno a prima vista (senza aver visto il typedef) potrebbe supporre che il passaggio stia avvenendo per valore. Questo lo indurrebbe a credere che l'assegnamento lis = NULL non abbia effetto sull'argomento attuale. Se tu indichi come argomento formale il tipo Lista* allora chi legge sa che maneggiando il contenuto puntato ottiene effetto anche all'esterno della funzione. Io lo interpreterei come un dettaglio di maggiore leggibilità. Anche io l'avrei fatto così, ha una sua logica: se stai passando per puntatore o per riferimento è perchè vuoi che all'esterno vi siano effetti, se passi per valore no. Se nascondi tutto col typedef non stai facendo una bella cosa! Chi deve leggere il tuo codice, vuole leggerlo non decifrarlo!
Non avevo considerato questo aspetto!! Sicuramente, visto che le funzioni dovrebbe essere scatole chiuse, io (client) non mi devo preoccupare tanto del loro contenuto.. il concetto dovrebbe essere questo
Ho trovato non poche difficoltà a capire quel codice.. ho provato a "revisionarlo" a modo mio...
Ecco i due file:
liste.c
typedef struct{
char titolo[30];
int video;
int audio;
char trama[255];
} film ;
struct StructLista {
film info;
struct StructLista *next;
} ;
typedef struct StructLista NodoLista;
// Inizializza e azzera la lista passata come parametro
void InizLista(NodoLista *lis){
lis=NULL;
if(lis==NULL) printf("\n\nLista inizializzata\n\n");
}
// Inserisce l'elemeno passato come parametro, in testa
void InserisciInTesta(NodoLista *lis, film elemento){
NodoLista *paux;
paux=malloc(sizeof(NodoLista));
if(paux==NULL) exit(1);
paux->info=elemento;
paux->next=lis;
lis=paux;
}
// Restituisce la testa della lista
void TestaLista(NodoLista *lis, film *uelem){
if(lis==NULL)printf("errore.. lista vuota!");
*uelem=lis->info;
}
// Visualizza la lista
void VediLista(NodoLista *lis){
while (lis!=NULL){
printf("%s", lis->info.titolo);
}
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include "liste.c"
#define MENU 200
#define OUT 255
void AcqElemento(film elem){
printf("Titolo film: ");
scanf("%s", elem.titolo);
printf("\nInsersci trama: ");
scanf("%s", elem.trama);
printf("\nQualità video: ");
scanf("%d", &elem.video);
printf("\nQualità audio: ");
scanf("%d", &elem.audio);
}
main(){
NodoLista *plist;
film elem;
film *uelem;
int scelta=MENU;
while (scelta==MENU){
printf("\n\n\t\t################-> Gestione Film <-################");
printf("\n\n 1. Crea la lista");
printf("\n\n 2. Inserisci in testa un'elemento");{}
printf("\n\n 3. Guarda il primo elemento in testa");
printf("\n\n 4. Guarda tutta la lista");
printf("\n\n 0. Esci");
printf("\n\n\n\t\t\t La tua scelta: ");
scanf("%d", &scelta);
switch(scelta){
case 0:
scelta=OUT;
break;
case 1:
InizLista(plist);
scelta=MENU;
break;
case 2:
AcqElemento(elem);
InserisciInTesta(plist, elem);
scelta=MENU;
break;
case 3:
TestaLista(plist, uelem);
scelta=MENU;
break;
case 4:
VediLista(plist);
scelta=MENU;
break;
}
}
}
Però ho dei problemi, nella visualizzazione della lista (solo titolo per ora)e nella testa.. magari mi sfugge qualcosa di importante.. sapreste indicarmelo?
GRAZIE
DanieleC88
02-06-2009, 18:25
Se te l'ha dato un professore quel codice allora stiamo messi male...
Ci sono alcune perle tipo questa:
if(paux==NULL) exit(1);
per le quali non so se ridere o piangere. :eek:
ciao ;)
DanieleC88
02-06-2009, 18:29
#include <stdio.h>
#include <stdlib.h>
#include "liste.c"
Quello non farlo mai, già a partire da progetti che vadano appena oltre il codice di test per una lista ti può creare casini che non vuoi, credimi. :D
Piuttosto metti l'implementazione del codice in un file C separato che compilerai a parte, e le definizioni dei tipi e delle costanti e i prototipi delle funzioni li metterai in un header da includere in giro dove ti serve.
ciao ;)
Quello non farlo mai, già a partire da progetti che vadano appena oltre il codice di test per una lista ti può creare casini che non vuoi, credimi. :D
Piuttosto metti l'implementazione del codice in un file C separato che compilerai a parte, e le definizioni dei tipi e delle costanti e i prototipi delle funzioni li metterai in un header da includere in giro dove ti serve.
ciao ;)
Cosa non va in quella "perla"?mi saresti d'aiuto se mi spiegassi l'errore!
DanieleC88
02-06-2009, 18:57
Un po' di considerazioni su liste.c:
struct StructLista {
film info;
struct StructLista *next;
} ;
Io definirei info come un film *, non un film e basta: film è infatti una struttura (un "bloccone" di memoria) che non può essere "assegnata" direttamente, ma per farlo ti appoggi ad una memcpy() (lento).
typedef struct StructLista NodoLista;
Per lo stesso motivo di prima, un nodo della lista potresti volerlo definire piuttosto come un puntatore alla struttura (quindi uno struct StructLista *), non come una struttura.
void InizLista(NodoLista *lis){
lis=NULL;
if(lis==NULL) printf("\n\nLista inizializzata\n\n");
}
In C tutti i parametri sono passati per valore, per avere un passaggio per riferimento devi ricorrere ai puntatori. In questa funzione stai ricevendo un puntatore ad un nodo della lista, e poi... lo sovrascrivi con un NULL. Pensaci: hai cambiato l'indirizzo contenuto in lis (sovrascrivendone il valore), non il contenuto della memoria che sta nell'indirizzo indicato da lis. Può sembrare intricato, ma se capisci bene questa differenza sei a buon punto nel saper lavorare coi puntatori. :D
Per farlo correttamente avresti dovuto usare:
(*lis) = NULL;
if (*lis == NULL) /* ... */
In questo modo sì che avresti sovrascritto la memoria puntata da lis, non lis stesso (che è passato per valore, le modifiche non sarebbero state propagate alla funzione chiamante). Ma attento, anche così non avrebbe funzionato: perché? Te lo lascio come esercizio. :P
void InserisciInTesta(NodoLista *lis, film elemento){
NodoLista *paux;
paux=malloc(sizeof(NodoLista));
if(paux==NULL) exit(1);
paux->info=elemento;
paux->next=lis;
lis=paux;
}
Quel "exit(1)" che termina l'applicazione senza dire nulla è un po' brutto, soprattutto se fatto in una funzione di utilità che non può (e non deve) effettuare alcun cleanup; io piuttosto preferirei restituire un codice di errore al chiamante, il quale vedrà come preferisce gestire la condizione imprevista.
Infine, riscrivi l'indirizzo di lis senza cambiare i contenuti della memoria a cui punta, come prima dicevo.
void VediLista(NodoLista *lis){
while (lis!=NULL){
printf("%s", lis->info.titolo);
}
}
Qui non entrerai per niente nel ciclo se la lista è nulla (il che è esattamente quanto ti aspetti), ma invece se la lista punta a qualcosa il ciclo risulterà infinito (non modifichi mai lis, non potrà mai diventare NULL se non lo è fin dall'inizio).
ciao :)
DanieleC88
02-06-2009, 19:01
Cosa non va in quella "perla"?mi saresti d'aiuto se mi spiegassi l'errore!
L'ho appena fatto, ma il post era un po' lunghetto, non sono mica Flash. :P (e meno male, al limite preferirei essere JavaFX... battuta tristissima :asd: )
Non ti offendere eh, cerco di aiutare, non di sfottere.
ciao ;)
In effetti rileggendo il mio post mi rendo conto di aver scritto una hazzata :(
Come ha correttamente spiegato Daniele, tu stai si passando un puntatore, ma per valore, non per indirizzo; quindi le modifiche ad essp non si vedranno al di fuori della funzione.
Conosci il passaggio di parametri per indirizzo? A mio avviso sarebbe il modo più pulito di fare la cosa. inoltre noto che hai lo stesso problema nell'ultima riga della funzione di inserimento.
@Daniele: ho sempre saputo che struct = struct e memcpy fossero equivalenti. Perchè la prima via è sbagliata?
DanieleC88
02-06-2009, 19:59
@Daniele: ho sempre saputo che struct = struct e memcpy fossero equivalenti. Perchè la prima via è sbagliata?
Infatti sono equivalenti (però dal C90 in poi, se non erro, nel C "K&R" non mi sembra ci sia l'assegnazione di strutture, qualcuno mi corregga se sparo boiate!), e infatti è proprio per quello che non mi piace. :p
Il fatto è che se ti abitui che puoi "assegnare" una struttura la prendi come fosse un'operazione elementare, quando non è così (è in effetti una copia binaria di un blocco di memoria più o meno grande). Per piccole strutture non è un grosso problema, ma chi ti impedisce questo, per esempio?
struct s_MyBlob
{
short nIndex;
char szName[512];
char szBlobData[2097152];
};
int main()
{
struct s_MyBlob a, b;
int count;
memset(&a, 0, sizeof(struct s_MyBlob));
memset(&b, 0, sizeof(struct s_MyBlob));
for (count = 0; count < 10000; ++count)
{
++(b.nIndex);
a = b;
}
printf("Terminato.");
return 0;
}
In questo codice ci sono, "nascoste", 10000 memcpy() di 2097152 + 512 + 2 byte di dati = più o meno 2MB (se non di più per via del padding) ognuna, ovvero oltre 20000MB di dati spostati a vuoto.
Se invece fosse stato:
struct s_MyBlob
{
short nIndex;
char szName[512];
char szBlobData[16777216];
};
typedef struct s_MyBlob *p_MyBlob;
int main()
{
p_MyBlob a, b;
int count;
a = (p_MyBlob) malloc(sizeof(struct s_MyBlob));
if (!a)
{
fprintf(stderr, " [**] Allocazione di a fallita.\n");
return 1;
}
memset(a, 0, sizeof(struct s_MyBlob));
b = (p_MyBlob) malloc(sizeof(struct s_MyBlob));
if (!b)
{
fprintf(stderr, " [**] Allocazione di b fallita.\n");
return 1;
}
memset(b, 0, sizeof(struct s_MyBlob));
for (count = 0; count < 10000; ++count)
{
p_MyBlob c;
++(b->nIndex);
c = b;
}
free(a);
free(b);
printf("Terminato.");
return 0;
}
ci sarebbero state 10000 assegnazioni semplicemente di un puntatore (tipicamente di 4 byte), quindi 40000 byte = più o meno 39MB di dati spostati a vuoto.
Sì, lo so, sono integralista, non ci posso fare niente. :D
EDIT: tanto per curiosità ho voluto fare un confronto sulla mia macchina (che monta un AMD Athlon™ 64 X2 5200+ con 4GB di RAM, forse non il top ma direi che è abbastanza veloce), il primo programmino con l'assegnazione impiega 16 secondi a terminare, il secondo 100 millisecondi... circa 160 volte più veloce, mica male. :D
DanieleC88
02-06-2009, 20:10
Per piccole strutture non è un grosso problema, ma chi ti impedisce questo, per esempio?
Errata corrige: me lo impedisce la dimensione dello stack. :asd:
In effetti c'ho buttato 32MB di memoria sullo stack, chiaramente il programmino va in segmentation fault. :stordita:
Ikon O'Cluster
03-06-2009, 01:36
Non avevo considerato questo aspetto!! Sicuramente, visto che le funzioni dovrebbe essere scatole chiuse, io (client) non mi devo preoccupare tanto del loro contenuto.. il concetto dovrebbe essere questo
Le funzioni sono scatole chiuse finchè non devi metterci le mani dentro ;) Non si programma solo per chi usa, ma anche per chi modifica :D
vBulletin® v3.6.4, Copyright ©2000-2026, Jelsoft Enterprises Ltd.