Aiuto Liste Autoreferenziali

di il
7 risposte

Aiuto Liste Autoreferenziali

Salve a tutti, volevo aprire questo topic per ricevere assistenza riguardo questo frammento di codice; Il metodo in questione è "void cancval(plist ,int )" che, dovrebbe eliminare tutti i nodi della lista contenenti il valore intero passato al metodo. L'esecuzione termina dopo la chiamata del metodo con un messaggio d'errore e il crash del programma senza che la chiamata torni nemmeno al main. Non mi sembra di aver sfanculato con i puntatori.. quale può essere il problema?
Un secondo errore, più marginare invece, interessa le linee di testo che riguardano le possibili azioni da compiere; queste vengono scritte su console per più di una volta ogni esecuzione del ciclo; nulla di che ma mi piacerebbe sapere cosa sbaglio.
Posto il codice compreso di main, header e eseguibile in caso un'eventuale esecuzione possa risultarvi utile e vi ringrazio in anticipo per l'aiuto che spero di ricevere. Buona Serata

#include <stdio.h>
#include <stdlib.h>
#include "elist.h"

int main(int argc, char *argv[]){
    int b,d,e,esci;
    char c;
    esci = 1;
    plist a = malloc(sizeof(plist));
    printf("GESTIONE INTERI V 1.0\n");
    printf("\n");
    printf("Scrivi un intero:\n");
    scanf("%d",&b);
    a->info = b;
    a->next = NULL;
    do {
       printf("Premi A per aggiungere un nodo:\n");
       printf("Premi B per eliminare un nodo con valore X:\n");
       printf("Premi E per uscire:\n");
       printf("Premi S per stampare:\n");
       scanf("%c",&c);
       if ( c == 'a' || c == 'A' ) {
          printf("\n");
          printf("che valore inserire?:\n");
          scanf("%d",&d);
          printf("\n");
          addnodoincoda(a,d);
          }
       if ( c == 'e' || c == 'E' ) {
          esci = 0;
          }
       if ( c == 'b' || c == 'B' ) {
          printf("\n");
          printf("i nodi da cancellare hanno valore:\n");
          scanf("%d", &e);
          printf("\n");
          cancval(a,e);
          }
       if ( c == 's' || c == 'S' ) {
          printf("\n");
          stampalista(a);
          }
    } while (esci);
  system("PAUSE");	
  return 0;
}
#include "elist.h"

/*cancella i nodi della lista l contenenti il valore a*/
void cancval(plist l,int a){
     plist prev,cur;
     prev,cur = l;
     while (prev->next!=NULL) {
           if (prev->info == a) {
              if (prev = cur) {
                prev = prev->next;
                l = prev;
                free(cur);
                cur = prev->next;
              }
              else {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
              }
           }
           prev = prev->next;
           cur = prev->next;
     }
     return;         
}

/*aggiunge un nodo in coda alla lista l con valore b*/
void addnodoincoda(plist l,int b) {
     plist temp = malloc(sizeof(plist));
     temp->info = b;
     temp->next = NULL;
     plist temp2;
     temp2=l;
     while (temp2->next != NULL) {
        temp2 = temp2->next;
     }
        temp2->next = temp;            
}

/*stampa la lista l*/
void stampalista(plist l) {
     if (l==NULL) {
        return;
     }
     else {
          printf("la lista è composta dagli elementi:\n");
          printf("\n");
          printf("%d\n", l->info);
          printf("\n");
          while (l->next != NULL) {
            printf("%d\n", l->next->info);
            printf("\n");
            l = l->next;
          }
     }  
     return;
}
#import <stdio.h>
#import <stdlib.h>
#import <stddef.h>

typedef struct elist {
        int info;
        struct elist* next;
} elist;
        
typedef elist* plist;

void cancval(plist,int);
void addnodoincoda(plist,int);
void stampalista(plist);

7 Risposte

  • Re: Aiuto Liste Autoreferenziali

    Mi permetto di darti alcuni consigli:

    usa per le variabili nomi esplicativi della loro funzione nel programma
    (ad es., la variabile d - che memorizza un nuovo int da mettere in lista -
    potrebbe chiamarsi nuovo_val, nuovo_int new_int e simili);

    e' meglio se i blocchi (all'interno del ciclo do-while)
    corrispondenti alle varie operazioni sono disposti con lo stesso ordine
    con cui appaiono nel menu le voci corrispondenti e se l'ultima opzione
    e' quella per l'uscita (dal ciclo/programma);

    scanf() utilizzata per leggere dati di tipo diverso (in questo caso,
    interi e carattere per la scelta dell'opzione) provoca varie stranezze;
    in questo caso il menu per le scelte viene visualizzato ad ogni esecuzione
    di ciclo due volte; si puo' ovviare usando un intero anche per la scelta
    dell'operazione da compiere;

    eviterei di usare istruzioni prima del ciclo per l'inserimento del primo nodo,
    la funzione addnodoincoda() dovrebbe essere in grado di distinguere
    i due casi (inserimento in lista vuota / non vuota) e regolarsi di conseguenza;
    e anche le altre funzioni devono prevedere il caso particolare della lista vuota;

    la condizione alla fine del ciclo do-while e' quella per la permanenza
    nel ciclo (non per l'uscita), quindi bisognerebbe assegnare a esci 0 (falso)
    per continuare l'esecuzione del ciclo e 1 (vero) per l'uscita; come hai fatto
    tu funziona, ma si riduce un po' la leggibilita'; io di per questi casi
    uso una variabile chiamata "continua", che assume valore 1 per proseguire
    l'esecuzione delle istruzioni di ciclo e 0 per finire;

    di solito, le funzioni per le liste (inserimento, cancellaz., stampa, ecc.)
    ricevono in input il puntatore al primo nodo, non la struct che rappresenta
    il primo nodo; forse qui si nasconde uno degli errori della funzione cancval().

    Adesso devo interrompere l'intervento, devo esaminare meglio cancval();
    sul mio PC la cancellazione non funziona se la lista ha piu' di un elemento
    e da' un errore di segmentazione se la lista ha un solo elemento.

    Ti segnalo anche che il mio compilatore gcc mostra dei warning a proposito
    della direttiva import; i warning spariscono se uso include.

    A presto.
  • Re: Aiuto Liste Autoreferenziali

    Ritiro quello che ho scritto sulla variabile passata alle tue funzioni:
    non e' una struct, ma e' il puntatore al primo nodo (a).
    Chiedo scusa a tutti.

    Comunque ho esaminato brevemente cancval(); ho notato alcune incongruenze:

    non ho capito l'istruzione prev, cur = l;

    il confronto (sembra di capire che serve per individuare il primo nodo)
    deve essere (prev==cur), non (prev=cur); peraltro, i due puntatori
    dovrebbero avere un valore sempre diverso (vedi sotto);

    l'istruzione l=prev dovrebbe servire a far puntare il puntatore iniziale a
    al secondo nodo, perche' il primo viene cancellato con free(cur);
    pero' l'istruzione non ha effetto al di fuori della funzione,
    visto che il puntatore l viene passato per valore, non per indirizzo;
    cancval() dovrebbe ricevere in input l'indirizzo di un puntatore
    a una struct di tipo elist; quindi si parla di puntatori doppi;

    le istruzioni dopo l'else (prev->next=cur->next...) mi sembrano inesatte;

    le ultime due istruzioni del ciclo (prev=prev->next; cur=prev->next)
    assegnano lo stesso indirizzo a prev e cur; ma i due puntatori
    devono sempre avere un valore diverso: cur deve puntare al nodo corrente
    (quello con il dato da verificare e - eventualmente - cancellare)
    e prev deve puntare al nodo precedente;

    non mi e' chiaro se il tuo algoritmo prevede anche il caso in cui
    il valore da eliminare occorra piu' volte nella lista, tenendo conto
    di tutti i casi possibili: valori consecutivi, inframmezzati ad altri valori, ecc.

    Un esame piu' approfondito non riesco a farlo, per mancanza di tempo;
    puo' darsi che mi sia sfuggito qualcosa.

    Secondo me, puoi rimaneggiare la tua funzione tenendo conto delle mie osservazioni
    (se ti paiono sensate);

    altrimenti potresti riscriverla ex-novo;
    ti propongo un algoritmo che ho usato in certi miei programmi
    (ma sicuramente ci sono anche altre soluzioni):

    INIZIO FUNZIONE CANC

    se la lista e' vuota, esci;

    p_corrente=puntatore_al_primo_nodo;
    p_precedente=NULL;

    INIZIO CICLO WHILE (finche' p_corrente!=NULL)

    SE il nodo puntato da p_corrente deve essere cancellato
    SE il nodo e' l'unico della lista
    cancellazione;
    uscita dalla funzione;
    SE il nodo e' il primo, ma non l'unico
    cancellazione
    continue;
    SE il nodo e' l'ultimo, ma non l'unico
    cancellazione;
    break;
    SE il nodo non e' primo o ultimo
    cancellazione;
    ALTRIMENTI (il nodo puntato da p_corrente NON deve essere cancellato)
    p_precedente=p_corrente;
    p_corrente=puntatore p_prossimo del nodo puntato da p_corrente

    FINE CICLO WHILE

    FINE FUNZIONE

    Ho separato i 4 casi di cancellazione, perche' ognuno richiede
    istruzioni diverse; e' anche possibile aggiungere un contatore
    dei nodi cancellati, il cui valore puo' essere reso in uscita dalla funzione.
  • Re: Aiuto Liste Autoreferenziali

    Ciao Korr. Innanzitutto ti ringrazio per avermi dedicato il tuo tempo e avermi offerto delucidazioni in merito ai dubbi che avevo. Siccome (giustamente) hai ritenuto che fosse necessario rispondermi per punti, essendo presenti più di un solo problema, cercherò di fare altrettanto con equal cura.
    usa per le variabili nomi esplicativi della loro funzione nel programma
    (ad es., la variabile d - che memorizza un nuovo int da mettere in lista -
    potrebbe chiamarsi nuovo_val, nuovo_int new_int e simili);

    e' meglio se i blocchi (all'interno del ciclo do-while)
    corrispondenti alle varie operazioni sono disposti con lo stesso ordine
    con cui appaiono nel menu le voci corrispondenti e se l'ultima opzione
    e' quella per l'uscita (dal ciclo/programma);

    scanf() utilizzata per leggere dati di tipo diverso (in questo caso,
    interi e carattere per la scelta dell'opzione) provoca varie stranezze;
    in questo caso il menu per le scelte viene visualizzato ad ogni esecuzione
    di ciclo due volte; si puo' ovviare usando un intero anche per la scelta
    dell'operazione da compiere;

    la condizione alla fine del ciclo do-while e' quella per la permanenza
    nel ciclo (non per l'uscita), quindi bisognerebbe assegnare a esci 0 (falso)
    per continuare l'esecuzione del ciclo e 1 (vero) per l'uscita; come hai fatto
    tu funziona, ma si riduce un po' la leggibilita'; io di per questi casi
    uso una variabile chiamata "continua", che assume valore 1 per proseguire
    l'esecuzione delle istruzioni di ciclo e 0 per finire;
    Mi sono permesso di raggruppare questi 4 punti perchè penso che per quanto essi possano essere indubbiamente utili ai fini di una programmazione pulita e chiara, essi rimangono comunque confinati nell'ambito della forma e vorrei concentrarmi di più su quello che penso sia il punto d'origine dei miei dubbi nonostante chiesi un'aiuto anche per risolvere quei problemi "minori".
    Ci tengo quindi a ringraziarti e cercherò di prenderli alla lettera per i futuri esercizi.
    eviterei di usare istruzioni prima del ciclo per l'inserimento del primo nodo,
    la funzione addnodoincoda() dovrebbe essere in grado di distinguere
    i due casi (inserimento in lista vuota / non vuota) e regolarsi di conseguenza;
    e anche le altre funzioni devono prevedere il caso particolare della lista vuota;
    niente da dire in merito, d'accordo su tutto.
    di solito, le funzioni per le liste (inserimento, cancellaz., stampa, ecc.)
    ricevono in input il puntatore al primo nodo, non la struct che rappresenta
    il primo nodo; forse qui si nasconde uno degli errori della funzione cancval().
    è qui che risiedono tutti i miei dubbi! nonostante il tuo ultimo intervento..
    Ritiro quello che ho scritto sulla variabile passata alle tue funzioni:
    non e' una struct, ma e' il puntatore al primo nodo (a).
    ..non mi sono ancora chiare un paio di cose:
    sono sempre riuscito a svolgere operazioni di qualsiasi tipo sulle mie strutture (aggiunta nodi,cancellazione,modifica) passando al metodo in questione quello che penso sia il puntatore al primo nodo della struttura "void addnodoincoda(plist a)" e, nonostante i risultati siano sempre stati positivi, mi trovo parecchio in difficoltà quando andando a vedere soluzioni di analoghi esercizi (tutto materiale accademico sia chiaro) mi ritrovo sempre a vedere signature di metodi di questo tipo "void addnodoincoda(plist* a)".. allora mi chiedo?
    se è vero che con
    void addnodoincoda(plist a)
    decido di passare al metodo il puntatore plist che punta al primo nodo di una struttura di tipo elist (sempre considerando l'esempio dell'esercizio postato in principo, tanto per capirsi) .. qual è il bisogno di passare, con
    void addnodoincoda(plist* a)
    il puntatore AL PUNTATORE della struttura;
    Non saprei neanche come fare in termini sintattici, puoi chiarirmi ulteriormente le idee su questo punto?
    Grazie Mille per tutto. Ciao
  • Re: Aiuto Liste Autoreferenziali

    Sono lieto di vedere che hai apprezzato le mie osservazioni.
    Il punto da te evidenziato e' della massima importanza:
    che differenza c'e' fra funzione(plist pp) e funzione(plist *pp)?
    (dove plist e' un tipo puntatore a una struct nodo,
    in particolare al primo nodo di una lista).

    Cominciamo con un esempio banale; facciamo finta che in un nostro
    programma ci serve una funzione che raddoppi un valore contenuto
    in una variabile numero intero; potremmo pensare di scrivere
    una funzione cosi':
    
    void raddoppia(int num)
     {
      num=num*2;
     }
    
    per poi usarla nel programma principale:
    
    ....
    int a; 
    ....
    raddoppia(a);
    ....
    
    la cosa non funzionerebbe, dopo la chiamata della funzione
    la variabile a continuerebbe ad avere il valore precedente.
    Il motivo e' che abbiamo passato il parametro di input per valore
    (non per indirizzo). Dopo la chiamata della funzione,
    la variabile num viene collocata nello stack
    (con il valore di a, insieme alle eventuali variabili locali)
    e viene cancellato quando si esce dalla funzione.
    Tutte le operazioni su num producono un effetto confinato
    all'interno della funzione, ma la variabile esterna a non viene toccata.

    Se vogliamo che il valore di a sia modificato dopo la chiamata
    della funzione, dobbiamo passare in input il suo indirizzo,
    non il suo valore:
    
    void raddoppia(int *p_num)
     {
      (*p_num)=(*p_num) * 2;
     }
    ....
    raddoppia(&a);
    ....
    
    cosi' funziona, perche' l'indirizzo (anch'esso inserito nello stack)
    ha un corrispondente in un puntatore esterno alla funzione, &a.

    Un discorso analogo si puo' fare per le funzioni che manipolano
    liste concatenate, specialmente per quelle che devono introdurre
    delle variazioni nel puntatore di testa, che serve ad accedere
    al primo nodo: dobbiamo passare alla funzione non il puntatore,
    ma il suo indirizzo: la funzione deve ricevere in input
    un puntatore a un altro puntatore.

    In questo modo, i cambiamenti operati sul puntatore all'interno
    della funzione si estendono anche al corrispondente esterno.

    Vediamo la sintassi (per generalizzare non uso plist,
    ma il tipo di dato struct nodo, cosi' pp punta a un puntatore
    che punta a una struct nodo):
    
    int cancella(nodo **pp, int num)  // num e' il numero da cancellare
     {
      nodo *p_altro_nodo;
      ....
      // visualizzare il campo info
      printf("%d", (*pp)->info);
      // si puo' anche cosi'
      printf("%d", (**pp).info);
      .... 
      // deallocare la memoria
      free(*pp);
      ....
      // assegnare al puntatore di testa 
      // il puntatore a un altro nodo
      *pp=p_altro_nodo;
      ....
     }
    
    main()
    ....
    nodo *p_lista;  // p_lista punta al primo nodo
    ....
    // chiamata della funzione 
    cancella(&p_lista, num_da_cancellare);
    ....
    
    Per semplificare, evitando di dover usare la notazione
    per i doppi puntatori, si puo' creare un puntatore locale
    dentro la funzione:
    
    int cancella(nodo **pp, int num)
     {
      nodo *p_primo_nodo;
      nodo *p_altro_nodo;
      ....
      p_primo_nodo=*pp;
      ....
      // visualizzare il campo info
      printf("%d", p_primo_nodo->info);
      // si puo' anche cosi'
      printf("%d", (*p_primo_nodo).info);
      .... 
      // deallocare la memoria
      free(p_primo_nodo);
      ....
      // assegnare al puntatore di testa 
      // il puntatore a un altro nodo
      p_primo_nodo=p_altro_nodo;
      ....
      // prima dell'uscita dalla funzione 
      // bisogna fare l'assegnamento seguente, 
      // altrimenti il puntatore esterno non cambia
      *pp=p_primo_nodo
      ....
     }
    
    Le tue funzioni passate di tipo f(plist a), dove a e' il puntatore di testa,
    potevano anche funzionare regolarmente, ma questo capitava solo
    nei casi in cui non veniva modificato il puntatore iniziale;
    ad es., inserimento alla fine di lista non vuota
    o cancellazione di nodo diverso dal primo.
    Ma visto che ci sono casi in cui bisogna variare il puntatore iniziale
    (inserimento/estrazione da pila, cancellazione primo nodo, inserimento
    in lista vuota) bisogna usare le funzioni f(plist *a) tutte le volte
    in cui bisogna introdurre modifiche nella lista.

    Quando si scrivono funzioni che gestiscono liste, bisogna pensare a tutti
    i casi particolari possibili e costruire la funzioni in modo che li sappiano
    riconoscere e trattare ognuno nel modo dovuto; molti errori nascono
    proprio dalla mancata considerazione di tutti i casi possibili.
  • Re: Aiuto Liste Autoreferenziali

    Dopo aver ripassato passaggio di parametri e puntatori, ho deciso di mettermi ancora alla prova; questa volta ho cercato di seguire il più possibile i consigli che mi hai dato in precedenza, anche se i risultati attesi non sono quelli desiderati
    Questa volta lo scopo dell'esercizio è il seguente:

    void BinaryGen(plist* L, int n): data una lista vuota L, tale funzione inizializza L
    generando una sequenza binaria casuale contenete DIM cifre binarie. DIM è un numero casuale compreso tra 0 e n (si ricorda che la libreria stdlib.h mette a disposizione il metodo rand() che restituisce un numero intero casuale compreso tra 0 e 32767). La lista L inizializzata non può presentare nel campo info del primo nodo il valore 0 (quindi la sequenza 0011 deve inizializzare la lista L con due nodi, corrispondenti a 11);

    int main(int argc, char *argv[]) {
      plist a = malloc(sizeof(elist));
      int b = 5;
      int n = 6;
      a->info = b;
      a->next = NULL;
      Binarygen(&a,n);
      stampalista(a);
      system("PAUSE");	
      return 0;
    }
    Seguendo i tuoi primi consigli ho cercato di sostituire il main con qualcosa di più spicciolo affidando ad ogni metodo dell'eseguibile le proprie responsabilità, ad eccezione del primo nodo della lista, che proprio non riesco a inizializzare tramite la condizione che la lista sia vuota del metodo addincoda(plist* ,int).. essendo costretto quindi ad affidarlo al main.
    Ho inoltre anche tralasciato, all'interno di Binarygen(plist*,int), la condizione per cui il valore del primo nodo debba necessariamente essere diverso da 0 cercando di focussarmi sul corretto funzionamento dei puntatori ai puntatori.
    L'errore a tempo di esecuzione sembra sia (o così ho capito dalle stampe buttate in giro in mezzo al codice) dovuto al puntatore che, ogni volta aggiunto un nodo, sembra resti su quello.. ma non sono completamente sicuro. Spero questa piccola analisi possa in qualche modo esserti d'aiuto in termini di tempo speso per capire quale sia il vero problema.. sempre che non sia veramente questo
    Posto nuovamente il codice, confidando in un tuo ulteriore aiuto. Ti ringrazio ancora una volta infinitamente. Luca.
    /*aggiunge un nodo in coda alla lista*/
    void addincoda(plist* l, int n){
        plist temp = malloc(sizeof(elist));
        temp->info=n;
        temp->next=NULL;
        if(*l!=NULL)
          *l=temp;
        else {
            plist temp2 = *l; 
            while(temp2->next!=NULL) {
                temp2 = temp2->next;
            }
            temp2->next=temp;
        }
    }
    /* a partire da una lista vuota,crea una sequenza binaria di lunghezza pari all'intero n*/
    void Binarygen(plist* a,int n) {
        int dim = rand()%1;
        int i;
        for (i=0; i<n; i++) {
              addincoda(a,dim);
        }         
    }
    /*stampa la lista*/
    void stampalista(plist l) {
         while (l->next!=NULL) {
               printf("%d",l->info);
               printf("\n");
               l = l->next;
         }
               printf("%d",l->info);
               printf("\n");
    }
    typedef struct elist {
            int info;
            struct elist *next;
            }elist;
    
    typedef elist *plist;
    
    void BinaryGen(plist*,int);
    void addincoda(plist*, int);
    void stampalista(plist);
  • Re: Aiuto Liste Autoreferenziali

    Risolto! Ci tenevo solo a fartelo sapere evitando di farti perdere altro tempo con il nuovo codice.
    Ci tengo quindi a dirti che senza i tuoi consigli mi troverei ancora in alto mare; grazie di tutto Korr.
    plist a = NULL;
      plist b = NULL;
      int n = 7;
      Binarygen(&a,n);
      Binarygen(&b,n);
      stampalista(a);
      stampalista(b);
      if (Align(a,b)==1) { 
         printf("le liste sono allineate\n");
      }
      else {
         printf("le liste non sono allineate\n");
      }
      system("PAUSE");	
      return 0;
    }
    #include <stdio.h>
    #include <stdlib.h>
    #include "elist.h"
    
    /*aggiunge un nodo in coda alla lista*/
    void addincoda(plist* l, int n){
        plist temp = malloc(sizeof(elist));
        temp->info=n;
        temp->next=NULL;
        if(*l==NULL) {
          *l = temp;
        }
        else {
            plist temp2 = *l; 
            while(temp2->next!=NULL) {
                temp2 = temp2->next;
            }
            temp2->next=temp;
        }
    }
    /*calcola la lunghezza della lista*/
    int lunghezza(plist l) {
        plist temp = malloc(sizeof(elist)); //puntatore rimasto alla fine a fine chiamata?
        int cont = 0;
        while (l->next != NULL) {
            l=l->next;
            cont++;
        }
        free(temp);
        return cont;
    }
    /*controlla che le due liste siano allineate*/
    int Align(plist L1, plist L2) {
         if (lunghezza(L1)==lunghezza(L2)) {
            if (L1->info==L2->info) {
               while (L1->next != NULL) {
                     L1 = L1->next;
                     L2 = L2->next;
               }
               if (L1->info == L2->info) {
                  if (contazeri(L1) == contazeri(L2)) {
                     return 1;
                  }
               }
               return 0;
            }
         }
    }
    /*conta gli uni*/
    int contauni(plist l) {
       int cont = 0;
       while ( l->next != NULL ) {
             if (l->info == 1) {
                cont++;
             }
       }
    }
    
    /*conta gli zeri*/
    int contazeri(plist l) {
       int cont = 0;
       while ( l->next != NULL ) {
             if (l->info == 0) {
                cont++;
             }
       }
    }
    
    /* a partire da una lista vuota,crea una sequenza binaria di lunghezza pari all'intero n*/
    void Binarygen(plist* a,int n) {
        int i;
        for (i=0; i<n; i++) {
              addincoda(a,rand()%2);
        }         
    }
    /*stampa la lista*/
    void stampalista(plist l) {
         printf("La lista è:\n");
         while (l->next!=NULL) {
               printf("%d",l->info);
               printf("\n");
               l = l->next;
         }
               printf("%d",l->info);
               printf("\n");
               printf("\n");
    }
    
    typedef struct elist {
            int info;
            struct elist *next;
            }elist;
    
    typedef elist *plist;
    
    void BinaryGen(plist*,int);
    void addincoda(plist*, int);
    void stampalista(plist);
    int Align(plist, plist);
    int lunghezza(plist);
    int contauni(plist);
    int contazeri(plist);
  • Re: Aiuto Liste Autoreferenziali

    Bene!

    Anche per me e' stata un'occasione per ripassare e imparare
    varie nozioni.

    Corrado
Devi accedere o registrarti per scrivere nel forum
7 risposte