学习构造函数、拷贝构造函数、析构函数和重载运算符

练习代码:

  1 #include <stdlib.h>
  2 #include <string>
  3 
  4 class Something
  5 {
  6 private:
  7     char* name;
  8     int weight;
  9 public:
 10     Something(){
 11         printf("调用了无参构造函数!\n");
 12         weight = 0; 
 13         name = NULL;
 14     }
 15     Something(int w, const char* str = NULL)
 16     {        
 17         printf("调用了带参构造函数, name=%s, weight=%d!\n", str, w);
 18 //        Something();                    // 调用上一级构造函数,初始化weight,name等变量,疑问:这一步没有起到效果,似乎不能这么调用,出现了name没有被初始化的错误
 19         weight = w;
 20         // 这里不需要提前释放name空间,因为name刚刚被构造
 21 
 22         if (str)
 23         {
 24             // 注意,此处必须 strlen(src)+1 ,因为还有足够的空间放下'\0'字符
 25             // 如果没有+1,会出现不可预期的结果,甚至访问越界
 26             int len = strlen(str)+1;
 27             name = (char*)malloc(len);
 28             memcpy(name, str, len);
 29         }else
 30             name = NULL;
 31     }
 32     // 拷贝构造函数
 33     // 拷贝构造函数往往会在传参或返回的时候被调用:
 34     // 例如void func(Something s){},在构造s的时候,会调用拷贝构造函数
 35     // 例如Something func(){return *this;},在构造返回值时,也会调用拷贝构造函数
 36     // 在声明对象时:Something s = something;
 37     // 在声明对象时:Something s(something);
 38     Something(const Something& s)
 39     {
 40         printf("调用了拷贝构造函数, name=%s, weight=%d!\n", s.name, s.weight);
 41 //        Something(s.weight+1, s.name);    // 调用上一级构造函数,疑问:这一步没有起到效果,似乎不能这么调用
 42         weight = s.weight+1;
 43         // 这里不需要提前释放name空间,因为name刚刚被构造
 44 
 45         if (s.name)                        
 46         {
 47             // 注意,此处必须 strlen(src)+1 ,因为还有足够的空间放下'\0'字符
 48             // 如果没有+1,会出现不可预期的结果,甚至访问越界
 49             int len = strlen(s.name)+1;
 50             name = (char*)malloc(len);
 51             memcpy(name, s.name, len);
 52         }else
 53             name = NULL;                
 54     }
 55     ~Something()
 56     {
 57         if (name)
 58         {
 59             printf("调用了析构函数, name=%s, weight=%d!\n", name, weight);
 60             free(name);
 61         }else
 62             printf("调用了析构函数, name=(null), weight=%d!\n", weight);
 63     }
 64 
 65     // 非const:自己可以被修改,例如 (a = b) = c; 这种操作有效,结果是对a赋予c的值
 66     // 返回引用,避免重复构造对象,同时,自身可被修改
 67     // 传入的参数最好是引用类型,否则会调用拷贝构造函数,没必要
 68     // 赋值运算不会在声明对象的时候调用,声明对象的时候会调用拷贝构造函数,而不是赋值运算符
 69     // 因此左值都是已经初始化过的对象,在这里,其name一定是初始化过的,因此,有必要释放name所指内存
 70     //Something& operator= (const Something& s)
 71     Something& operator= (const Something& s)
 72     {
 73         printf("调用了赋值运算函数, name=%s!\n", s.name);
 74         weight = s.weight;
 75         if (!name)
 76         {
 77             free(name);            // 有必要释放name所指内存
 78             name = NULL;
 79         }
 80         if(s.name)
 81         {
 82             // 注意,此处必须 strlen(src)+1 ,因为还有足够的空间放下'\0'字符
 83             // 如果没有+1,会出现不可预期的结果,甚至访问越界
 84             int len = strlen(s.name)+1;
 85             name = (char*)malloc(len);
 86             memcpy(name, s.name, len);
 87         }
 88 
 89         return *this;
 90     }
 91     // 重载前置++运算符,完成:++Something
 92     Something& operator++ ()
 93     {
 94         ++weight;
 95         return *this;
 96     }
 97 
 98     // 重置后置++运算符,完成:Something++
 99     Something operator++(int i)
100     {
101         Something tmp = *this;    // 调用拷贝构造函数初始化tmp
102         ++*this;
103         return tmp;                // 调用拷贝构造函数初始化返回值对象,随后析构tmp
104     }
105 
106     // b + c运算的返回值为const类型,可以避免b + c = a这种无效赋值操作
107     // 由于返回的是临时对象,所以返回值不能是引用,否则会出现指向无效栈空间的BUG,导致不可预期的结果
108     Something operator+ (const Something& s)
109     {
110         // 此处返回临时对象,且返回值类型不是引用,所以会调用构造函数
111         //Something tmp(weight+s.weight);        // 构造tmp
112         //return tmp;                            // 拷贝构造返回对象,语句结束时析构tmp
113         return Something(weight+s.weight);
114     }
115         
116     void setName(const char* str)
117     {
118         if(!name)
119         {
120             free(name);
121             name = NULL;
122         }
123         if(str)
124         {
125             // 注意,此处必须 strlen(src)+1 ,因为还有足够的空间放下'\0'字符
126             // 如果没有+1,会出现不可预期的结果,甚至访问越界
127             int len = strlen(str)+1;
128             name = (char*)malloc(len);
129             memcpy(name, str, len);
130         }
131     }
132     void toPrint()
133     {
134         printf("name=%s, weight = %d\n", name, weight);
135     }
136 };
137 
138 Something foo(Something s)            // 因为参数不是引用类型,所以此处会调用拷贝构造函数
139 {
140     s.toPrint();
141     s.setName("s6");
142     return s;                        // 此处会调用s的拷贝构造,产生一个返回对象,然后调用s的析构函数
143 }
144 
145 int main()
146 {
147 
148     Something s1(1, "s1"), s2(2, "s2");
149     s1.toPrint();
150     s2.toPrint();
151     s1+s2;                        // 构造返回的对象,本语句结束时析构返回的对象
152     Something s3 = s1+s2;        // 为何此处没有调用拷贝构造函数?也没有调用赋值运算,也没调用析构函数析构返回对象,仅仅调用一个构造函数
153                                 // 一般的声明赋初值操作会调用拷贝构造函数,这里没有调用拷贝构造函数
154                                 // 一般的函数,返回的对象会被析构,这里没有析构
155                                 // 暂认为是编译器在此做了优化,直接构造了s3,相当于优化成了 Something s3(s1.weight+s2.weight);
156     
157     s3.setName("s3");
158     s3.toPrint();
159     Something s4 = s3;            // 此处会调用拷贝构造函数
160     s4.setName("s4");
161     s4.toPrint();
162     Something s5(s4);            // 此处会调用拷贝构造函数
163     (s5 = s4) = s1;                // 此处会调用两次赋值运算符,略奇葩的赋值,合法,但是没啥特殊意义,等同于 s5 = s1
164     s5.setName("s5");
165     s5.toPrint();
166     Something s6 = foo(s5);        // 函数返回一个Something对象,调用拷贝构造函数,表达式结束后,返回的对象被析构
167     s6++;                        // 返回值都是非引用类型,操作符返回一个SoSomething对象,随后被析构
168     s6.toPrint();
169     ++s6;                        // 参数和返回值均为引用类型,不调用任何构造、拷贝构造和析构函数
170     s6.toPrint();
171     
172     getchar();
173     return 0;
174 }

 

输出结果:

调用了带参构造函数, name=s1, weight=1!
调用了带参构造函数, name=s2, weight=2!
name=s1, weight = 1
name=s2, weight = 2
调用了带参构造函数, name=(null), weight=3!
调用了析构函数, name=(null), weight=3!
调用了带参构造函数, name=(null), weight=3!
name=s3, weight = 3
调用了拷贝构造函数, name=s3, weight=3!
name=s4, weight = 4
调用了拷贝构造函数, name=s4, weight=4!
调用了赋值运算函数, name=s4!
调用了赋值运算函数, name=s1!
name=s5, weight = 1
调用了拷贝构造函数, name=s5, weight=1!
name=s5, weight = 2
调用了拷贝构造函数, name=s6, weight=2!
调用了析构函数, name=s6, weight=2!
调用了拷贝构造函数, name=s6, weight=3!
调用了拷贝构造函数, name=s6, weight=4!
调用了析构函数, name=s6, weight=4!
调用了析构函数, name=s6, weight=5!
name=s6, weight = 4
name=s6, weight = 5

 

 

posted @ 2013-09-22 00:12  铁甲小宝  阅读(198)  评论(0编辑  收藏  举报