在类定义——主要是类的构造函数中使用new时,需要注意很多问题:
①按照一个new对应一个delete的原则。
②构造函数中使用new/ new [],那么析构函数中就应该使用delete/ delete[];
③析构函数中使用delete/ delete[],那么构造函数、默认构造函数、复制构造函数,都应该使用new/ new[]。
④且new和delete应该保持一致,要么都不带[],要么都带[];
⑤不同构造函数中的new也应该保持一致,要么都带,要么都不带[]。
⑥但可以把指针初始化为空指针(空指针支持被delete和delete[])(空指针的定义方式是,=NULL或者=nullptr,也有=0的,不过前两者比较好)
⑦当遇见new时,复制构造函数 应自定义(深度复制),否则按值传递会导致出现问题。复制构造函数的定义思路,有一些类似构造函数。
参数:const 类引用
返回值和返回类型:无
基本类型的(非指针):按值传递;
指针:构造函数用new的,也跟着一起new,但不需要delete(因为是初始化对象时使用)
计数器(如果有):需要更新;
⑧应自定义 赋值运算符 (面对对象时),通过深度复制将一个对象赋值给另一个对象。思路类似构造函数:
参数:const 类引用
返回值:*this;
返回类型:类对象的引用;
注意:需要有防止自己赋值自己的语句判断,判断条件是地址(this==&目标对象,原因在于==判断需要有运算符重载才可以)
基本类型:按值传递;
指针:先delete再new(因为被赋值的对象,是已经被初始化过的,也就是说new过,因此应对应delete);
计数器(如果有):不更新;
例如:
类为Man,私有成员int a和char*b,int len; 分别表示数值,字符串,字符串长度。
代码:
class Man { int a; //数值 char*b; //指针 int len; //指针指向字符串的长度 public: Man::Man(); Man::Man(int m, const char* n); Man::Man(const Man& n); Man& Man::operator=(const Man&n); }; //默认构造函数: Man::Man() //这里没使用默认参数 { a = 0; //a为0 b = nullptr; //空指针,可以被delete或者delete[] len = 0; //长度为0 } //构造函数: Man::Man(int m, const char* n) { len = strlen(n); //len=被传递的字符串长度 b = new char[len + 1]; //然后new strcpy_s(b, len + 1, n); //复制到成员b a = m; //给a一个初始值 } //复制构造函数: Man::Man(const Man& n) { len = n.len; //长度自然相等 b = new char[len + 1]; //new,是因为这是初始化对象时调用 strcpy_s(b, len + 1, n.b); //复制过去 a = n.a; //a相等 } //赋值运算符重载(深度复制); Man& Man::operator=(const Man&n) //调用的是被赋值的对象,隐式传递给函数 { if (this == &n) return *this; //用于自己赋值给自己,判断地址的原因在于对象==对象需要运算符重载 a = n.a; //相等 len = n.len; //自然也相等 delete[]b; //因为已经初始化过了,新的长度不确定,因此要delete[]掉老的再重新申请动态内存 b = new char[len + 1]; //new strcpy_s(b, len + 1, n.b); //复制 return *this; //返回对象,*this表示当前对象 }
一个类包含另一个类对象作为成员时:
假如有String类,在之前,我们已经自定义String类的复制构造函数了(也就是说可以把一个类对象作为参数,在初始化另一个类对象时使用)。
又有Man类,Man类里有两个数据成员,一个是int类型变量m,一个是String类对象n。
那么,对于Man类对象a和b来说,可以无需自定义赋值运算符,即可直接使用a=b这种方法,将b赋值给a。
原因在于,默认的赋值运算符,是逐值赋值的。也就是a.m=b.m; a.n=b.n; 这样。
int类型属于基本类型,可以直接使用赋值运算符,这是我们知道的。
String类型是我们自定义的类型,因为重载了赋值运算符(面向对象的),因此我们可以直接将一个String类对象赋值给另一个String类对象(调用重载赋值运算符函数,而不是调用默认赋值运算符函数),此时调用的是 重载后的赋值运算符 。
又已知,自定义的重载赋值运算符函数,并不会生成临时对象(默认的也不会),也不会让两个对象的指针成员指向同一个地址(因此不会导致析构函数调用时出错)。于是,不会带来不良后果,可以直接将String类对象赋值给另一个。
因此,对Man类使用赋值运算符(逐值赋值2个数据成员)并不会带来什么不良后果。
但假如Man类还有其他成员,需要定义复制构造函数和赋值运算符,情况会更复杂,在这种情况下,这些函数必须显式的调用String类的复制构造函数和赋值运算符,将在13章介绍。
——不明白什么叫显式的调用String类的复制构造函数和赋值运算符,是指像a.n=b.n这样么?