Operatoren überladen Komplexe Zahlen
-
Du brauchst entweder einen Operator zur Multiplikation mit einem Integer oder einen Konstruktor, um einen Integer implizit in deinen Datentyp umzuwandeln.
Andere Sache, die mich auch beschäftigt, wie kann ich trotz Überladung der Operatoren die Funktion setprecision auf zB. zwei Nachkommastellen definieren?
Wo ist das Problem?
P.S.: std:;complex ist dir bekannt?
-
johnboyder2 schrieb:
Complex operator* (const Complex &a, const Complex &b) { Complex retVal; retVal.re = a.re * b.re; retVal.im = a.im * b.im; return retVal; }
.. erkundige Dich doch mal, wie man zwei komplexe Zahlen miteinander multipliziert
-
SeppJ schrieb:
P.S.: std:;complex ist dir bekannt?
es geht hier um eine Aufgabe für die Uni, da soll vermutlich nicht auf fertige std-Sachen zurückgegriffen werden.
Und bevor jetzt das Uni-Bashing wieder losgeht: Es ist manchmal zu Lernzwecken durchaus sinnvoll, Sachen zu programmieren, die es schon fertig in der std/boost/... gibt.
-
daddy_felix schrieb:
Und bevor jetzt das Uni-Bashing wieder losgeht: Es ist manchmal zu Lernzwecken durchaus sinnvoll, Sachen zu programmieren, die es schon fertig in der std/boost/... gibt.
Durchaus. Allerdings nicht, bevor die nötigen mathematischen Grundlagen (wie das Rechnen mit komplexen Zahlen) vermittelt worden ist *SCNR*
-
pumuckl schrieb:
Allerdings nicht, bevor die nötigen mathematischen Grundlagen (wie das Rechnen mit komplexen Zahlen) vermittelt worden ist *SCNR*
Das ist natürlich ein ganz anderer Aspekt
-
gut der mathematische teil kann doch erstmal außer acht gelassen werden und ist glaube ich mein kleinstes problem ...
Du brauchst noch einen Complex-Konstruktor für ein double-Argument.
Außerdem solltest deine Implementierung der Multiplikation nochmal mit der mathematischen Definition abgleichen...
was ist damit genau gemeint?
-
johnboyder2 schrieb:
gut der mathematische teil kann doch erstmal außer acht gelassen werden und ist glaube ich mein kleinstes problem ...
Du brauchst noch einen Complex-Konstruktor für ein double-Argument.
Außerdem solltest deine Implementierung der Multiplikation nochmal mit der mathematischen Definition abgleichen...
was ist damit genau gemeint?
alles klar ok habe es glaube ich verstanden, einfach nur noch einen Konstruktor für ein double hinzufügen, vielen Dank.
-
johnboyder2 schrieb:
was ist damit genau gemeint?
Wenn du willst, dass der Compiler aus einem double automatisch einen Complex machen kann, dann musst du ihm sagen wie.
-
struct Complex { double real, imag; // Defaultkonstruktor (0) // Konvertierungskonstruktor (double->Complex) // Konstruktor mit Real-/Imaginär-Teil Complex(double re=0, double im=0) : real(re), imag(im) {} .... }; Complex operator*(Complex const& a, Complex const& b) { return Complex(a.real*b.real-a.imag*b.imag, a.real*b.imag+a.imag*b.real); } int main() { Complex x (0,1); Complex z = 3*x*x; // klappt, äquivalent zu // operator*(operator*(3,x),x) // wobei die 3 wegen des Konvertierungskonstruktors // implizit zu Complex(3,0) konvertiert wird }
Ob ich da beim operator* einen Fehler gemacht habe, musst du nochmal nachrechnen.
-
ok, ich habe es anscheinend doch nicht verstanden :((
ich kann mir gerade pberhaupt nicht mehr vorstellen, wie der kopierkonstruktor aussehen soll, geschweige denn wie es realisierbar sein soll
das beispiel von krümelkacker hat mich jetzt ehrlich gesagt auch fast mehr verwirrt
-
johnboyder2 schrieb:
ok, ich habe es anscheinend doch nicht verstanden :((
Was hast du nicht verstanden?
ich kann mir gerade pberhaupt nicht mehr vorstellen, wie der kopierkonstruktor aussehen soll, geschweige denn wie es realisierbar sein soll
Welcher Kopierkonstruktor? Niemand hat auch nur ein Wort von Kopierkonstruktor gesagt. Bitte versuch doch
1. aufmerksam lesen
2. mitdenken
3. Wenn 2. nicht funktioniert, mitgooglen
Naturwissenschaftler und verwandtes Pack drücken sich gerne sehr genau und sehr korrekt aus. Da zählt jedes Wort, es gibt keine hohlen Floskeln und kaum ein Stichwort wird unnötigerweise genannt. Da kann man nicht einfach mal querlesen sondern man muss versuchen alles ganz genau zu verstehen. Dazu ist auch Eigeninitiative nötig, sonst vergrault man mit seiner Einstellung die Leute. Es kommt äußerst selten vor, dass sich bei genügend Quengelei doch noch jemand erbarmt, eine Komplettlösung zu posten.das beispiel von krümelkacker hat mich jetzt ehrlich gesagt auch fast mehr verwirrt
Beispiel? Dies ist der seltene Fall einer Komplettlösung trotz Quengelei. Schätze dich glücklich. Am besten (für dich) wäre es noch, wenn du nun versuchen würdest, die bisherigen Antworten und die Komplettlösung zu verstehen, anstatt einfach die Komplettlösung zu nehmen. Dann würdest du nämlich erkennen, dass dir diese Lösung bereits in der allerersten Antwort gegeben wurde und alle folgenden Antworten nur Verdeutlichungen waren. Ich weiß schon, dass du dies nicht machen wirst. Aber es wäre schön gewesen.
-
Hallo Profis,
folgendes ist nichts für Johnboy
Die Lösung von Krümelkacker ist zwar pragmatisch, aber nicht performant. Bei einer Multiplikation Skalar mit Komplex verdoppelt sich die Anzahl der Multiplikationen und es kommen zwei Additionen hinzu. Die Lösung bestünde darin, einen Operator* mit Skalar und Komplex (und umgekehrt) anzubieten. So ist es auch bei std::complex gelöst, wo es den Operator
template<class T> std::complex<T> operator*(const T& lhs, const std::complex<T>& rhs);
und sein Pendant gibt.
Der Nachteil dieser Lösung besteht darin, dass man z.B.
complex<double>
nicht mitint
multiplizieren kann. Also3*complex<double>(1.,2.);
compiliert nicht, da der Typ T mehrdeutig ist. Es könnte sowohl
int
als auchdouble
sein.Eine Lösung, die mit C++11 möglich wird, sähe so aus, den Typ des Skalars als eigenen von T unabhängigen Typ U zu deklarieren und gleichzeitig mit
std::enable_if<std::is_convertible<U,T>,..>
zu beschränken um Kollisionen mit anderen Operatoren* zu vermeiden.Das ganze sähe (nur für Operator*) so aus:
#include <iostream> #include <type_traits> // enable_if, is_convertible template< typename T > class basic_complex { public: basic_complex( T r = T(), T i = T() ) : real_( r ), imag_( i ) {} basic_complex operator*=( const basic_complex& b ) { const T r = real_*b.real_ - imag_*b.imag_; imag_ = real_*b.imag_ + imag_*b.real_; real_ = r; return *this; } template< typename U > typename std::enable_if< std::is_convertible< U, T >::value, basic_complex >::type& operator*=( U b ) { real_ *= b; imag_ *= b; return *this; } template< typename E, typename Traits > friend std::basic_ostream< E, Traits >& operator<<( std::basic_ostream< E, Traits >& out, const basic_complex& c ) { return out << "(" << c.real_ << ";" << c.imag_ << "i)"; } private: T real_, imag_; }; template< typename T > basic_complex< T > operator*( basic_complex< T > a, const basic_complex< T >& b ) { return a *= b; } template< typename T, typename U > typename std::enable_if< std::is_convertible< U, T >::value, basic_complex< T > >::type operator*( basic_complex< T > a, U b ) { return a *= T(b); } template< typename T, typename U > typename std::enable_if< std::is_convertible< U, T >::value, basic_complex< T > >::type operator*( U a, basic_complex< T > b ) { return b *= T(a); } typedef basic_complex< double > Complex; int main() { using namespace std; Complex c2(0.1,-5.1); // -- Multiplikation mit beliebigen nach double kovertierbaren Typen ist möglich c2 *= char(9); cout << short(3)*c2*-2 << "; " << 3.*c2*unsigned(2) << endl; return 0; }
sieht jemand irgendwelche Pferdefüße?
fehlt noch ein
basic_complex<??> operator*( basic_complex<T1> a, basic_complex<T2> b )
Fällt jemanden dazu was ein?
Gruß
Werner
-
Werner Salomon schrieb:
Eine Lösung, die mit C++11 möglich wird, sähe so aus, den Typ des Skalars als eigenen von T unabhängigen Typ U zu deklarieren und gleichzeitig mit
std::enable_if<std::is_convertible<U,T>,..>
zu beschränken um Kollisionen mit anderen Operatoren* zu vermeiden.Wahrscheinlich die richtige Lösung, falls beliebig genaue Typen im Spiel sind (in diesem Falle wird man den exakten Typ solange wie möglich behalten wollen, um ggf. von entsprechnd spezialisierten Routinen profitieren zu können. Für Allerweltszwecke dürfte eine Variante der Form
template <typename T> struct identity { using type = T; }; template <typename T> complex<T> operator+(typename identity<T>::type a, complex<T> b);
besser sein, weil gleich mit der richtigen Genauigkeit gerechnet wird und insgesamt weniger Spezialisierungen existieren, was ggf. die Codebloat vorbeugt. Die Konvertierung wird hier bereits vor dem Aufruf durchgeführt und nicht erst bei der eigentlichen Berechnung. Hat nebenbei den angenehmen Effekt, dass Warnungen wegen Genauigkeitsverlusten auch gleich an der richtigen Stelle auftauchen und nicht irgendwo im Librarycode.
Werner Salomon schrieb:
fehlt noch ein
basic_complex<??> operator*( basic_complex<T1> a, basic_complex<T2> b )
Fällt jemanden dazu was ein?
sollte wahrscheinlich common_type<T,U> sein. Meiner Ansicht nach aber nicht sinnvoll, praktischerweise wird man solche Konvertierungen eher explizit ausführen wollen. Man muss ja schließlich nicht jeden Designfehler aus C mitmachen.
-
camper schrieb:
Für Allerweltszwecke dürfte eine Variante der Form
template <typename T> struct identity { using type = T; }; template <typename T> complex<T> operator+(typename identity<T>::type a, complex<T> b);
besser sein, ...
Hallo camper,
auf so was muss man erst mal kommen
Wobei ichtemplate <typename T> struct identity { using type = T; };
nicht verstehe (übersetzt auch nicht im MS VS10). Ich habe es geändert nach
template <typename T> struct identity { typedef T type; };
dann funktioniert es.
camper schrieb:
Werner Salomon schrieb:
fehlt noch ein
basic_complex<??> operator*( basic_complex<T1> a, basic_complex<T2> b )
Fällt jemanden dazu was ein?
sollte wahrscheinlich common_type<T,U> sein. Meiner Ansicht nach aber nicht sinnvoll, praktischerweise wird man solche Konvertierungen eher explizit ausführen wollen. Man muss ja schließlich nicht jeden Designfehler aus C mitmachen.
common_type
war mit bisher entgangen, obwohl ich es im wahren Leben schon mal gebraucht hätte. Dein Einwand ist sicher berechtigt, obwohl genau diese Konstruktion das Beispiel für common_type in boost ist.Nochmal Danke & Gruß
Werner
-
Ich habe das Beispiel von camper nicht ganz verstanden. Was ist denn der Unterschied zwischen
template <typename T> struct identity { using type = T; }; template <typename T> complex<T> operator+(typename identity<T>::type a, complex<T> b);
und
template <typename T> complex<T> operator+(T a, complex<T> b);
Ich erinnere mich zwar daran, eine solche Konstruktion im Zusammenhang mit TMP schon mal gesehen zu haben, konnte jetzt aber durch kurzes googlen keine Erläuterung dafür finden. Vielleicht könnte es jemand kurz erklären.
-
ananas schrieb:
Ich habe das Beispiel von camper nicht ganz verstanden. Was ist denn der Unterschied zwischen
template <typename T> struct identity { using type = T; }; template <typename T> complex<T> operator+(typename identity<T>::type a, complex<T> b);
und
template <typename T> complex<T> operator+(T a, complex<T> b);
Ich erinnere mich zwar daran, eine solche Konstruktion im Zusammenhang mit TMP schon mal gesehen zu haben, konnte jetzt aber durch kurzes googlen keine Erläuterung dafür finden. Vielleicht könnte es jemand kurz erklären.
In
operator+(T a, complex<T> b);
stellt jeder Parameter einer Kontext dar, aus dem das Templateargument beim Funktionsaufruf deduziert werden kann. Stimmt das so deduzierte Templateargument für diese beiden Parameter aber nicht überein, schlägt die Argumentdeduktion insgesamt fehl, und die Überladung wird nicht weiter beachtet (sfinae). Beispiel
float a; complex<double> b; a+b; // deduziert T=float aus a, T=double aus b
In
operator+(typename identity<T>::type a, complex<T> b);
kann dagegen das erste Funktionsargument nicht dazu benutzt werden, das Templateargument zu ermitteln (ein verschachtelter (nested) Bezeichner eines abhängigen Namensraumes stellt generell einen nicht deduzierbaren Kontext dar).
T kann und wird also nur aus dem zweiten Funktionsargument ermittelt werden, sofern diese nicht fehlschlägt, kann es nicht zu Widersprüchen kommen. Während nun generell implizite Konvertierungen für Funktionsparameter, die bei der Templateargumentation teilnehmen, stark eingeschränkt sind, gilt das nicht für alle anderen Funktionsparameter. Da der erste Funktionsparameter nicht zur Deduktion herangezogen wird, sind hier also alle impliziten Konvertierungen einschließlich nutzerdefinierter Konvertierungen beim Aufruf möglich.Man kann das Ganze auch einfacher ohne Templatefunktionen haben
template <typename T> class complex { ... friend complex operator*(const complex& a, const complex& b) { ... } // optional auch (T,complex) bzw. (complex,T), wenn das effizienter ist };
Nachteilig dabei, dass die Funktion nicht im umliegenden Namensraum sichtbar wird (kann nur über ADNL gefunden werden). Damit kann die Funktion nicht mehr direkt z.B. als Funktor genutzt werden. Ein wesentliches Problem sehe ist darin allerdings nicht, man könnte ja z.B. auf std::multiplies ausweichen.
-
Vielen Dank an camper für die ausführliche Erklärung.
-
Werner Salomon schrieb:
camper schrieb:
Für Allerweltszwecke dürfte eine Variante der Form
template <typename T> struct identity { using type = T; }; template <typename T> complex<T> operator+(typename identity<T>::type a, complex<T> b);
besser sein, ...
Hallo camper,
auf so was muss man erst mal kommen
inline friends bieten sich da auch an. Damit spart man dann auch
typename identity<T>::type
.ich weiß aber nicht, ob sich der Aufwand für eine Übungs-/Hausaufgabe da jetzt lohnt. Würde auch tippen, dass die Multiplikation mit und die Addition von Null recht flott geht. Aber selbst für so einen Test wäre ich zu faul.