C Verkettete Liste

WIng93

Cadet 2nd Year
Registriert
Sep. 2019
Beiträge
17
Hallo zusammen,

ich schreibe nächste Woche eine Klausur in C und ich blicke leider bei den Thema verkettete Listen nicht durch, worauf der Professor aber auch seit letztem Semester einen Schwerpunkt gelegt hat. Die Aufgabe macht einen Drittel der Note aus.

Ich hatte mich im Internet darüber informiert gehabt und das Grundprinzip verstanden gehabt (dachte ich zumindest). Ich bin leider bei der Altklausuraufgabe nicht voran gekommen.


Ich habe die Aufgabe hier mal hochgeladen. Mir geht es weniger darum, die Aufgabe gelöst zu bekommen sondern viel mehr , dass ich das Thema verstehe .

Ich habe die Themen Pointer, Arrays, Strings und Dynamische Speicherverwaltung gut verstanden und konnte dort auch alle Aufgaben bislang lösen (Übung sowie Altklausuren ) . Bei verkettete Liste , verstehe ich jedoch leider wirklich kaum etwas.

Ich hoffe jemand wäre so nett und könnte mir die Vorgehensweise zur Lösung (vor allem aber auch wieso der jeweilige Schritt erfolgt ist) erklären. Ich will wirklich nicht , mich durch mogeln , oder dass mir jemand Arbeit abnimmt. Ich verstehe leider nichts .
Screenshot_20200210_002844.jpg
Screenshot_20200210_002859.jpg
Screenshot_20200210_002913.jpg
Screenshot_20200210_002940.jpg
Screenshot_20200210_002951.jpg



Ich danke jeden für jede Hilfe im voraus.Screenshot_20200210_002844.jpgScreenshot_20200210_002859.jpgScreenshot_20200210_002913.jpgScreenshot_20200210_002940.jpgScreenshot_20200210_002951.jpgScreenshot_20200210_002913.jpg
 
Die Idee der verketten Liste ist die, dass du eine beliebig große Menge an Einträgen abspeichern willst und dynamisch Speicher dafür allokieren.

Das heißt die einzelnen Listeneinträge liegen Kreuz und Quer im Speicher, wobei das einzelne Listenelement (node_t) zusammenhängend abgelegt ist. Damit meine ich, dass du next, nummer, name und anzahl zusammenhängend abspeicherst.

In dem Head speicherst du die Adresse für die erste node, die ja irgendwo abgelegt ist. Über diesen Pointer (Adresse) kannst du nun die node_t finden. Da die Einträge zusammenhängend liegen kannst du über den Pointer + Offset die Einträge der node finden.

Nun haben wir allerdings das Problem, dass das nächste Listenelement (node_t) wieder irgendwo abgelegt wurde (da wo halt Platz war). Um dieses zu finden wird der Speicherort im Pointer next abgelegt.

Der Speicherort des nächsten Listenelements ist also immer im Listenelement dafür hinterlegt, so dass man sich durch "durchhangeln" zu dem Eintrag vorarbeiten kann den man haben will.

Ist dass das was du nicht verstanden hast? Is schon spät und meine Grammatik ist nicht mehr die Frischeste für heute. ;)

Mein C ist etwas rostig aber das hier gibt dir vielleicht die richtige Idee:

someDataType firstelement;
someDataType *head = &firstelement;

Der Pointer head bekommt die Adresse von firstelement zugewiesen. Für alle weiteren Elemente würde man dies genauso machen, nur dass die Pointer dann im Element gespeichert werden.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: WIng93 und Bonanca
A) ist nur die structs anlegen die du im bild siehst.
also struct stoff_t { int nummer; int anzahl; char name[41] };
struct note_t { note_t* next; stoff_t; } hier ist das erste feld ein pointer auf die naechste node wenn du keine naechste node ist es ein nullptr da mit initzialistetst du es auch.

B)
createList sollte einen pointer zuruck geben der auf 0 steht weil du noch keine elemente hast. speater zeigt er immer auf das erste Element.

C)
erzeuge mit malloc speicher fuer note_t und lege die daten darin an sieht dann so aus newElement(int nummer,int anazhl,char* name) fehler kann sein das der namme zu lang fuer char name[41] ist das musst du pruefen und malloc auch pruefen.

D)
du gehst durch deine liste und printest die werte schreibe am besten eine weitere subroutine um den inhalt zu printen du musst hier immer die next addresse vom momentanen element nehmen bis next null ist bedenke das du das bei C) auf null gesetzt hast.

e) ist so aenhlich wie F du musst nur die naechst felder auf die richtigen nodes zeigen lassen

F) du loescht ein element in dem du das vorherige element von dem zu loschen element auf das naechste element vor dem zu loeschen element setzt beachte hier die spezial falle von head und dem letzen element.
wenn du das gemachst hast den speicher mit free frei geben von dem zu loschen element. ist kompliziert erklaert guck dir einfahc das bild. du musst immer auf das neachste element zeigen.

sollte deine liste leer sein muss head auch wieder auf 0 zeigen.!!! werden viele vergessen.


wenn du genaue antworten haben willst frag spezifisch.
 
  • Gefällt mir
Reaktionen: LencoX2, WIng93 und Bonanca
das meiste wurde ja schon gesagt.

eine verkettete liste ist nichts anderes, als objekte, die aufeinander zeigen und dadurch eine "kette" bilden. mehr ist das nicht. nur dieser zeiger auf die nächste instanz (oder NULL, wenn es kein nächstes element gibt)

du musst jetzt also einerseits die datenstrukturen definieren, einmal die "innere" struktur mit den attributen des stoffes und dann noch allgemein einen listenknoten mit dem zeiger und dem eigentlichen inhalt, wie mein vorredner ja schon geschrieben hat.

bei createList gibst du dann das eigentliche listen-objekt zurück, eine instanz einer dritten struct, die letztlich doch nur aus einem parameter besteht: node_t * head, der zeiger auf dein erstes listenelement. und dieser ist dann zu beginn null.

die nummer des stoffes soll übrigens ein unsigned int sein - vorzeichenlos, sprich immer positiv.

kleiner tipp zu D) du weist zuerst einem zeiger dein head zu und dann interierst du mit einer while schleife durch deine nodes, bis du am ende angekommen bist und der zeiger next null ist... also so in der art:
C:
node_t *listenelement = MeineListe.head;

while(listenelement != NULL) {
    /* print out listenelement.. */
    listenelement = listenelement->next;
}

die while schleife verfolgt dich auch bei den weiteren aufgaben.. hier musst du halt noch etwas vergleichsarbeit reinstecken etc..
vergiss nicht beim löschen eines objektes mit dynamisch allokiertem speicher, diesen mit free wieder freizugeben.
 
  • Gefällt mir
Reaktionen: WIng93
Multivac schrieb:
struct note_t { note_t* next; stoff_t; } hier ist das erste feld ein pointer auf die naechste node wenn du keine naechste node ist es ein nullptr da mit initzialistetst du es auch.
korrigiere bitte nochmal diesen Absatz

@TE ich kann mir eigentlich nicht vorstellen, dass du bei der Klausur gar nichts versehst. Vor allem so eine Woche vor der Pruefung. In dem Text ist doch alles sogar noch mal dankbar beschrieben und zusaetzlich sogar noch eine sehr hilfreiche Abbildung. Mindestens die erste Teilaufgabe a) haettest du doch wohl umsetzen koennen muessen, oder? Und b) und c) damit auch, die sind ja nur Hilfsmehtoden. Was ein Struct ist solltest du in der Vorlesung schon laengst mal gesehen haben, oder? Also wie man komplexere Dinge in einer Datenstruktur "wrapt". Woran genau ist es denn bei Aufgabe a) gescheitert? Nur um das mal nachzuvollziehen. Und nicht sagen "ich verstehe gar nix" - du stehst ne Woche vor der Pruefung, du verstehst garantiert was. Den Rest wie Pointer etc. hast du doch auch verstanden.

Ich dachte auch immer dass dieses Konzept einer verketteten Liste ziemlich intuitiv ist, aber scheint wohl jeder anders zu sehen. Was nicht schlimm ist.

Viel Erfolg bei der Pruefung!
 
  • Gefällt mir
Reaktionen: Cai-pirinha, WIng93 und new Account()
Ohne konkretere Fragen oder funktionierende Code-Häppchen deinerseits kann hier nicht sinnvoll gearbeitet werden. Das Internet ist voller Anleitungen und Skripte zum Thema, in einem Forum musst du deutlich spezifischer sein.
 
Vielen Dank für alle Antworten . Die Theorie hinter den verketteten Listen habe ich durch zahlreiche YouTube Videos und einem Buch im Internet verstanden .
Leider scheitert es bei mir in der Anwendung.

Ich studiere eigentlich Wirtschaftsingenieurswesen , weshalb auch immer haben die uns mit den Informatikern zusammengepackt in dem Kurs "programmieren in C" . Uns Wirtschaftsingenieure fällt das leider alle sehr schwer, bei vielen ist der Kurs sogar der letzte vor dem Abschluss weil man den immer schiebt .

Worauf ich hinaus wollte, ich verstehe das Thema verkettete Liste in der Anwendung leider nicht, weil es schon überhaupt sehr schwer war für mich generell Pointer, Strings, Dynamische Speicherverwaltung etc. zu verstehen .


Ich versuche Mal mein Problem besser zu erklären.

Ich weiss, dass ich eine Struktur benötige , in dieser Struktur node_t befinden sich die einzelnen Parameter und ein Pointer "*next =NULL" .

Außerhalb der Struktur , habe ich dann noch 2 Pointer mit der Struktur von node_t . Der eine zeigt auf NULL und bildet den Listenanfang und der andere Pointer bildet das Listenende .


Ich brauche dann noch eine Funktion die Parameter übergibt .
Dort kann ich ehrlich gesagt nichts erklären weil da zu oft die Pointer aufeinander zeigen, dass ich nicht mehr verstehe wie ich sowas anwenden kann.
Prinzip geht es bei der Übergabe Funktion darum, dass die Struktur aus a) die verschiedenen Werte zugewiesen bekommt mit dem Pfeiloperator und dem variablen Name . Am Ende der Funktion muss dann auch der eine Pointer auf dem Anfang zeigen , der andere auf das Ende und next auf die nächste Instanz .

Bei der Ausgabefunktion brauche ich eine while Schleife die prüft ob der Zeiger auf NULL zeigt (da dort die Liste zuende ist ) und bei jedem Durchlauf wird die Instanz die sich momentan in der Schleife befindet ausgegeben.


Mein Problem hierbei ist, dass ich die Theorie verstanden habe aber es bei der Anwendung scheitert . Also darum den Code zu schreiben.
Mir geht es nicht darum die Aufgabe die ich gepostet habe zu lösen , da es weder eine Hausaufgabe ist noch eine Abgabe ist. Ich wollte gerne Schritt für Schritt dem Code verstehen , der sich hinter der Aufgabe befindet . a) - d)
Ich kann liebend gerne nachher meinen Lösungsansatz Posten aber ich kenne mich nicht so gut aus und weiss das das sehr falsch ist
 
WIng93 schrieb:
Außerhalb der Struktur , habe ich dann noch 2 Pointer mit der Struktur von node_t . Der eine zeigt auf NULL und bildet den Listenanfang und der andere Pointer bildet das Listenende .

Der Listenanfang reicht vollkommen aus bei der einfach verketteten Liste. Sobald du den Pointer mit NULL (als Wert) findest weißt du du bist am Ende angekommen. Du hangelst dich im Prinzip vom Anfang der Liste immer bis zum Ende oder dem gewünschten Element durch. Etwas einfügen würdest du einfach indem du den Pointer (der Einzige) der auf NULL zeigt durch die neue Adresse (diese ist dann NULL) austauscht.

Also du schaust an der Stelle (Wert des Pointers) was dort ist (node oder NULL). Und dann schaust du in der node was steht in next. Der Wert von next ist dann dein Pointer auf das nächste Element. Und so weiter.

Mich verwirrt das Ganze übrigens auch immer wenn ich lange keine Praxis in C hatte. ;)
Also Pointer = Wo ist der Wert. Wert des Pointers (Was steht an der Stelle wo der Pointer hinzeigt) = dein neuer Pointer (bei Node Wert von next). Das wäre dann node_t *next ist die Adresse. Mit *next kommst du dann an den Wert von next.

Die Seite erklärt das glaube ich ganz gut, wobei du Doublepointer nicht brauchst:
https://www.geeksforgeeks.org/double-pointer-pointer-pointer-c/
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: WIng93
Erst einmal muss man Unterscheiden zwischen einer einfach vereketteten Liste und einer doppelt verketteten Liste.
Bei beiden Arten werden Daten gespeichert, z.B. als int oder string-Variablen. Als Beispiel kannst Du Dir ja vorstellen, dass Du Kunden verwaltest mit Name, Alter usw. Diese Daten können auch später noch verändert werden. Damit man nun von einem Knoten (eine Art "Datensatz") zum nächsten navigieren kann, benötigt man logischerweise die Adresse des nächsten Datensatzes. Diese Adresse wird als Zeiger gespeichert und zwar als gleicher Typ wie die Struktur selber. Das heißt, möchte ich nun vom ersten Knoten zum zweiten Knoten navigieren, muss im ersten Knoten die Adresse des zweiten Knotens gespeichert sein, der zweite Knoten muss wiederum die Adresse des dritten Knotens besitzen usw. Der letzte Knoten wird mit NULL oder nullptr gekennzeichnet, damit kann mittels einer Schleife durch alle Knoten gegangen werden, bis nullptr registriert wird und die Schleife damit stoppt (sonst gibt es eine Bereichsüberschreitung und führt zu einer Fehlermeldung oder gar Absturz des Programms). Du musst also, wenn Du zum nächsten Knoten navigieren willst, erst überprüfen, ob es überhaupt einen nächsten Knoten gibt, das machst Du, indem Du im aktuellen Knoten prüfst ob der Zeiger, ich nenne ihn mal "next", == nullptr ist oder eben nicht. Dies ist eine einfach verkettete liste.

Eine doppelt verkettete Liste speichert nicht nur die Adresse des nächsten Knotens, sondern auch die des vorigen. Somit kann man vorwärts und rückwärts navigieren, was sich Positiv auf die Geschwindigkeit auswirkt, besonders bei großen Listen.

Hier mal ein kleines Beispiel mit erklärenden Kommentaren. Dabei handelt es sich bewusst nicht um eine fertige Lösung nach Deiner Aufgabe, auch wenn es so funktioniert. Du musst es noch ein bisschen für Deine Aufgabe anpassen.:
C++:
#include <iostream>

using namespace std;

    //die Struktur für die Listenelemente
    struct listenelement
    {
        string daten;
        listenelement* next;
    };

    //eine Funktion zum Anhängen von Elememnten an die Liste
    void newElement(string datenneu, listenelement* listenanfang)
    {
        //Solange wiederholen, bis das Ende erreicht ist
        if(listenanfang->next != NULL)
        {
            newElement(datenneu, listenanfang->next);
        }
        else
        {
        //ein neues Element an das Ende der Liste hängen
        listenanfang->next = new(listenelement);
        //den Zeiger auf das neue Element setzen
        listenanfang = listenanfang->next;
        //die Daten in das neue Element eintragen
        listenanfang->daten = datenneu;
        //es gibt keinen Nachfolger, daher wird next auf NULL gesetzt
        listenanfang->next = NULL;
        }

    }

    //Eine Funktion zum Ausgeben aller Elemente
    void ausgeben(listenelement *listenanfang)
    {
        //das erste Element ausgeben
        cout << listenanfang->daten << '\n';

        //alle weiteren Elemente ausgeben. Hierbei handelt es sich um eine rekursive Methode. Die Methode ruft sich also selber auf.
        //Eine Alternative wäre eine Schleife. Wie man es löst ist Geschmacksache.
        if(listenanfang->next != NULL)
        {
            ausgeben(listenanfang->next);
        }
    }

    //die Liste leeren und Speicher freigeben
    void ende(listenelement* listenanfang)
    {

       if(listenanfang->next != NULL)
        {
            listenanfang = listenanfang->next;
            delete(listenanfang);
       }
        }

    int main()
    {
        //ein Zeiger der auf den Anfang der Liste zeigt.
        listenelement* listenanfang;
        //das erste Element erzeugen
        listenanfang = new(listenelement);
        //daten in das erste Element schreiben
        listenanfang->next = NULL;
        listenanfang->daten = "Element 1";

        //und jetzt weitere Elemente erzeugen. Das erledigen wir hier mit der Methode  newElement().
        newElement("Element 2", listenanfang);
        newElement("Element 3", listenanfang);
        newElement("Element 4", listenanfang);

        //die Liste ausgeben
        ausgeben(listenanfang);         //Ruft die Methode  ausgeben()  auf und übergibt ihr den Zeiger für den Listenanfang.
        //die Liste wieder abbauen      //Löscht die Liste wieder
        ende(listenanfang);
    }

Jetzt musst Du es nur noch umbauen/anpassen. Die Elemente für die Struktur musst Du entsprechend ändern, also in "nummer", "name", "anzahl". Ebenso verfahren musst Du mit den Parametern des Methodenkopfes für die Funktion newElement() und ebenso auch die Parameter beim Methodenaufruf mit angeben.

Ich hoffe, ich konnte Dir damit etwas helfen, musste mich leider kurz fassen. Bei Fragen, schreib mir ruhig, melde mich dann. Viel Erfolg noch, und vorallem, nicht dem Mut verlieren! Ist eigentlich alles ganz einfach wenn man da erst einmal hintergekommen ist.
 
Zuletzt bearbeitet: (code ergänzt)
  • Gefällt mir
Reaktionen: WIng93
Danke an alle für all eure Antworten, ich weiss eure Mühe echt zu schätzen und bin wirklich sehr Dankbar für jede Hilfestellung. Ich war am Lehrstuhl heute um mir einige Fragen beantworten zu lassen, leider hat der Professor keine Zeit gehabt und hat mir dann die Lösung gegeben.

Ich habe jetzt mal die Lösung analysiert und hab explizit zu den Sachen die ich nicht verstehe , die Frage hin geschrieben.


C:
// AUFGABE a)

struct data
{
    unsigned int nummer;
    char name[42];
    int anzahl;
};        // Wieso wurde hier kein Bezeichner eingefügt bzw. muss hier kein Bezeichner für die Struktur stehen?

typedef struct node
{
    struct data data;    // Soll das hier heißen wir haben eine Struktur data die wir data zuweisen?
    struct node *next;    // Der Pointer hier , soll auf die nächste Instanz in der Liste später zeigen
}node_t;

// AUFGABE b)

node_t *createlist() // Hier habe ich nicht verstanden erstens wieso note_t als Typ für die Funktion angegeben worden ist
{                    // und zweitens wozu diese Funktion dient, da sie ja nichts macht
    return 0;
}

// AUFGABE c)
// Hier wieder die selbe Frage wieso node_t als Datentyp der Funktion und wieso ist die Funktion mit einem * deklariert?
node_t *newElement(unsigned int nummer, char name[42], int anzahl) // Hier stehen die variablen drin, die einen Wert zugewiesen bekommen sollen
{   
    node_t *new = malloc(sizeof(node_t)); // Wir deklarieren einen Zeiger new mit dem Typ node_t, damit man auf die variablen zugreifen kann   
    
    if(new == NULL)    // prüfen ob Speicherreservierung erfolgreich war bzw. nicht erfolgreich war
    {
        return NULL;  // wieso return NULL und nicht 0 , macht das einen Unterschied? und wenn ja welchen?
    }
    
    new->next=NULL                    // Warum und was bewirkt der Befehl hier?   
    new->data.nummer=nummer;        // wir greifen auf data zu und dort auf die variable nummer und weisen ihr nummer vom funktionskopf zu (der durch main übergeben wird)
    strcpy(new->data.name, name);    // string copy damit wir den string der übergeben wurde auf dem string name kopieren
    new->data.anzahl = anzahl;        // übergebenen wert für anzahl weisen wir der variable anzahl von der struktur data zu auf die new zeigt (ich hoffe das war richtig)
    
    return new;        // wieso geben wir hier new zurück? oder besser wohin übergeben wir new ?
    
}


Allgemein würde mich interessieren so als Satz aufgeschrieben was ich hiermit ganz genau eigentlich mache:

new->data.nummer = nummer

Ich weiss das ich damit der Variable die sich in der Struktur data befindet , nummer zuweise die ich aus der main Funktion an die funktion newElement übergeben bekommen habe.
Mich interessiert hierbei ob mein Gedankengang richtig ist:

Zeiger new hat sozusagen den Datentyp von node_t zugewiesen bekommen.
Das heißt ich greife mit new->data auf data der Struktur node_t zu. wiederum will ich dort auf die variable die sich in der struktur von data zugreifen daher der Punktoperator hinter Data



Ich kann es nicht genug erwähnen, vielen dank für eure Mühe :)
 
C:
// AUFGABE a)

struct data
{
    unsigned int nummer;
    char name[42];
    int anzahl;
};        // Wieso wurde hier kein Bezeichner eingefügt bzw. muss hier kein Bezeichner für die Struktur stehen?

//==> Das Struct wurde ohne typedef deklariert und kann so nur als "struct data" benutzt werden. Deswegen gibt es auch keinen Bezeichner nach der Klammer

typedef struct node
{
    struct data data;    // Soll das hier heißen wir haben eine Struktur data die wir data zuweisen?
  
    //==> Wir haben ein "struct data" das wir in der node data nennen. Im Prinzip ist struct data einfach neuer Variablentyp.
  
    struct node *next;    // Der Pointer hier , soll auf die nächste Instanz in der Liste später zeigen
  
    //==> Ein Pointer auf ein "struct node" mit Bezeichner next.
  
}node_t;

//==> Über typedef und dann node_t als Namen können wir das node struct einfach als neuen Typ node_t verwenden.

// AUFGABE b)

node_t *createlist() // Hier habe ich nicht verstanden erstens wieso note_t als Typ für die Funktion angegeben worden ist

//==> Die Function hat einen node_t Pointer als Rückgabewert

{                    // und zweitens wozu diese Funktion dient, da sie ja nichts macht
    return 0;
  
    //==> Eine leere Liste besteht aus einem Pointer (Head) auf NULL. Speicheradresse 0 ist wohl gleichbedeutend mit NULL (lange kein C mehr gemacht ^^).
}

// AUFGABE c)
// Hier wieder die selbe Frage wieso node_t als Datentyp der Funktion und wieso ist die Funktion mit einem * deklariert?
node_t *newElement(unsigned int nummer, char name[42], int anzahl) // Hier stehen die variablen drin, die einen Wert zugewiesen bekommen sollen
  
//==> Die Funktion gibt einen Pointer auf das neue Element zurück. 
{ 
    node_t *new = malloc(sizeof(node_t)); // Wir deklarieren einen Zeiger new mit dem Typ node_t, damit man auf die variablen zugreifen kann
  
    //==> Mit malloc holen wir uns einen Block Speicher, mit unbekanntem Inhalt und Größe von node_t und speichern die Adresse mit dem Pointer.
  
    if(new == NULL)    // prüfen ob Speicherreservierung erfolgreich war bzw. nicht erfolgreich war
    {
        return NULL;  // wieso return NULL und nicht 0 , macht das einen Unterschied? und wenn ja welchen?
      
        //==> Die Lösung ist wohl inkonsistent oder es gibt einen Grund den ich grad nicht kenne. Ist aber vermutlich beides das Gleiche.
    }
  
    new->next=NULL                    // Warum und was bewirkt der Befehl hier?
  
    //==> Wir haben ja einen Speicherbereich mit der Datenstruktur node_t reserviert (new Pointer). Über new->next, new->data haben wir Zugriff auf die im Struct abgelegten Variablen (Werte).
    //==> Wir setzen den next Pointer von new auf den Wert Null, da hinter dem neuen Element erstmal kein weiteres Element kommt.
  
    new->data.nummer=nummer;        // wir greifen auf data zu und dort auf die variable nummer und weisen ihr nummer vom funktionskopf zu (der durch main übergeben wird)
    //==> In new greifen wir auf die Structvariable data zu und in data auf nummer und schreiben einen Wert.
    //==> new->data ist übrigens das selbe wie (*new).data
    strcpy(new->data.name, name);    // string copy damit wir den string der übergeben wurde auf dem string name kopieren
  
    new->data.anzahl = anzahl;        // übergebenen wert für anzahl weisen wir der variable anzahl von der struktur data zu auf die new zeigt (ich hoffe das war richtig)
  
    return new;        // wieso geben wir hier new zurück? oder besser wohin übergeben wir new ?
  
    //==> Die Funktion alleine soll ein neues Element erzeugen und dieses zurück geben. Wo genau das verwendet wird ist doch erstmal egal. ;)
    //==> Aka "für sich alleine wird der Code so nicht funktionieren"
}

Wie gesagt etwas rostig in C, aber ich hoffe meine Anmerkungen sind brauchbar. Wenn was falsch ist darf mich gerne jeder korrigieren. ;)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: LencoX2 und WIng93
struct data
{
unsigned int nummer;
char name[42];
int anzahl;
}; // Wieso wurde hier kein Bezeichner eingefügt bzw. muss hier kein Bezeichner für die Struktur stehen?
Antwort: Fügst Du dort einen Bezeichner ein, wird automatisch eine Strukturvariable angelegt mit der Du arbeiten kannst. Bsp.:
C++:
struct data
{
//...
}struktur1;

Du fasst also 2 Schritte zusammen. Getrennt sieht es so aus:
C++:
struct data
{
//...
};

data struktur1;
Du kannst natürlich auch weitere Strukturvariablen erstellen:
C++:
data struktur2;
data struktur3;
data struktur4;

Zugriff bzw. Wertzuweisung auf ein Attribut der Struktur struktur1:
C++:
struktur1.anzahl = "2";

Zeiger new hat sozusagen den Datentyp von node_t zugewiesen bekommen.
Das heißt ich greife mit new->data auf data der Struktur node_t zu. wiederum will ich dort auf die variable die sich in der struktur von data zugreifen daher der Punktoperator hinter Data

Der Zugriff auf einen Speicherbereich über einen Zeiger nennt man Dereferenzierung. Das Sternchen * ist dann der Dereferenzierungsoperator.
Das Sternchen bei der Vereinbarung, welches direkt vor dem Bezeichner steht sagt dem Compiler dass es sich bei der Vereinbarung um einen Zeiger handelt. Damit der Compiler weiß, auf welchen Datentyp der Zeiger positioniert wird, steht am Anfang der Vereinbarung ganz normal eine Typangabe. z.B. der Typ int, string oder auch struktur1.
Die Typangabe für den Zeiger ist zwingend erforderlich. Denn gespeichert wird nur die Startadresse des Bereichs. Die Endadresse wird automatisch aus der Startadresse und der Größe des Datentyps ermittelt.

Will man z.B. indirekt auf einen Wert zugreifen wird der Dereferenzierungsoperator benötigt:
(*data).anzahl = 123;

Stattdessen kann man auch den -> Operator ohne Klammern und Sternchen verwenden:
data->anzahl = 123

Zweitere Variante ist weniger Schreibaufwand.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: WIng93
8: Hier steht kein Bezeichner, da hier nur die Struct definiert wird, keine Variable davon angelegt wird. Siehe folgendes Beispiel zur Definition von structs. (Die Typenbezeichnung und die Variablenbezeichnung sind jeweils optional.)
Code:
struct [structure tag] {

   member definition;
   member definition;
   ...
   member definition;
} [one or more structure variables];
12: Es wird eine Variable namens data vom Datentyp struct data definiert. Nicht gerade das beste Namensschema, da hätte man sich auch was anderes einfallen lassen können.
18/19: Doch, die Funktion tut etwas, und zwar legt sie einen Listenkopf an. Der Listenkopf ist ein Pointer vom Typ node_t. Der Übersichtlichkeit halber würde man als returnwert eigentlich NULL schreiben statt 0. Noch klarer wäre es, vorher noch zum Typ "pointer auf node_t" zu casten:
Code:
return (node_t*) NULL
24: "Rückgabewert ist ein Zeiger auf den neuen Knoten." Dass es ein Pointer ist, wird durch * gekennzeichnet und der Pointer zeigt auf den Typ node_t. Ich persönlich schreibe das lieber mit anderem Leerzeichen:
Code:
node_t* newElement(unsigned int nummer, char name[42], int anzahl)
25: Hier stehen die Funktionsargumente. Beim Aufruf der Funktion müssen Werte für die Argumente übergeben werden.
27: Wir allokieren mit malloc Speicher im Heap. In diesem Speicherbereich wird das neu angelegte Listenelement gespeichert. Wir deklarieren einen Zeiger new, der auf diesen Speicherbereich zeigt, damit wir ihn ansprechen können.
31: Es macht keinen wirklichen Unterschied, ob man 0 oder NULL schreibt. Die beiden werden gleich interpretiert. Sauber wäre es, (node_t*) NULL zu schreiben (siehe Anmerkung zu 18/19).
34: Ein einzelner Knoten außerhalb der Liste hat keinen Nachfolger. Daher kann der Zeiger next nicht auf (s)einen Nachfolger zeigen. Stattdessen wird dieser Zeiger mit dem Nullpointer initialisiert.
39: new ist der Name deiner temporären Strktur in der Funktion newElement. Oder genauer, new ist ein Pointer (also die Adresse) auf den Speicherbereich, in dem deine neuen Daten stehen. Dies wird zurückgegeben als Funktionsrückgabewert, also an die übergeordnete Codeebene. Rufst du später anderswo die Funktion auf, so erhälst du den Pointer auf den Datenbereich, in den du die Funktionsargumente geschrieben hast.
 
  • Gefällt mir
Reaktionen: WIng93
Vielen dank eure Antworten haben mich einen riesen Schritt nach vorne gebracht. Ich wiederhole das ganze jetzt und werde versuchen anhand anderer Beispiele das anzuwenden, bis ich es zu 100% verstanden habe.

Eine letzte Frage beschäftigt mich jedoch trotzdem noch stark und zwar bei der (und auch der Funktion davor)

C:
node_t *newElement(unsigned int nummer, char name[42], int anzahl)
{
    node_t *new = malloc(sizeof(node_t));
    if(new == NULL)
    {
        return NULL;     
    }

    new->next=NULL
    ...
}

Wieso ist die Funktion als Pointer deklariert?
Also ich hätte es deklariert als :
C:
note_t newElement(....)

Nicht weil ich eine Begründung dafür hätte, sondern weil ich nicht weiss weshalb ich diese Funktion als pointer deklarieren sollte.
 
Ich glaube dein Problem ist weniger verkettete listen sondern die Sprache C. NULL wird immer vom preprocessor durch ein null_Pointer Literal ersetzt. Ein Literal ist ein wert der ohne Name steht und nur bis zum nächsten Semikolon existiert. Er hat keine Adresse. bsp: int a = 5; a ist eine variable hat eine Adresse und '5' ist das Literal. du kannst also nicht int* a = &5; schreiben. C kann implizit eine type Konvertierung vornehmen int* a = 5. jetzt steht in dem pointer a die Adresse 5 und nicht die Adresse von 5.

die Adresse 0 wird benutzt um zu behaupten das dieser Pointer auf eine nicht gültige stelle zeigt. Ein Pointer kann auf ein struct zeigen oder auf 0 um zu signalisieren das er auf nichts zeigt. statt int* a = NULL; kannst du auch schreiben int* a = 0; oder meine Lieblingsvariante int* a = (int*)0;

"->" ist ein Dereferenzierung Operator. im Gegensatz zu "*" Operator sparst du dir klammern und den '.'operator.

struct_v a;
struct_v* b;

(*b).name = 5;
b->name =5;
sind gleichwertig
"=" ist der Zuweisung Operator

>Außerhalb der Struktur , habe ich dann noch 2 Pointer mit der Struktur von node_t . Der eine zeigt auf NULL und >>bildet den Listenanfang und der andere Pointer bildet das Listenende .
du brauchst nur einen Pointer der auf das erste Element zeigt. das letzte element zeigt auf NULL. im feld next.
WIng93 schrieb:
ata der Struktur node_t zu. wiederum will ich dort auf die variable die sich in der struktur von data zugreifen daher der Punktoperator hinter Data

>new->data.nummer = nummer
Zeiger new hat sozusagen den Datentyp von node_t zugewiesen bekommen.
Vielleicht meinst du das richtige aber du schreibst es nicht. der type deines Zeigers ändert sich nicht. new zeigt auf ein struct mit zwei Feldern. mit -> dereferenzierst du das Feld data. (new->data) kannst du benutzen wie eine normale variable. mit dem operator '.' waehltst du feld aus dem type von data aus. also Nummer. (new->data.nummer) verhaelt sich dann wie eine variable z.B. int nummer = 20;.


>Das heißt ich greife mit new->data auf data der Struktur node_t zu.
ja nur node_t ist ein Type du greifst auf eine variable des typen zu.

wiederum will ich dort auf die variable die sich in der struktur von data zugreifen daher der Punktoperator hinter Data

am ende ist es einfach frage ob du ueber einen pointer gehst oder es direkt ist. dein zweites struct koennte ja auch wieder ein pointer sein deswegen brauchst du statt dem "." operator den "->" operator."
a->b ist das gleiche wie (*a).b sieht nur klarer aus. wenn du das ziel eines pointer anderen willst musst du deref.
bei dem struct hast du "deref" + feld op in einem.
Ergänzung ()

>Wieso ist die Funktion als Pointer deklariert?
weil sie malloc benutzen. sie bekommen einen pointer der auf dem heap zeigt du würdest dein note_t auf dem stack anlegen
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: simpsonsfan und WIng93
Multivac schrieb:
Ich glaube dein Problem ist weniger verkettete listen sondern die Sprache C.

Da muss ich dir recht geben, leider muss ich den Kurs "Programmieren in C" schreiben , da er in meiner Prüfungsordnung ist. Ich gebe mir auf jeden Fall mühe und versuche vertieft auf Pointer nochmal einzugehen bevor ich mich an das Thema verkette Listen setze.

Ich danke dir für deine Hilfestellungen und deiner Mühe sehr :)
 
Ich habe jetzt nach einigen YouTube Videos und einigen Skripten von Fremdunis einen anderen Lösungsansatz entwickelt für diese Aufgabe. Leider gibt sie nur das letzte eingegebene Element aus.

Ich habe bei fast jedem Befehl dazu geschrieben was dieser Befehl bewirken soll (bzw. was ich möchte was dieser Befehl bewirkt) , damit vielleicht der Fehler oder Denkfehler schneller gefunden werden kann.

C:
#include<stdio.h>
#include<stdlib.h>

struct data                     // Innere Struktur
{
    unsigned int nummer;
    char name[42];
    int anzahl;
};

typedef struct node             // Äußere Struktur
{
    struct data daten;
    struct node *next;

}node_t;



node_t *liste;              // Diese Instanz ist der Beginn der Liste. (wird verändert im laufe des Programms)
node_t *knoten;             // Speichert den ersten Knoten und verändert sich nicht mehr (Kopf der Liste)



// Funktion nimmt Variablen für den ersten Knoten entgegen
void erzeuge_knoten(unsigned int nummer, char name[42], int anzahl)
{
    node_t *neu = malloc(sizeof(node_t));   // Temporäres Element, vom typ node_t , bekommt dynamischen Speicher zugwiesen

    // Die durch main übergebende Werte, werden der Variablen der Struktur daten zugewiesen.
    neu->daten.nummer = nummer;
    strcpy(neu->daten.name, name);
    neu->daten.anzahl = anzahl;
    
    neu->next = NULL;   // Dem Zeiger next wird NULL zugewiesen [Da hinter dem letzten Element sich nichts befindet. Das letzte Element muss daher auf NULL zeigen]
    liste = neu;        // Temporäres Element neu , wird zur Anfangsinstanz
    knoten = neu;       // ERSTER Knotenpunkt in der Liste = Kopf der verketteten Liste
}



// Funktion bekommt neue Werte zugewiesen
void anhaengen(unsigned int nummer_neu, char name_neu[42], int anzahl_neu)
{
    node_t *neu = malloc(sizeof(node_t));   // temporären Knoten vom Typ node_t mit der Speichergröße von node_t

    // Variablen bekommen Werte zugewiesen vom Benutzer des Programms
    neu->daten.nummer = nummer_neu;       
    strcpy(neu->daten.name, name_neu);
    neu->daten.anzahl = anzahl_neu;

    neu->next = NULL;   // next zeiger muss auf NULL zeigen, denn hier ist aktuell der letzte Knotenpunkt der Liste
    liste = knoten;     // Liste wieder zum Anfang zurück spulen, da sie sich irgendwo mittendrin befinden könnte (knoten = kopf der liste)

    while(liste != NULL)           
    {
        if(liste->next == NULL)     // Überprüfen ob der nächste Knoten NULL ist
        {
            liste -> next = neu;    // next bekommt neu zugewiesen (Temporäres Element)
            break;                  // wenn das Ende gefunden wurde, dann höre ich auf zu suchen und springe raus aus der Schleife
        }
        else                        // falls das nächste next nicht NULL ist
        {   
            liste = liste->next;    // liste zeigt jetzt auf den nächsten Knoten, so lange bis der Nächste Knoten ein NULL-Zeiger ist (Abbruchbedingung while)
        }
    }
}



// Funktion zur Ausgabe der Liste
void ausgeben()
{
    liste = knoten; // Damit immer zum Kopf (anfang) der Liste gesprungen wird und nicht irgendwo mittendrin angefangen wird.

    while(liste != NULL)    // Schleife soll solange laufen bis es kein Knoten mehr gibt (Sobald NULL erreicht worden ist)
    {
        printf("%03u : %s (%02d) \n" ,liste->daten.nummer ,liste->daten.name ,liste->daten.anzahl); // aktueller Knoten wird ausgegeben
        liste = liste->next;    // Auf next zeigen, damit der nächste Knoten als nächstes abgerufen wird. (nach der Ausgabe des aktuellen Knotens)
    }   
}



int main()
{
    erzeuge_knoten(1, "rot", 10);
    erzeuge_knoten(2, "lila", 5);
    erzeuge_knoten(3, "schwarz", 50);

    ausgeben();

    free(liste);
}
 
erzeuge_knoten und Anhaengen der code ist doppelt und falsch
deine ausgabe sieht richtig aus bis auf das du zwei globale variablen hast
Ersetz diese durch
so bleibst du im Namensschema von deinem Aufgabenblatt

ausgebe sieht richtig aus benutze dort eine tmp variable
also
void ausgeben()
{
node_t* tmp = head;
while(tmp)
{...}
}

schreibe doch mal eine am ende der liste einfügen Funktion.

void erzeuge...
liste = neu; // Temporäres Element neu , wird zur Anfangsinstanz
knoten = neu; // ERSTER Knotenpunkt in der Liste = Kopf der verketteten Liste
das hier stimmt nicht weil deine liste so immer nur auf das zuletzt erzeugete element von erzeugete element zeigt.
 
  • Gefällt mir
Reaktionen: simpsonsfan
WIng93 schrieb:
Leider gibt sie nur das letzte eingegebene Element aus.
Was daran liegt, dass deine Liste immer nur aus einem Knoten besteht. Das war wohl schlicht ein Leichtsinnsfehler, dass du in Zeile 88 und 89 nochmal dein erzeuge_knoten statt deinem anhaengen aufrufst. Mit letzterem funktioniert dein Code soweit, wenngleich es natürlich (wie bereits von Multivac angemerkt) sinnfrei ist, für eine Tempvariable eine globale Variable zu verwenden. Und auch den eigentlichen Listenkopf (head, bei dir knoten) würde man gemeinhin eher nicht als globale Variable definieren, sondern innerhalb deiner Main und dann deinen Erzeugen/Anhängen-Funktionen übergeben. Es funktioniert aber natürlich auch als globale Variable.
 
Zurück
Oben