Risolto Creazione Liste [C]

Cingetorge

Utente Iron
22 Gennaio 2023
31
5
2
12
salve a tutti...
Nella creazione di una lista come da esempio sotto riportato, dal momento che, se ho ben capito, NULL non fa puntare il puntatore a niente, la 9° riga "node_t *head = NULL;"non equivalrebbe a "node_t *head;". Se no, quale sarebbe la differenza tra le due scritture? grazie
C:
// definizione della struttura nodo
typedef struct node {
    int val; // valore
    struct node * next; // puntatore alla struttura successiva
} node_t;

int main()
{
    node_t *head = NULL; // creazione del puntatore
    head = (node_t *) malloc(sizeof(node_t)); // inizializzazione del puntatore e allocazione in memoria di un nodo

    head->val = 1;      // assegno un valore al nodo
    head->next = NULL;  // marco il nodo come ultimo nodo della lista

    printf("%d\n", head->val);


}
 
Nella creazione di una lista come da esempio sotto riportato, dal momento che, se ho ben capito, NULL non fa puntare il puntatore a niente, la 9° riga "node_t *head = NULL;"non equivalrebbe a "node_t *head;". Se no, quale sarebbe la differenza tra le due scritture?
NULL ha un valore ben preciso, che sarebbe 0. Una variabile non inizializzata, che sia puntatore o altro, ha un valore non ben definito. Quando si parla di comportamenti non ben definiti (undefined behaviors) vuol dire che il compilatore può scegliere cosa fare. Nel tuo caso se compili in debug (ottimizzazioni disabilitate) quella variabile potrebbe valere sempre zero, mentre se compili in release (ottimizzazioni abilitate) solitamente il valore cambia ad ogni esecuzione perché si tiene buono il valore che quel'area di memoria aveva in precedenza. Non si sa esattamente cosa succede perché le variabili non inizializzate sono undefined behaviors.

Nel tuo caso NULL viene usato come placeholder per indicare che un certo nodo non ha un successore. Al posto di NULL potresti usare qualsiasi altro valore e il tuo programma sarebbe altrettanto corretto, però NULL è il valore più naturale per rappresentare il concetto di "non punta a niente".
 
  • Love
Reazioni: DanyDollaro
Per indicare che quel nodo non ha un sucessore, non viene mica indicata con la 13ma riga "head->next = NULL;" ?
Esatto. Se rimuovi la linea 13 non vai più a dire "non punta a niente", ma vai a dire "punta a qualcosa che non so cos'è e se provo ad accedere a quel puntatore probabilmente il mio programma andrà in crash". Il NULL alla riga 9 invece non serve a niente perché nella riga 10 vai a sovrascrivere quella variabile, quindi queste due righe:
C:
node_t *head = NULL; // creazione del puntatore
head = (node_t *) malloc(sizeof(node_t)); // inizializzazione del puntatore e allocazione in memoria di un nodo
si possono accorciare in
C:
node_t *head = malloc(sizeof(node_t)); // creazione del puntatore, inizializzazione del puntatore e allocazione in memoria di un nodo
Il cast esplicito (che io ho tolto) è una cosa che serve in C++ ma non serve in C.
 
  • Mi piace
Reazioni: Cingetorge
Considerando il seguente listato, invece, se volessi far accodare il vecchio elemento "lista" al nuovo elemento "elem"( 1° nodo = elem: 2° nodo=lista)come dovrei scrivere correttamente la riga 16?grazie
C:
#include <stdio.h>
#include <stdlib.h>

typedef struct obj{
    int val;
    struct obj *pnext;
    }oggetto;
main(){
    oggetto lista, *elem;
lista.val=3;
lista.pnext=NULL;
//elem= nuovo puntatore
    elem=(oggetto*)malloc(sizeof(oggetto));
    elem->val=2;
  
    elem->pnext=lista;
  
printf("valore 1 lista %d",lista.val);
printf("valore 2 puntan %d", lista.pnext->val);

}
 
Hai fatto la cosa giusta, solo che la tua testa è elem invece di lista. In ogni caso, credo che ti convenga avere una struttura separata che distingua la lista da un nodo; oppure, più semplicemente, usare un puntatore anche per lista. Altrimenti non riesci a creare la lista vuota. Ti faccio un esempio:
C:
#include <stdio.h>
#include <stdlib.h>

typedef struct obj {
  int val;
  struct obj *pnext;
} oggetto;

oggetto* make_obj(int val) {
  struct obj *elem = malloc(sizeof(struct obj));
  *elem = (struct obj) { .val = val, .pnext = NULL };
  return elem;
}

int main() {
  // lista vuota
  oggetto *lista = NULL;

  // aggiungo 5 elementi
  lista = make_obj(1);
  lista->pnext = make_obj(2);
  lista->pnext->pnext = make_obj(3);
  lista->pnext->pnext->pnext = make_obj(4);
  lista->pnext->pnext->pnext->pnext = make_obj(5);

  // stampo tutti gli elementi
  for (oggetto *p = lista; p != NULL; p = p->pnext) {
      printf("%d\n", p->val);
  }

  return 0;
}
 
Ciao St3ve,
grazie per la tua pazienza.
Ho 3 domande per quanto riguarda la funzione make obj:
-10° riga: perchè non hai fatto subito il cast per il puntatore elem;
- perche non hai usato il tipo della struttura definito con typedef invece di usare struct obj?c'è qualche vantaggio?
- 11° riga: non capisco il significato(nn l'ho mai visto!)suppongo che quello tra parentesi graffe indicail contenuto della struttura obj... perche prima di ".val" e ".pnext"non c'è niente?cosa stanno a significare?
grazie
 
Ultima modifica:
-10° riga: perchè non hai fatto subito il cast per il puntatore elem;
Non l'ho fatto perché non serve. In C i puntatori a void vengono automaticamente a puntatori di altro tipo. In altre parole, non l'ho fatto per la stessa ragione per cui tu se hai una variabile intera x scriveresti double y = 1.5 * x; e non double y = 1.5 * (double)x;. Se hai un qualche tipo di errore/warning è perché stai usando un compilatore C++, quindi dovresti sistemare il tuo IDE per dirgli di compilare in C.

- perche non hai usato il tipo della struttura definito con typedef invece di usare struct obj?c'è qualche vantaggio?
Errore mio, sentiti pure libero di cambiare quella riga di codice per usare oggetto invece di struct obj. Se ti posso dare un consiglio di stile, anche se discutibile, non abusare troppo del typdef. Il più delle volte è meglio espliciti e se dovessi scrivere quel codice io il typedef in questo caso non lo userei.

- 11° riga: non capisco il significato(nn l'ho mai visto!)suppongo che quello tra parentesi graffe indicail contenuto della struttura obj... perche prima di ".val" e ".pnext"non c'è niente?cosa stanno a significare?
È un modo di inizializzare le struct in C, qui puoi trovare più informazioni a riguardo. Il codice che ho scritto è equivalente a questo:
C:
oggetto* make_obj(int val) {
  oggetto *elem = malloc(sizeof(oggetto));
  elem->val = val;
  elem->pnext = NULL;
  return elem;
}
 
Ultima modifica:
Hai fatto la cosa giusta, solo che la tua testa è elem invece di lista. In ogni caso, credo che ti convenga avere una struttura separata che distingua la lista da un nodo; oppure, più semplicemente, usare un puntatore anche per lista
Al di la del tuo suggerimento che apprezzo, non riesco ancora ad eliminare l'errore del compilatore. Io vorrei inserire un nuovo nodo "elem" tra il puntatore *lista pre esistente e la sturrura lista di tipo oggetto, in modo che il vecchio nodo risulti come secondo nodo, ed il nuovo nodo risulti come primo nodo.grazie
Nuova immagine bitmap (3).jpg
La tua riga " *elem = (struct obj) { .val = val, .pnext = NULL };" è di fatti un cast del puntatore o ci assomiglia solamente come indicato qui?
grazie
Messaggio unito automaticamente:

non abusare troppo del typdef. Il più delle volte è meglio espliciti
perche occupano troppa memerio o per quale altro motivo? grazie
 
Al di la del tuo suggerimento che apprezzo, non riesco ancora ad eliminare l'errore del compilatore. Io vorrei inserire un nuovo nodo "elem" tra il puntatore *lista pre esistente e la sturrura lista di tipo oggetto, in modo che il vecchio nodo risulti come secondo nodo, ed il nuovo nodo risulti come primo nodo.grazie
Non sono sicuro di aver ben capito, ma ti faccio un esempio riprendendo il codice che avevi scritto tu e senza introdurre funzioni. Forse è più chiaro.
C:
#include <stdio.h>
#include <stdlib.h>

typedef struct obj {
  int val;
  struct obj *pnext;
} oggetto;

int main() {
  // Lista vuota
  oggetto *lista = NULL;

  // Creo un elemento
  oggetto *elem1 = malloc(sizeof(oggetto));
  elem1->val = 1;
  elem1->pnext = NULL;

  // La lista parte dal primo elemento
  lista = elem1;

  // Stampo la lista
  printf("Lista: ");
  for (oggetto *p = lista; p != NULL; p = p->pnext)
      printf("%d ", p->val);
  printf("\n");

  // Creo un altro elemento che ha per successore elem1
  oggetto *elem2 = malloc(sizeof(oggetto));
  elem2->val = 2;
  elem2->pnext = elem1;

  // La lista parte dal secondo elemento
  lista = elem2;

  // Stampo la lista
  printf("Lista: ");
  for (oggetto *p = lista; p != NULL; p = p->pnext)
      printf("%d ", p->val);
  printf("\n");

  return 0;
}

La tua riga " *elem = (struct obj) { .val = val, .pnext = NULL };" è di fatti un cast del puntatore o ci assomiglia solamente come indicato qui?
No, non è un cast e quella cosa che sembra che stai castando non è un puntatore ma una struttura.

perche occupano troppa memerio o per quale altro motivo? grazie
È solo una differenza stilistica, al pari di "la variabile val chiamala value" che non ha alcun impatto una volta che il codice è stato compilato. Ti faccio un esempio di come puoi usare il typedef in modo un po' più significativo, per astrarre un concetto.
C:
#include <stdio.h>
#include <stdlib.h>

struct node {
  int value;
  struct node *next;
};

typedef struct node *list;

struct node *new_node(int value, struct node *next) {
  struct node *node = malloc(sizeof(struct node));
  *node = (struct node){.value = value, .next = next};
  return node;
}

list prepend(list head, int value) {
  return new_node(value, head);
}

list append(list head, int value) {
  if (!head) return new_node(value, NULL);
  struct node *tail = head;
  while (tail->next) tail = tail->next;
  tail->next = new_node(value, NULL);
  return head;
}

void print_list(list head) {
  printf("[ ");
  for (; head; head = head->next) printf("%d ", head->value);
  printf("]\n");
}

int main() {
  list mylist = NULL;
  print_list(mylist); // [ ]

  for (int i = 0; i < 10; i++) mylist = append(mylist, i);
  print_list(mylist); // [ 0 1 2 3 4 5 6 7 8 9 ]

  for (int i = -1; i > -10; i--) mylist = prepend(mylist, i);
  print_list(mylist); // [ -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 ]

  // in teoria dovremmo mettere le free() per liberare la memoria, in questo esempio me le risparmio
  return 0;
}
Quando uso il tipo list (che in realtà è un puntatore a struct node) indico il primo elemento di una lista e quando uso struct node* indico il puntatore a un nodo generico che magari è il primo e magari no. Onestamente non so se in un codice scritto da me userei il typedef anche in questo caso, però è più significativo rispetto all'uso che ne fai tu: non sto semplicemente usando typedef per evitare la noia di ripetere struct di volta in volta, sto esprimendo un concetto diverso (lista vs. puntatore a un nodo) che ho scelto di implementare usando lo stesso tipo. Non so se rendo l'idea. Comunque è una scelta opinabile quella di non usare il typedef come l'hai usato tu. È una scelta stilistica, un po' come l'indentazione del codice o il nome delle variabili, non ha alcun impatto a livello di memoria o di codice generato.
 
Ultima modifica:
Non riesco a capire perchè, pur seguendo il duo esempio del post #10 im mio listato non funziona !! (errore.jpge continua a darmi l'errore alle righe 16 e 17)
C:
#include <stdio.h>
#include <stdlib.h>

typedef struct obj{
    int val;
    struct obj *pnext;
    }oggetto;
main(){
    oggetto lista, *elem; //dichiaro variabile lista e un puntatore di tipo oggetto
lista.val=3;//assegno campo di nome val della struttura lista =3
lista.pnext=NULL;// lo faccio puntare a niente
//elem= nuovo puntatore
    elem=malloc(sizeof(oggetto)); //creo puntatore elem di tipo oggetto
    elem->val=2;//assegno valore 2 al campo val puntato da elem
//voglio inserire il nuovo nodo elem tra il puntatore lista e in nodo lista :
    elem->pnext=lista;//assegno l'indirizzo di lista al puntare il puntatore pnext del nodo elem
    lista=elem;//assegno l'indirizzo del nodo elem al puntatore lista
printf("valore 1 lista %d",lista.val);
printf("valore 2 puntan %d", lista.pnext->val);

}
 
La tua variabile lista non è un puntatore. Alla riga 9 devi aggiungere un asterisco anche su lista e subito dopo devi fare una malloc, esattamente come hai fatto per elem.
 
  • Mi piace
Reazioni: nfvblog
Giusto per fare un po' il punto della situazione, visto che l'anno scorso ho fatto un esame sulla tematica, mostro un po' il codice per evidenziare come si frutta correttamente la lista in C, ovviamente ringrazio St3ve perché ogni volta riesce sempre a torglere ogni possibile dubbio e sicuramente avrà da ridire anche sul seguente codice
C:
#include<stdio.h>
#include<stdlib.h>

struct lista { // dichiaro la struttura lista
    int num;
    struct lista *next;

};
int* popola_array(int dim);
void stampa_array(int *v,int dim);
struct lista* inserisci_valore(struct lista* p, int i);
void stampa_list(struct lista* p);

int main (){
    int *v,dim;
    printf("quali elementi vuoi inserire: ");
    scanf("%d",&dim);
    v=popola_array(dim);
    stampa_array(v,dim);
    printf("---------------\n");
    struct lista* p=NULL;
    for(int i=0;i<dim;i++){
        if (v[i]==i)
            inserisci_valore(p,i);
    }
    int x=10;
    FILE *f;
    f=fopen("pippo.txt","w");
    for(int i=0;i<dim;i++)
        for(int j=0;j<dim;j++)
            if(v[i]+v[j]==x)
                fprintf(f,"%d %d ",i,j);
    fclose(f);
    return 0;
}

int* popola_array(int dim){
    int i=0, *v,ins;
    if(dim == 0)
        return 0;
    else{
        v=malloc(sizeof(int)*dim);
        do{
            printf("inserisci numero numero: ");
            scanf("%d",&ins);
            if(ins%2==0){
                v[i]=ins;
                i++;
            }else{
                printf("non è un numero pari!\n");
            }

        }while(i<dim);
        return v;

    }
}
void stampa_array(int *v, int dim){
    for (int i=0;i<dim;i++)
        printf("%d ",*(v+i));
    printf("\n");
}
struct lista* inserisci_valore(struct lista* p, int i){
    struct lista* p1=malloc(sizeof(struct lista));
    p1->num=i;
    if(p==NULL)
        p=p1;
    else{
        struct lista* paux=p;
        while(paux->next!=NULL)
            paux=paux->next;
        paux->next=p1;
    }
    p1->next=NULL;
    return p;
}

void stampa_list(struct lista* p){
    while(p!=NULL){
        printf("%d ",p->num);
        p=p->next;
    }
    printf("\n");
}
 
e sicuramente avrà da ridire anche sul seguente codice
Ebbene sì :asd: C'è un piccolo errore quando vai ad inserire elementi nella linked list ma non l'hai visto perché non usi mai quella lista. Te ne puoi accorgere scrivendo stampa_list(p); nel main, vedrai che non stampa niente perché la variabile p rimane NULL. Però è un errore da poco e ti basta scrivere p = inserisci_valore(p,i); invece di inserisci_valore(p,i); per risolverlo, altrimenti dovresti usare un puntatore a puntatore.
 
Ebbene sì :asd: C'è un piccolo errore quando vai ad inserire elementi nella linked list ma non l'hai visto perché non usi mai quella lista. Te ne puoi accorgere scrivendo stampa_list(p); nel main, vedrai che non stampa niente perché la variabile p rimane NULL. Però è un errore da poco e ti basta scrivere p = inserisci_valore(p,i); invece di inserisci_valore(p,i); per risolverlo, altrimenti dovresti usare un puntatore a puntatore.
Ecco, vero, non avevo controllato, quindi questa è la versione precedente all'invio al docente :asd:
:asd::asd:
 
Ultima modifica:
La tua variabile lista non è un puntatore. Alla riga 9 devi aggiungere un asterisco anche su lista e subito dopo devi fare una malloc, esattamente come hai fatto per elem.
non so perchè ma mi ero impuntato sul fatto che fosse "automatico" e sufficiente la dichiarazione e l' inizializzazione della struttura lista per la creazione del puntatore *lista .
grazie mille
 
Ultima modifica:
Nel post #6 ad ogni chiamata della funzione "make_obj(int val)" viene creato una struttura obj con il relatipo puntatore che punta a quest'ultima. Dal momento che tale puntatore viene soppiantato scrivendo l'indirizzo in esso contenuto, che è uguale a quello del nuovo nodo appena creato con la malloc, nel nodo precedente , e considerando che i puntatori "costano" memoria, non si possono eliminare? se si come?
E se volessi inserire dei nodi dalla testa quali modifiche dovrei apportare al listato?grazie
 
Nel post #6 ad ogni chiamata della funzione "make_obj(int val)" viene creato una struttura obj con il relatipo puntatore che punta a quest'ultima. Dal momento che tale puntatore viene soppiantato scrivendo l'indirizzo in esso contenuto, che è uguale a quello del nuovo nodo appena creato con la malloc, nel nodo precedente , e considerando che i puntatori "costano" memoria, non si possono eliminare? se si come? grazie
Non ho capito la domanda, prova a spiegarti diversamente.
 
ad ogni chiamata della funzione "make_obj(int val)" viene creata una stuttura obj ed un puntatore che punta a quest'ultima . Dal momento che l'indirizzo a cui punta il puntatore, viene ricopiato nel nodo precedente, tale puntatore risulta inutilizzato.E' possibile elminare il puntatore per risparmiare memoria o è superfluo?se si come?
E se volessi inserire dei nodi dalla testa quali modifiche dovrei apportare al listato?grazie
Spero di essere stato almeno leggermente più chiaro !
 
La funzione make_obj serve per creare un nuovo nodo, che io ho chiamato oggetto come hai fatto tu nel post precedente.
C:
oggetto* make_obj(int val) {
  struct obj *elem = malloc(sizeof(struct obj));
  *elem = (struct obj) { .val = val, .pnext = NULL };
  return elem;
}
Nella prima riga creo una variabile il cui valore è un indirizzo di memoria in cui ho riservato abbastanza spazio per contenere un nodo (a.k.a., oggetto). Nella seconda riga sto inserendo dei valori in questa area di memoria, ovvero sto inizializzando l'oggetto. Nella terza riga sto restituendo l'indirizzo. Quindi, quando tu scrivi make_obj(7); ottieni un indirizzo ad un'area di memoria in cui è contenuto un oggetto (a.k.a., nodo della lista) già inizializzato.

E' possibile elminare il puntatore per risparmiare memoria o è superfluo?
La variabile elem non esiste al di fuori di quella funzione e puoi tranquillamente assumere che non ci sia nemmeno perché una volta che compili quella roba sparisce. L'unica cosa che ti rimane in mano è l'indirizzo di memoria restituito da make_obj, che ti dovrai salvare da qualche parte, e un'area di memoria nello heap che se ti lasci sfuggire sarà persa per sempre (memory leak).

E se volessi inserire dei nodi dalla testa quali modifiche dovrei apportare al listato?grazie
Basta che guardi il post #10. Ho fatto un esempio dove c'è sia append (inserisci in coda) che prepend (inserisci in testa).
 
  • Mi piace
Reazioni: fennek
Basta che guardi il post #10.
Codice:
typedef struct node *list;
e una dichiarazione del puntatore list che punta alla struttura struct node.Non bastava scrivere struct node *list; . Perche typedef per definire un nuovo tipo?
Nel listato compare head ma non lo vedo dichiarato da nessuna . cos'è?
grazie
 
Codice:
typedef struct node *list;
e una dichiarazione del puntatore list che punta alla struttura struct node.Non bastava scrivere struct node *list; . Perche typedef per definire un nuovo tipo?
Nel listato compare head ma non lo vedo dichiarato da nessuna . cos'è?
grazie

Typedef è stato usato solo per creare un alias, scrivere list è come scrivere struct node*, rende solo il codice più leggibile e conciso.
Nel post 10 head è il nome del parametro, in contesto linked list si riferisce al puntatore al primo elemento della lista, infatti St3ve passa a quelle funzioni mylist come parametro head.
 
  • Mi piace
Reazioni: nfvblog
ciao JunkCoder, hrazie per la tua risposta.

Facendo riferimento al secondo listato del post #10

Codice:
list append(list head, int value) {

      if (!head) return new_node(value, NULL);

      struct node *tail = head;

      while (tail->next) tail = tail->next;

      tail->next = new_node(value, NULL);

      return head;

    }

1° riga: viene fatto un controllo: se il puntatore (MY_LIST) alla struttura è NULL viene creato un nuovo nodo. Domanda: l'indirizzo restituito dalla funzione new_node che fine fa ? Non venendo associato a nessuna variabile non viene perso alla chiusura di append?
2° riga: diversamente, cioè il valore di head ( ossia il valore del puntatore (MY_LIST) alla struttura se diverso da NULL vienecopiato nel puntatore tail che è dello stesso tupo di MY_LIST.
3° riga: Finche il valore di next puntato da tail vale 1 cioè diverso da NULL , copia il suo valore in tail. Ma tail ha il vaore di head che sarebbe il mio puntatore a truttura my_listc on valore Null .come fa a puntare a next?
grazie
 
1° riga: viene fatto un controllo: se il puntatore (MY_LIST) alla struttura è NULL viene creato un nuovo nodo. Domanda: l'indirizzo restituito dalla funzione new_node che fine fa ? Non venendo associato a nessuna variabile non viene perso alla chiusura di append?
Viene restituito dalla funzione append. All'interno del main assegno questo valore alla variabile mylist, sovrascrivendo il valore precedente.

2° riga: diversamente, cioè il valore di head ( ossia il valore del puntatore (MY_LIST) alla struttura se diverso da NULL vienecopiato nel puntatore tail che è dello stesso tupo di MY_LIST.
2° riga: Finche il valore di next puntato da tail vale 1 cioè diverso da NULL , copia il suo valore in tail. Ma tail ha il vaore di head che sarebbe il mio puntatore a truttura my_listc on valore Null .come fa a puntare a next?
Uhm, no okay. Hai fatto un bel po' di confusione. Ti posto il codice commentato, poi se hai altre domande chiedi pure.
C:
// Funzione per creare un nuovo nodo con valore `value` in coda alla lista che inizia da `head`
// Questa funzione restituisce una nuova lista (i.e., la nuova testa) oppure la vecchia lista con il nuovo elementi in fondo
list append(list head, int value) {
    // Se la lista è vuota creo un nuovo nodo ed è quella la mia lista
    if (!head) return new_node(value, NULL); // restituisco new_node, quindi esco dalla funzione append

    // Da qui in poi possiamo assumere `head != NULL`, ovvero che la lista non è vuota

    // voglio raggiungere la coda della lista
    struct node *tail = head;                // assumo che la coda sia uguale alla testa
    while (tail->next)                       // finché ciò che credo sia la coda ha un successore:
        tail = tail->next;                   //     assumo che il successore di ciò che credevo fosse la coda sia la coda

    // Da qui in poi posso assumere che `tail` punta all'ultimo nodo (i.e., la coda) della lista

    tail->next = new_node(value, NULL);      // assegno un nuovo valore a tail->next
    return head;                             // la mia nuova lista parte sempre dalla vecchia testa
}

// Nel main
mylist = append(mylist, 123);
// Se mylist era NULL adesso mylist prende il valore di new_node.
// Se invece mylist non era NULL (i.e., se la lista non era vuota) adesso `mylist->next->next->...->next->value == 123`
Il while, in particolare, serve per raggiungere la coda. Inizialmente non lo so dov'è la coda, per rifarmi all'esempio del post #6: non lo so quante volte devo scrivere head->next->next->next->next->next->next->... prima di arrivare alla coda. Per risolvere questo problema assumo che la coda sia la testa (che non è NULL, perché altrimenti uscivo prima) e poi mi sposto di un next alla volta fino a trovare quello che il next non ce l'ha: l'ultimo elemento (la coda) è l'unico elemento che non ha nessun successore.

e una dichiarazione del puntatore list che punta alla struttura struct node.Non bastava scrivere struct node *list; . Perche typedef per definire un nuovo tipo?
Ha detto bene JunkCoder, ma comunque ti faccio notare che alla fine del post #10 avevo motivato la mia scelta. Il typdef mi serve per dire che una lista e un nodo sono due cose concettualmente diverse, ma implementate con lo stesso tipo. Il C è weakly typed quindi questo concetto lascia un po' il tempo che trova, ma per capirci meglio è un po' come quando nelle lezioni di matematica alle elementari ti spiegano che non puoi sommare le mele con le pere: sono entrambi numeri interi, ma vengono usati per indicare cose concettualmente diverse. Nei linguaggi strongly typed, tipo il Go, questo concetto diventa molto più evidente.