Verkettete dyn. Listen ohne Hilfszeiger?
-
Hallo,
ich komme nicht mehr weiter(so wie wohl alle hier die fragen)
ich soll eine Funktion schreiben mit folgendem Funktionskopf:
pperson_t ReadPerFromKbd( void )
Dazu habe ich eine Headerdatei bekommen die folgende Vereinbarungen enthält:
//************************************************************************** // Vereinbarung von Datentypnamen //************************************************************************** // typedef char nam_t [NAMLEN]; /* Typname: String bzw. char[], max. 20 Z. */ typedef unsigned int ui_t; /* Typname: unsigned int */ // typedef struct gd_e { /* Etikett: Struktur Geburtsdatum */ ui_t gdTag; ui_t gdMonat; ui_t gdJahr; } gd_t; /* Typname: Struktur fuer ein Geburtsdatum */ // typedef struct person_e { /* Etikett: Struktur Person */ ui_t identNr; nam_t fNam, vNam; gd_t gebDat; } person_t; /* Typname: Struktur fuer eine Person */ // typedef person_t* pperson_t; /* Typname: Zeiger auf Struktur */ // typedef pperson_t* ppperson_t; /* Typname: Zeiger auf Zeiger auf Struktur */
Die Aufgabe der Funktion ist es nun, mehrere Personendatensätze mithilfe dieser Struktur dynamisch abzuspeichern.
Nur wie soll ich das anstellen, wenn die Struktur gar keine Hilfszeiger hat, die einzelnen dynamischen Datensätze gehn doch im Arbeitsspeicher verloren...
Jemand ne Idee?Gruß
Johann
-
Na, Deine Funktion ReadPerFromKbd( void ) soll den Speicher selber anlegen und den pointer drauf zurückgeben, wenn's geklappt hat, wenn nicht, ist NULL als Rückgabe üblich.
Es geht mal nicht um Hilfszeiger, sondern darum, daß Du strukturierten Speicher anforderst, füllst und im Erfolgsfall den Zeiger drauf zurückgibst.
Verstehst Du ungefähr, was ich meine?
-
Hi,
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 Speicherstruktur kann so aussehen:typedef struct plist_array PLArray; struct plist_array { ppperson_t arr; // Array speichert Zeiger auf Strukturen (deine Hilfszeiger). unsigned n; // Anzahl der gespeicherten Zeiger. };
Bei der Tastatureingabe kannst du das Array ruhig bei jeder neu hinzukommenden Struktur reallokieren ( z.B. mit der Funktion realloc ).
Beim Einlesen aus Dateien mit großen Datenmengen ist es besser, das Array blockweise zu reallokieren ( Laufzeitvorteil ). Man belegt also einen Speicherblock von z.B. 1000 Arrayelementen im Voraus und vergrößert den Block um weitere 1000, wenn kein Platz mehr vorhanden ist. Bei Tastatureingaben ist das aber nicht relevant.
Gruß,
B.B.
-
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 Dankesoviel 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 eintypedef 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.