Klasse fuer komplexe Zahlen|Operatorproblem

Surma

Erfahrenes Mitglied
Tach!

Ich wendem ich gerade Mandelbrot zu, und wollte deshalb eine Klasse fuer komplexe Zahlen bauen. Nun beschaeftige ich mich zum ersten mal mit Operatoren in Klassen und bleibe bei meinem "="-Operator haengen.
Code:
class KOMPLEXFLOAT
{
	public:
		float fRe, fIm ;
		inline KOMPLEXFLOAT ()
		{
			fRe = 0 ;
			fIm = 0 ;
		}
	
		inline KOMPLEXFLOAT (float _fRe, float _fIm)
		{
			fRe = _fRe ;
			fIm = _fIm ;
		}

		inline KOMPLEXFLOAT operator + (KOMPLEXFLOAT a)
		{
			return KOMPLEXFLOAT (fRe + a.fRe, fIm + a.fIm) ;
		}

		inline KOMPLEXFLOAT operator + (float a)
		{
			return KOMPLEXFLOAT (fRe + a, fIm + a) ;
		}

		inline KOMPLEXFLOAT operator - (KOMPLEXFLOAT a)
		{
			return KOMPLEXFLOAT (fRe - a.fRe, fIm - a.fIm) ;
		}

		inline KOMPLEXFLOAT operator - (float a)
		{
			return KOMPLEXFLOAT (fRe - a, fIm - a) ;
		}

		inline KOMPLEXFLOAT operator * (KOMPLEXFLOAT a)
		{
			return KOMPLEXFLOAT (fRe * a.fRe - fIm * a.fIm, fRe * a.fIm + fIm * a.fRe) ;
		}

		inline KOMPLEXFLOAT operator = (KOMPLEXFLOAT a) 
		{
			return *this ;
		}

};

Da meckert nun mein Compiler, das da riesen Pointerprobleme auftreten. Wie muss so ein =-Operator aufgebaut sein? Ich hatte mir dies jetzt aus einer Engine (TriBase - Matrixklasse) geklaut, was anscheinend doch nicht so einfach funktioniert...

Gruss
Crock
 
Ich würde es so machen:
Code:
KOMPLEXFLOAT& operator = ( const KOMPLEXFLOAT& a ) 
{ 
  if ( this != &a )
  { 
    fRe = a.fRe ;
    fIm = a.fIm ;  
  } 
  
  return *this; 
}


// Copy-Constructor micht vergessen
KOMPLEXFLOAT ( const KOMPLEXFLOAT& a ) 
{ 
  fRe = a.fRe ;
  fIm = a.fIm ;  
}
 
Danke erstmal! Aber:
Was bedeuten immer diese &-Zeichen?
Wozu sind die hier gut?

Und nun ist folgendes:
Code:
class KOMPLEXFLOAT
{
	public:
		float fRe, fIm ;
		KOMPLEXFLOAT ()
		{
			fRe = 0 ;
			fIm = 0 ;
		}
	
		KOMPLEXFLOAT (const float& _fRe, const float& _fIm)
		{
			fRe = _fRe ;
			fIm = _fIm ;
		}

		KOMPLEXFLOAT (const KOMPLEXFLOAT& a)
		{
			fRe = a.fRe ;
			fIm = a.fIm ;
		}

		inline KOMPLEXFLOAT& operator + (const KOMPLEXFLOAT& a)
		{
			return KOMPLEXFLOAT (fRe + a.fRe, fIm + a.fIm) ;
		}

		inline KOMPLEXFLOAT& operator + (const float& a)
		{
			return KOMPLEXFLOAT (fRe + a, fIm + a) ;
		}


		inline KOMPLEXFLOAT& operator - (const KOMPLEXFLOAT& a)
		{
			return KOMPLEXFLOAT (fRe - a.fRe, fIm - a.fIm) ;
		}

		inline KOMPLEXFLOAT& operator - (const float& a)
		{
			return KOMPLEXFLOAT (fRe - a, fIm - a) ;
		}

		inline KOMPLEXFLOAT& operator * (const KOMPLEXFLOAT& a)
		{
			return KOMPLEXFLOAT (fRe * a.fRe - fIm * a.fIm, fRe * a.fIm + fIm * a.fRe) ;
		}

		inline KOMPLEXFLOAT& operator = (const KOMPLEXFLOAT& a) 
		{
			if (this != &a)
			{
				fRe = a.fRe ;
				fIm = a.fIm ;
			}

			return *this ;
		}

};
//[...]
a = KOMPLEXFLOAT (5.0f, 5.0f) ;
b = KOMPLEXFLOAT (2.0f, -3.0f) ;
c =  a+b;
Und raus kommt c={-107374176,-107374176}

wo ist nun mein Fehler? (Wahrscheinlich der, das ich die &-Zeichen ueberall uebernommen habe, aber die scheinen ja weit verbreitet und somit Vorteile zu haben...)

PS: Hat jemand n gutes Tut wo das mit den Konstruktoren in Klassen erkalert wird? Googlen bringt nur Muell!
 
Zuletzt bearbeitet:
Code:
int a; // ein int
int* b = &a; // b ist ein Pointer auf ein int, b wird mit der Adresse von a initalisiert
int& c = a; // c ist eine Referenz auf ein int, nämlich a
int d = *b; // d wird der wert zugewiesen, auf den b zeigt.
Referenzen sind im Prinzip Pointer mit einer anderen Syntax. Im Unterschied zu Pointern kann bei einer Referenz nicht geändert werden, auf was die Referenz verweist. Bei einem Pointer macht man das, indem man ihm einen anderen Wert zuweist, wodurch der Pointer auf etwas anderes zeigt. Eine Zuweisung an eine Referenz ändert dagegen immer den Wert des referierten Objektes.

(Wahrscheinlich der, das ich die &-Zeichen ueberall uebernommen habe, aber die scheinen ja weit verbreitet und somit Vorteile zu haben...)
"Viel hilft viel" ist dennoch nicht zu empfehlen! ;)

Das Problem liegt bei deinem Additionsoperator, genauer gesagt beim Rückgabewert:
Code:
inline KOMPLEXFLOAT& operator + (const KOMPLEXFLOAT& a)
		{
			return KOMPLEXFLOAT (fRe + a.fRe, fIm + a.fIm) ;
		}
Du gibst da eine Referenz auf ein (unbenanntes) KOMPLEXFLOAT-Objekt, das du in der return-Zeile konstruierst. Das ist ein lokales Objekt für diesen Operator. Es wird so lange auf dem Stack existieren, bis die Funktion verlassen wird, also ist es im Moment der Zuweisung an c nicht mehr definiert. Deshalb bekommst du bei der Zuweisund "c =" nichts Vernünftiges raus.
Besser wäre es so:
Code:
inline KOMPLEXFLOAT operator + (const KOMPLEXFLOAT& a) const
		{
			return KOMPLEXFLOAT (fRe + a.fRe, fIm + a.fIm) ;
		}
Hier wird tatsächlich eine Kopie zurückgeben, die zugewiesen werden kann.

Die beste Erklärung der Konstruktoren und Operatoren habe ich bisher bei Scott Meyers entdeckt. Sein "Effektiv C++ programmieren" und die Folgebände dazu sind wirklich grossartig.
 
Hmmnja,... danke, nun geht alles.
Und verstanden hab ich's auch... das ist doch wunderbar!

MfG
Crock
 
Schön, dass ich helfen konnte.

Ich habe hier noch etwas: Eine Beispielklasse mit den 4 wichtigsten Methoden in korrekter Form (nach Scott Meyers), wobei der Destruktor nur dann virtuell sein sollte, wenn die Klasse irgendwie an Vererbung beteiligt ist. Ich poste es deshalb, weil ich mir mal ein Makro dafür gebastelt habe und es damit so einfach war, die Klasse zu machen. ;)

Getter/Setter-Beispiele sind noch dabei. Operatoren dagegen leider nicht. Vielleicht sollte ich mal ein Tutorial dazu schreiben.

Nochmal die 4 wichtigsten Methoden (die 'Fab Four' -- bitte lachen ;) ):
1. Konstruktor
2. Kopier-Konstruktor
3. Zuweisungsoperator
4. Destruktor

Anmerkungen:
Der Zuweisungsoperator ist wichtig für Klassen, die Werte repräsentieren und kopierbar sein müssen, wie zum Beispiel deine Komplex-Klasse. Gerade für solche Klassen ist aber der explizite Destruktor oft überflüssig. Aus stilistischen Gründen sollte man ihn meiner Meinung nach dennoch definieren, auch wenn der Rumpf leer ist.

Und jetzt der Code:
Code:
class CMeineKlasse
{
public:
  //- constructor
  CMeineKlasse() : m_iWert( 4711 ) {}

  //- copy constructor
  CMeineKlasse( const CMeineKlasse& rhs ) : m_iWert( rhs.m_iWert ) {}

  //- assignment operator
  CMeineKlasse& operator=( const CMeineKlasse& rhs )
  {
    if ( this == &rhs ) return *this;
    m_iWert = rhs.m_iWert;
    return *this;
  }

  //- destructor (virtual)
  virtual ~CMeineKlasse() {}

  //- Setter für m_iWert
  inline void wert( const int& einwert ) { m_iWert = einwert; } //- & sinnvoll für komplexere Datentypen
  //- Getter für m_iWert
  inline int wert() const { return m_iWert; }

protected:
private:
  int m_iWert;
};
 
Zu den Konstruktoren kann man dann noch sagen, dass der Konstruktor immer beim initialisieren einer Variable vom Typ der Klasse aufgerufen wird, egal ob man eine Klasse mit new initialisiert oder direkt eine Variable der Klasse (ohne Zeiger) erstellt. Also Bsp:

Code:
class Quad
{
public:
  Quad(float f);
  float ergebnis;
};

//beim Deklarieren einer Variable muss man den Konstruktor jetzt berücksichtigen
//entweden als direktes Quad-Objekt

Quad variable(10.4f);

//oder als Zeiger

Quad *variable = new Quad(10.4f);

//letzteres sollte später mit delete wieder gelöscht werden:

delete variable;

Beim löschen wird übrigens immer der Destruktor aufgerufen, also entweder beim Aufruf von delete (nur bei Zeigern, auch nur dann verwenden!) oder wenn der Compiler das selbst übernimmt.

Ach ja, noch das fundamentalste zu Konstruktoren: Sie heißen wie die Klasse, haben keinen Rückgabetyp (auch nicht void) und können überladen werden. Selbiges gilt für Destruktoren, dem Namen ist aber noch ein ~ vorangestellt.
 
Zuletzt bearbeitet:
Zurück