Verkettete dyn. Listen ohne Hilfszeiger?



  • Wow, das Forum ist echt klasse! 🙂 hier wird schnell geantwortet 👍

    Big Brother:

    da mehrere Strukturen gespeichert werden sollen, kannst du die Zeiger auf die Strukturen, die deine Funktion ReadPerFromKbd zurückgibt, in einem dynamisch wachsenden Array speichern.

    Die Funktion soll doch aber nur einmal aufgerufen werden, alle Daten einlesen und dementsprechend nur einmal pperson_t zurückgeben.

    Hier mal ein Auszug aus der Aufgabenstellung:

    Die Datensätze aller Personen werden in einem dynamischen, zur Laufzeit des Programms erzeugten Feld mit Strukturelementen
    abgespeichert (Funktion calloc).
    Das Ende der gültigen Datensätze wird im letzten Datensatz durch das Zeichen '$' in der Komponenten Familienname (FNam)
    markiert. Bei der Erzeugung des dynamischen Feldes muss der Schlussdatensatz mit dem $-Zeichen berücksichtigt werden.

    *2. Datensätze von der Tastatur einlesen pperson_t ReadPerFromKbd( void );
    Die Funktion liest die Datensätze von der Tastatur ein und legt sie in einem dynamischen Feld ab.

    a. internes Hilfsfeld für "PERANZMAX" Personen einrichten, ggf. weitere benötigte Variablen vereinbaren
    b. Dateneingabe für maximal "PERMAXANZ" Personen, vorzeitiges Eingabeende bei leerem Familiennamen
    c. Dynamisches Personenfeld in exakt benötigter Größe erzeugen und die eingelesenen Personendaten hineinkopieren. 😕
    d. Rückgabe des Zeigers auf das dynamische Personenfeld.*

    ich habe die Funktion jetzt mal soweit geschrieben wie ich konnte, dass mit der dyn. abspeichern kapier ich nicht.

    /*****************************************************************************/
    /* ReadPerFromKbd(): Einlesen der Datensaetze ueber Tastatur                 */
    /*****************************************************************************/
    /* Rueckgabewert: pperson_t (Zeiger auf Struktur)                            */
    /* Eingang : keiner(void)                                                    */
    /* Ausgang : Zeiger auf ?? */
    /*****************************************************************************/
    pperson_t ReadPerFromKbd( void ) {
    
      person_t PerArr[PERANZMAX+1];/* Hilfsfeld fuer Personen                    */
      nam_t    FNam;               /* Hilfsvariable Familienname                 */
    
      int      PerAnz,             /* Personenanzahl                             */
    	       iAktP,              /* Laufvariable fuer Hilfsfeld                */
               EingabeEnde = 0;    /* Steuerung der Eingabeschleife              */
    
      printf("\n\n Eingabe von Personendaten (Ende: leerer Familienname)\n");
      printf("\n Anzahl der einzulesenden Datensaetze (%d-%d): "
    	     ,PERANZMIN,PERANZMAX);
      scanf("%d",&PerAnz); scanf ( "%*[^\n]" ); scanf ( "%*c" );
    
      if       ( PerAnz < PERANZMIN )     /* Personenanzahl ggf. begrenzen       */
          {
            PerAnz = PERANZMIN;
          }
        else if( PerAnz > PERANZMAX )
          {
            PerAnz = PERANZMAX;
          }
    
      for( iAktP = 0; EingabeEnde == 0; iAktP++ )/* pro Durchlauf eine Person    */
        {
          if( iAktP == PerAnz + 1 )              /* Personenanzahl + 1 erreicht? */
    	      {
                EingabeEnde = 1;                 /* Schleifenendbedingung setzen */
                printf("\n\a Speicherfeld ist voll."
    				   "\n Weiter zur Anzeige mit [return]");
                while ( getchar() != '\n' )      /* Auf Return-Taste warten      */
                  ;
                strcpy( PerArr[iAktP].fNam, EMPTYTAG_S ); /* Endzeichen $ setzen */
    	      }
    	    else
    	      {
                printf("\n Familienname: "); fgets( FNam, NAMLEN, stdin);
                if( FNam[0] == '\n' )            /* Bei leerer Eingabe..         */
    			    {
                      EingabeEnde = 1;           /* Schleifenendbedingung setzen */
                      strcpy( PerArr[iAktP].fNam, EMPTYTAG_S ); /* Endzeichen $  */
    			    }
    			  else
    			    {
                      FNam[strlen(FNam)-1] = '\0';/* ggf. '\n' entfernen         */
    				  fflush(stdin);
    				  strcpy(PerArr[iAktP].fNam, FNam);
    			    }
    	      }
          if( EingabeEnde == 0 )        /* ggf. restliche Personendaten einlesen */
    	    {
    	      printf("Vorname     : ");  fgets( PerArr[iAktP].vNam, NAMLEN, stdin);
              PerArr[iAktP].vNam[strlen(PerArr[iAktP].vNam)-1] = '\0';
    		  fflush(stdin);
    
              printf("Geburt Tag  : "); scanf("%u", &PerArr[iAktP].gebDat.gdTag);
              scanf ( "%*[^\n]" ); scanf ( "%*c" );
    
              printf("Geburt Monat: "); scanf("%u", &PerArr[iAktP].gebDat.gdMonat);
    		  scanf ( "%*[^\n]" ); scanf ( "%*c" );
    
              printf("Geburt Jahr : "); scanf("%u", &PerArr[iAktP].gebDat.gdJahr);
    		  scanf ( "%*[^\n]" ); scanf ( "%*c" );
    
              printf("Personal-Nr.: "); scanf("%u", &PerArr[iAktP].identNr);
              scanf ( "%*[^\n]" ); scanf ( "%*c" );
    	    }
        } /* for( iAktp.. ) {..                                                  */
    
    }/* ReadPerFromKbd()                                                         */
    

    ok und jetzt weiß ich nicht mehr weita 🙄

    Gruß
    Johann



  • if( PerAnz < PERANZMIN )     /* Personenanzahl ggf. begrenzen              */
          {
            PerAnz = PERANZMIN;
          }
        else
          {
            PerAnz = PERANZMAX;
          }
    

    ist doch eher sinnfrei? und sollte eher so lauten

    //Personenanzahl ggf. begrenzen
    if(PerAnz < PERANZMIN){
      PerAnz = PERANZMIN;
    }else if(PerAnz > PERANZMAX){
      PerAnz = PERANZMAX;
    }
    
    void readPersonFromKbd(person_t *p){
      ...
    }
    
    int main(void){
    ...
      person_t PerArr[PERANZMAX+1];/* Hilfsfeld fuer Personen                    */ 
    ...
      for(iAktP = 0; iAktP < PerAnz; iAktP++){
        //das einlesen passt doch super in eine extra function
        readPersonFromKbd(PerArr+iAktP);
      }
    ...
    }
    

    btw. falls das in der angabe steht

    /*****************************************************************************/
    /* ReadPerFromKbd(): Einlesen der Datensaetze ueber Tastatur                 */
    /*****************************************************************************/
    /* Rueckgabewert: pperson_t (Zeiger auf Struktur)                            */
    /* Eingang : keiner(void)                                                    */
    /* Ausgang : Zeiger auf ?? */
    /*****************************************************************************/
    

    ist sowohl deins als auch meins falsch und du mußt dir das PerArr[] || oder die person_t struct per malloc() oder calloc() in der ReadPerFromKbd function besorgen da es sonst nach beenden deiner function nicht mehr verfügbar ist (sein sollte)

    lg lolo



  • if( PerAnz < PERANZMIN )     /* Personenanzahl ggf. begrenzen              */
          {
            PerAnz = PERANZMIN;
          }
        else
          {
            PerAnz = PERANZMAX;
          }
    

    😃 Jop, das is echt Quatsch, habe das mal editiert.

    noobLolo

    ist sowohl deins als auch meins falsch und du mußt dir das PerArr[] || oder die person_t struct per malloc() oder calloc() in der ReadPerFromKbd function besorgen da es sonst nach beenden deiner function nicht mehr verfügbar ist (sein sollte)

    Das weiß ich doch, aber genau das is doch mein Problem^^

    c. Dynamisches Personenfeld in exakt benötigter Größe erzeugen und die eingelesenen Personendaten hineinkopieren.
    d. Rückgabe des Zeigers auf das dynamische Personenfeld.

    Wie geht das?

    Gruß
    Johann



  • mytype *getType(){
      mytype *ret = malloc(sizeof(mytype));
      //ret->identNr setzen
      //...
      return ret;
    }
    

    so in der richtung



  • Johann2 schrieb:

    Die Funktion soll doch aber nur einmal aufgerufen werden, alle Daten einlesen und dementsprechend nur einmal pperson_t zurückgeben.

    Ja, ok. Ich würde das aber nicht alles in eine Funktion reinstopfen, sondern
    die Eingabeaufforderung, Eingabe, Allokation in separate Funktionen auslagern.
    Ein Array vorzubelegen und deshalb die Datesätze alle umzukopieren ist IMO auch nicht gerade toll, aber was solls.
    Nun gut, nehmen wir eine Funktion und packen all die schönen Sachen da rein. Ich habe deinen Code ein bisschen bearbeitet und u.a. um die gewünschte Rückgabe ergänzt, guckst du, was du davon verwerten kannst:

    void cb() { // clear buffer
    	int c = 0;
    	while ( c = getchar() != '\n' && c != EOF ) {}
    }
    
    void view_input ( pperson_t p ) {
    	unsigned i = 0;
    	if ( p == NULL ) return;
    	puts("**************************************************");
    	puts("************* Anzeige der Datensaetze ************");
    	puts("**************************************************");
    	while ( p[i].fNam[0] != EMPTYTAG_C ) {
    		printf ( "%s\n", p[i].fNam );
    		printf ( "%s\n", p[i].vNam );
    		printf ( "%u %u %u",
    			p[i].gebDat.gdTag, p[i].gebDat.gdMonat, p[i].gebDat.gdJahr );
    		printf ( "%u\n", p[i].identNr );
    		puts("**************************************************");
    		i++;
    	}
    }
    
    pperson_t ReadPerFromKbd( void ) { 
    	pperson_t parr = NULL;
    	// Alle Strukturelemente auf 0, inklusive Nullterminierung der char Arrays.
    	person_t PerArr[PERANZMAX+1] = {0}; 
    	unsigned i = 0, n = 0; // Index und Anzahl der eingegebenen Datensätze.
    	char* p = NULL; // Hilft, das '\n' zu entfernen.
    	char nam_fmt[256] = {0}; 
    	sprintf ( nam_fmt, "%%%us", NAMLEN ); 
    
    	puts ( "Eingabe von Personendaten (Ende: leerer Familienname)." );
    	printf ( "Maximale Anzahl der einzulesenden Datensaetze: %u\n", PERANZMAX );
    
    	while ( 1 != scanf ( "%u", &n )) {
    		printf ("Fehler in der Eingabe! Noch einmal: " ); cb();
    	}cb();
    
    	if ( n > PERANZMAX ) // Personenanzahl ggf. begrenzen.
    		printf ("Die Anzahl wurde auf %u reduziert.\n", n = PERANZMAX );
    
    	for( i = 0; i < n; i++ ) {
            printf ("Familienname: "); fgets ( PerArr[i].fNam, NAMLEN, stdin );
    		if ( NULL == ( p = strrchr ( PerArr[i].fNam, '\n' ))) // Ist der Eingabepuffer leer?
    			cb(); // Nein, es wurden mehr als NAMLEN-2 Zeichen eingegeben, Puffer leeren.
    		if ( PerArr[i].fNam[0] == '\n' ) { // Wurde die Eingabe beendet?
    			PerArr[i].fNam[0] = EMPTYTAG_C; // Jepp, Ende der Eingabe kennzeichnen.
    			break;
    		}
    		else
    		{
    			*p = 0; // Das '\n' Zeichen aus dem Nachnamen entfernen.
    		}
    		printf("Vorname: "); scanf ( nam_fmt, PerArr[i].vNam ); cb();
    		printf("Geburtstag ( TT MM JJ ): ");
    		while ( 3 != scanf ( "%u %u %u", &PerArr[i].gebDat.gdTag,
    											&PerArr[i].gebDat.gdMonat,
    											&PerArr[i].gebDat.gdJahr ))
    		{
    			printf ("Fehler in der Eingabe! Noch einmal: " ); cb();
    		}cb();
    		printf("Personal-Nr.: "); 
    		while ( 1 != scanf ( "%u", &PerArr[i].identNr )) {
    			printf ("Fehler in der Eingabe! Noch einmal: " ); cb();
    		}cb();
    	} 
    
    	PerArr[i].fNam[0] = EMPTYTAG_C; 
    
    	if ( NULL == ( parr = calloc ( i+1, sizeof(*parr) ))) {
    		puts ( "Kein Arbeitsspeicher!" );
    		return NULL;
    	}
    	memcpy ( parr, PerArr, (i+1) * sizeof(*parr) );
    	return parr;
    }
    
     int main() {
    	pperson_t parr = ReadPerFromKbd();
    	view_input ( parr );
    	free ( parr );
    	return 0;
     }
    

    Gruß,
    B.B.



  • Da ist noch ne böse Fall im else Zweig, wenn mehr als NAMELEN-2 Zeichen eingegeben werden. Es fehlt die Prüfung auf NULL, sonst gibts nen Absturz:

    ...
    else
    {
        if ( *p != NULL ) *p = 0; // Das '\n' Zeichen aus dem Nachnamen entfernen.
    }
    ...
    


  • Noch ein kleiner Bug 😃
    Damit das Array nicht gesprengt werden kann, sollte man den Formatstring so anlegen:

    sprintf ( nam_fmt, "%%%us", NAMLEN - 1 );  :warning: :p
    


  • Najaaa ... andererseits, nimmt man NAMELEN als Bedeutung namelength ernst, schreibt man

    typedef char nam_t [NAMLEN+1];
    

    damit der Name auch wirklich NAMELEN Zeichen haben kann.
    Dann lässt man den Salat stehen wie er war:

    sprintf ( nam_fmt, "%%%us", NAMLEN );
    

    Besser ist das, Bruder. 🙂



  • @Big Brother
    LOLOL dickes Danke 🙂 soviel Zeitaufopferung für meine Frage habe ich nicht erwartet 🙂

    if ( NULL == ( parr = calloc ( i+1, sizeof(*parr) ))) {
            puts ( "Kein Arbeitsspeicher!" );
            return NULL;
        }
        memcpy ( parr, PerArr, (i+1) * sizeof(*parr) );
        return parr;
    

    Das löst mein Problem 😉 die Funktion memcpy() war mir bis jetzt noch nicht bekannt.
    Nur frage ich mich warum denn auch ein

    typedef pperson_t* ppperson_t; /* Typname: Zeiger auf Zeiger auf Struktur */
    

    in der Headerdatei steht, die wir verwenden sollen.

    Dein Code hat mich gestern noch bischen beschäftigt, da sind mir paar dinge noch unklar:

    char nam_fmt[256] = {0}; 
        sprintf ( nam_fmt, "%%%us", NAMLEN );
    

    Was genau bewirkt "%%%us" ?

    while ( c = getchar() != '\n' && c != EOF ) {}
    

    Wozu das EOF, was macht denn das?

    *p = 0; // Das '\n' Zeichen aus dem Nachnamen entfernen.
    

    Ein Char im String bekommt den int Wert 0?

    while ( 1 != scanf ( "%u", &n )) {
            printf ("Fehler in der Eingabe! Noch einmal: " ); cb();
        }cb();
    

    Also das find ich klasse 👍 Frage mich nur noch wie man führende White Space Zeichen auch abfängt.. (aba das krieg ich notfalls auch wo anders raus^^)

    Und:

    Ein Array vorzubelegen und deshalb die Datesätze alle umzukopieren ist IMO auch nicht gerade toll, aber was solls.

    Geht das denn auch eleganter?

    Gruß
    Johann



  • Johann2 schrieb:

    char nam_fmt[256] = {0}; 
        sprintf ( nam_fmt, "%%%us", NAMLEN );
    

    Was genau bewirkt "%%%us" ?

    %% gibt ein einzelnes % aus, %u sagt 'printf', dass ein unsigned-wert ausgegeben werden soll und das 's' hängt einfach nur ein s an. die ausgabe müsste etwa so aussehen, wenn NAMELEN 3 ist:

    %3s
    


  • Johann2 schrieb:

    Was genau bewirkt "%%%us" ?

    will man das % in einem string haben, muss man es mit dem gleichen zeichen maskieren. guckst du auch frickys antwort.

    Johann2 schrieb:

    Nur frage ich mich warum denn auch ein

    typedef pperson_t* ppperson_t; /* Typname: Zeiger auf Zeiger auf Struktur */
    

    in der Headerdatei steht, die wir verwenden sollen.

    das ist eine weitere möglichkeit, es werden die zeiger auf die strukturen in einem zeigerarray gespeichert. d.h. zu jedem datensatz wird ein weiterer zeiger benötigt. guckst du auch big brothers erste antwort.

    Johann2 schrieb:

    while ( c = getchar() != '\n' && c != EOF ) {}
    

    Wozu das EOF, was macht denn das?

    manche systeme haben als zeilenende angeblich ein EOF.

    Johann2 schrieb:

    *p = 0; // Das '\n' Zeichen aus dem Nachnamen entfernen.
    

    Ein Char im String bekommt den int Wert 0?

    Das '\n' wird durch die 0 ersetzt, dadurch wird der zeilenumbruch aus dem array entfernt. strlen liefert dann einen um 1 kleineren wert.

    Johann2 schrieb:

    Also das find ich klasse Frage mich nur noch wie man führende White Space Zeichen auch abfängt.. (aba das krieg ich notfalls auch wo anders raus^^)

    das array einfach inplace umkopieren.

    Johann2 schrieb:

    Und:

    Ein Array vorzubelegen und deshalb die Datesätze alle umzukopieren ist IMO auch nicht gerade toll, aber was solls.

    Geht das denn auch eleganter?

    vor jeder eingabe eines datensatzes speicher anfordern und die eingabe gleich abspeichern. den zeiger in eine verkettete liste oder in deinem ppperson zeigerarray speichern.



  • hier is noch ein bug, den ich nicht rauskriege:

    printf("\n\nPerArr[0].fNam ist %s",PerArr[0].fNam); //wird richtig angezeigt
      printf("\n\nPerArr[1].fNam ist %s",PerArr[1].fNam); //wird richtig angezeigt
      printf("\n\nPerArr[2].fNam ist %s",PerArr[2].fNam); //wird richtig angezeigt
    
      if ( NULL == ( pPerArr = (pperson_t)calloc( iAktP+1, sizeof(person_t) ))) { 
          puts ( "Kein Arbeitsspeicher!" ); 
          return NULL; 
      } 
      memcpy( pPerArr, PerArr, (iAktP+1) * sizeof(pPerArr) ); 
    
      printf("\n\npPerArr[0].fNam ist %s",pPerArr[0].fNam); //wird richtig angezeigt
      printf("\n\npPerArr[1].fNam ist %s",pPerArr[1].fNam); //ab hier wird
      printf("\n\npPerArr[2].fNam ist %s",pPerArr[2].fNam); //nichts mehr angezeigt
      return pPerArr; 
    
    }/* ReadPerFromKbd()                                                         */
    

    Stimmt was beim memcpy() nicht, oder is meine Ansprache auf die dyn. Strukturkomponenten falsch? Sieht so aus als ob nur das erste Feldelement pPerArr[0] kopiert wird und die anderen nicht. 😮

    Gruß
    Johann



  • Es wird nichts angezeigt, weil zu wenig kopiert wird, es werden nur sizeof(pPerArr) Bytes kopiert (lass dir die Größe mit printf anzeigen), das ist zu wenig. Du musst beim Kopieren den Zeiger dereferenzieren:

    memcpy( pPerArr, PerArr, (iAktP+1) * sizeof(*pPerArr) );
    


  • 4534kj schrieb:

    Johann2 schrieb:

    while ( c = getchar() != '\n' && c != EOF ) {}
    

    Wozu das EOF, was macht denn das?

    manche systeme haben als zeilenende angeblich ein EOF.

    dieses EOF ist ein künstliches 'nicht-zeichen', es steckt nicht in der datei oder am zeilenende, sondern wird von 'getchar' selbst erzeugt. deshalb gibt 'getchar' auch einen 'int' zurück. normale zeichen sind 0...255 und EOF liegt ausserhalb davon.
    🙂


Anmelden zum Antworten