verkettete Liste speichern/laden



  • Hallo erstmal!

    hab ein sehr großes Problem. Ich will eine verkettete Liste in einer datei speichern und sie dann späte rwieder laden können.
    Die verkette Liste besteht aus dem Typ struct, der bei mir so aussieht:

    struct person
    {
    	char vname [MAX];
    	char nname [MAX];
    	char wohnort [MAX];
    	int plz;
    	char strasse [MAX];
    	int hausnummer;
    	long int vorwahl;
    	long int telnr;
    	struct person *next;
    };
    

    Brauche ganz dringend eine Lösung! Danke schonmal!



  • Ist doch simpel.

    Gehe zum Rootknoten, traversiere die gesamte Liste und schreib jedes Element in eine File

    Zum Lesen lege eine Leere Liste an, lies aus dem File jedes einzelne Element und trage es
    in die Liste ein.



  • Einfach ist gut gesagt - mach es noch nicht lange.
    Wie mach ich es denn am Besten? Es gibt ja verschiedene Möglkichleiten zB mit fprintf, fwrite,... . Kannst du mir einen Ansatz schreiben?



  • Zum Schreiben
    1 öffne das file mit fopen Mode "wb"
    2 Gehe zum Rootknoten
    3 Nimm die Daten als deine Struktur und schreibe diese mit fprintf als eine Zeile in das file
    mit dem Trennzeichen #,
    fprintf(fp,"%s#%s#%s#%i#%s#%li#%li\r\n", vname, nname, wohnort, plz, strasse, hausnummer, vorwahl, telnr);
    4 Gehe zum nächsten Element bis du durch die Liste durch bist
    5 schließ das File mit fclose

    Zum Lesen
    1 öffne das file mit fopen Mode "rb"
    2 Erzeuge Leere Liste
    3 lies eine Zeile aus dem file mit fgets
    4 zerlege den Stgring an den Stellen wo beim Schreiben ein # als Trennzeichen steht
    5 kopiere die Teile in die Struktur
    6 Gehe nach 3 und wiederhole das bis du am Ende des Files bist
    7 schließ das File mit fclose

    Als Vorschlag, trenne in deiner Liste die Verwaltung von den Nutzdaten, damit kann man den beiden Dingen leichter Änderungen vornehmen

    typedef struct
    {
        char vname [MAX];
        char nname [MAX];
        char wohnort [MAX];
        int plz;
        char strasse [MAX];
        int hausnummer;
        long int vorwahl;
        long int telnr;
        struct person *next;
    } PersSTRUCT;
    
    struct Liste
    {
    struct Liste *next;
    PersSTRUCT *Data;
    }
    
    typedef struct Liste ListenSTRUCT;
    
    //Definition ROOTKnoten
    ListenSTRUCT Personen;
    

    Diese Definitionen halte ich nicht für sinnvoll
    char Definition passender Länge sind besser
    int plz; damit auch PLZ A-2211, CH-333 für Österreich, Schweiz speichern kann,
    int hausnummer; damit auch Hausnummer 37a gespeichert werden kann
    long int vorwahl was ist mit der Vorwahlform +49761
    long int telnr;
    Es sollten auch nicht alle Felder gleich lang sein, braucht sehr viel Speicher



  • fwrite/fread
    damit schreibst du die datensätze binär (muss dir nichts sagen). damit hat jeder datensatz in der datei die gleiche länge und du kannst sie auch einfach lesen. wichtig: fopen mit zusätzlichem "b" als flag. sonst könnts passieren, dass die daten kaputt gehen (crlf/lf umwandlung).

    beim schreiben geh die liste durch und speichere einfach.

    beim lesen musst du die <next> pointer der jeweils vorherigen struct anpassen, wenn du eine weitere liest.

    da kannst du stöbern:
    http://www.cppreference.com/



  • Danke. Ich probiers gleich mal aus. Und danke auch für den Tip mit den Datentypen!



  • Die binäere Schreibweise halte ich nur bei großen Datenvolumen für sinnvoll, da oftmals eine impliziete Komprimierung vorhanden ist allerdings nicht bei Strings. Bei kleineren Datein finde ich die ASCII-Methode für sinnvoller.

    Wenn ich Daten binaer schreibe muß ich für jede Änderung in der Struktur ein Konvertierungsprogramm schreiben, da ich die alten Daten nicht mehr lesen kann.

    Bei ASCII kodierten Dateien kann ich solche Änderungen viel leichter adaptieren.
    - Kommt ein neues Element in die Struktur hinzu kann ich sie im ASCII-File ans Ende anfügen in dem ich bei allen alten Records mit einem Editor ein Trennzeichen hinzufüge. Das Handling von diesen Daten betrifft nur die Schreib und Lese Routine.
    - Ändert sich die Länge eines Feldes bin im ASCII File FAll davon nicht betroffen, da das Ende eines Elements durch den Delimiter bestimmt wird und beim schreiben mit %s unnötige Blanks vermieden werden können, damit kann ausnahmsweise eine ASCII-Datei sparsamer mit dem Speicherplatz auf Platte umgehen als eine Binaerdatei.
    -Einen Nachteil habe ich allerdings bei dieser Methode, der Delimiter darf nicht als Wert in einem Datenelement auftreten.



  • Also speichern klappt schon mal.
    Ich hänge allerdings beim Lesen bei Punkt 4. Ich weiß nicht so richtig, wie ich das anstellen soll! Meinst du eigentlich fgets oder fgetc?



  • ich meinte fgets, und im Anschluß einen kleinen Code-Ausriss für die Lese Routine, es ist noch eine von meinen Toolroutinen dabei.

    Viel Spaß 😃

    #define BUFFERLEN 1024
    char buffer0[BUFFERLEN+1];
    char buffer1[BUFFERLEN+1];
    
    // vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
          fgets(buffer0,BUFFERLEN,ifp);	// get one line
          if (feof(ifp))		     // checking for end of file to finisch process
    	break;
    // Buffer0 vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
         substr(buffer0,buffer,'#');
    // Buffer0 nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
    // Buffer1 vname
    
    //und das wiederhoöen solange bis du alle Elemente hast.
    
    ///////////////////////////////////////////////////////////////
    
    /***********************************************************************.FA*
     .FUNCTION [ gets the first part of a string loosing the delimiter ]
    --------------------------------------------------------------------------
     .GROUP    [ String                         ]
     .AUTHOR   [ PAD /DDT                       ]
    --------------------------------------------------------------------------
     .DESCRIPTION
       This routine copies all chars from the beginning of the string in
       to lowout until ist finds the delimiter. the delimiter gets skipped
       an the rest of the string is moved to in. That means that the first
       part of the string ist in lowout, the rest of the string is in except
       the delimiter
    --------------------------------------------------------------------------
     .INDEX
       Utilities
    --------------------------------------------------------------------------
     .PARAMETER
       CHANGED  char * in        rest of string
          OUT   char * lowout    first part of the string
    --------------------------------------------------------------------------
     .RETURNVALUE
       0 allways
    --------------------------------------------------------------------------
     .VARIABLE_REFERENCES
    --------------------------------------------------------------------------
     .HISTORY
      Date      Author          Comment
      14.06.94  PAD /DDT    Definition
    **********************************************************************.HE**/
    int substr(char * in, char * lowout,char search)
    {
      char *highout;
      highout=in;
      while ( ( search != * in ) && ( * in ) )
        *lowout++ = *in++;
      *lowout=0;
      if (0 != *in)in++;
      while ( * in  )
        *highout++= *in++;
      *highout=0;
      return 0;
    }
    


  • ok, ascii kann man per hand bearbeiten. das ist aber auch der einzige vorteil.

    eine ascii datei zu parsen, ist komplizierter als nötig.
    dein argument, man kann so besser erweitern, zieht schlecht,
    weil beim parsen von neueren/älteren daten probleme aufkommen, wenn was fehlt.



  • Wenn man die Daten im File immer nur am Ende der Zeile erweitert, und das leseverfahren tolerant gegen fehlende Elemente am Ende auslegt ist das kein Problem.
    Den Parser habe ich ja eigentlich schon gepostet und zwar sowohl zum schreiben als auch zum Lesen. Beim LeseVerfahren fehlt noch etwas vom Fehlerhandling aber sonst ist der eigentlich nicht so kompliziet.

    Fehlerhandling beim lesen von BinaerDateien muß auch sein, den fread liest hemmungslos die angegebene Anzahl von Bytes aus dem File und stopft diese ungesehen in die Struktur. Also must du eigentlich jedem Lesen zumindest eine Plausibiltätscheck nachfolgen lassen und schon haben wir die gleiche Komplexität.

    D.h. mit einem Binärleser kannst du jedes File öffnen und jeden Blödsinn in die internen Daten stecken. Wenn sich jetzt durch eine absichtliche oder unabsichtliche Strukturänderung eine Kleinigkeit ändert, kann ich meine Daten nicht mehr sinnvoll lesen.



  • @PAD

    Kansst du mir das hier bitte mal erklären:

    #define BUFFERLEN 1024 
    char buffer0[BUFFERLEN+1]; 
    char buffer1[BUFFERLEN+1];
    


  • Diese Schreibweise habe ich mir angewöhnt, damit die üblichen Fehler mit der schließenden NULL eines C-Strings nicht passieren.
    Typischerweise plant man einen String der Länge 80 um 80 Zeichen unterzubringen und vergisst dabei das 81. Zeichen für die schließende NULL, typischer Anfänger/flüchtigkeitsfehler.



  • Das trennen funktioniert, aber wie sage ich, dass ich in der nächsten Zeile weitermachen will!?



  • Ich verstehe nicht ganz was du meinst.

    Wenn du die ganze Datei lesen willst müßte es ungefähr so aussehen

    //
    // Funktion um eine Zeile auszuwerten
    // Input char * String mit den Daten
    // Output Struktur Typ PersSTRUCT gefüllt mit Datem
    //
    int ParseOneline(char *Buffer,PersSTRUCT *Data)
     {
    // Buffer vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
         substr(Buffer,Data->vname,'#');
        // Hier Plausibiltätscheck ob Daten gültig
    // Buffer nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
         substr(Buffer,Data->nname,'#');
        // Hier Plausibiltätscheck ob Daten gültig
        if (0==strlen(Data->nname))
        {
         printf("Nachname ist leer");
         return 1;    
        }
    // Buffer wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
    ....
    
    return 0; // Alles Ok
    }
    
    //.... Ausriss aus dem Lesen eines Files
    //....
    while(fgets(buffer0,BUFFERLEN,ifp)
    {
    // bereitstellen eines neuen Datenelements
    if (0==(Data=calloc(1,sizeof(PersSTRUCT)))
      {
       printf("calloc fehlgeschlagen");
       break;
      }
    if (0!=ParseOneline(buffer0,Data))
     {
       printf("Parser fehlgeschlagen");
       break;
     }
    // Bereitstellen eines neuen Listenelements
    // Eintragen der Daten
    Personen->Daten=Data;
    }
    ...
    


  • Die Datei sieht ja wie folgt aus:

    vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr
    vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr
    vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr
    u.s.w.

    die erste Zeile kann ich einlesen, aber wie springe ich dann zur 2. Zeile?



  • wie du im CodeAusriss siehst ist da ein

    while(fgets(buffer0,BUFFERLEN,ifp)
    {
    
    ...
    }
    

    dies sorgt dafür das ganze file Zeile für Zeile gelesen und verarbeitet wird

    Deine Demodatei sollte zumindest so aussehen

    vname1#nname1#wohnort1#plz1#strasse1#hausnummer1#vorwahl1#telnr1
    vname2#nname2#wohnort2#plz2#strasse2#hausnummer2#vorwahl2#telnr2
    vname3#nname3#wohnort3#plz3#strasse3#hausnummer3#vorwahl3#telnr3
    u.s.w.
    

    Ich würde fürs debugging die Funktion ParseOneline wie folgt abändern
    Damit du sehen kannst welche Zeile er gerade Beabeitet

    int ParseOneline(char *Buffer,PersSTRUCT *Data)
     {
    // Buffer vname#nname#wohnort#plz#strasse#hausnummer#vorwahl#telnr\r\n
    printf("\nDebugAusgabe gelesen:]%s[", Buffer)
    ....
    

    Außerdem würde ich mir eine Funktion schreiben die den Inhalt der gesamten Liste auf dem Bildschirm ausgibt, damit
    kann man überprüfen ob das Lesen korrekt erfolgt, wenn man sie vor das Ende der while Schleife in den Code einfügt.



  • Hi, irgendwie funktioniert es noch nicht.
    So sieht es bei mir bis jetzt aus:

    datei = fopen("benutzer.txt","rb");
    zeiger=anfang;
    while(fgets(buffer0,BUFFERLEN,datei))
    { 
    	if(anfang == NULL)
    	{
    		anfang = (struct person*) malloc (sizeof(struct person));
    		ParseOneline(buffer0,data);
    		anfang=data;
    		anfang->next=NULL;
    	}
    	else
    	{
    		while(zeiger->next != NULL)
    			zeiger=zeiger->next;
    		zeiger->next= (struct person*) malloc (sizeof(struct person));
    		ParseOneline(buffer0,data);
    		zeiger=data;
    		zeiger->next=NULL;
    	}
    }
    fclose(datei);
    

    und

    int ParseOneline(char *Buffer,struct person *data) 
    {
    	substr(Buffer,data->vname,'#'); 
         	substr(Buffer,data->nname,'#');
         	substr(Buffer,data->wohnort,'#');
    	substr(Buffer,data->plz,'#');
    	substr(Buffer,data->strasse,'#');
    	substr(Buffer,data->hausnummer,'#');
    	substr(Buffer,data->vorwahl,'#');
    	substr(Buffer,data->telnr,'#');
    
    return 0;
    
    }
    

    - anfang heißt bei mir das erste Listenelement
    - zeiger die anderen



  • Das es nicht funktioniert ist schade.

    Was funktioniert denn nicht, wenn ich diese Information hätte könnte ich vielleicht helfen.

    - Comnpiler Fehler ?
    - Runtime Fehler ?
    - Falsche Daten ?

    Nicht böse sein, bin neugierig aber leider keine Hellseher 🙂



  • Ist ein Runtime-Fehler:
    "Unbehandelte Ausnhame :System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
    at substr(SByte* in, SByte* lowout, Sbyte search)
    at ParseOnline(SByte* Buffer, person* data)
    at main()"


Anmelden zum Antworten