effective C++ 构造 析构 赋值

 

 

    转载 http://www.2cto.com/kf/201305/209871.html

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

    这个条款的原因在哪里呢?

    就是如果你创建一个类,什么都不做,那么类会给你创建一个默认构造函数,默认析构函数,默认拷贝函数和默认赋值函数。

 

 

    所以出问题就出在默认上面去了,尤其是默认拷贝函数和默认赋值函数出的问题最多。

    默认拷贝函数会怎么做呢,对于a=b,它会将b中的成员逐位拷贝给另一个a,如果通过指针动态分配内存,则仅仅将指针的值赋给a。

    这会导致至少两个问题:

    第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。

 

    看下面代码:

 

 

    [cpp]

    #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;

    

    }

 

    #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得不到正确的值,同时析构函数调用的时候出现崩溃的问题。

 

 

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

 

 

    那么下面就看看如何显示的声明他们,如下

    [cpp]

    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;

    }

 

    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 效率高。

         初始化的流程,就一步,将类成员初始化。

         而构造函数内赋值则有两步:第一,调用默认构造函数,第二,调用赋值构造函数。

        类的构造函数的本质上是函数,函数就有形参和实参。

       例如

     [cpp]

       class A

       { 

       pulic:

              A(B &bInput);

       private:

              B b;

      }

      A::A(B &bInput)

      {

           b = bInput;

    }

 

       class A

       {

       pulic:

              A(B &bInput);

       private:

              B b;

      }

      A::A(B &bInput)

      {

           b = bInput;

    }

 

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

 

 

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

    举个简单例子:

    [cpp]

    #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();

    }

 

    #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和&引用相等么?看个例子

 

 

    [cpp]

    int p = 1;

    int *q = &p;

    int &t = p;

    int &s = *q;

 

    int p = 1;

    int *q = &p;

    int &t = p;

    int &s = *q;

 

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

 

 

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

posted @ 2014-03-11 00:37  snker_tan  阅读(73)  评论(0编辑  收藏  举报