(四)C++基础之类的构造和析构函数

1.1、构造函数和析构函数

在我们定义一个类的时候,总是会存在该类的属性需要在创建时候需要初始化怎么办?一般情况下我们我们可以提供一个init初始化的方法,但是这种方法也存在他自身的弊端,比如被别人篡改属性怎么办。
于是就提出了构造函数这个概念,构造函数在创建类的时候就会被运行。
构造函数定义方式:

class 类名
{
	类名(参数列表)
	{

	}
};

最终在创建对象的时候,根据参数列表会进行不同的构造函数,如果没有创建显式构造函数,系统会默认给我们创建一个隐式构造函数。
如下面这段代码:

#include <iostream>

using namespace std;


class Test
{
public:
	Test()
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
	}
	Test(int m_x)
	{
		cout << "Test(int m_x)" << endl;
		x = m_x;
		y = 0;
	}
	Test(int m_x,int m_y)
	{
		cout << "Test(int m_x,int m_y)" << endl;
		x = m_x;
		y = m_y;
	}
	void print(void)
	{
		cout << "x = " << x << " y = " << y << endl;
	}
private:
	int x;
	int y;
};

int main(void)
{
	Test t(10,23);
	t.print();
	cout << "-----------------------" << endl;
	Test t2(10);
	t2.print();
	cout << "-----------------------" << endl;
	Test t3;
	t3.print();
	return 0;
}

运行结果:
在这里插入图片描述

对于结果我们可以发现,创建对象的时候最终会根据形参类匹配对应的构造函数,特别对于第三种,和我们平时没有写构造函数一样也可以编译通过,就知道隐式构造函数如下:

Test()
	{
	}

里面没有任何东西。
讲完构造函数,接下来说一下析构函数,析构函数就是在删除对象之前需要运行的一个对象方法,最明显的特点就是比如我们在对象内有一个申请内存指向的指针,需要在类被删除时候将申请的内存释放掉,这样的话我们还专门去确定对象什么时候被删除,然后自己实现一个方法释放,这样反而麻烦,所以析构函数就出现了。
析构函数定义方式:

class 类名
{
	~类名()
	{

	}
};

他是没有任何形参列表的,他也和构造函数一样,如果你写显示析构函数,那么他会运行你写的析构函数,否则他会运行隐式析构函数。
我们看一下下面这段代码:

#include <iostream>

using namespace std;

class Test
{
public:
	Test(char *pname)
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
		int len = strlen(pname);
		name = (char *)malloc(len + 1);
		if (name == NULL){
			cout << "init name err" << endl;
		}
		else {
			strcpy(name, pname);
		}
	}
	void print(void)
	{
		cout  << " name = " << name << endl;
	}

	~Test()
	{
		cout << "~Test()" << endl;
		if (name != NULL){
			free(name);
			name = NULL;
		}
	}
private:
	int x;
	int y;
	char *name;
};

void test1(void)
{
	Test t("gale");
	t.print();
}

int main(void)
{
	cout << "begin" << endl;
	test1();
	cout << "end" << endl;
	return 0;
}

运行结果:
在这里插入图片描述
从结果我们看到,在该类被摧毁后,会调用析构函数,在里面我们 可以做一下需要清理内存的事情。
我们在来看一段代码:

#include <iostream>

using namespace std;

class Test
{
public:
	Test(char *pname)
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
		int len = strlen(pname);
		name = (char *)malloc(len + 1);
		if (name == NULL){
			cout << "init name err" << endl;
		}
		else {
			strcpy(name, pname);
		}
	}
	void print(void)
	{
		cout  << " name = " << name << endl;
	}

	~Test()
	{
		cout << "~Test()" << endl;
		if (name != NULL){
			cout << "name:" << name << endl;
			free(name);
			name = NULL;
		}
	}
private:
	int x;
	int y;
	char *name;
};

void test1(void)
{
	Test t("gale");
	t.print();
	cout << "---------------------" << endl;
	Test t2("feichang");
	t2.print();
}

int main(void)
{
	cout << "begin" << endl;
	test1();
	cout << "end" << endl;
	return 0;
}

运行结果:
在这里插入图片描述
我们在函数里面增加多一个对象,然后在类里面的析构函数打印名字,主要是为了区分不同的对象,在结果我们看到,后定义的对象会先被析构函数进行,其实也可以理解,毕竟是谁先压栈,肯定是后面出栈。

1.2、拷贝构造函数

我们来看一段代码:

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int m_x,int m_y)
	{
		cout << "Test()" << endl;
		x = m_x;
		y = m_y;
	}
	void print(void)
	{
		cout << "x = " << x << " y = " << y << endl;
	}

private:
	int x;
	int y;
};


int main(void)
{
	Test t(10,20);

	cout << "t2----------" << endl;
	Test t2(t);
	t2.print();
	return 0;
}

运行结果:
在这里插入图片描述
从中发现,使用下面这个语句:

Test t2(t);

是可以将t的属性初始值付给t2,但是我们并没有写对应的构造函数,他是如何进行元素赋值的呢。
其实这个是在类里面和构造函数一样,系统也给类创建了一个隐式拷贝构造函数,如果我们没有定义拷贝构造函数,那么系统将会调用系统的拷贝构造函数。
我们也可以写一个显式的拷贝构造函数:
在类里面增加这个函数:

Test(class Test &another)
	{
		cout << "Test(class Test &another)" << endl;
		x = another.x;
		y = another.y;
	}

然后运行结果如下:
在这里插入图片描述
你就会发现,系统在运行拷贝构造函数时候,就已经调用我们写的显式拷贝函数了。
其中:

Test t2(t);
//等价于
Test t2 = t;

但是下面这种是不相等的:

Test t2(t);
//不等于
Test t2;
t2 = t

很明显,构造函数是在对象初始化的时候才会调用,而下面的 t2 = t 这个明显不是在创建对象时候运行的,这种是等号运算,我们可以看下面这个例子:

#include <iostream>

using namespace std;

class Test
{
public:
	Test()
	{
		cout << "Test()" << endl;
		x = 0;
		y = 0;
	}
	Test(int m_x,int m_y)
	{
		cout << "Test(int m_x,int m_y)" << endl;
		x = m_x;
		y = m_y;
	}
	Test(class Test &another)
	{
		cout << "Test(class Test &another)" << endl;
		x = another.x;
		y = another.y;
	}
	void print(void)
	{
		cout << "x = " << x << " y = " << y << endl;
	}
	void operator=(class Test &another)
	{
		cout << "void operator=(class Test &another)" << endl;
		x = another.x;
		y = another.y;
	}

private:
	int x;
	int y;
};


int main(void)
{
	Test t(10,20);

	cout << "t2----------" << endl;
	Test t2(t);
	t2.print();

	cout << "t3----------" << endl;
	Test t3;
	t3 = t2;
	t3.print();
	return 0;
}

运行结果:
在这里插入图片描述
我们发现t3运行的是等号符号函数,所以在初始化对象时候需要注意这种情况。

1.3、构造函数的两个注意点

第一点:
当提供一个有参、无参、的构造函数或者拷贝构造函数,默认无参构造函数就会失效。
第二点:
定义一个函数:

void fanc(Test t)

将一个类做形参的时候,比如你想传递进去的是对象t1,那么最终传递过程等于:

Test t = t1;

所以最终参数t会调用构造函数进行处理。

1.4、拷贝函数的浅拷贝和深拷贝

我们先看一段浅拷贝的代码:

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int m_id, char *pname)
	{
		id = m_id;
		int len = strlen(pname);
		name = (char *)malloc(len + 1);
		if (name == NULL){
			cout << "name init err" << endl;
		}
		else{
			strcpy(name, pname);
		}
	}
	void print()
	{
		cout << "id = " << id << " name = " << name << endl;
	}

	~Test()
	{
		if (name != NULL){
			cout << "name: " << name << endl;
			free(name);
			name = NULL;
		}
	}
private:
	int id;
	char *name;
};

void func(void)
{
	Test t1(1,"gale");
	Test t2(t1);
}

int main(void)
{
	func();
	return 0;
}

运行结果:
在这里插入图片描述
其实只有在析构函数里面才有name的输出,可是他只输出gale后,便报错了,我们来分析一下这种情况。
如下面这个图:
在这里插入图片描述
因为我们运行的是系统的隐式拷贝构造函数,他只是单纯的赋值相等,所以是浅拷贝函数,所以两个对象所指向的名字都是同一个地址,但是当t2运行析构函数的时候,他将张三这个地址申请的内存释放掉,所以t2运行没有问题。
出现问题的是在t1也打算调用析构函数去释放掉申请的内存时,他就出现问题了,因为在之前该块地址早就被t2释放掉了,所以就出现问题了。
所以要切记:
如果属性中有指针的,一定要切记使用显示拷贝构造函数,在里面进行深拷贝,下面这个函数增加到类里面,他是显式的拷贝构造函数:

Test(class Test &another)
	{
		int len = strlen(another.name);
		name = (char *)malloc(len +1);
		if (name == NULL){
			cout << "name init err" << endl;
		}
		else{
			strcpy(name,another.name);
		}
	}

单独给对象中的名字申请一片独立的内存,这样就可以避免出现刚才那种错误,也就是得使用显式深拷贝构造函数,在里面单独申请一片内存给对应的对象。
运行结果:
在这里插入图片描述
这样就可以避免出错了。

1.5、构造函数列表

由于存在构造函数,那么就哟一个疑惑,就是我先定义一个类,然后在另外一个类中申明两个之前定义的类的对象,那么我该如何进行触发之前的对象的构造函数来初始化呢?

#include <iostream>

using namespace std;

class Test1
{
public:
	Test1(int m_a, int m_b)
	{
		cout << "Test1(int m_a, int m_b)" << endl;
		a = m_a;
		b = m_b;
	}
	void printAB(void)
	{
		cout << "a = " << a << ",b = " << b << endl;
	}
private:
	int a;
	int b;
};

class Test2
{
public:
	Test2(Test1 &a, Test1 &b, int m)
	{
		a1 = a;
		b1 = b;
		c = m;
	}
private:
	Test1 a1;
	Test1 b1;
	int c;
};

int main(void)
{
	Test1 a(10,20);
	Test1 b(30,40);

	Test2 a1(a,b,50);
	return 0;
}

如果根据我们之前学到知识,一般来讲都是这样写,可是这样写,在

	Test2(Test1 &a, Test1 &b, int m)
	{
		a1 = a;
		b1 = b;
		c = m;
	}

其中 a1 = a;b1 = b; 这两个是赋值语句,不是触发构造函数,所以这样根本没法给第二个类中的两个对象初始化,但是如果单独写一个方法又破坏类的统一,于是构造函数列表出现了。
其实也很简单,只需要在第二个类中的构造函数改变一下,如下:

	Test2(Test1 &a, Test1 &b, int m) :a1(a), b1(b)
	{
		c = m;
	}

这样就可以达到触发拷贝构造函数来初始化了。
最终代码如下:

#include <iostream>

using namespace std;

class Test1
{
public:
	Test1(int m_a, int m_b)
	{
		cout << "Test1(int m_a, int m_b)" << endl;
		a = m_a;
		b = m_b;
	}
	void printAB(void)
	{
		cout << "a = " << a << ",b = " << b << endl;
	}
private:
	int a;
	int b;
};

class Test2
{
public:
	Test2(Test1 &a, Test1 &b, int m) :a1(a), b1(b)
	{
		c = m;
	}
	void print(void)
	{
		cout << "a1:" << endl;
		a1.printAB();
		cout << "b1:" << endl;
		b1.printAB();
	}
private:
	Test1 a1;
	Test1 b1;
	int c;
};

int main(void)
{
	Test1 a(10,20);
	Test1 b(30,40);

	Test2 a1(a,b,50);

	a1.print();
	return 0;
}

运行结果如下:
在这里插入图片描述
这样就可以完成之前想要在一个类中初始化类中的对象。

发布了29 篇原创文章 · 获赞 0 · 访问量 422

猜你喜欢

转载自blog.csdn.net/weixin_42547950/article/details/104323565