• Wir präsentieren Dir heute ein Stellenangebot für einen Frontend-Entwickler Angular / Java in Braunschweig. Hier geht es zur Jobanzeige

C - Datei einlesen Inhalt in struct speichern

O

ocsme

Top Contributor
Guten Tag zusammen,

ich weiß nicht wie ich in C eine Datei die wie folgt aufgebaut ist:

Code:
Integer String Integer
Integer String Integer
Integer String Integer
....

in eine struktur gespeichert bekomme.
Die Struktur besitzt 2 Integer Werte und ein Char - Array.

Mein versuch ist / war ich lese eine Zeile komplett ein und dann laufe ich mit einer Schleife bis zum ersten Leerzeichen. Dann würde ich diesen Teilstring gerne in einen neuen String speichern (mhh... schon das erste Problem das geht ja unter C nicht so leicht wie in Java z. B.). Dann würde ich bis zum zweiten Leerzeichen suchen und eben den String speichern.

Hat jemand eine bessere Idee wie ich das gelöst bekomme?

Grüße
 
O

ocsme

Top Contributor
Bin meiner Aufgabe auf die Spur gekommen =D

C:
int strtoken(char *str, char *token[]) {
    int i = 0;

    token[0] = strtok(str, " ");

    while (token[i]) {
        i++;
        token[i] = strtok(NULL, " ");
    }
    return (i);
}

Jetzt werde ich den Rest mit atoi erledigen :)
Und dann mal weiter schauen ob es so klappt wie ich es mir vorstelle :-D
 
mihe7

mihe7

Top Contributor
Hier mal eine Zeile :)
C:
#include <stdio.h>

int main(int argc, char *argv[]) {
    FILE *f = fopen("input.txt", "r");
    int n1, n2;
    char text[80];

    fscanf(f, "%d %s %d\n", &n1, text, &n2);

    printf("%d\n%d\n%s\n", n1, n2, text);
    return 0;
}
 
O

ocsme

Top Contributor
:eek: das geht also mit fscanf 🙃
Die Methode muss ich mir aber mal genauer anschauen!

Danke auch dafür :)
 
O

ocsme

Top Contributor
Noch eine Frage, ich möchte ja aus einer Datei immer nur eine Zeile in eine Struktur packen.
Gibt es eine gute Lösung das der File Descriptor dann dort weiter macht wo er aufgehört hat?
Sprich wenn ich die erste Zeile lese, soll er wenn ich nochmal eine Struktur anfordere die zweite lesen Fertig. Geht so etwas?

Ich würde einfach eine Zähler variable mit schleppen und dann immer und immer wieder das Dokument von oben nach unten durch lesen bis zu der Zeile die ich dann benötige =( Finde ich nicht so gut gelungen. Es funktioniert zwar aber naja =(
 
mihe7

mihe7

Top Contributor
Gibt es eine gute Lösung das der File Descriptor dann dort weiter macht wo er aufgehört hat?
Sprich wenn ich die erste Zeile lese, soll er wenn ich nochmal eine Struktur anfordere die zweite lesen Fertig. Geht so etwas?
Das geht automatisch: so lange Du den Deskriptor nicht per fclose() schließt (habe ich ganz vergessen) bzw. das Programm endet.
 
O

ocsme

Top Contributor
Benötigt der dann aber nicht laufend Ressourcen?

So bleibt der Lese Schreibkopf doch die ganze Zeit auf diesem Sektor oder ist das auch wieder Falsch?
In Java hab ich die IOStream immer in den Try-Block geschrieben da sie ja closable sind :) Doch auch in Java hätte ich das gleiche Problem und müsste einen Zähler mit schleppen :-(
 
mihe7

mihe7

Top Contributor
So bleibt der Lese Schreibkopf doch die ganze Zeit auf diesem Sektor oder ist das auch wieder Falsch?
Das ist ganz falsch :)

Benötigt der dann aber nicht laufend Ressourcen?
Ja, das ist aber nichts, worüber man sich bei ein paar geöffneten Dateien großartig Gedanken machen müsste (Dateihandle des Betriebssystems plus ein paar Bytes für das FILE-struct).

In Java hab ich die IOStream immer in den Try-Block geschrieben da sie ja closable sind :) Doch auch in Java hätte ich das gleiche Problem und müsste einen Zähler mit schleppen :-(
Hm... reden wir evtl. aneinander vorbei? In Java kannst Du ja auch aus einem Stream einen Satz nach dem anderen lesen.
 
O

ocsme

Top Contributor
Das ist ganz falsch :)

Achso ich dachte immer das sich der Schreib Lese Kopf dann nicht mehr weiter bewegt.
Das ich in Java auch eine Zeile lesen kann habe ich mir so wieder klar gemacht =D

Java:
public static String readALine(BufferedReader r) throws IOException {
        return r.readLine();
    }

    public static void closeReader(BufferedReader r) throws IOException {
        r.close();
    }

BufferedReader br = new BufferedReader(new FileReader("Test.txt"));

Dort kann ich so lange der BufferedReader noch offen ist Zeile für Zeile lesen.
Mhhh doch ich dachte eben immer der Schreib Lese Kopf bleibt dann auf dem Sektor. Wo geht der denn dann wieder hin? Natürlich wenn ein anderer Schreibbefehl oder Lesebefehl vom Betriebssystem oder anderen Programmen kommt wandert er wo anders hin.

Bis jetzt habe ich es meistens auch in Java so gemacht (da wir nur mit kleine Daten gearbeitet haben) das der Stream komplett durchläuft und er gleich wieder geschlossen wird. Gespeichert habe ich die Daten von der Datei dann eben im Speicher per List<> oder Arrays.

In C unterscheidet man ja schon Low-Level und High-Level Funktionen beim I/O. Da ich ja ein FILE-Struktur übergebe handelt es sich um eine High-Level Funktion. Kann ich diese Struktur also ungefähr gleichsetzen mit dem InputStream von Java? Falls so etwas überhaupt möglich ist.

Dann bleibt der Stream in C eben auch so lange offen wie ich Daten benötige und nehme Zeile für Zeile raus. Das spart mir ja jetzt echt viel Arbeit :)
Also mal wieder ein Mangel an Grundlegendem Wissen :confused:
 
Zuletzt bearbeitet:
mihe7

mihe7

Top Contributor
Mhhh doch ich dachte eben immer der Schreib Lese Kopf bleibt dann auf dem Sektor. Wo geht der denn dann wieder hin? Natürlich wenn ein anderer Schreibbefehl oder Lesebefehl vom Betriebssystem oder anderen Programmen kommt wandert er wo anders hin.
Genau, zumal "auf dem Sektor bleiben" bedeuten würde, dass die Platte sich nicht mehr dreht :) Wenn, dann bliebe der Kopf in der Spur (Zylinder). Alles andere ist tatsächlich abhängig vom Controller und dem Betriebssystem. Es gibt ja mehrere Anfragen parallel und die können so abgearbeitet werden, dass der Aufwand minimal wird.

Wenn Du die Datei nach jedem Zugriff wieder schließen musst/möchtest, musst Du Dir die Position merken, an der Du in der Datei warst (ftell() und fseek() sind Deine Freunde).
 
O

ocsme

Top Contributor
🤪 Ich hätte mir die Position mit einer Variable gemerkt und wäre dann schön "doof" mit einer while-Schleife durch die Datei immer wieder von vorne durch gelaufen bis ich den Counter (meine Variable) erreicht hätte.

Da nichts gesagt wurde ob der FILE Pointer per fclose() geschlossen werden soll jedes mal, nehme ich jetzt einfach mal den angenehmen Weg und lass ihn offen :)

Achso mit ftell() kann ich mir die Position zurück geben lassen. Ohha diese ganzen Funktionen mal wieder ^.^ ob man wirklich irgendwann das alles mal gelernt bekommt? Klar da hätte ich mich ja echt blöde angestellt mit meiner Counter Variable. Dann ermittelt man mit ftell() die Position und mit fseek() Positioniert man den Kopf neu.

Glaube das Probiere ich morgen mal aus ;-)

Nachtrag: Geht so etwas auch in Java?
 
kneitzel

kneitzel

Top Contributor
Java hat ein RandomAccessFile, damit kann man dann problemlos beliebige Stellen lesen oder schreiben.
 
O

ocsme

Top Contributor
Guten Abend,
danke für die schnelle Hilfe :)

Hab gerade noch eine Frage :-D
Wenn ich in einer Struktur
C:
typedef struct txt {
    int length;
    char *data;
}string;

habe und ein char aus dem Array zurück geben möchte.
Hab ich es derzeit so gemacht:
C:
char getCharAt(string s, int index) {
    if(index > 0 && index < s.length - 1)
        return s.data[index];
    return '\0';
}

Jetzt wollte ich aber auf data zugreifen über den Zeiger also das ganze Umschreiben:
C:
int ival[2];
ival[0] = 123;
*(ival + 1) = 456;

Wie wird jetzt aber aus s.data[index] = s.*(data + index) ? <- das geht so nämlich nicht :-D
 
H

httpdigest

Top Contributor
X[ i] ist dasselbe wie *(X + i)
Dein X in diesem Fall ist s.data und dein i ist index, also:
*(s.data + index)
 
kneitzel

kneitzel

Top Contributor
Wobei ich mich frage, wieso du da nicht einfach mit dem Array Operator arbeiten willst. Findest du das leserlicher? Oder was übersehe ich gerade?
 
mihe7

mihe7

Top Contributor
Ist es eigentlich Absicht, dass für das erste und das letzte Zeichen \0 zurückgegeben wird?
 
B

BestGoalkeeper

Gast
Ich hab mich mal an die Aufgabe gewagt...
Wenn du eine Datei hast:
Code:
45 hallo du daa 67
89 moin 1010
-15 lang weil ig +16
(man beachte die leere Zeile 4...)
und diese einlesen möchtest, würde ich das so machen:
C:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

const char *const FILEN = "hallo.txt";
typedef struct dataf
{
    int32_t first;
    int32_t second;
    char data[1000];
} sDataf;
sDataf array[10];
int32_t size = 0;

void readFILEN()
{
    FILE *f = fopen(FILEN, "r");
    char line[1000];
    char buf[1000];
    int len, space1, space2;
    sDataf *df;
    while (fgets(line, sizeof line, f) != NULL)
    {
        len = -1;
        while (line[++len] != '\n' && line[len] != '\0')
            ;
        printf("%d\n", len);
        if (len == 0)
            break;
        df = &array[size++];
        space1 = 0;
        while (line[++space1] != ' ')
            ;
        space2 = len;
        while (line[--space2] != ' ')
            ;

        memcpy(buf, &line[0], space1);
        buf[space1] = '\0';
        sscanf(buf, "%d", &df->first);

        memcpy(df->data, &line[space1], space2 - space1 + 1);
        df->data[space2 - space1 + 1] = '\0';

        memcpy(buf, &line[space2 + 1], len - space2);
        buf[len - space2] = '\0';
        sscanf(buf, "%d", &df->second);
    }
    fclose(f);
}

int main(int argc, char **args)
{
    int i;
    sDataf *df;
    readFILEN();
    for (i = 0; i < size; i++)
    {
        df = &array[i];
        printf("+++%d %d%s+++\n", df->first, df->second, df->data);
    }
    return EXIT_SUCCESS;
}
Ausgabe ist dann:
Code:
18
12
20
0
+++45 67 hallo du daa +++
+++89 1010 moin +++
+++-15 16 lang weil ig +++
etwas einfacheres ist mir nicht eingefallen...
 
O

ocsme

Top Contributor
Ist es eigentlich Absicht, dass für das erste und das letzte Zeichen \0 zurückgegeben wird?
Nö gestern war es für mich schon zu spät und ich hab wieder nur misst gebaut :(

Wobei ich mich frage, wieso du da nicht einfach mit dem Array Operator arbeiten willst. Findest du das leserlicher? Oder was übersehe ich gerade?
Leserlich ist es sicherlich nicht. Doch ich kam gestern Abend nicht mehr drauf wie ich das umschreiben kann =( irgendwann sieht man nur noch Symbole und Zeichen =D

und diese einlesen möchtest, würde ich das so machen:
Ja so ähnlich Kompliziert wollte ich es auch machen. Hab es dann in Token aufgeteilt und diese dann wieder zurück gecastet doch das ganze geht doch viel leichter wie von @mihe7 schon gesagt per:

C:
fscanf(f, "%d %s %d\n", &n1, text, &n2);

Bei mir läuft das ganze jetzt auch so. Was ich noch ändern möchte ist das die Datei immer wieder geclosed wird und die Lese- Schreib Position gespeichert werden =)
 
kneitzel

kneitzel

Top Contributor
Bei mir läuft das ganze jetzt auch so. Was ich noch ändern möchte ist das die Datei immer wieder geclosed wird und die Lese- Schreib Position gespeichert werden =)
Aus welchem Grund?

Ich frage, weil ich die fürchte, dass die Datenintegrität nicht gewährleistet ist.

Wenn Du die Datei öffnest und geöffnet lässt, dann ist die Datei mehr oder weniger gesperrt. (Je nach Betriebssystem und wie die Datei geöffnet wurde, ist es etwas unterschiedlich. Aber Standard ist, dass da ein gewisser Schutz vor Veränderungen ist.

Beispiel:
Datei ist jetzt:
1 a 2
3 b 4

Du liest die erste Zeile ein und merkst Dir die Position: Du bist nach dem 7ten Zeichen (Windows, 5 Zeichen + Zeilenumbruch \r\n). Du schließt die Datei und damit kann diese frei verändert werden. Und ich ändere diese nun:
17 antonUndBerta 23
3 b 4

Nun willst Du die nächste Zeile lesen. Dazu gehst Du nach das 7te Zeichen und liest bis einschließlich Zeilenumbruch:
nUndBerta 23

Das ist unter dem Strich eine einfache Problematik, wenn mehrere "Threads/Prozesse" Daten anpassen.
Also sollte man Daten entsprechend schützen (Also z.B. exklusiver Zugriff) oder den Zugriff so gestalten, dass wenigstens Datensätze nicht falsch gelesen werden. (Dann würdest Du Dir merken: 2. Zeile - und dann liest Du alle Zeilen bis zur zweiten Zeile ...)

Das nur als kleine Anregung.
 
B

BestGoalkeeper

Gast
das ganze geht doch viel leichter wie von @mihe7 schon gesagt per:

C:
fscanf(f, "%d %s %d\n", &n1, text, &n2);
Bei mir läuft das ganze jetzt auch so. Was ich noch ändern möchte ist das die Datei immer wieder geclosed wird und die Lese- Schreib Position gespeichert werden =)
Nein, wenn s Leerzeichen enthält, funktioniert das nicht mehr
 
O

ocsme

Top Contributor
Nein, wenn s Leerzeichen enthält, funktioniert das nicht mehr
:eek: stimmt. Dann werden erst mal keine Leerzeichen im String vorkommen. Das werde ich aber nochmal abklären. Sobald ein Leerzeichen im String vor kommt geht es nicht mehr auf diese nette weise mhhh...
Danke für den Hinweis =)

Aus welchem Grund?
Das stimmt, ich möchte das einfach versuchen das ist der Grund. :) Kann auch sein das ich es einfach sein lasse, da es ja wieder mal schnell weiter geht und man fast keine Chance hat da mit zu kommen :-(

Vielen Dank für die schnell und Liebe Hilfe von euch :) halte euch auf dem laufenden ;)
 
B

BestGoalkeeper

Gast
Hier das ganze etwas robuster, aber immernoch fragil:
C:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

const char *const FILEN = "hallo.txt";
typedef struct dataf
{
    int32_t first;
    int32_t second;
    char data[1000];
} sDataf;
sDataf array[10];
int32_t size = 0;

int lineLen(char *ca)
{
    int len = -1;
    while (ca[++len] != '\n' && ca[len] != '\0')
        ;
    return len;
}

int firstIdxOf(char *ca, int len, char c)
{
    int idx = -1;
    while (++idx < len && ca[idx] != c)
        ;
    if (idx == len)
        return -1;
    return idx;
}

int lastIdxOf(char *ca, int len, char c)
{
    int idx = len;
    while (--idx >= 0 && ca[idx] != c)
        ;
    return idx;
}

void readFILEN()
{
    FILE *f = fopen(FILEN, "r");
    char line[1000];
    char buf[1000];
    int len, space1, space2;
    sDataf *df;
    while (fgets(line, sizeof line, f) != NULL)
    {
        len = lineLen(line);
        space1 = firstIdxOf(line, len, ' ');
        space2 = lastIdxOf(line, len, ' ');
        printf("%d %d %d\n", len, space1, space2);
        if (len == 0 || space1 == -1 || space2 == -1 || space2 <= space1)
            break;

        df = &array[size++];

        memcpy(buf, &line[0], space1);
        buf[space1] = '\0';
        sscanf(buf, "%d", &df->first);

        memcpy(df->data, &line[space1], space2 - space1 + 1);
        df->data[space2 - space1 + 1] = '\0';

        memcpy(buf, &line[space2 + 1], len - space2);
        buf[len - space2] = '\0';
        sscanf(buf, "%d", &df->second);
    }
    fclose(f);
}

int main(int argc, char **args)
{
    int i;
    sDataf *df;
    readFILEN();
    for (i = 0; i < size; i++)
    {
        df = &array[i];
        printf("|||%d|%d|%s|||\n", df->first, df->second, df->data);
    }
    return EXIT_SUCCESS;
}
 
O

ocsme

Top Contributor
Hier das ganze etwas robuster, aber immernoch fragil:
Das ganze wird ja auch sehr Problematisch wenn die Datei dann auch mal Random gemischt wird =( sprich wenn da nicht Int Int String steht!

Geht das auch anders? Sprich kann ich erkennen in C was drunter liegt, welcher Datentyp? Dann könnte man es ja dorthin Casten wo es hin gehört. Geht so etwas überhaupt?
 
B

BestGoalkeeper

Gast
In (reinem) C muss man vieles bis alles selber machen ;) Ja es geht, aber es ist unheimlich aufwändig... Also Kladderadatsch parsen ist nicht einfach
 
kneitzel

kneitzel

Top Contributor
Wobei es ja auch genug Libraries gibt, die sowas machen können ... So ist die Boost Library zu einer Art Standard geworden ... Und da ist dann die Boost String Algorithms Library Teil: https://www.boost.org/doc/libs/1_74_0/doc/html/string_algo.html

Aber hier würde ich wohl auch eher auf Regex setzen:

Das liegt ja nicht an der Sprache sondern an der Library. Java hat halt schon einiges im Framework. Bei C++ hat man per default halt erst einmal sehr wenig dabei...
 
B

BestGoalkeeper

Gast
Hier hättest du noch eine robustere Version ;) :
C:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

int main(int argc, char **args)
{
    FILE *fp = fopen("hallo.txt", "r");

    if (fp)
    {
        char line[1024];
        char string[1024];

        while (fgets(line, sizeof(line) - 1, fp))
        {
            char *p = line, *sp = string;

            if (*p == '\n')
                continue;

            memset(string, '\0', 1024);
            printf("%s", "|||");

            for (; *p; ++p)
            {
                if (isdigit(*p) || *p == '-' || *p == '+')
                    printf("%ld|", strtol(p, &p, 10));

                else
                    *sp++ = *p;
            }

            *sp = 0;
            printf(" %s |||\n", string);
        }
    }

    return EXIT_SUCCESS;
}
 
W

White_Fox

Top Contributor
Geht das auch anders? Sprich kann ich erkennen in C was drunter liegt, welcher Datentyp? Dann könnte man es ja dorthin Casten wo es hin gehört. Geht so etwas überhaupt?

Nein. C ist relativ hardwarenahe. Manche sagen C sei Assembler mit syntaktischem Zucker. Und das stimmt.
Das hat gewisse Vorteile, aber eben u.a. auch den Nachteil daß du es nur mit 0 und 1 zu tun hast. Du mußt dir selber überlegen, was sie bedeuten sollen.

Dazu kommen dann so lustige Umstände, daß es in C nichtmal eine einheitliche Datentypbreite gibt. Ein int in Java ist stets 32 Bit breit, das ist in C nicht so. Da kann ein int durchaus 32 Bit breit sein, ist es oft auch. Aber eben nicht immer, auf anderen Architekturen sind es dann nur 16 Bit. (Dafür hat irgendwer mal die stdint-Bibliothek gebaut, mit Datentypen wie int16_t oder uint16_t.)
In Java ist ein int auch stets mit Vorzeichen, auch das kannst du in C drehen wie du willst.

Eigentlich sollte man die Sprachen andersherum lernen, erst C und dann Java. Das macht vieles einfacher. Wenn du eine Weile Java programmiert hast, dann kotzt dich C so richtig an.
 
W

White_Fox

Top Contributor
Übrigens, noch ein paar Häßlichkeiten zu C, weil ich mich damit gelegentlich rumschlage:

In Java meckert der Compiler ja jeden Scheiß an, der auch nur ansatzweise unzulänglich sein könnte. Wehe du addierst ein Byte auf ein Integer ohne Cast... und für wirklich jeden Shitkram gibt es eine Festlegung.

In C mußt du dir -- wie bereits gesagt -- selber überlegen was der Haufen 0 und 1 bedeuten soll. Das heißt nicht weniger, als daß du auf beliebigem Speicherinhalt beliebige Operationen ausführen kannst. So etwas z.B.:

C:
if(1+1){
    //...
}

würde dir in Java gnadenlos um die Ohren gehauen werden, weil nichts an diesem Ausdruck irgendwie boolsch ist. Weder die Operation selber noch die Operanden. In C gibt es da eine sehr freizügige Festlegung:
  • 0 ≙ falsch
  • wahr = nicht falsch (das KANN 1 sein, aber auch alles mögliche andere was nicht 0 ist)
Ein x-beliebiger Speicherbereich gilt als wahre Aussage, wenn er nicht null ist.

Warum ich gerade darüber gestolpert bin: Weil die beiden Ausdrücke in C NICHT äquivalent sind.
C:
if(outputRmsCurrent > 3.0f / 24.0f * NOMINAL_OUTPUTCURRENT_LIMIT) {
    //blah...
}

if(outputRmsCurrent > (3.0f / 24.0f * NOMINAL_OUTPUTCURRENT_LIMIT)) {
    //blah...
}
 
O

ocsme

Top Contributor
Erste einmal wieder Danke =)

Jetzt verzweifel ich doch tatsächlich wieder an den ADT Binärbaum =(
Einfügen, Suchen, etc. geht ohne Probleme aber wehe ich will einen Knoten Löschen =(

Im Endeffekt möchte ich die Datei die ich eingelesen habe in einem Baum speichern und diesen Baum dann untersuchen. Nun dachte ich mir versuchst du mal ein Entfernen in C =D haha

C:
artikel *entfernen(baum *b, int nr) {
    node *s = entf(b->root, nr);
    return s->artikel;
}

node *entf(node *n, int nr) {
    if(n == NULL)
        return NULL;
    if(n->artikel->artikelnr < nr)
        n->right = entf(n->right, nr);
    else if(n->artikel->artikelnr > nr)
        n->left = entf(n->left, nr);
    else if(n->left->right == NULL){
        n->left->right = n->right;
        n = n->left;
    }
    else {
        node *hZ = n->left;
        node *p = NULL;
        while(hZ->right)
            p = hZ;
        p->right = hZ->left;
        hZ->left = n->left;
        hZ->right = n->right;
        n = hZ;
    }
    return n;
}

Die Entfernen Methode soll mir den vorher Eingelesen Artikel "einfach" nur aus dem Baum werfen und zurück geben.
Da dachte ich mir naja schreib ich mir eine Hilfsmethode und übergebe der den ersten Knoten (root) der bei mir ein node ist im Baum =) Also in der Node Struktur ist der Artikel gespeichert.

Bin aber auch selbst schuld ich hab mein C Verzeichnis gelöscht ohne es vorher zu speichern denn ich glaube das Thema hatten wir ähnlich schon mal =( Naja Backups denke das Thema kennt ihr =D

Kann mir gerade jemand sagen wo mein Gedanken Fehler ist?

Ich möchte die Knoten auch wie folgt löschen lassen:
Wenn mein Node ein Blatt ist einfach entfernen,
Wenn der Node einen Sohn hat, soll der Sohn die Stelle des zu löschenden Node übernehmen und falls ich 2 Söhne habe suche ich im linken Teilbaum den am weitesten Rechts stehenden Node und ersetze Ihn mit dem zu löschenden Node.
 
Zuletzt bearbeitet:
O

ocsme

Top Contributor
Hey,
ich hab das Löschen wieder neu geschrieben.
Doch wieso löscht er immer noch keine Blattknoten?

C:
node *entferne_Knoten(node *root, int data) {
    if (root == NULL)
        return NULL;
    else if (data < root->artikel->artikelnr)
        root->left = entferne_Knoten(root->left, data);
    else if (data > root->artikel->artikelnr)
        root->right = entferne_Knoten(root->right, data);
    else {
        if (root->left == NULL && root->right == NULL) { //Fall 1 Blattknoten
            node *tmp = root;
            root = NULL;
            return tmp;
        } else if (root->left == NULL) { //Fall 2 ein untergeordnetes Element
            node *tmp = root;
            root = root->right;
            return tmp;
        } else if (root->right == NULL) { //Fall 2 ein untergeordnetes Element
            node *tmp = root;
            root = root->left;
            return tmp;
        } else {                        //Fall 3 im linken Teilbaum den größten Wert
            node *tmp = root->left;
            while (tmp->right != NULL)
                tmp = tmp->right;

            root->artikel = tmp->artikel;
            return root;
        }
    }
    return root;
}
 
kneitzel

kneitzel

Top Contributor
Wenn du den knoten löschen willst, dann ist das der Fall, dass left und Right null sind.
da gibst du den Knoten zurück, aber der Knoten soll doch weg, also müsstest du doch null zurück geben. Oder habe ich da die Struktur jetzt falsch verstanden?
 
O

ocsme

Top Contributor
Der Artikel soll zurück gegeben werden.
Doch im Baum soll der Knoten in dem der Artikel liegt gelöscht werden.
 
kneitzel

kneitzel

Top Contributor
Und root zu setzen scheint mir unsinnig, denn das ist ja nur die übergebene Variable. Das ist ja kein Call by Reference.... das scheinst du einen generellen Denkfehler zu haben.
Beim rekursiven Aufruf gibst du den behandelten Node als Parameter und nimmst den neuen Zeiger als Returnwert. Somit reicht es, den Zeiger zurück zu geben, den du setzen willst. Das ist beim Blatt null, aber wenn rechts oder links was ist, dann ist es da der Wert, denn du da in Root reinschreibst derzeit.

Und wie ist die Speicherverwaltung? Evtl. musst du da ggf Speicher freigeben.
 
O

ocsme

Top Contributor
wow irgendwie hab ich gerade einen super Gedankenfehler :-(

So gehen die ersten 2 Fälle, doch ich möchte in der Main doch den Artikel haben, jetzt ist doch alles weg oder liege ich schon wieder falsch =D

C:
node *entferne_Knoten(node *root, int data) {
    if (root == NULL)
        return NULL;
    else if (data < root->artikel->artikelnr)
        root->left = entferne_Knoten(root->left, data);
    else if (data > root->artikel->artikelnr)
        root->right = entferne_Knoten(root->right, data);
    else {
        if (root->left == NULL && root->right == NULL) { //Fall 1 Blattknoten
            node *tmp = root;
            free(root);
            return tmp;
        } else if (root->left == NULL) { //Fall 2 ein untergeordnetes Element
            node *tmp = root;
            root = root->right;
            free(tmp);
            return root;
        } else if (root->right == NULL) { //Fall 2 ein untergeordnetes Element
            node *tmp = root;
            root = root->left;
            free(tmp);
            return root;
        } else {                        //Fall 3 im linken Teilbaum den größten Wert
            node *tmp = root->left;
            while (tmp->right != NULL)
                tmp = tmp->right;

            root->artikel = tmp->artikel;
            return root;
        }
    }
    return root;
}
 
kneitzel

kneitzel

Top Contributor
Also generell sollte klar sein:
Java:
root->left = entferne_Knoten(root->left, data);
Damit hast Du kein Pattern, bei dem Du das gelöschte Element zurück gibst, sondern wie du siehst, übergibst Du root->left und die Rückgabe ist das neue root->left.

Wenn Du das anders haben willst, dann geht das natürlich auch. Aber dann musst Du den Returnwert weiter zurück geben. Also etwas wie
Java:
return entferne_Knoten(root->left, data);

Da Du aber den übergebenen Knoten ändern können willst, dann kannst Du statt den Pointer ein Pointer auf einen Pointer übergeben:
Java:
node *entferne_Knoten(node **root, int data) {

In C++ hast Du auch das Call By Reference, d.h. wenn es kein reines C sein soll sondern C++ sein darf, dann ginge auch:
Java:
node *entferne_Knoten(node &root, int data) {

Damit kannst Du den eigentlichen Node, der übergeben wurde, anpassen. Und die Rückgabe ist davon unabhängig und kann ein Node sein, der nicht mehr referenziert sein soll.
 
O

ocsme

Top Contributor
Oh stimmt, das heißt wenn ich den Knoten mit meiner Entferne Methode zurück gebe kann ich den Wert an den root -> left und -> right zuweisen.
Da ich das aber nicht möchte mach ich die Methode void bzw. ich schau mir das morgen früh mal mit dem **p an :) Keine Ahnung ob ich es hin bekomme =D

Denn ich hab nicht nur beim Löschen ein Problem sondern auch noch beim Schreiben eines Artikels.
Vielleicht hast du da auch noch einen guten Tipp für mich =)

C:
typedef struct artikel {
    unsigned int artikelnr;
    char name[N];
    int bestand;
} artikel;

void schreibeArtikel(FILE *ziel, artikel *a) {
    char c = a->artikelnr + '0';
    char c2 = a->bestand + '0';
    fputs(a->name,ziel);
}

Natürlich ist das was ich dort mache Quatsch. Ich möchte gerne den ganzen "String" wieder zurück schreiben.
Jetzt muss ich ja die Artikelnr und den Bestand da Int erst mal zu einem Char casten oder gleich zu einem Char * über z. B. itoa. Bei itoa hab ich nur das Proble das ich ja angeben muss wie groß der Int ist. Keine Ahnung ob wir das dürfen ob ich dann einfach dort eine 10 rein schreiben darf also so z. B.:
C:
int bestand = 78 ;
char arr[10] ="" ;

itoa(bestand,arr,10) ;

Und wenn ich die 2 int Werte dann zu meinem String gecastet habe muss ich aus den 3 Strings wieder einer machen. Geht das ganze nicht auch wieder schöner?
 
kneitzel

kneitzel

Top Contributor
Ich verstehe noch nicht genau, was Du da machen möchtest. Was soll genau in der Datei stehen?

Wenn Du es als Text schreiben willst, dann kannst Du natürlich mit fprintf auch Integer Zahlen in die Datei schreiben.
Die Addition mit '0' würde nur für Zahlen von 0-9 gültig sein um dann ein Ascii Zahlenwert zu bekommen. Aber das ist wohl nicht Dein Zeil denke ich mal.

Also Du kannst sowas machen wie fprintf(ziel, "%d %d %s\n", a->artikelnr, a->bestand, a->name);
Dann hättest Du eine Zeile in die Datei geschrieben: Erst die zwei Zahlen und dann der Name.
 
O

ocsme

Top Contributor
genau so etwas habe ich gesucht.
Geht es nicht aber auch irgendwie mit fputs?
Dazu dachte ich mir man müsse die 2 Zahlen (Integer) umwandeln. Doch dazu müsste ich ja eine bestimmte größe als char-Array angeben um einen String zu erhalten über atoi. Ich wollte 2x die atoi Mehtode aufrufen mit 2 Char Arrays machen und die 3 Char Arrays dann später in eines packen und mit fputs das ganze dann in FILE * schreiben. So meine Idee.
Doch fprintf geht ja viel schöner =)
 
O

ocsme

Top Contributor
Nochmal kurz zum Löschen im Baum zurück.
C:
} else {                    //Fall 3 im linken Teilbaum den größten Wert
            node *tmp = root->left;
            while (tmp->right != NULL)
                tmp = tmp->right;

            //Was muss ich hier zurück geben?

Im dritten Fall gehe ich im linken Teilbaum ganz nach rechts. Der Knoten soll ja jetzt mein neuer Aktueller Knoten werden für den der gelöscht werden soll.
Muss ich dann nur return tmp; machen?
Nein kann nicht sein, dann verliere ich ja den Rest des Baumes =D
 
kneitzel

kneitzel

Top Contributor
Also erst einmal aufpassen: atoi wandelt eine char* Zeichenkette mit Ziffern in einen Integer (Ascii to Integer). Aber Du willst ja den anderen Weg gehen: Integer to Ascii, also itoa. Und das kannst Du natürlich auch mittels puts dann schreiben. Also etwas wie fputs(itoa(a->artikelnr, ziel);

Aber da brauchst Du dann mehrere Befehle.

Wenn Es nicht C sein muss sondern auch C++, dann kannst Du generell auch Streams verwenden. http://www.cplusplus.com/doc/tutorial/files/
Das macht es noch einmal einfacher.

Darf an fragen, was Du da genau treibst? Ist es eine reine C Sache? Oder darf es C++ sein? Und dürfen es Libraries sein? Wenn Das Studium ist und mit C Datentypen implementiert werden sollen, dann entfällt der Punkt jetzt natürlich. Bezüglich Projekte wäre aber mein Hinweis:
Wenn Du wirklich mit C++ entwickeln willst (und da einen aktuellen Standard nutzen willst und kannst), dann schau dir auf jeden Fall Boost an. Und wenn Du nicht so gerne im Browser Dokumentation lesen willst, dann kauf das PDF bei https://theboostcpplibraries.com/ - die Autoren freut es ...

Nochmal kurz zum Löschen im Baum zurück.
C:
} else {                    //Fall 3 im linken Teilbaum den größten Wert
            node *tmp = root->left;
            while (tmp->right != NULL)
                tmp = tmp->right;

            //Was muss ich hier zurück geben?

Im dritten Fall gehe ich im linken Teilbaum ganz nach rechts. Der Knoten soll ja jetzt mein neuer Aktueller Knoten werden für den der gelöscht werden soll.
Muss ich dann nur return tmp; machen?
Was genau willst Du jetzt machen? Wie sieht die Methode generell aus? Was soll zurück gegeben werden? Von dem Ausschnitt her sieht es so aus, dass root weder als Referenz noch als Zeiger auf Zeiger übergeben wurde ... Daher soll vermutlich nicht das gelöschte Element zurück gegeben werden. Dann brauchst Du evtl. gar kein temp, denn du gibst dann ja direkt einen Wert zurück. Aber mir fehlt im Augenblick der Überblick, was Du da gerade im Code treibst. Das waren bisher immer nur kurze Blicke auf den Code .... evtl. kann ich da aber morgen einmal genauer drauf schauen. Evtl. kannst Du ja mal deine Implementation geben, dann kann ich auch lokal anpassen und ausprobieren - das reduziert die Fehlerwahrscheinlichkeit bei Hinweisen.
 
O

ocsme

Top Contributor
Hallo,
sehr gerne lass uns das ganze auf morgen verschieben :) Ich muss auch gleich mal zur Ruhe kommen :-D

Das ganze soll nur in C geschrieben werden ;-) C++ kommt auch noch später aber erst einmal das ganze hier in C :)
Diese ganzen Methoden wieder :-D Ich probiere das noch mit itoa aus und mach dann schluss =)
 
B

BestGoalkeeper

Gast
Hier hab ich eine einfach-verkettete Liste mit beliebigem Byte-Inhalt, vielleicht hilft es :)
C:
#include <stdio.h>
#include <stdlib.h>

struct ByteContent
{
    char *content;
    int len;
};

struct Node
{
    struct Node *next;
    struct ByteContent *byte_content;
};

struct Node *root = 0;
int len = 0;

struct Node *get_node(int n)
{
    struct Node *tn = root;
    int i = 0;
    if (n < 0)
    {
        return 0;
    }
    for (; i < len && i < n; i++)
    {
        tn = tn->next;
    }
    return tn;
}

struct ByteContent *get_byte_content(int n)
{
    if (n < 0 || n >= len)
    {
        return 0;
    }
    return get_node(n)->byte_content;
}

void insert(int n, struct ByteContent *bc)
{
    struct Node *tn0 = get_node(n - 1);
    struct Node *tn1 = get_node(n);
    struct Node *tn2 = 0;
    if (n < 0 || n > len || bc == 0)
    {
        return;
    }
    if (tn0 == 0)
    {
        tn2 = (struct Node *)malloc(sizeof(struct Node));
        tn2->byte_content = bc;
        tn2->next = tn1;
        root = tn2;
    }
    else
    {
        tn2 = (struct Node *)malloc(sizeof(struct Node));
        tn2->byte_content = bc;
        tn2->next = tn1;
        tn0->next = tn2;
    }
    len++;
}

struct ByteContent *delete (int n)
{
    struct Node *tn0 = 0;
    struct Node *tn1 = 0;
    struct ByteContent *temp = 0;
    if (n < 0 || n >= len)
    {
        return 0;
    }
    tn0 = get_node(n - 1);
    tn1 = get_node(n);
    temp = tn1->byte_content;
    if (tn0 == 0)
    {
        root = root->next;
    }
    else
    {
        tn0->next = tn1->next;
    }
    free(tn1);
    len--;
    return temp;
}

int main(int argc, char **argv)
{
    int i = 0;
    struct ByteContent *byte_array[10];
    char *content = 0;
    struct ByteContent *temp = 0;
    for (i = 0; i < 10; i++)
    {
        byte_array[i] = (struct ByteContent *)malloc(sizeof(struct ByteContent));
        content = (char *)malloc(sizeof(char) * 9);
        sprintf(content, "hallo %d!", i);
        byte_array[i]->content = content;
        byte_array[i]->len = 9;
    }
    for (i = 0; i < 10; i++)
    {
        insert(rand() % (len + 1), byte_array[i]);
    }
    for (i = 0; i < 10; i++)
    {
        delete (rand() % len);
    }
    for (i = 0; i < 10; i++)
    {
        insert(rand() % (len + 1), byte_array[i]);
    }
    for (i = 0; len > 0; i++)
    {
        temp = delete (0);
        printf("%s\n", temp->content);
        free(temp->content);
        free(temp);
        temp = 0;
    }
    return 0;
}
 
O

ocsme

Top Contributor
So ich hab mir das ganze nochmal angeschaut und kann jetzt so wie es aussieht alle drei Fälle Löschen.
Wäre super wenn jemand kurz über den Code schauen könnte. Denn nun habe ich ja das Problem das ich die Funktion void gemacht habe. Ich möchte aber ja den Knoten im Baum zwar freigeben aber den Artikel (der im Knoten gespeichert ist) zurück bekommen :)

C:
void loesche_knoten(node **zeiger) {
    node *temp;

    if ((*zeiger) != NULL) {
        if ((*zeiger)->left == NULL && (*zeiger)->right == NULL) { //Fall 1 Blattknoten
            free(*zeiger);
            *zeiger = NULL;
        } else if ((*zeiger)->left == NULL) { //Fall 2 ein untergeordnetes Element
            temp = *zeiger;
            *zeiger = (*zeiger)->right;
            free(temp);
        } else if ((*zeiger)->right == NULL) { //Fall 2 ein untergeordnetes Element
            temp = *zeiger;
            *zeiger = (*zeiger)->left;
            free(temp);
        } else { //Fall 3 im linken Teilbaum den größten Wert
            temp = (*zeiger)->left;
//            node *wegDamit = *zeiger;
            node *hilfsZeiger = NULL;
            while(temp->left != NULL) {
                hilfsZeiger = temp;
                temp = temp->right;
            }
            hilfsZeiger->right = temp->left;
            temp->left = (*zeiger)->left;
            temp->right = (*zeiger)->right;
            free(*zeiger); //muss der dereferenziert werden hier?
        }
    }
}

void loesche(node **zeiger, int such) {
    if ((*zeiger) == NULL)
        printf("Baum ist leer\n");
    else if ((*zeiger)->artikel->artikelnr == such) /* Gefunden! */
        loesche_knoten(zeiger);
    else if ((*zeiger)->artikel->artikelnr >= such)
        loesche(&((*zeiger)->left), such);
    else
        loesche(&((*zeiger)->right), such);
}

void entfernen(baum *b, int nr) {
    loesche(&b->root, nr);
}

Nochmals Entschuldigung für die ganze überstürzten Handlungen von gestern 🙃 da hab ich mir das ganze überhaupt nicht durchdacht, sondern viel eher gleich mal wieder um Hilfe gerufen 🙁 Musste mir das ganze auch wieder aufzeichnen, denn gestern habe ich gar nicht mehr dran gedacht das ich ja auch eine Referenz im Fall 3 auf den vorherigen Knoten benötige!

Falls der Code nun so weit okay sein sollte muss ich Ihn jetzt dahin gehen ändern das ich den Artikel zurück gebe und den Speicherbereich des Knoten wieder ans Betriebssystem zurück gebe.
 
O

ocsme

Top Contributor
Kann mir nochmals jemand Helfen?
Ich stelle fest das ich ein Fundamentales Nichtwissen über Zeuger, Stack und Heap habe :-(

Wenn eine Methode in C abläuft werden doch alle Daten vom Stack gelöscht.
Wieso kann ich dann einen dreier Tausch in C so durchführen?
C:
void test(int *a, int *b) {
    int q = *a; //wieso sind die Daten nach der Methode noch vorhanden?
    a = b;
    b = &q;
}

int main(void) {
    int a = 5;
    int b = 3;
    test(&a, &b);
    printf("A: %d\n", a);
    printf("B: %d\n", b);
}

Wie im Kommentar zu sehen ist frage ich mich, die Methode liegt doch auf dem Stack. Somit sollte doch die Daten q nach erreichen der } Klammer abgeräumt werden! Wieso sind die Daten dann weiterhin in der main existent? Ich hab mir das ganze aufgemalt und verstehe nur noch Bahnhof :-(

Denn ich wollte versuchen mein Problem mit dem Artikel so zu umgehen indem ich die Lösche Funktion so umschreibe:
C:
void loesche_knoten(node **zeiger, artikel *a) {
    node *temp;

    if ((*zeiger) != NULL) {
        if ((*zeiger)->left == NULL && (*zeiger)->right == NULL) { //Fall 1 Blattknoten
            a = (*zeiger)->artikel;
//            free(*zeiger);
            *zeiger = NULL;
        } else if ((*zeiger)->left == NULL) { //Fall 2 ein untergeordnetes Element
            a = (*zeiger)->artikel;
            temp = *zeiger;
            *zeiger = (*zeiger)->right;
//            free(temp);
        } else if ((*zeiger)->right == NULL) { //Fall 2 ein untergeordnetes Element
            a = (*zeiger)->artikel;
            temp = *zeiger;
            *zeiger = (*zeiger)->left;
//            free(temp);
        } else { //Fall 3 im linken Teilbaum den größten Wert
            temp = (*zeiger)->left;
//            node *wegDamit = *zeiger;
            node *hilfsZeiger = NULL;
            while(temp->left != NULL) {
                hilfsZeiger = temp;
                temp = temp->right;
            }
            hilfsZeiger->right = temp->left;
            temp->left = (*zeiger)->left;
            temp->right = (*zeiger)->right;
            a = (*zeiger)->artikel;
//            free(*zeiger);
        }
    }
}

void loesche(node **zeiger, int such, artikel *a) {
    if ((*zeiger) == NULL)
        printf("Baum ist leer\n");
    else if ((*zeiger)->artikel->artikelnr == such) /* Gefunden! */
        loesche_knoten(zeiger, a);
    else if ((*zeiger)->artikel->artikelnr >= such)
        loesche(&((*zeiger)->left), such, a);
    else
        loesche(&((*zeiger)->right), such, a);
}

artikel *entfernen(baum *b, int nr) {
    artikel *back = NULL;
    loesche(&b->root, nr, back);
    return back;
}

Das Problem ist nur das bei artikel *entfernen(baum *b, int nr) NULL bei back ankommt :-(
 
mihe7

mihe7

Top Contributor
Wieso kann ich dann einen dreier Tausch in C so durchführen?
Kannst Du nicht.

Die test-Funktion hat zwei Parameter, die jeweils einen Zeiger entgegennehmen. D. h. das a und b sind dort lokale Variablen und in der letzten Zeile der Funktion setzt Du den Zeiger b (lokal) auf die Adresse der lokalen Variablen q.

In main ändert sich dadurch nichts.
 
O

ocsme

Top Contributor
Stimmt, hab es gerade eben mal laufen lassen!
Wo hab ich dann diesen Quatsch schon wieder her? Das hat mich jetzt nur durcheinander gebracht =(

Und so wird ein Schuh draus =)
C:
void test(int *x, int *y){
    int q = *x;
    *x = *y;
    *y = q;
}

Ich deref. x also den Wert von x schreibe ich in die int Variable.
Dann wird x Dereferenziert so das wir mit = deref. y den Wert von y in x schreiben können.
Am Ende nur noch y Dereferenzieren und den Wert von q rein schreiben.

Danke =)

Wo verliere ich aber beim durchreichen des artikel Zeiger die Referenz? Irgendwie will mir das weiterhin nicht in den Kopf =(
 
B

BestGoalkeeper

Gast
Wieso kann ich dann einen dreier Tausch in C so durchführen?
Du kannst sogar beliebige "Daten" tauschen ;) :
C:
void swap(void *p1, void *p2, int size)
{
    unsigned char *p3 = p1, *p4 = p2, tmp;
    int i = 0;
    for (; i != size; i++)
    {
        tmp = p3[i];
        p3[i] = p4[i];
        p4[i] = tmp;
    }
}

int main()
{
    int i = 2;
    int j = -3;
    long long int x = -5;
    long long int y = 55;
    swap(&i, &j, sizeof(int));
    swap(&x, &y, sizeof(long long int));
    printf("%d %d\n%d %d\n", i, j, x, y);
    return 0;
}

Rate mal die Ausgabe...
 

Ähnliche Java Themen

Anzeige

Neue Themen


Oben