(一二九)类中使用new

简介:

在类定义——主要是类的构造函数中使用new时,需要注意很多问题:

①按照一个new对应一个delete的原则。

 

②构造函数中使用new/ new [],那么析构函数中就应该使用delete/ delete[]

 

③析构函数中使用delete/ delete[],那么构造函数、默认构造函数、复制构造函数,都应该使用new/ new[]

 

④且newdelete应该保持一致,要么都不带[],要么都带[]

 

⑤不同构造函数中的new也应该保持一致,要么都带,要么都不带[]

 

⑥但可以把指针初始化为空指针(空指针支持被deletedelete[])(空指针的定义方式是,=NULL或者=nullptr,也有=0的,不过前两者比较好)

 

⑦当遇见new时,复制构造函数 应自定义(深度复制),否则按值传递会导致出现问题。复制构造函数的定义思路,有一些类似构造函数。

参数:const 类引用

返回值和返回类型:无

基本类型的(非指针):按值传递;

指针:构造函数用new的,也跟着一起new,但不需要delete(因为是初始化对象时使用)

计数器(如果有):需要更新;

 

⑧应自定义 赋值运算符 (面对对象时),通过深度复制将一个对象赋值给另一个对象。思路类似构造函数:

参数:const 类引用

返回值:*this

返回类型:类对象的引用;

注意:需要有防止自己赋值自己的语句判断,判断条件是地址(this==&目标对象,原因在于==判断需要有运算符重载才可以

基本类型:按值传递;

指针:先deletenew(因为被赋值的对象,是已经被初始化过的,也就是说new过,因此应对应delete);

计数器(如果有):不更新;

 

 

例如:

类为Man,私有成员int achar*bint 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类对象ab来说,可以无需自定义赋值运算符,即可直接使用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这样么?

 


目录
相关文章
C# 继承类中(父类与子类)构造函数的调用顺序
C# 继承类中(父类与子类)构造函数的调用顺序
|
5月前
|
Java
java反射-获取类的属性、构造方法、方法
java反射-获取类的属性、构造方法、方法
|
6月前
类的无参方法
类的无参方法
|
6月前
深入类的方法
深入类的方法
继承类的方法
继承类的方法
112 0
|
Java C++
C++类中在构造器中调用本类的另外构造器
C++类中在构造器中调用本类的另外构造器
98 0
|
C++
C++类的静态方法
C++类的静态方法
83 0
学生类-构造函数
学生类-构造函数
180 0
|
编译器
类的构造函数
类的构造函数
158 0
|
程序员 C++ 编译器
c++模板类
理解编译器的编译模板过程 如何组织编写模板程序 前言常遇到询问使用模板到底是否容易的问题,我的回答是:“模板的使用是容易的,但组织编写却不容易”。看看我们几乎每天都能遇到的模板类吧,如STL, ATL, WTL, 以及Boost的模板类,都能体会到这样的滋味:接口简单,操作复杂。
993 0