C++类中隐藏的六个默认函数

Test类中隐藏的六个默认的函数

class Test
{
public:
	//默认的构造函数
	Test()//析构函数 
	~Test()//拷贝构造函数 
	Test(const Test &t)//赋值函数 
	Test& operator=(const Test &x)//一般对象取地址函数 
	Test* operator&()//常对象取地址函数 
	const Test* operator&()constprivate:
		int data;
		//int *data;
};

1.构造函数

作用:对象所在的内存空间做初始化 、给对象赋资源
特点

  1. 可以重载 :可以根据实际需要进行缺省的、多参重载
  2. 不依赖对象:对象无法调用构造函数,只能在对象定义点被调用
//成员函数类外实现,需在函数名前指定作用域,否则编译器会认为在定义一个普通的函数
Test::Test()	//类中默认的构造函数
{
}
//此外,构造函数可以支持重载,我们可以根据需要自己写一些构造函数
//需要注意的是,如果我们自己写了构造函数,那么编译器就不会提供默认的构造函数了
Test::Test(int d = 0 )	//缺省的构造函数
{
	data = d;
}
Test::Test(int d = 0 ):data(d)	//缺省的构造函数,用初始化器的方式初始化
{
}

两者初始化的区别在于,初始化器是真正意义上的初始化,它告诉编译器在实例化对象的时候以何种方式对成员赋值,而在前者的赋值规则写在了构造函数内部,是在已经生成了成员变量之后再进行的赋值操作。
初始化器实例:比如以下操作,成员变量定义为const类型,在C语言中const类型是一个常变量在定义的时候可以不初始化,而在C++中规定const类型为一个常量,定义时必须初始化。所以以下操作只能使用初始化器的方式初始化。

class Test
{
public:
	Test(int a)
		:ma(a)
	{
		//ma = a;
	}
private:
	const int ma;
};

此外,如果有多个成员变量需要使用初始化器的方式初始化,需要注意一点细节,初始化的顺序与成员变量的定义顺序相关。如以下程序,可以写成Test(int a):ma(mb), mb(a){}Test(int a):mb(a),ma(mb){}因为成员变量的定义顺序为int mb; int ma;,也就是说赋值顺序与初始化器无关,只与成员变量被定义的顺序有关。

class Test
{
public:
	Test(int a):ma(mb), mb(a)	//mb先被定义出来,先给mb赋值,再给ma赋值
	{
	}
	Test(int a):mb(ma), ma(a)	//error mb先定义出来,此时ma还未被定义,所以mb(ma)赋值无效,最终结果为,mb无效值,ma=a
	{
	}
	void Show()
	{
		std::cout << "ma: " << ma << std::endl;
		std::cout << "mb: " << mb << std::endl;
	}
private:
	int mb;
	int ma;
};

2.析构函数

作用:释放对象所占的其他资源。
特点

  1. 不可重载 : 对象销毁时会调用析构函数,并释放空间。
  2. 依赖对象:可用手动调用,但是不建议,因为对象销毁时会自动调用,如果手动调用可能会引起内存空间的重复析构导致程序崩溃
//默认的析构函数 
Test::~Test()
{		//没有额外的资源,什么都不写
}

//如果程序中有额外的空间需要释放
class Test
{
public:
	//构造函数 
	Test()
	{
		data = new int;		//data指向一块堆区内存
	}
	//析构函数
	~Test();
private:
		int* data;
};

//析构函数 
Test::~Test()
{
	delete data;	//把额外空间的释放写进析构函数
}

3.拷贝构造函数

作用:拿一个已存在的对象来生成相同类型的新对象
注意:类中提供的拷贝构造函数为一个浅拷贝类型,即如果成员变量中含有指针类型,它在进行拷贝构造的时候不会进行额外空间的开辟,最终会造成函数析构时的错误。

class Test
{
public:
	//构造函数 
	Test()
	{
		data = new int;		//data指向一块堆区内存
	}
	//拷贝构造函数 
	Test(const Test &t);	//一定要传引用,否则在开辟形参的过程中会递归的调用拷贝构造函数来构造形参,而函数始终无法执行
private:
		int* data;
};

//默认的拷贝构造函数 
Test::Test(const Test &t)
{
	data = t.data;		//浅拷贝,只把现有的成员变量进行拷贝,没有对堆区内存进行拷贝,使多个对象的data指向了同一片堆区空间,在对象销毁时会造成空间的重复释放引发程序崩溃。
}
//拷贝构造函数
Test::Test(const Test &t)
{
	data = new int;		//如果是字符类型data = new char[strlen(t.data) + 1];
	strcpy_s(data,sizeof(int), t.data);
}

4.赋值运算符的重载函数

作用:拿一个已存在的对象给相同类型的已存在对象赋值
实现步骤

  1. 自赋值判断
  2. 释放旧资源
  3. 生成新资源
  4. 赋值
class Test
{
public:
	//构造函数 
	Test()
	{
		data = new int;		//data指向一块堆区内存
	}
	//赋值函数
	Test& operator=(const Test &x);	//以自身类类型的引用的方式返回
private:
		int* data;
};

//默认的赋值函数(浅拷贝)
Test& operator=(const Test &x)
{
	if(this!=&x)		//自赋值判断
	{
		data=x.data;	//浅拷贝
	}
	return *this;		//返回自身类类型的引用
}
//赋值函数(深拷贝)
Test& operator=(const Test &x)
{
	if(this!=&x)		//自赋值判断
	{
		delete[] data;	//释放原资源,比如 a = 4, b = 5, a = b此时a放弃原先的资源 4
		data = new int;
		strcpy_s(data,sizeof(int), t.data);
	}
	return *this;		//返回自身类类型的引用
}

5.一般对象取地址函数

//一般对象取地址函数 
Test::Test* operator&()
{
	return this;
}

6.常对象取地址函数

//常对象取地址函数 
const Test::Test* operator&()const
{
	return this;
}
发布了75 篇原创文章 · 获赞 88 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43919932/article/details/102971931