Konstruktoren in C++

Hallo zusammen,

ich habe mal wieder eine Frage diesmal bezüglich C++ :D
Wollte mir eine Klasse schreiben die eine Liste repräsentiert.
Das ganze sieht bis jetzt wie folgt aus:

Liste.h
C++:
class Liste {
    class Node {
    public:
        int value;
        Node *next;
        Node *prev;
        Node(int value = 0, Node *next = nullptr, Node *prev = nullptr);
    };
    Node *start;
public:
    Liste();
    Liste(int value);
    Liste(const Liste &l);    //Kopier Konstruktor
    Liste(Liste &&l);        //Verschiebe Konstruktor
    ~Liste();
    Liste &operator=(const Liste &original);
    Liste &operator=(Liste &&l);
};
Nun scheitere ich am Kopier-Konstruktor in C++!!!
Finde das in C++ auch extrem SCHWER Gegensatz zu Java!
Vielleicht kann mir dabei ja jemand helfen?

Hier mal meine Liste.cpp:
Code:
#include <iostream>
#include "Liste.h"
using namespace std;

Liste::Node::Node(int value, Node* next, Node* prev) :
        value(value), next(next), prev(prev) {
}

void Liste::Node::setNext(Node* next) {
    this->next = next;
}

Liste::Node* Liste::Node::getNext() {
    return next;
}

int Liste::Node::getValue() {
    return value;
}

void Liste::Node::setValue(int value) {
    this->value = value;
}

void Liste::Node::printNode() {
    cout << value << endl;
}

Liste::Liste() :
        start(nullptr) {
}

Liste::Liste(int value) {
    Liste::Node *n = new Liste::Node;
    n->value = value;
    start = n;
}

//Kopier Konstruktor
Liste::Liste(const Liste &l) {
    Node *tmp = l.start;
    while (tmp != nullptr) {
        this->start = insert(tmp->value);
        tmp = tmp->next;
    }
}

//Verschiebe Konstruktor
Liste::Liste(Liste &&l) {

}

Liste::~Liste() {
  
}
Der Kopier- Konstruktor soll doch ein neues Objekt erschaffen das die selben Daten hat wie mein als Parameter übergebenes Objekt.
Ich verstehe nur gerade überhaupt nicht wie ich das so machen soll :( Start ist ein Pointer auf ein Object vom Typ Node. Wie bekomme ich diesen denn gefühlt?

Hab es wie oben mit einer insert Methode versucht die ich auch der Klasse zur Verfügung stelle. Doch so klappt es nicht da mekert schon der Compiler!

Hier die insert Methode:
C++:
void Liste::recInsert(Liste::Node *s, Liste::Node *n) {
    if (s->next == nullptr)
        s->next = n;
    else
        recInsert(s->next, n);
}

void Liste::insert(int value) {
    Liste::Node *n = new Liste::Node { value };
    if (start == nullptr)
        start = n;
    else
        Liste::recInsert(start, n);
}
 
Dein Problem dürfte sein:
C++:
this->start = insert(tmp->value);
Da überschreibst Du start immer mit dem aktuellen Node und das war es.

Aber Du willst doch nicht ständig start der Liste überschreiben. Die erste Lösung, die Du machen kannst (wenig optimiert) ist, dass Du einfach für jeden Node ein insert(value) aufrufst.

Wenig optimiert, da du für jedes Insert durch die Liste bis zum Ende laufen musst.

Ansonsten kannst Du auch einfach hingehen und dir den letzten eingefügten Node merken um da direkt einen neuen Node dran zu hängen. Dann sparst Du Dir dieses durchhangeln.

Und am Anfang hast Du halt ein paar Prüfungen?
- Übergebene Liste leer? -> entsprechend behanden.
- ersten Node erstellen (Hier wird dann start gesetzt)
- Schleife um restliche Nodes zu erstellen und einzuhängen.
 
Aber Du willst doch nicht ständig start der Liste überschreiben. Die erste Lösung, die Du machen kannst (wenig optimiert) ist, dass Du einfach für jeden Node ein insert(value) aufrufst.
Das klappt doch nicht weil die übergebene Liste const ist. Somit kann ich durch diese Liste mit start = start->next nicht wandern. Ich stehe hier gerade mega auf dem Schlauch! Werde erst mal ein paar Minuten Pause machen.
Danke trotzdem für die schnelle Antwort!

LG

So zerstöre ich die Ausgabe wieso auch immer :(
C++:
Liste::Liste(const Liste &l) {
    Node *tmp = l.start;
    while(tmp != nullptr) {
            insert(tmp->value);
            tmp = tmp->next;
        }
}
 
Das klappt doch nicht weil die übergebene Liste const ist. Somit kann ich durch diese Liste mit start = start->next nicht wandern. Ich stehe hier gerade mega auf dem Schlauch! Werde erst mal ein paar Minuten Pause machen.
Danke trotzdem für die schnelle Antwort!

LG
Das ist doch überall das Gleiche. Auch in Java sollte man (meiner Meinung nach) Parameter final kennzeichnen. Und wenn man da mit einem Wert arbeiten will, dann macht man sich eine eigene Variable der man den Wert des kontanten Parts zuweist als Initialisierung. Die neue Variable ist dann änderbar.

Also bei Dir kannst Du eine
C++:
Liste::Node *current = start->next;
erstellen um dann bei der Abarbeitung immer weiter zu gehen:
C++:
current = current->next;
 
@JustNobody Danke :)
Kann das sein das mein Einfügen schon FALSCH ist?

Hier jetzt mal das was ich gemacht habe:
C++:
Liste::Liste(const Liste &l) {
    if (l.start != nullptr) {
        Liste::Node *tmp = l.start;
        while (tmp != nullptr) {
            cout << "Test" << endl;
            insert(tmp->value);
            tmp = tmp->next;
        }
    }
}
Denn danach ist mal wieder die Liste weg.
Main.cpp
C++:
Liste l { 1 };
    l.insert(55);
    l.insert(42);
    l.insert(155);
    l.insert(142);
    l.insert(525);
    l.insert(242);
    l.print();
    printf("\n");
    Liste test {l};
    l.deleteNode(1);
    l.print();
    printf("\n");

    test.print();
Er gibt nur die erste Liste aus!
 
Also Dein insert habe ich noch nicht im Detail angesehen - das waren bisher nur ganz oberflächige Betrachtungen.

Bei dem Konstruktor: Da das Objekt erst erzeugt wird, dürfte da kein Copy-Konstruktor möglich sein. Den Fall musst Du also nicht wirklich betrachten meine ich.

Und ehe Du insert aufrufst musst Du natürlich start setzen. Ohne initialisierung ist das auf irgendwas und führt zu einem Speicherfehler. Also als erstes ein start = nullptr; in den Konstruktor und schon geht es (zumindest bei mir, aber ich musste mir ja auch viel selbst zusammen basteln :) )
 
mhh... so klappt es!

C++:
Liste::Liste(const Liste &l) {
    start = nullptr;
    if (l.start != nullptr) {
        Liste::Node *tmp = l.start;
        while (tmp != nullptr) {
            insert(tmp->value);
            tmp = tmp->next;
        }
    }
}
man könnte auch start = new Node(); machen dann bekomme ich nur den ersten value = 0 noch mit rein :(
So und nun noch zu den sonderfällen!
Danach noch verschiebe Konstruktor, =operator und verschiebe zuweisung :D
Wenn das dann Fertig ist werde ich das ganze noch mit einem Baum versuchen! uff!!! Ich vermisse die JVM!!! Wieso gibt es so etwas ähnliches nicht für C/C++? Auf aktuellen Betriebssystem ist das zu viel Aufwand oder einfach nicht umsetzbar durch z. B. Index unterschreitung bei einem Array und der gleichen?
 
Ich habe nun mein Destruktor und Kopierkonstruktor in verschiedene Methoden gelagert und damit dann den Zuweisungsoperator gemacht. Macht das Sinn oder ist das Falsch an der Stelle?

C++:
Liste& Liste::operator=(const Liste &original) {
    if (original.start != nullptr && &original != this) {
        loesche();
        erschaffe(original);
    }
    return *this;
}
C++:
void Liste::loesche() {
    Node* toDel = start;
    while (start) {
        start = start->next;
        delete toDel;
        toDel = start;
    }
}
void Liste::erschaffe(const Liste &l) {
    start = nullptr;
        if (l.start != nullptr) {
            Liste::Node *tmp = l.start;
            while (tmp != nullptr) {
                insert(tmp->value);
                tmp = tmp->next;
            }
        }
}
C++:
Liste::Liste(const Liste &l) {
    erschaffe(l);
}

Liste::~Liste() {
    loesche();
}
Wenn das so Korrekt wäre! dann würde ich noch den verschiebe Konstruktor und die Verschiebe Zuweisung versuchen und mich dann an den Baum wagen :D
 
Also auch für c++ gibt es extrem viel. Wenn Du z.B. einen Garbage Collector haben willst, dann könntest Du Dir selbst einen bauen. (Und es gibt da bestimmt auch Libs fertig zur Nutzung). Bezüglich des selbst bauen könnte man sich das Buch von Herbert Schild "The Art of C++" ansehen - da wird das in einem Kapitel behandelt.

Aber das ist eigentlich nicht der Weg, den man bei C++ gehen will. Aber das ist alles ein anderes Thema und muss nicht im Detail ausdiskutiert werden.
 
Aber das ist eigentlich nicht der Weg, den man bei C++ gehen will. Aber das ist alles ein anderes Thema und muss nicht im Detail ausdiskutiert werden.
Danke für die Antwort. NEIN muss nicht ausdiskutiert werden! Hast mir ja auch schon eine Antwort gegeben :) Ja es gibt so etwas (Fertig!). Das reicht erst einmal. Kann man selber Bauen auch sehr cool! Aber wie man sieht scheitere ich an allen Ecken und Kanten noch an den Anfängen ähnlich wie bei Java!!!

LG

PS: Werde mal nach dem Buch schauen Herbert Schild "The Art of C++"
 
Ich hoffe, das mit dem nicht ausdiskutieren ist nicht missverstanden worden. Mir ging es da nur darum, dass ich den Thread nicht durch sowas aufblähen wollte und Dich auch nicht groß verwirren.

Generell solltest Du Dich erst einmal rein mit den Grundlagen beschäftigen. Das Buch von Herbert Schild fand ich gut und interessant, aber den Hinweis hätte ich mir evtl. sparen sollen, um Dich nicht damit abzulenken. Nach den Grundlagen würde ich mich lieber verstärkt auf die neuen C++ Standards stürzen.
 
So hab den Baum versucht und denke bis jetzt läuft alles ganz gut.
Hatte nur 2 Stunden lang ein Problem. Meine Header Datei sah wie folgt aus:

C++:
#include "Node.h"

class Baum {
Node *root;
   
public:
    Baum();
    Baum(int value);
//    Baum(const Baum &b);
    Baum(Baum &&b);
    ~Baum();
//    Baum &operator=(const Baum &b);
//    Baum &operator=(Baum &&b);
    void insert(int value);
    void print();
private:
    void erase(); //Dekonstruktor benutzen
//    void create(const Baum &b); //Copy Konst benutzen
    Node *reInsert(Node *n, int value);
    void rePrint(Node *n);
    void reErase(Node *root);
};
Als ich dann in meiner Main.cpp folgendes versucht hatte, weil ich wieder mal keine Ausgabe bekommen habe:
C++:
Baum b (1);
    cout << b.root->value << endl;
    b.insert(22);
    b.print();
Bekomme ich direkt in der Zeile 2 eine Warnung:
Multiple markers at this line
- ‘Node* Baum::root’ is private within this
context
- ‘int Node::value’ is private within this context

Naja wenn er nicht zugreifen kann kommt auch keine Ausgabe. Da ich 2 Stunden lang das Problem gesucht habe. Habe ich meine Node Klasse in eine neue Datei geschrieben, denn vorher war Sie auch in der Baum Klasse als Innere Klasse wie bei der Liste. Das hat das Problem nicht gelöst. Also habe ich wie in Java gelernt den Node *root Knoten in private geschrieben und einen GETTER UND SETTER eingebaut.

Die Header Datei sieht nun so aus:
C++:
#include "Node.h"

class Baum {

public:
    Baum();
    Baum(int value);
//    Baum(const Baum &b);
    Baum(Baum &&b);
    ~Baum();
//    Baum &operator=(const Baum &b);
//    Baum &operator=(Baum &&b);
    void insert(int value);
    void print();
    void setRoot(Node *root);
    Node *getRoot();
private:
    Node *root;
    void erase(); //Dekonstruktor benutzen
//    void create(const Baum &b); //Copy Konst benutzen
    Node *reInsert(Node *n, int value);
    void rePrint(Node *n);
    void reErase(Node *root);
};

WIESO KONNTE ER NICHT AUF DAS ROOT ELEMENT ZU GREIFEN? In der Liste klappt das doch auch soweit!
Was übersehe ich mal wieder bzw. wo ist der Fehler :(
 
In der Liste hast Du auf das Element start bestimmt nur aus der Liste selbst zugegriffen. Innerhalb der Klasse Liste kannst Du natürlich auf das Element start zugreifen.

In der Main.cpp bist Du außerhalb der Klasse Baum, daher kannst Du auf das Element root nicht zugreifen.

Die Lösung mit dem Getter ist aber nicht so gut. Du gibst ein Detail der Implementierung nach außen, das Du eigentlich kapseln möchtest.

Wenn Du (Log/Trace) Ausgaben der implementierungs-Details haben willst, dann bau diese beim Baum selbst ein.
 
Die Lösung mit dem Getter ist aber nicht so gut. Du gibst ein Detail der Implementierung nach außen, das Du eigentlich kapseln möchtest.
So lernten wir das in Java. Gilt das dann auch für Java das so etwas nicht gut ist?

mhhh... Ich muss mir das ganze mal wieder genauer anschauen. Jetzt geht es aber immerhin schon mal :) Werde den Baum fertig machen und später das ganze wieder mit der Inneren Klasse Node versuchen :)

Ich schreibe ja eine Baum.h und eine Baum.cpp. Wenn ich die .h in der .cpp includeiert habe dann kann ich doch auf alles zugreifen! Verstehe einfach nicht wo da gestern der Fehler war. Weil ich jetzt alles so rum gestrickt habe schreibe ich den Baum später nochmal komplett neu und schau mir das ganze noch einmal an. Wenn nicht Poste ich den ganzen Baum hier mal dann wird es sicherlich klarer was ich mal wieder Falsch mache :(
 
Also wenn Du eine reine Entity (=Datenklasse) hast, dann ist es so, dass die Daten nach außen bekannt sind. Aber das ist dann auch kein wirklicher Objektorientierter Ansatz!

Bei einem objektorientierten Ansatz hast Du Objekte mit einem Verhalten und das Verhalten ist dann wichtig. Das Innenleben interessiert nicht!

Daher gibt es dann auch tatsächlich Tools, die den Code prüfen, und dann Warnungen auswerfen, wenn Du so komische Konstrukte hast wie someInstance.getSomething().getSomethingElse(). Wenn Du objektorientiert arbeitest, dann magst Du ein "someInstance" haben, aber dann arbeitest Du damit. Du holst dir nicht irgend was internes um darauf etwas zu machen....

Hier auch immer an die realen Objekte denken.
Du hast ein auto mit einem Verhalten und mit einer Außenschnittstelle, die Du bedienst. Du kannst beim Auto Gas geben.
Aber Du machst kein auto.getMotor().getSteuerung().setSomeValue(auto.getMotor().getSteuertung().getSomeValue() + 10);
wobei getSomeValue halt von mir aus die Benzinzufuhr war, die du erhöhen willst ....

Oder bei dem Computer: Du prüfst da doch nicht: myComputer.getMotherboard().getResistor(14).getTemperature() um zu prüfen, ob der Widerstand 14 evtl. warm geworden ist. Morgen baut jemand ein anderes Motherboard ein und der wichtige Wderstand ist nicht mehr Nr. 14 sondern Nr. 27.... Außerdem interessiert Dich doch der einzelne Widerstand nicht. Du willst wissen: Läuft das Motherboard noch in normalen Bereichen? Da ist dir egal, ob ein Widerstand oder ein IC zu heiß wird.
Und wie das Motherboard das prüft, ist dir auch egal. Ob da 100 Sensoren oder nur 3 verbaut wurden. Das ist ein Implementierungsdetail.

Was aber in der Praxis tatsächlich regelmäßig vorkommt: Man arbeitet mit Datenklassen. Das sind dann die Entities und da kommt wohl kaum eine Business Applikation drum herum. Da hat man dann halt Eigenschaften und jede Eigenschaft bekommt Getter und Setter. Equals prüft alle Eigenschaften, Hashcode bezieht alle Eigenschaft mit ein. Der Code, der da geschrieben wird, ist fast immer gleich! Und da kommt dann Lombok ins Spiel - da schreibst Du diesen ganzen Code nicht mehr. Da schreibst Du nur noch

Java:
public class MyEntity {
    private SomeType1 attribute1;
    private SomeType2 attribute2;
    private SomeType3 attribute3;
    ...
}
Und dann kommen da paar Annotations vor die Klasse wie z.B. @Data und dann wird alles von Lombok generiert. Getter, Setter, Konstruktoren (Mit Parametern, ohne Parametern), equals, hashCode, Builder so gewünscht, ....

Aber da hat man nicht wirklich einen objektorientierten Ansatz. Das ist dann auch vergleichbar mit einer Struct in C mit ein paar Funktionen drumherum...
 
Danke erst einmal :)

So und jetzt läuft auch der Kopier- Konstruktor mit dem hatte ich gerade noch Probleme.
Kann man das ganze effizienter gestallten?

C++:
void Baum::reCreate(Node *n) {
    if (n != nullptr) {
        insert(n->getValue());
        reCreate(n->getLeft());
        reCreate(n->getRight());
    }
}

void Baum::create(const Baum& b) {
    root = nullptr;
    if(b.root != nullptr) {
        Node *neu = new Node {b.root->getValue()};
        root = neu;
        reCreate(b.root->getLeft());
        reCreate(b.root->getRight());
    }
}
 
Zuletzt bearbeitet:
So wie auch bei der Liste: Du fügst immer beim Baum selbst ein, so dass da der Baum durch gelaufen werden muss zum Einfügen. Aber bei der Rekursion kannst Du doch auch beim Baum mit weiter durch gehen. Dann hast Du es gleich um log(N) verbessert.
 
Bei einem objektorientierten Ansatz hast Du Objekte mit einem Verhalten und das Verhalten ist dann wichtig. Das Innenleben interessiert nicht!
Also die Getter und Setter alle raus schmeißen und direkt mit den Datentypen arbeiten oder wie soll ich das verstehen?

Zu uns wurde gesagt zu jedem Dateityp in einer Klasse sollte man Getter und Setter erstellen um es so zu machen wie ich gepostet habe! Wie du schreibst der Praxis orientierte Ansatz.
Aber da hat man nicht wirklich einen objektorientierten Ansatz
Verstehe ich zwar dann überhaupt nicht doch ich frage mich gerade ob ich überhaupt verstehe was Objektorientierung heißt!
mhh... glaube ich werfe gerade wieder alles schön durcheinander :(




Aber bei der Rekursion kannst Du doch auch beim Baum mit weiter durch gehen.
Ja so füge ich bis jetzt jeden Knoten von vorne ein das meinst du doch sicherlich! Und ich soll dann in meinem derzeitigen Baum mit wandern? Das verstehe ich nicht ganz ich weiß ja nicht was der nächste Wert ist wo soll ich dann hin laufen Links oder Rechts o_O
 
Hallo,

wollte mal nachfragen ob mir das vielleicht nochmal jemand erklären kann.
Wie kann ich hier das Laubzeitverhalten verbessern?

Beispiel an meiner Liste:
C++:
void Liste::create(const Liste &l) {
    start = nullptr;
        if (l.start != nullptr) {
            Liste::Node *tmp = l.start;
            while (tmp != nullptr) {
                insert(tmp->value);
                tmp = tmp->next;
            }
        }
}

void Liste::recInsert(Liste::Node *s, Liste::Node *n) {
    if (s->next == nullptr)
        s->next = n;
    else
        recInsert(s->next, n);
}

void Liste::insert(int value) {
    Liste::Node *n = new Liste::Node { value };
    if (start == nullptr)
        start = n;
    else
        Liste::recInsert(start, n);
}
Mir ist bewusst das ich ja dann durch den Rekrusiven Aufbau und Abbau das X-mal mache :( Was Speicher und Zeit kostet.
Doch wenn ich es Rekursiv lasse weiß ich nicht wie ich es verbessern kann :(

Kann da jemand etwas helfen?
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben