Konstruktoren und Kopiekonstruktoren



  • Hallo,

    ich versuche gerade das Thema Klassen & Objekte zu verstehen. Leider stehe ich etwas auf dem Schlauch. wie es scheint.

    Die header Datei sieht wie folgt aus:

    #pragma once
    #include <string>
    #include <iostream>
    #include <cassert>
    #include <ctime>
    using namespace std;
    
    class cellphone
    {
    private:
    	string name;
    	int rfPower;			// in mW
    	float accuCharge;			// in mAh
    	int accuCapacity;			// in mAh
    	const float accuVoltage;
    	enum status { bereit, verbunden };
    	status cellphoneOperationStatus;
    	clock_t startTime;
    
    public:
    	cellphone(const string & aName , int rfPower, int accuCapacity);
    	cellphone(const cellphone&);
    	~cellphone(void);
    	void morePower(int soMuch);
    	void lessPower(int soMuch);
    	void beginCall();
    	void endCall();
    	void showStatus() const;
    	void chargeAccu();
    	int getAccuCapacity() const;
    	float getAccuVoltage() const;
    
    };
    

    Die dazugehörige cpp Datei so:

    #include "cellphone.h"
    
    cellphone::cellphone(const string & aName, int aRfPower, int anAccuCapacity): accuVoltage(8)
    {
    	//check: 2000 >= rfPower >= 100, rfPower % 100 == 0
    	name = aName;
    	rfPower = aRfPower;
    	accuCapacity = anAccuCapacity;
    	accuCharge = .0;
    	cellphoneOperationStatus = bereit;
    	cout << "Handy " << name << " wurde erstellt." << endl;
    	assert ((rfPower >= 100) && (rfPower <= 2000) && (rfPower % 100 == 0));
    }
    
    cellphone::cellphone(const cellphone& h): accuVoltage(h.accuVoltage)
    {
    	name =h.name;
    	rfPower = h.rfPower;
    	accuCapacity = h.accuCapacity;
    	accuCharge = h.accuCharge;
    	cellphoneOperationStatus = h.cellphoneOperationStatus;
    	startTime = h.startTime;
    }
    
    cellphone::~cellphone(void)
    {
    	cout << "Folgendes Handy wird vernichtet: " << endl
    		<< "Name: " << name << endl
    		<< "Sendeleistung: " << rfPower << "mW" << endl
    		<< "Akku Ladung: " << accuCharge << "mAh" << endl
    		<< "Akkukapazitaet: " << accuCapacity << "mAh" << endl
    		<< "Akku Spannung: " << accuVoltage << "V" << endl
    		<< "Betriebszustand: " << (cellphoneOperationStatus == bereit ? "bereit" : "verbunden") << endl << endl;
    }
    
    void cellphone::showStatus() const
    {
    	cout << "Name: " << name << endl
    		<< "Sendeleistung: " << rfPower << "mW" << endl
    		<< "Akku Ladung: " << accuCharge << "mAh" << endl
    		<< "Akkukapazitaet: " << accuCapacity << "mAh" << endl
    		<< "Akku Spannung: " << accuVoltage << "V" << endl
    		<< "Betriebszustand: " << (cellphoneOperationStatus == bereit ? "bereit" : "verbunden") << endl << endl;
    }
    
    void cellphone::morePower(int soMuch)
    {
    	//check: 0 < rfPower < 2000 , rfPower%100 = 0
    	bool correctInput;
    	correctInput = ((soMuch >= 100) && (rfPower + soMuch <= 2000) && (soMuch % 100 == 0)) ? true : false;
    	correctInput == true ? rfPower += soMuch : soMuch = 0;
    	cout << (correctInput == false ? "Falscher Paramter" : "");
    	cout << endl << "Sendeleistung wurde um " << soMuch << " mW erhoeht." << endl << endl;
    	assert ((rfPower >= 100) && (rfPower <= 2000) && (rfPower % 100 == 0));
    }
    
    void cellphone::lessPower(int soMuch)
    {
    	//check: 0 < rfPower < 2000 , rfPower%100 = 0
    	bool correctInput;
    	correctInput = ((soMuch >= 100) && (rfPower - soMuch >= 0) && (soMuch % 100 == 0)) ? true : false;
    	correctInput == true ? rfPower -= soMuch : soMuch = 0;
    	cout << (correctInput == false ? "Falsches Paramter" : "");
    	cout << endl << "Sendeleistung wurde um " << soMuch << " mW erniedrigt." << endl << endl;
    	assert ((rfPower >= 100) && (rfPower <= 2000) && (rfPower % 100 == 0));
    }
    
    void cellphone::beginCall()
    {	
    	cout << (cellphoneOperationStatus == bereit ? "Gespraech wird begonnen" : "Gespraech bereits aktiv") << endl;
    	cellphoneOperationStatus = cellphoneOperationStatus == bereit ? verbunden : bereit;
    	startTime = clock();
    }
    
    void cellphone::endCall()
    {
    	cout << (cellphoneOperationStatus == verbunden ? "Gespraech wird beendet" : "Kein Gespräch aktiv") << endl;
    	cellphoneOperationStatus = cellphoneOperationStatus == verbunden ? bereit : verbunden;
    	accuCharge = accuCharge - (rfPower / accuVoltage * static_cast<float>(clock() - startTime) );
    }
    void cellphone::chargeAccu()
    {
    	accuCharge = static_cast<float>(accuCapacity);
    }
    
    int cellphone::getAccuCapacity() const
    {
    	return accuCapacity;
    }
    
    float cellphone::getAccuVoltage() const
    {
    	return accuVoltage;
    }
    

    die main.cpp so

    #include "cellphone.h"
    #include "charger.h"
    #include <ctime>
    #pragma comment(lib, "PG1_Uebung3_PG1Lib.lib")
    
    typedef long clock_t;
    
    void warten(double seconds);
    
    void main()
    {
    	clock_t begin = clock();
    	cellphone h1("Nokia N70", 900, 1250);
    	h1.showStatus();
    	charger a1("Ladestation N70", 125);
    	a1.showStatus();
    	a1.powerOn();
    	a1.chargeAccu(h1);
    	h1.morePower(100);
    	h1.lessPower(200);
    	h1.beginCall();
    	warten(4);
    	clock_t end = clock();
    
    	cellphone h2("Samsung SGH-D 600E", 900, 1250);
    	h2.showStatus();
    	charger a2("Ladestation Samsung SGH-D 600E", 125);
    	a2.showStatus();
    	a2.powerOn();
    	a2.chargeAccu(h2);
    	h2.morePower(100);
    	h2.lessPower(200);
    }
    

    wenn ich das nun richtig verstehe, brauche ich den Kopiekonstruktor, um mehrere Objekte zu erstellen(?) die er mit den Daten des default konstruktors füllt, sofern er nicht per Parameter andere Daten übergeben bekommt, richtig?

    irgendwie verstehe ich das Prinzip so rein überhaupt nicht. Müsste nicht ein einziger Konstruktor zur Erstellung von Objekten genügen? Und wieso ist hier call by reference (dafür steht das & doch?) eigentlich zwingend erforderlich?

    Tut mir leid, wie gesagt anscheinend stehe ich ziemlich auf dem Schlauch. Zuvor hatte ich schon eine aufgabe mit Klassen und Objekten, aber ein objekt zu generieren mit name.funktion(Parameter) und dann andere funktionen mit objektname.funktion() aufzurufen war irgendwie viel simpler.... 🙄 😕 😞

    [Edit:]

    Wozu genau ist & h bzw & l ?

    Wieso wird im Standardkonstruktor von handy "string & einName" benutzt und bei dem Standardkonstruktor von charger nicht, wenn doch beide einen kopykonstructor haben?



  • Ich werde erstmal nur eine kurze Antwort geben (grad unterwegs):

    Du hast definitiv einiges durcheinander geworfen. Beginnen wir also ganz einfach:

    Eine Klasse ist eine abstrakte Darstellung von etwas, ein Objekt eine konkreter Gegenstand. So kann z.B Stift eine Klasse sein, die vor dir liegenden Stifte sind die konkreten Instanzen (Objekte) der Klasse.

    Ein Konstruktor definiert wie ein Objekt angelegt wird. So könnte der Standardkonstruktor z.B. sagen das ein neuer Stift 12 cm lang ist, und eine Bleistiftmine hat. Bei einem anderen Konstruktor könnte man z.B. die Minenart übergeben und z.B. einen roten Buntstift anlegen.

    Bis hierhin spielt der Kopierkonstruktor keine Rolle, du kannst über die normalen Konstruktoren beliebige Mengen an Objekten anlegen.

    Nun möchtest du aber eine Kopie von einen Stift, sprich ein Objekt das die gleichen Eigenschaften wie ein bereits vorhandenes hat (bis hin zu der aktuellen Größe...). Hier kommt der Kopierkonstruktor zum tragen. Du erstellst dabei ein Objekt über eine Kopie eines bestehenden.

    cu André



  • Als Ergänzung dazu: Der Compiler erzeugt automatisch einen Copy-Konstruktor, operator= und Destruktor* für deine Klassen, wenn du keinen explizit angibst (diese führen in festgelegter Reihenfolge die entsprechenden Methoden für die Basisklasse(n) und alle Member aus). Eine eigene Version davon brauchst du nur, wenn die Default-Version nicht möglich ist (z.B. Zuweisung für Klassen mit konstanten Membern oder Referenzen) oder nicht das macht, was du erwartest (z.B. Zeiger werden per Default flach kopiert).

    (und beim Copy-Ctor mußt du das Objekt per Referenz übergeben, weil eine Übergabe per Wert zu einer Endlosrekursion führen würde - um das Argument des Copy-Ctors zu liefern, müsste der übergebene Wert kopiert werden - und dazu wird wieder der Copy-Ctor verwendet)

    PS: Übrigens ist die Übergabe als "const Typ&" praktisch eine Wert-Übergabe, weil du das übergebene Objekt nicht ändern darfst - nur sparst du dir auf diese Weise unnötige Kopien (die mitunter recht langsam sind).

    * wenn du der Klasse keine eigenen Konstruktoren spendierst, bekommst du auch noch einen automatisch erzeugten Default-Konstruktor.



  • Hm also ich versuche mal zusammen zu fassen:

    • Eine klasse ist der Oberbegriff für eine größere Anzahl Objekte mit gleichen Eigenschaften oder Methoden
    • Ein Objekt ist ein virtueller "Gegenstand" der gewisse Eigenschaften der Objekte, der Klasse zu der er gehört haben muss (muss er immer alle haben oder kann ich auch objekte erzeugen, denen ich zb nur namen gebe?)
    • Der Konstruktor erstellt eine art Standardobjekt
    • Mit dem KopyKonstruktor kann ich objekte erstellen, die ihre individuellen Werte über die Parameter bekommen (zb. cellphone h1("Nokia N70", 900, 1250); erstellt handy h1 mit dem namen nmokia N70, rf Power 900 und accucapacity von 1250) und ansonsten andere Eigenschaften des Standard Objekts automatisch kopiert bekommen (wobei sich mir da immer noch die Frage stellt wieso das nicht ohne kopy konstruktor bzw nur mit dem normalen konstruktor geht)
    • Mit const &typ übergebe ich einen Wert den ich nicht ändern darf (wobei ich das in dem Zusammenhang nicht verstehe, der Name ist doch immer ein anderer?)

    Man möge mich bitte korrigieren denn das ist es wie ich es verstanden habe und das wirkt auf mich wie großer Müll 😞



  • ThaRealMatix schrieb:

    Eine klasse ist der Oberbegriff für eine größere Anzahl Objekte mit gleichen Eigenschaften oder Methoden

    Abgesehen davon, daß das vorletzte Wort "und" lauten sollte, ist das korrekt

    Ein Objekt ist ein virtueller "Gegenstand" der gewisse Eigenschaften der Objekte, der Klasse zu der er gehört haben muss (muss er immer alle haben oder kann ich auch objekte erzeugen, denen ich zb nur namen gebe?)

    Das Objekt hat alle Eigenschaften seiner Klasse, kann aber in verschiedenen Ausprägungen vorliegen (z.B. hat jedes Auto eine Höchstgeschwindigkeit, aber der Wert unterscheidet sich zwischen einem Ferrari und einem VW Polo)

    Der Konstruktor erstellt eine art Standardobjekt

    "Konstruktor" heißen alle Methoden, die auf irgendeine Art ein Objekt der Klasse erzeugen können. Besondere Bedeutung haben der Default-Konstruktor (erzeugt ein Standardobjekt ohne zusätzliche Informationen) und der Kopier-Konstruktor (kopiert die Attributwerte eines existierenden Objekts, um ein neues zu erzeugen).

    Mit dem KopyKonstruktor kann ich objekte erstellen, die ihre individuellen Werte über die Parameter bekommen (zb. cellphone h1("Nokia N70", 900, 1250); erstellt handy h1 mit dem namen nmokia N70, rf Power 900 und accucapacity von 1250) und ansonsten andere Eigenschaften des Standard Objekts automatisch kopiert bekommen (wobei sich mir da immer noch die Frage stellt wieso das nicht ohne kopy konstruktor bzw nur mit dem normalen konstruktor geht)

    Was du da beschreibst, ist ein beliebiger Konstruktor. Der Kopier-Konstruktor nimmt ein fertiges Objekt und überträgt dessen Eigenschaften komplett auf ein weiteres (z.B. cellphone h2=h1; (jetzt hast du zwei identische Handys)).

    Mit const &typ übergebe ich einen Wert den ich nicht ändern darf (wobei ich das in dem Zusammenhang nicht verstehe, der Name ist doch immer ein anderer?)

    Mit der Übergabe als Referenz typ& wert wird der Parameter zu einem neuen Namen des Objekts, das übergeben wurde - das spart Kopier-Operationen (wenn eine Kopie überhaupt möglich ist) und ermöglicht es der Funktion auch, das Objekt direkt zu manipulieren. Mit der Übergabe als Wert typ wert übergibst du eine eigenständige Kopie des Objekts - Änderungen, die in der Funktion stattfinden, machen sich nach außen nicht bemerkbar, aber dafür muß das Objekt kopiert werden*.
    Die Übergabe als konstante Referenz const typ& wert verbindet sozusagen die Vorteile beider Lösungen - durch die Referenz sparst du dir das Kopieren und das 'const' sorgt dafür, daß das übergebene Objekt nicht verändert werden darf.

    * Es gibt Objekte, bei denen ist es technisch nicht möglich sie zu kopieren (z.B. die IO-Streams) oder sie sind so groß, daß das Kopieren eine halbe Ewigkeit brauchen würde (das betrifft zum Beispiel Containerklassen). Bei solchen Objekten ist eine Übergabe als Wert nicht möglich oder ineffizient. Der Copy-Ctor ist ein anderes Beispiel, wo eine Wert-Übergabe technisch nicht möglich ist.



  • ThaRealMatix schrieb:

    Hm also ich versuche mal zusammen zu fassen:

    • Eine klasse ist der Oberbegriff für eine größere Anzahl Objekte mit gleichen Eigenschaften oder Methoden

    Ich würde eher sagen eine Klasse ist eine Vorlage/Schablone für Objekte. Eine Klasse selbst hat keine Werte, ein Objekt einer klasse schon. Wir Menschen denken auch quasi Objektorientiert. Bei dem Begriff "Haus" haben wir eine Vorstellung was gemeint ist, auch wenn dies noch lange nichts über das konkrete Aussagt (Ein Objekt der Klasse Haus könnte z.b. lauten: 2-Familienwohnung in guter Lage,...)

    • Ein Objekt ist ein virtueller "Gegenstand" der gewisse Eigenschaften der Objekte, der Klasse zu der er gehört haben muss (muss er immer alle haben oder kann ich auch objekte erzeugen, denen ich zb nur namen gebe?)

    Ein Objekt ist ein konkreter Gegenstand, die Klasse die grobe Umschreibung. Eine Klasse beschreibt was für Merkmale jedes Objekt dieser Klasse hat, ein Objekt hat aber konkrete Eigenschaftswerte (Die Klasse sagt es gibt eine Eigenschaft Farbe, das konkrete Objekt sagt: Farbe=gelb...).

    Dein Satz hier ist auch sehr missverständlich. Von einer Klasse kann man mehrere Objekte anlegen, jedes Objekt hat alle Eigenschaften der Klasse, aber in eigenen konkreten Ausprägungen. Sie können identische Werte haben, müssen aber nicht.

    Eine Klasse braucht auch keinen Speicherplatz, da sie selbst nicht "existiert" ein Objekt belegt Speicherplatz weil es im gegensatz zur Klasse eine Existenz hat (greifbar ist).

    ThaRealMatix schrieb:

    • Der Konstruktor erstellt eine art Standardobjekt

    Du kannst mehrere Konstruktoren haben, jeder Konstruktor dient dazu ein Objekt der Klasse mit bestimmten Vorbelegungen anzulegen. Von einem Standardkonstruktor spricht man wenn dieser keine Angaben braucht (Dies kann auch einer mit Parametern sein, sofern diese alle Vorgabewerte besitzen).

    class Klasse {...};
    
    int main()
    {
        Klasse objekt1;           // objekt1 angelegt mittels Standardkonstruktor
        Klasse objekt2(1);        // objekt2 angelegt über einen Konstruktor mit einem Zahlenwert
        Klasse objekt3(objekt2);  // objekt3 angelegt als Kopie von objekt2 über Kopierkonstruktor
    }
    

    ThaRealMatix schrieb:

    • Mit dem KopyKonstruktor kann ich objekte erstellen, die ihre individuellen Werte über die Parameter bekommen (zb. cellphone h1("Nokia N70", 900, 1250); erstellt handy h1 mit dem namen nmokia N70, rf Power 900 und accucapacity von 1250) und ansonsten andere Eigenschaften des Standard Objekts automatisch kopiert bekommen (wobei sich mir da immer noch die Frage stellt wieso das nicht ohne kopy konstruktor bzw nur mit dem normalen konstruktor geht)

    Nein, ein Kopierkonstruktor kopiert alle Werte eines bestehenden Objektes und legt damit ein neues an:

    cellphone h1("Nokia N70", 900, 1250);
    cellphone h2(h1);

    Im ersten Fall wird ein Konstruktor im zweiten der Kopierkonstruktor aufgerufen. h2 ist eine Kopie, also auch ein Nokia N70 mit den gleichen Eigenschaften.

    ThaRealMatix schrieb:

    • Mit const &typ übergebe ich einen Wert den ich nicht ändern darf (wobei ich das in dem Zusammenhang nicht verstehe, der Name ist doch immer ein anderer?)

    Das übergebene Objekt darf nicht geändert werden. Du willst doch den Inhalt kopieren und nicht ändern. Du kannst ja auch von einem konstanten Objekt eine Kopie anlegen, und das "const" zeigt das hier keine Änderung an dem Übergebenen gemacht wird.

    ThaRealMatix schrieb:

    Man möge mich bitte korrigieren denn das ist es wie ich es verstanden habe und das wirkt auf mich wie großer Müll 😞

    Ich hoffe damit wird dir etwas weitergeholfen.

    cu André



  • Hm... okay also ein Konstruktor erschafft immer ein Objekt. ich kann objekte über einen Konstruktor mit Parametern erschaffen, z.b

    cellphone(const string & aName , int rfPower, int accuCapacity);

    (Ich nehme mal an, dass ich in dem konstruktor dann aber angeben muss, welcher Parameter welcher Objekteigenschaft zuzuordnen ist, also mit

    {
    name =h.name;
    rfPower = h.rfPower;
    accuCapacity = h.accuCapacity;
    accuCharge = h.accuCharge;
    cellphoneOperationStatus = h.cellphoneOperationStatus;
    startTime = h.startTime;
    }

    ?)

    Ein kopiekonstruktor macht eine einfache Kopie eines bestehenden Objektes. Aber wozu ist dann der hier?

    cellphone::cellphone(const cellphone& h): accuVoltage(h.accuVoltage)
    {
    name =h.name;
    rfPower = h.rfPower;
    accuCapacity = h.accuCapacity;
    accuCharge = h.accuCharge;
    cellphoneOperationStatus = h.cellphoneOperationStatus;
    startTime = h.startTime;
    }

    Er kann doch gar keine kopie erstellen, weil es noch kein zu kopierendes Objekt gibt? Ausserdem müsste ich doch als "Parameter" das zu kopierende Objekt angeben?

    Ich bin total verwirrt 😞



  • ThaRealMatix schrieb:

    Hm... okay also ein Konstruktor erschafft immer ein Objekt. ich kann objekte über einen Konstruktor mit Parametern erschaffen, z.b

    cellphone(const string & aName , int rfPower, int accuCapacity);

    (Ich nehme mal an, dass ich in dem konstruktor dann aber angeben muss, welcher Parameter welcher Objekteigenschaft zuzuordnen ist, also mit

    Ja - aber dabei muß nicht jedes Objektattribut direkt aus den Parametern hervorgehen. (bei deinem Paramter-Ctor oben werden z.B. accuCharge mit 0 und cellphoneOperationStatus mit bereit vorbelegt)

    Ein kopiekonstruktor macht eine einfache Kopie eines bestehenden Objektes. Aber wozu ist dann der hier?

    cellphone::cellphone(const cellphone& h): accuVoltage(h.accuVoltage)
    {
        name =h.name;
        rfPower = h.rfPower;
        accuCapacity = h.accuCapacity;
        accuCharge = h.accuCharge;
        cellphoneOperationStatus = h.cellphoneOperationStatus;
        startTime = h.startTime;
    }
    

    Die Kopie wird letztlich erzeugt, indem jeder Teil des übergebenen Objekts (hier als 'h' bezeichnet) kopiert wird. Und genau das macht dieser Copy-Ctor.

    Er kann doch gar keine kopie erstellen, weil es noch kein zu kopierendes Objekt gibt? Ausserdem müsste ich doch als "Parameter" das zu kopierende Objekt angeben?

    Du übergibst als Parameter doch ein existierendes Objekt namens const cellphone& h - und genau von diesem Objekt wird eine Kopie angelegt.



  • ehm... gut.... aber wann wurde denn das Objekt h erzeugt? Mit dem Parameter Konstruktor? Der wurde in der main ja nie aufegrufen?



  • Vermutlich ja - in deinem Beispielprogramm wird der Copy-Ctor übrigens gar nicht verwendet (außer evt. für Parameterübergaben per Wert). Ich nehme mal das Beispielfragment von asc:

    cellphone h1("Nokia N70", 900, 1250); 
    cellphone h2(h1);
    

    Das Objekt h1 wurde mit dem "normalen" Ctor erzeugt, h2 mit dem Copy-Ctor (wobei h1 als Parameter 'h' übergeben wurde).



  • ehm moment... jetzt bin ich verwirrt...

    Also in dem Programm wird der

    cellphone::cellphone(const cellphone& h): accuVoltage(h.accuVoltage)
    {
        name =h.name;
        rfPower = h.rfPower;
        accuCapacity = h.accuCapacity;
        accuCharge = h.accuCharge;
        cellphoneOperationStatus = h.cellphoneOperationStatus;
        startTime = h.startTime;
    }
    

    konstruktor demnach gar nicht benutzt?



  • Probier's doch aus, indem du eine Ausgabe dort reinsetzt 😉

    In dem Code, den du gepostet hast, braucht es keinen Copy-Ctor. Die einzige Stelle, bei der ich mir nicht sicher bin, wäre die charger::chargeAccu()-Methode (es wäre also durchaus möglich, daß deren Parameter per Wert übergeben und dafür der Copy-Ctor verwendet wird).


Anmelden zum Antworten