练习代码:
1 #include <stdlib.h> 2 #include <string> 3 4 class Something 5 { 6 private: 7char* name; 8int weight; 9 public: 10Something(){ 11 printf("调用了无参构造函数!\n"); 12 weight = 0; 13 name = NULL; 14} 15Something(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); 38Something(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) 71Something& 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 92Something& operator++ () 93{ 94 ++weight; 95 return *this; 96} 97 98// 重置后置++运算符,完成:Something++ 99Something operator++(int i)100{101 Something tmp = *this; // 调用拷贝构造函数初始化tmp102 ++*this;103 return tmp;// 调用拷贝构造函数初始化返回值对象,随后析构tmp104}105 106// b + c运算的返回值为const类型,可以避免b + c = a这种无效赋值操作107// 由于返回的是临时对象,所以返回值不能是引用,否则会出现指向无效栈空间的BUG,导致不可预期的结果108Something operator+ (const Something& s)109{110 // 此处返回临时对象,且返回值类型不是引用,所以会调用构造函数111 //Something tmp(weight+s.weight); // 构造tmp112 //return tmp; // 拷贝构造返回对象,语句结束时析构tmp113 return Something(weight+s.weight);114}115 116void 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}132void toPrint()133{134 printf("name=%s, weight = %d\n", name, weight);135}136 };137 138 Something foo(Something s) // 因为参数不是引用类型,所以此处会调用拷贝构造函数139 {140s.toPrint();141s.setName("s6");142return s; // 此处会调用s的拷贝构造,产生一个返回对象,然后调用s的析构函数143 }144 145 int main()146 {147 148Something s1(1, "s1"), s2(2, "s2");149s1.toPrint();150s2.toPrint();151s1+s2; // 构造返回的对象,本语句结束时析构返回的对象152Something s3 = s1+s2; // 为何此处没有调用拷贝构造函数?也没有调用赋值运算,也没调用析构函数析构返回对象,仅仅调用一个构造函数153 // 一般的声明赋初值操作会调用拷贝构造函数,这里没有调用拷贝构造函数154 // 一般的函数,返回的对象会被析构,这里没有析构155 // 暂认为是编译器在此做了优化,直接构造了s3,相当于优化成了 Something s3(s1.weight+s2.weight);156157s3.setName("s3");158s3.toPrint();159Something s4 = s3; // 此处会调用拷贝构造函数160s4.setName("s4");161s4.toPrint();162Something s5(s4); // 此处会调用拷贝构造函数163(s5 = s4) = s1;// 此处会调用两次赋值运算符,略奇葩的赋值,合法,但是没啥特殊意义,等同于 s5 = s1164s5.setName("s5");165s5.toPrint();166Something s6 = foo(s5); // 函数返回一个Something对象,调用拷贝构造函数,表达式结束后,返回的对象被析构167s6++; // 返回值都是非引用类型,操作符返回一个SoSomething对象,随后被析构168s6.toPrint();169++s6; // 参数和返回值均为引用类型,不调用任何构造、拷贝构造和析构函数170s6.toPrint();171172getchar();173return 0;174 }
输出结果:
调用了带参构造函数, name=s1, weight=1!调用了带参构造函数, name=s2, weight=2!name=s1, weight = 1name=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 = 4name=s6, weight = 5