C++ 容器存储对象时,指针调用析构函数触发的一系列BUG

今天给大家分享一篇BUG文章,请耐心看完,也许你以后也会遇到这样的BUG而解决不了!

需求是这样的:
定义一个Student类,里面有私有成员整型和指针!
例如:int age; char *name;

我们需要把类定义的对象存入容器中。
其中会发生一系列好玩的事情,拭目以待!

不懂容器的可以点击下面去学习:
list容器
deque容器
vector容器


我们这里以vector为例子说明,其他容器都是一样的!

代码:

#include <iostream>
#include <Windows.h>
#include <vector>
#include <deque>
#include <list>


using namespace std;

class Student {
public:
	Student(int age, const char* name) {
		this->age = age;
		
		// 分配内存
		this->name = new char[strlen(name) + 1];
		strcpy_s(this->name, strlen(name) + 1, name);
	}

	~Student() {
		// 如果name中有内存,那么就释放掉
		if (name) {
			delete[] name;
			name = NULL;
			age = 0;
		}
	}

	int getAge() const {
		return age;
	}

	char* getName() const {
		return name;
	}


private:
	int age;
	char* name;
};


int main(void) {
	vector<Student> v1;
	Student stu1(33, "张三");
	Student stu2(34, "李四");

	// 将张三李四存入容器中
	v1.push_back(stu1);
	v1.push_back(stu2);

	// 打印容器里的元素
	for (vector<Student>::iterator it = v1.begin(); it != v1.end(); it++) {
		cout << "姓名:" << it->getName() << ", 年龄:" << it->getAge() << endl;
	}

	system("pause");
	return 0;
}

很普通的一段代码,看似也没什么问题啊。
我们来运行一下:
在这里插入图片描述
哎,这是怎么回事???
本该打印张三的名字,怎么会打印乱码了呢???

我们先按任意键停止程序先,再看一下代码:
在这里插入图片描述

哎哎哎,这是怎么回事,按任意键后,程序竟然直接崩溃了。。。
这搞什么啊,明明代码没啥问题啊,怎么会有这么多错误,甚至程序直接崩掉了。

我们先来看一下代码:

首先看main函数:

int main(void) {
	vector<Student> v1;
	Student stu1(33, "张三");
	Student stu2(34, "李四");

	v1.push_back(stu1);
	v1.push_back(stu2);

	for (vector<Student>::iterator it = v1.begin(); it != v1.end(); it++) {
		cout << "姓名:" << it->getName() << ", 年龄:" << it->getAge() << endl;
	}

	system("pause");
	return 0;
}

再看Student类:

class Student {
public:
	Student(int age, const char* name) {
		this->age = age;

		this->name = new char[strlen(name) + 1];
		strcpy_s(this->name, strlen(name) + 1, name);
	}

	~Student() {
		if (name) {
			delete[] name;
			name = NULL;
			age = 0;
		}
	}

	//// 拷贝构造函数
	//Student(const Student& student) {
	//	this->age = student.age;

	//	this->name = new char[strlen(student.name) + 1];
	//	strcpy_s(this->name, strlen(student.name) + 1, student.name);
	//}

	int getAge() const {
		return age;
	}

	char* getName() const {
		return name;
	}


private:
	int age;
	char* name;
};

貌似也没什么问题啊。

那么我们来分析一下运行结果:

  1. 首先他是可以运行的,说明语法上没有任何问题!
  2. 运行后打印结果,就第一次打印张三时出了问题,第二次李四都没问题!
  3. 显示结果后,程序停留在暂停页面,等待按任意键继续!
  4. 按任意键后,程序就蹦掉了!

1)从第二条分析中,我们可以推出,因该是在打印结果前,张三的内存就被释放掉,所以才会打印乱码;
2)紧接着第四条分析中,程序崩掉了,程序结束前,会调用析构函数将指针的内存释放掉,那么,结合第二条分析,张三的指针内存已经释放掉了,当他再次释放时,那不就会报错了吗?
3)由此,我们初步推断出问题出自析构函数中。

接下来我们来调试一下,在析构函数中加上打印调试语句:
cout << “调用了析构函数~” << endl;

~Student() {
		if (name) {
			cout << "调用了析构函数~" << endl;
			delete[] name;
			name = NULL;
			age = 0;
		}
	}

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

没想到竟然调用了三次析构函数,明明才定义了两个对象,怎么会调用三次析构函数呢???

而且在打印前真的调用析构函数将张三析构掉了。

由以上证据,可以证实我们的推论是正确的!

那么该如何解决这个BUG呢?

我们再来会看main函数:

int main(void) {
	vector<Student> v1;
	Student stu1(33, "张三");
	Student stu2(34, "李四");

	v1.push_back(stu1);
	v1.push_back(stu2);

	for (vector<Student>::iterator it = v1.begin(); it != v1.end(); it++) {
		cout << "姓名:" << it->getName() << ", 年龄:" << it->getAge() << endl;
	}

	system("pause");
	return 0;
}

没问题啊!!!
问题不是出在析构函数吗???

到了这里,我要给大家补充一个知识点:

  1. 对象存入容器时,存的是他的值,而不是引用;
  2. 然而,存储时程序会自动调用拷贝构造函数将对象存入容器中。
  3. 因为我们上面没有自己定义的拷贝构造函数,所以,程序会调用默认的拷贝构造函数,即使用浅拷贝来拷贝对象!
  4. 使用先拷贝拷贝对象的话,也就是会使用浅拷贝来拷贝指针;浅拷贝拷贝指针啊,问题出来了;
  5. 浅拷贝拷贝指针,拷贝后两个对象的指针都会指向同一块内存,所以当内存发生变化是,是两个对象中的指针都会跟着一起发生变化。

如图:
在这里插入图片描述

这也是上面第一次运行时,张三的名字出现乱码的原因。

所以,解决办法就是使用深拷贝,自己定义一个拷贝构造函数!

// 拷贝构造函数
	Student(const Student& student) {
		this->age = student.age;

		this->name = new char[strlen(student.name) + 1];
		strcpy_s(this->name, strlen(student.name) + 1, student.name);
	}

代码:

#include <iostream>
#include <Windows.h>
#include <vector>
#include <deque>
#include <list>


using namespace std;

class Student {
public:
	Student(int age, const char* name) {
		this->age = age;

		this->name = new char[strlen(name) + 1];
		strcpy_s(this->name, strlen(name) + 1, name);
	}

	~Student() {
		if (name) {
			cout << "调用了析构函数~" << endl;
			delete[] name;
			name = NULL;
			age = 0;
		}
	}

	// 拷贝构造函数
	Student(const Student& student) {
		this->age = student.age;

		this->name = new char[strlen(student.name) + 1];
		strcpy_s(this->name, strlen(student.name) + 1, student.name);
	}

	int getAge() const {
		return age;
	}

	char* getName() const {
		return name;
	}


private:
	int age;
	char* name;
};


int main(void) {
	vector<Student> v1;
	Student stu1(33, "张三");
	Student stu2(34, "李四");

	v1.push_back(stu1);
	v1.push_back(stu2);

	for (vector<Student>::iterator it = v1.begin(); it != v1.end(); it++) {
		cout << "姓名:" << it->getName() << ", 年龄:" << it->getAge() << endl;
	}

	system("pause");
	return 0;
}

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

看完美解决BUG!!!


细心的人会发现,为什么会多调用一次析构函数呢?
多调用一次析构函数,也是我们刚开始运行时张三出现了乱码的事情!

原因很简单:

  1. 我们的容器刚开始时没有内存的
  2. 当我们存入第一个元素时,他会自动分配一块内存用于存储第一个元素
  3. 当我们再次存入一个元素时,这时候因为第一次分配的内存空间不够存入两个元素,所以容器会自动分配另一块足够存储两个元素的内存;
  4. 然后容器会先把第二个元素存入新分配的内存中,然后再把第一个内存的元素拷贝到新的内存中;
  5. 再把存储第一个元素的内存和元素一起释放掉。

我们再拷贝构造函数中假如一句话调试一下:
在这里插入图片描述

由结果我们可以验证我们刚刚分析的是正确的!

最后四次析构函数是我们定义的两个对象和容器中的两个对象释放时打印的结果!

去掉调式打印语句的运行结果:
在这里插入图片描述


总结:
自此,我已经把这个BUG完整的展示和解决方法展示给大家,涉及到的知识点也不多,希望大家看这篇文章后,以后不会再遇到这样的BUG了!

发布了40 篇原创文章 · 获赞 24 · 访问量 6502

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/104732079
今日推荐