明明白白c++ effective c++ 条目11-15

来源:互联网 发布:小学生电脑画图软件 编辑:程序博客网 时间:2024/06/05 10:06

effective c++的下载地址

http://download.csdn.net/detail/mlkiller/5335383


条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符

这个条款的原因在哪里呢?
就是如果你创建一个类,什么都不做,那么类会给你创建一个默认构造函数,默认析构函数,默认拷贝函数和默认赋值函数。

所以出问题就出在默认上面去了,尤其是默认拷贝函数和默认赋值函数出的问题最多。
默认拷贝函数会怎么做呢,对于a=b,它会将b中的成员逐位拷贝给另一个a,如果通过指针动态分配内存,则仅仅将指针的值赋给a。
这会导致至少两个问题:
第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。
看下面代码:

#include <iostream>#include <stdlib.h>#include <string.h>using namespace std;class MyString{public:      MyString(const char* value);  ~MyString();      friend ostream& operator << (ostream& os,MyString &c);private:  char *data;};MyString::MyString(const char* value){if(value){data = new char[strlen(value)+1];strcpy(data,value);}else{data = new char[1];data = '\0';}}ostream& operator << (ostream& os,MyString &c){os<<c.data;return os;}MyString::~MyString(){delete []data;}int main(){MyString a("hello");{    MyString b("world");            b  = a;}MyString c = a;cout<<c<<endl;}

看到输出的结果是这样的
▒▒#a▒▒#a
Aborted (core dumped)
可以看到c得不到正确的值,同时析构函数调用的时候出现崩溃的问题。

所以,如果你会调用拷贝构造函数和赋值函数,那么一定要显示的声明他们,否则将他们定义为私有的。

那么下面就看看如何显示的声明他们,如下
MyString::MyString(){data = NULL;}MyString::MyString(MyString &myString){if (this == &myString){return;}else{if(myString.data != NULL){data = new char[strlen(myString.data)];strcpy(data,myString.data);}else{data = new char[1];data = '\0';}}}MyString & MyString::operator= (const MyString &myString){if (this == &myString){return *this;}else{delete []data;if(myString.data != NULL){data = new char[strlen(myString.data)];strcpy(data,myString.data);}else{data = new char[1];data = '\0';}}return *this;}

这里需要注意几点:
1 MyString c = a; 实际上调用的是MyString::MyString(MyString &myString), 而不是重载 =号的函数。
   你可以用vs或者gdb来单步调试。原因在于,这种形式是初始化,而不是赋值。
2 下面两个才是赋值 
             MyString b("world");
     MyString c;
     b  = a;
     c = a;
     这个时候,你需要先删除之前的data数据,考虑到有的时候参数为0,所以就定义一个参数为0的构造函数,或者将上面的构造函数写成MyString::MyString(const char* value = NULL)的缺省参数的形式。
3 拷贝构造函数的时候,不需要删除data,因为拷贝构造函数的时候,this->data肯定没有申请空间,删除会引起错误。
4 上面代码其实有二个错误,就是应该申请的空间为strlen(data)+1,最后一位'\0';
   我在cygwin 下面运行是没问题的,估计是侥幸,但是 vs里面运行就会崩溃。

另外,大家可以练习怎么写这个函数,面试经常会问到的。真正的理解,需要一个过程,主要很多细节:要判断是否相等,要注意什么时候可以删除data,什么时候不可以, 要注意new [] 和delete[] 的对应。

条款12: 尽量使用初始化而不要在构造函数里赋值

书上介绍的很清楚了,我这里主要总结一下、
初始化的好处:
1 const 和引用必须使用初始化,而不能用赋值。
2 效率高。
     初始化的流程,就一步,将类成员初始化。
     而构造函数内赋值则有两步:第一,调用默认构造函数,第二,调用赋值构造函数。
    类的构造函数的本质上是函数,函数就有形参和实参。
   例如
 
   class A   {    pulic:          A(B &bInput);   private:          B b;  }  A::A(B &bInput)  {       b = bInput;}

    它的流程是什么呢?先调用B()生成b,然后在调用operator = 赋值函数,效率当然无法保证了。这也是第一条的原因,如果是 const或者引用则等于是声明了一下,而没有初始化,那么编译的时候肯定报错。

条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同

举个简单例子:
#include <iostream>using namespace std;class A{public:A(int value);void print();private:int i;int j;};A::A(int value):j(value),i(j){}void A::print(){cout<<i<<" "<<j<<endl;}int main(){   A a(10);a.print();}

这个输出的结果是什么?
有些人以为是10 10
我电脑上的结果是
2281060 10
第一个数是随机数,因为执行的顺序是j(i) 然后才是j(10)

条款14: 确定基类有虚析构函数

我之前有篇文章专门将这个,大家可以看看
http://blog.csdn.net/mlkiller/article/details/8884321   
这里就不展开了。

条款15: 让operator=返回*this的引用

刚才在写前面的例子,重载符号<<和=的时候,我还想返回值怎么去写。
<<符号是和=都是二元操作符,但是后面跟的参数不一样,<<跟了两个参数,流对象和操作对象,它最后返回流对象。
而=只有自己,它的返回值呢应该是这个对象本身,我就在纠结&引用怎么返回,它和this指针之间什么关系。
等知道答案的时候,还是有些疑惑*this和&引用相等么?看个例子

int p = 1;int *q = &p;int &t = p;int &s = *q;

你看到引用本身怎么用指针初始化。

关于文中返回const类型,我没有搞得太清楚,时间比较晚了,以后弄明白在写进来。
原创粉丝点击