Bootstrap

C++——构造函数和析构函数

一、初识构造函数和析构函数

简单来说,有对象生成必然会调用构造函数,有对象销毁必然会调用析构函数。构造函数的作用是初始化成员变量,是由编译器去调用的,而析构函数同理也是由编译器调用,不过他的作用则是清理。可以由下面的代码体验两个函数的使用。

注意:

相同点:两个函数都没有返回值,不能用void

不同点:构造函数可以有参数,析构函数没有参数

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	M() {//无参构造
		a = 101;
		cout << "构造函数执行" << endl;
	}
	~M()//在对象销毁前,编译器调用析构函数
	{
		cout << "析构函数执行" << endl;
	}
	int a;
};

void fun() {
	M m;
	int b = m.a;
	cout << b << endl;
}

class M2 {
public:
	//有参构造
	M2(const char *name,int age){
		cout << "有参构造" << endl;
		//从堆区空间申请
		pName = (char*)malloc(strlen(name) + 1);
		strcpy(pName,name);
		mAge = age;
	}
	void Print() {
		cout << "name=" << pName << endl;
		cout << "age=" << mAge << endl;

	}
	~M2()
	{
		cout << "析构函数执行" << endl;
		//释放堆区空间
		if (pName != NULL) {
			pName = NULL;
		}
		if (mAge != NULL) {
			mAge = NULL;
		}
	}
private:
	char* pName;
	int mAge;
};

void fun2() {
	M2 m2("卡卡",14);
	m2.Print();
}
int main() {
	
	fun();
	M m;
	cout << "***********" << endl;
	
	fun2();
	system("pause");
	return EXIT_SUCCESS;
}

运行结果:

  1. 构造函数可以重载

class M3 {
public:
	M3() {

	}
	M3(int a) {

	}
};

 2.当构造函数私有时,无法实例化对象,所以构造函数和析构函数都是公有的

class M3 {
//public:
private:
	M3() {

	}
	M3(int a) {

	}
};
void fun3() {
	M3 m3;//该行会报错
}

补充:实例化对象的时候内部操作:1.分配空间(将对象m分配到栈区)2.调用构造函数进行初始化

二、默认的构造函数和析构函数 

        现在我们已经简单了解了构造函数和析构函数,但当我们在声明类的时候没有声明这两个函数,那我们在实例化一个对象的时候还会有这两个函数的事情吗?

        答案是有的。

        前面的学习我们知道,构造函数和析构函数是由编译器去调用的,所以当我们没有声明他们的情况下,编译器会去提供默认的构造函数和析构函数。如下面的例子,程序运行可以打印a=100

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	void pfun() {
		a = 100;
		cout << "a=" << a << endl;
	}
private:
	int a;
};

void fun() {
	M m;
	m.pfun();
}
int main() {
	
	fun();
	system("pause");
	return EXIT_SUCCESS;
}

        而所提供的默认的构造函数和析构函数的函数体是空的。

三、拷贝构造


用一个已有的对象去初始化另一个对象

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	M() {
		cout << "M的无参构造函数" << endl;
		a = 10;
	}
	M(const M& t) {
		cout << "拷贝构造函数" << endl;
		a = t.a;
	}
	void printinform() {
		cout << "a=" <<a<< endl;
	}
private:
	int a;
};


void fun() {
	M m;
	m.printinform();
	M t(m);//用一个已有的对象去初始化另一个对象
	t.printinform();
}
int main() {
	
	fun();
	system("pause");
	return EXIT_SUCCESS;
}

运行结果:

相信大家这时候会注意到,在拷贝构造函数那里,我们用到了const和&。

const是为了保证其值不会改变,这里可有可没有,而&在这里表示的是引用。为了看出引用的的作用,我们可以在这里先去掉&,这个时候会发现编译器报错。

下面情况下,当对象以值的方式给函数参数时,调用的也是拷贝函数

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	M(int x) {
		cout << "M的有参构造函数" << endl;
	}
	M() {
		cout << "M的无参构造函数" << endl;
	}
	M(const M& t) {
		cout << "拷贝构造函数" << endl;
	}
	~M()
	{
		cout << "析构函数" << endl;
	}

};

void f0(M x) {//对象以值的方式给函数参数

}

void fun() {
	M m;
	f0(m);//对象以值的方式给函数参数
	
}
int main() {
	
	fun();
	system("pause");
	return EXIT_SUCCESS;
}

 运行:

另外,拷贝构造也可以这样调用,但不常用

M t = m;

注意区别赋值操作和拷贝构造

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	M() {
		cout << "M的无参构造函数" << endl;
		a = 10;
	}
	M(const M& t) {
		cout << "拷贝构造函数" << endl;
		a = t.a;
	}
	void printinform() {
		cout << "a=" <<a<< endl;
	}
private:
	int a;
};


void fun() {
	M m;
	m.printinform();
	M t(m);//用一个已有的对象去初始化另一个对象
	t.printinform();
	M t2;
	t2 = t;//赋值操作!!!!
	t2.printinform();

	
}
int main() {
	
	fun();
	system("pause");
	return EXIT_SUCCESS;
}

可见,构造函数大概分成了以下三种。

有了拷贝构造,那么编译器提供的默认构造函数和之前有什么区别呢?

四、构造函数的调用

1.如果提供了有参构造,编译器不会提供默认构造函数,但会提供拷贝构造函数

2.如果提供了拷贝构造函数,那么编辑器不会提供默认的构造函数和默认的拷贝构造函数

五、类中有多个对象的函数调用情况

当在一个类中有多个其他类声明对象的情况下,构造函数和析构函数又是怎样调用呢?

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class A {
public:

	A() {
		cout << "A的无参构造函数" << endl;
	}
	A(const A& t) {
		cout << "A的拷贝构造函数" << endl;
	}
	~A()
	{
		cout << "A的析构函数" << endl;
	}
};
class B {
public:

	B() {
		cout << "B的无参构造函数" << endl;
	}
	B(const B& t) {
		cout << "B的拷贝构造函数" << endl;
	}
	~B()
	{
		cout << "B的析构函数" << endl;
	}
};
class M {
public:
	M(int x) {
		cout << "M的有参构造函数" << endl;
	}
	M() {
		cout << "M的无参构造函数" << endl;
	}
	M(const M& t) {
		cout << "M的拷贝构造函数" << endl;
	}
	~M()
	{
		cout << "M的析构函数" << endl;
	}
	
private:
	A m1;
	B m2;
};


void fun() {
	M m;
}
int main() {
	fun();
	system("pause");
	return EXIT_SUCCESS;
}

 

由此可见,当对象m运行时候,先是按照顺序运行 m内声明的对象a和b,分别调用其构造函数,并按照栈的顺序调用析构函数

六、explicit的使用

在我们写代码的时候,编译器有时候会对一些情况进行自动优化,虽然程序可以运行,但会降低代码的可读性,于是我们可以用explicit来阻止自动优化.

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	M(int x) {
	}
};

int main() {
	M m1 = 10;//这里自动优化成了M m1(10)
	system("pause");
	return EXIT_SUCCESS;
}

如果在构造函数前面加上explicit就会报错 

七、匿名对象

匿名对象就是没有给根据类声明的对象命名,如有一个类M,

M();(而不像之前声明实例对象M m)便是一个匿名对象

而匿名对象的生命周期与之前的不同,该周期是在当先行,对比下面代码的匿名对象和m对象的运行结果。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class M {
public:
	M(int x) {
		cout << "M的有参构造函数" << endl;
		a = 10;
	}
	M() {
		cout << "M的无参构造函数" << endl;
		a = 10;
	}
	M(const M& t) {
		cout << "拷贝构造函数" << endl;
		a = t.a;
	}
	~M()
	{
		cout << "析构函数" << endl;
	}
	void printinform() {
		cout << "a=" <<a<< endl;
	}
private:
	int a;
};


void fun() {
	M();//匿名对象
	cout << "1111111111111" << endl;
	cout << "******************************" << endl;
	M m;
	cout << "2222222222" << endl;
	
}
int main() {
	
	fun();
	system("pause");
	return EXIT_SUCCESS;
}

注意:匿名对象有名字的话,就不是匿名对象,如下面的m1

M m1 = M();

;