原文
http://www.gotw.ca/gotw/004.htm难度:7.5/10 (好高难度..怕怕)
你书写类的熟练程度怎么样?这一节关注的不只是明显的错误,还有更专业的风格。
问题:
你又在做code review。码农写了这么一段类的代码,里边有很多错误和很差的风格。你能找出多少错误并改正?
class Complex {
public:
Complex( double real, double imaginary = 0 )
: _real(real), _imaginary(imaginary) {};
void operator+ ( Complex other ) {
_real = _real + other._real;
_imaginary = _imaginary + other._imaginary;
}
void operator<<( ostream os ) {
os << "(" << _real << "," << _imaginary << ")";
}
Complex operator++() {
++_real;
return *this;
}
Complex operator++( int ) {
Complex temp = *this;
++_real;
return temp;
}
private:
double _real, _imaginary;
}; (我找到4个错误。不知道对不对。感觉自己好菜啊。。看看答案吧)
答案:
序:(答案都有序,估计又是茫茫多的错啊。。 我好菜。。)
这个类里有非常多的错。。。本题的重点在类的机制。(比如:operator<<的标准形式是什么?operator+能作成员吗?)而不是糟糕的设计。不过,我们还是从一个很有用的批评开始吧。
0.为什么要写一个复数类?标准库里不是有现成的complex容器了吗?!(而且,顺带说一句,那里没有我们下面将要揭露的任何问题,凝聚了最牛叉的大神们数年心血的精华,为什么不用它?撒泡尿照照你自己吧!)
[指导意见]:重用标准库算法,别再造些糟透了的轮子了。更快更简单更安全!!
class Complex {
public:
Complex( double real, double imaginary = 0 )
: _real(real), _imaginary(imaginary) {}; 1.风格:
由于第二个参数有默认值,所以可以用作一个单参数构造函数,所以会出现隐式的转换。而很可能本意不是这样的。(容易造成double被隐式转换成Complex)
[指导意见]:留神隐式转换。用显式(explicit)定义的构造函数就没问题了。
void operator+ ( Complex other ) {
_real = _real + other._real;
_imaginary = _imaginary + other._imaginary;
} 2.风格:
参数应该为const Complex & other,而a = a+b应为a+=b。
[规则]:传引用,别传值。
[指导意见]:算术运算时这样更好,用a op=b代替a = a op b。视情况定。(毕竟有的类不允许这个操作op=)
3.风格:
operator+不应该作成员函数。而在本题的实现之下,你只能写a=b+1,不能写a=1+b。(1不是class Complex)出于效率的考虑,你可能想提供operator+(Complex, int)和operator+(int, Complex)。
[规则]:遵守这几条建议足矣。一元操作符作成员。= [] () 和->必须作成员。+= -= /= *=等作成员。其他二元操作符都不作成员。(Lakos96: 143-144; 591-595; Murray93: 47-49)
4.错误:operator+不应该改变对象的值。应该返回一个临时对象,临时对象含有相加的和。注意这里的返回值类型应为const Complex(不是Complex),防止出现a+b=c的形式。
(事实上,这道题里的operator+更像是operator+=)
5.风格:如果你定义了运算符op,那你还应该定义op=。这里定义了operator+,那就应该同时定义operator+=。
void operator<<( ostream os ) {
os << "(" << _real << "," << _imaginary << ")";
} (注意:如果重载了<<,那同时也应该处理常用的格式符。)
6.错误:第3条讲了,operator<<不应该作成员,参数也应该是(ostream &, const Complex &)。而且,最好也不要把它作友元。还有,它应该调用一个公共的成员函数来做输出用。
Complex operator++() {
++_real;
return *this;
} 7.错误:这个函数应该返回ostream &,而且应该以return os结束,这样可以实现链式表达式,就像cout<<a<<b。
8.风格:前缀++应该返回引用Complex &。更符合直觉。
Complex operator++( int ) {
Complex temp = *this;
++_real;
return temp;
} 9.风格:后缀++应该返回const Complex,防止a++++,把别人搞晕。
10.[指导意见]:用前缀操作来实现后缀,更好。
private:
double _real, _imaginary;
}; 11.风格:尽量不要用下划线开头来命名。是,我以前习惯这么干,连《设计模式》这种牛书里面都这么写。因为C++标准里很多地方用到了下划线开头的标识符,所以尽量避免这么写。(我已经不用下划线开头去命名了,我改用下划线结尾了。。。。。)
给个改过的代码:
class Complex {
public:
explicit Complex( double real, double imaginary = 0 )
: real_(real), imaginary_(imaginary) {} //显示的构造函数
Complex& operator+=( const Complex& other ) {
real_ += other.real_;
imaginary_ += other.imaginary_;
return *this;
} //注意输入参数个数和类型,返回类型,和+=
Complex& operator++() {
++real_;
return *this;
} //返回类型
const Complex operator++( int ) {
Complex temp = *this;
++(*this);
return temp;
} //返回类型,为什么输入int?
ostream& print( ostream& os ) const {
return os << "(" << real_
<< "," << imaginary_ << ")";
} //公共public,返回ostream &
private:
double real_, imaginary_;
friend ostream&
operator<<( ostream& os, const Complex& c );
};
const Complex operator+( const Complex& lhs,
const Complex& rhs ) {
Complex ret( lhs );
ret += rhs;
return ret;
} //注意输入类型,返回类型,非成员
ostream& operator<<( ostream& os,
const Complex& c ) {
return c.print(os);
} //友元,调用公共print
写完,睡觉。