Ces deux opérateurs ont des problèmes:
class Complex {
// ...
Complex operator*(double m, const Complex & c)
{return c * m;}
ostream & operator<<(ostream & os, Complex & c)
{os << c.real <<"," << c.imaginary; return os;}
// ...
};
Avant toute chose: Ce operator<<
n'est pas censé changer les sorties complexes, ce qui devrait être const
. (Sinon, vous ne pouvez pas générer d'objets temporaires, car ils ne peuvent pas être liés à des références non const.)
Comme ce sont des fonctions membres non statiques, ils ont un paramètre this
implicite. Avec cela, ils ont trois paramètres. Cependant, les deux sont des opérateurs binaires. Puisque vous ne pouvez pas les rendre statiques (c'est juste parce que les règles le disent), vous devez les implémenter en tant que fonctions libres.Cependant, comme ils sont mis en œuvre, ils ont besoin d'un accès aux membres privés, donc vous devez les faire des amis de votre classe:
class Complex {
// ...
friend Complex operator*(double m, const Complex & c);
friend ostream & operator<<(ostream & os, const Complex & c);
// ...
};
Complex operator*(double m, const Complex & c)
{return c * m;}
ostream & operator<<(ostream & os, const Complex & c)
{os << c.real <<"," << c.imaginary; return os;}
Sur sidenote, il est possible de les mettre en œuvre en ligne au moment de la déclaration d'ami , ce qui vous ramène presque à votre version originale:
// note the friend
class Complex {
// ...
friend Complex operator*(double m, const Complex & c)
{return c * m;}
friend ostream & operator<<(ostream & os, const Complex & c)
{os << c.real <<"," << c.imaginary; return os;}
// ...
};
Cependant, si vous implémentez un type mathématique concrète, les utilisateurs de votre type s'attendront à toutes les opérations communes pour ces types de travailler avec elle. Autrement dit, ils s'attendent, par exemple, à ce que c*=r
fonctionne simplement. Donc, vous aurez besoin de surcharger operator*=
, aussi. Mais cet opérateur fait presque la même chose que operator*
, donc ce serait une bonne idée d'implémenter l'un sur l'autre. Un idiome commun consiste à implémenter *=
(et +=
etc.) en tant que fonctions membres (puisqu'elles changent leur argument gauche, c'est une bonne idée pour elles d'avoir accès à ses données privées) et operator*
en tant que non-membre en plus de cela. (Habituellement, c'est plus efficace que l'inverse.):
// note the friend
class Complex {
// ...
Complex& operator*=(double rhs)
{return /* whatever */;}
friend ostream & operator<<(ostream & os, const Complex & c)
{os << c.real <<"," << c.imaginary; return os;}
// ...
};
inline Complex operator*(Complex lhs, double rhs) // note: lhs passed per copy
{return lhs*=rhs;}
inline Complex operator*(double lhs, const Complex& rhs)
{return rhs*lhs;}
IMO c'est la meilleure solution.
J'ai. Cependant, un peu plus de choses à dire:
La façon dont vous avez implémenté votre multiplication est inefficace:
Complex Complex::operator*(const Complex & c) const
{
Complex mult;
mult.imaginary = imaginary * c.imaginary;
mult.real = real * c.real;
return mult;
}
Quand vous dites Complex mult;
, vous appelez le constructeur par défaut de votre classe, initialisant les parties réelles et imaginaires à 0
. La prochaine chose que vous faites est d'écraser cette valeur. Pourquoi ne pas le faire en une seule étape:
Complex Complex::operator*(const Complex & c) const
{
Complex mult(real * c.real, imaginary * c.imaginary);
return mult;
}
ou même plus concis
Complex Complex::operator*(const Complex & c) const
{
return Complex(real * c.real, imaginary * c.imaginary);
}
Bien sûr, il est juste deux missions par multiplication. Mais alors - vous ne voudriez pas avoir cela dans une boucle interne de votre pilote graphique.
En outre, vos constructeurs ne sont pas implémentés comme il devrait être (TM). Pour l'initialisation des données membres, vous devez utiliser la liste initialiseur:
Complex::Complex()
: real(), imaginary()
{
}
Complex::Complex(double r, double i)
: real(r), imaginary(i)
{
}
Alors qu'il ne fait aucune différence pour les types intégrés comme double
, il ne fait pas mal non plus et il est bon de ne pas entrer dans la habitude. Avec les types définis par l'utilisateur (un nom un peu malheureux, puisque c'est pour tous les non-intégrés, même des types comme std::string
, qui n'est pas défini par les utilisateurs) qui ont un constructeur par défaut non trivial, cela fait la différence: c'est beaucoup moins efficace.
La raison est que, lorsque l'exécution passe ce {
initial, C++ garantit que vos objets de membre de données sont accessibles et utilisables. Pour cela, ils doivent être construits, parce que la construction est ce qui transforme la mémoire brute en objets. Ainsi, même si vous n'appelez pas explicitement un constructeur, le système d'exécution appellera toujours les constructeurs par défaut. Si la prochaine chose que vous faites est de remplacer les valeurs construites par défaut, vous perdez à nouveau des cycles CPU.
Enfin, ce constructeur Complex(double r, double i = 0)
sert d'opérateur de conversion implicite.Si, par exemple, vous vouliez appeler f(real)
, mais avez oublié de l'inclure, mais qu'il existe un f(const complex&)
dans la portée, le compilateur exerce son droit d'effectuer une conversion définie par l'utilisateur et votre appel f(4.2)
devient f(Complex(4.2))
et la mauvaise fonction est appelé silencieusement. C'est très dangereux.
Pour éviter cela, vous devez marquer tous les constructeurs explicit
que l'on pourrait appeler avec un seul argument:
class Complex {
// ...
explicit Complex(double r, double i = 0)
// ...
};
La réponse de l'OSMŒ est correcte, je voulais juste faire des commentaires sur votre multiplication complexe. '(a + bi) * (c + di)' n'est pas égal '(ac + bdi)', mais '((ac-bd) + (ad + bc) i)' –