Effective C++笔记3

来源:互联网 发布:淘宝大v达人申请 编辑:程序博客网 时间:2024/06/05 08:47

【条款19】分清成员函数,非成员函数和友元函数
1.operator*运算符
class rational {
public:
  ...
  const rational operator*(const rational& rhs) const;
};

rational oneeighth(1, 8);
rational onehalf(1, 2);
rational result = onehalf * oneeighth;   // 运行良好
result = result * oneeighth;             // 运行良好
result = onehalf * 2;         // 运行良好
result = 2 * onehalf;         // 出错!
对象onehalf是一个包含operator*函数的类的实例,所以编译器调用了那个函数。而整数2没有相应的类,所以没有operator*成员函数。编译器还会去搜索一个可以象下面这样调用的非成员的operator*函数(即,在某个可见的名字空间里的operator*函数或全局的operator*函数):
result = operator*(2, onehalf);      // 错误!
但没有这样一个参数为int和rational的非成员operator*函数,所以搜索失败。

result = onehalf * 2之所以成功,秘密在于隐式类型转换。调用参数为int的构造函数,把2转换成了const rational对象。它只对函数参数表中列出的参数进行转换,决不会对成员函数所在的对象(即,成员函数中的*this指针所对应的对象)进行转换。所以,result = 2 * onehalf不能工作。

乘法需要满足交换律,所以要么onehalf * 2和2 * onehalf都能运行,要么都不能运行。如果希望能运行,operator*则不得不作为非成员函数。
const rational operator*(const rational& lhs,
                         const rational& rhs)
{
  return rational(lhs.numerator() * rhs.numerator(),
                  lhs.denominator() * rhs.denominator());
}
至于是否要作为友元函数,则看这个函数是否需要访问rational的非公有成员。


2.operator>>和operator<<运算符
如果作为成员函数:
class string {
public:
  string(const char *value);
  ...
  istream& operator>>(istream& input);
  ostream& operator<<(ostream& output);

private:
  char *data;
};
则需要这样用:s>>cin; s<<cout;虽然合法,但有违常规。所以不能作为成员函数。
istream& operator>>(istream& input, string& string)
{
  delete [] string.data;

  read from input into some memory, and make string.data
  point to it

  return input;
}

ostream& operator<<(ostream& output,
                    const string& string)
{
  return output << string.data;
}
因为这两个运算符需要访问string的私有成员,所以需要作为string的友元函数。

本条款得出的结论如下。假设f是想正确声明的函数,c是和它相关的类:
·虚函数必须是成员函数。如果f必须是虚函数,就让它成为c的成员函数。
·operator>>和operator<<决不能是成员函数。如果f是operator>>或operator<<,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
·只有非成员函数对最左边的参数进行类型转换。如果f需要对最左边的参数进行类型转换,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
·其它情况下都声明为成员函数。如果以上情况都不是,让f成为c的成员函数。

【条款20】避免public接口出现数据成员
避免public数据成员,至少有两个好处:
1.安全。通过函数访问数据成员。可以在函数中对数据的访问进行控制;
2.避免用户依赖于实现细节。只要接口不变,实现细节可以改变而不影响用户。

 

【条款21】尽可能使用const
如果希望某个值不被改变,不要希望其他程序员能够自觉地遵守,而应当依赖于编译器的约束。方法就是使用const。
1.const在*左边,指针指向的内容不可修改;const在*右边,指针不可修改。
2.const函数。const放在括号后边,指定这个函数只可以由const对象调用。在这个函数中,不能修改成员变量(mutable和static修饰的除外)。
3.重载函数,可以通过不同的参数来区分,也可以通过const来区分。
char& operator[](int position)
  { return data[position]; }

  // 用于const对象的operator[]
  const char& operator[](int position) const
  { return data[position]; }

private:
  char *data;
};

string s1 = "hello";
cout << s1[0];                  // 调用非const
                                // string::operator[]
const string s2 = "world";
cout << s2[0];                  // 调用const
                                // string::operator[]

 

【条款22】尽量使用“传引用”而不用“传值”
传值起码有两个缺点:
1.增加了函数调用,开销大。
student returnstudent(student s) { return s; }
student plato;
returnstudent(plato);
 这里,会先调用student的拷贝构造函数,以plato构造s;在returnstudent中,调用student的拷贝构造函数,以s构造函数返回值临时对象;退出returnstudent函数时,调用s的析构函数;调用返回值对象的析构函数。如果student有基类或成员变量,调用的构造函数和析构函数会更多。 为了避免函数调用的开销,可以使用引用。

2.按值传递会引起“切割问题”。当一个派生类的对象作为基类对象被传递时,派生类所具有的特性会被“切割”掉。
class Base
{
public:
 virtual void f()
 {
  cout<<"Base::f()"<<endl;
 }
};

class A : public Base
{
 virtual void f()
 {
  cout<<"A::f()"<<endl;
 }
};

void g(Base b)
{
 b.f();
}

A a;
g(a);
这里会打印“Base::f()”。实际上,在调用g时,是用a构造了一个Base对象b,b没有任何的A特征,在拷贝时都被切割掉了。
如果希望能打印"A::f()",可以这样:
void g(Base& b)
{
 b.f();
}

A a;
g(a);

 

【条款23】必须返回一个对象时不要试图返回一个引用
错误方法1:
inline const rational& operator*(const rational& lhs,
                                 const rational& rhs)
{
  rational result(lhs.n * rhs.n, lhs.d * rhs.d);
  return result;
}
返回了局部变量result的引用,不幸的是退出operator*后,result就会被析构,引用无效了。

错误方法2:
inline const rational& operator*(const rational& lhs,
                                 const rational& rhs)
{
  rational *result =
    new rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}
问题是,谁来delete result。要delete,就要记住result指针值,但有时候并不会去记住它。例如:
rational w, x, y, z;
w = x * y * z;
两个operator*产生的result,都没有被记住,也就没法delete,造成了内存泄露。

 

【条款24】在函数重载和设定参数缺省值间慎重选择
第一,确实有那么一个值可以作为缺省吗?第二,要用到多少种算法?一般来说,如果可以选择一个合适的缺省值并且只是用到一种算法,就使用缺省参数(参见条款38)。否则,就使用函数重载。

原创粉丝点击