练习代码:
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("调用了无参构造函数! "); 12 weight = 0; 13 name = NULL; 14 } 15 Something(int w, const char* str = NULL) 16 { 17 printf("调用了带参构造函数, name=%s, weight=%d! ", str, w); 18 // Something(); // 调用上一级构造函数,初始化weight,name等变量,疑问:这一步没有起到效果,似乎不能这么调用,出现了name没有被初始化的错误 19 weight = w; 20 // 这里不需要提前释放name空间,因为name刚刚被构造 21 22 if (str) 23 { 24 // 注意,此处必须 strlen(src)+1 ,因为还有足够的空间放下' '字符 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! ", 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 ,因为还有足够的空间放下' '字符 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! ", name, weight); 60 free(name); 61 }else 62 printf("调用了析构函数, name=(null), weight=%d! ", 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! ", 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 ,因为还有足够的空间放下' '字符 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 ,因为还有足够的空间放下' '字符 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 ", 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