今天给大家分享一篇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了!

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

  1. python在删除对象时会自动调用析构函数_Python面向对象程序设计构造函数和析构函数用法分析...

    本文实例讲述了Python面向对象程序设计构造函数和析构函数用法.分享给大家供大家参考,具体如下: 构造函数和析构函数 1.构造方法的使用 很多类都倾向于将对象创建为有初始化状态.因此类可以定义一个名 ...

  2. C++ 笔记(17)— 类和对象(构造函数、析构函数、拷贝构造函数)

    1. 构造函数 构造函数是一种特殊的函数(方法),在根据类创建对象时被调用.构造函数是一种随着对象创建而自动被调用的函数,它的主要用途是为对象作初始化. 构造函数的名称与类的名称是完全相同的,并且不会 ...

  3. 10.C++-构造函数初始化列表、类const成员、对象构造顺序、析构函数

    首先回忆下,以前学的const 单独使用const修饰变量时,是定义的常量,比如:const int i=1; 使用volatile const修饰变量时,定义的是只读变量 使用const & ...

  4. 基类指针调用派生类函数_C++ 多态性:虚函数--基类与派生类类型转换(第7章 05)例子问题解析(学习笔记:第8章 05)...

    虚函数[1] 问题:还记得第7章的例子吗[2]? 例7-3 类型转换规则举例 #include <iostream> using namespace std; class Base1 { ...

  5. Java操作Redis存储对象类型数据

    背景描述 关于JAVA去操作Redis时,如何存储一个对象的数据,这是大家非常关心的问题,虽然官方提供了存储String,List,Set等等类型,但并不满足我们现在实际应用.存储一个对象是非常常见的 ...

  6. 23.C++类对象的指针为空时,调用成员函数不会挂掉

    最近工作的时候遇到了一个现象,当通过C++类对象的空指针调用没有使用this指针的成员函数时,不会出现段错误 测试代码 #include <iostream>using namespace ...

  7. Effective_STL 学习笔记(七) 当使用 new 得指针容器时,记得在销毁容器前 delete 那些指针...

    STL 容器非常优秀.它们提供了前向和逆向遍历的迭代器(通过 begin.end.rbegin等):它们能告诉你所容纳的对象的类型(通过 value_type 和 typedef):在插入和删除中,它 ...

  8. spring@Autowired的对象为null,非容器中的类如何调用容器中的类

    1.问题描述 我们平时使用@Autowired注入对象时,一般被注入的类都带有@Coponent.@Controller.@Service .@repository等注解才可以.注入类和被注入类都被s ...

  9. 2020-12-16子类对象指针强转成父类对象指针,父类对象指针调用子类函数问题(待整理)

    当父类中声明某个函数为虚函数,并且子类重载了这个虚函数以后,用父类对象的指针可以调用子类的相应函数,但前提是该指针指向的对象是子类的对象,否则没有意义. 1.新建一个子类对象时,它首先执行它父类的构造 ...

最新文章

  1. java多线程中方法_java中多线程 - 多线程中的基本方法
  2. Double Strings
  3. C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现
  4. 用jquery在必填表单字段前加红星总结
  5. Java 多线程 之 wait等待 线程实例
  6. 二层交换机的安全方案与实施
  7. matlab矩阵的表示和简单操作
  8. linux chmod 777 r,chmod -R 777 的3种补救办法,附有linux chmod命令语法和结构详解
  9. VeriSign SSL证书产品及服务_VeriSign证书|SSL证书|EVSSL证书|服务器证书|数字证书
  10. retroarch游戏模拟器使用
  11. coreldraw2021全名和序列号 cdr2021安装下载图文教程
  12. [转]Cookie详解
  13. win11问题记录:
  14. Mac截图很大很怎么变小?
  15. python之路day3_python之路:day3
  16. s22服务器未响应,王者荣耀资源包升级失败怎么办_王者荣耀S22资源包升级失败解决办法_玩游戏网...
  17. 实时折线图php mysql 源码_超级漂亮网址导航源码,自助链源码(PHP+MYSQL完整版)...
  18. oracle 导出身份证号_ORACLE对身份证号码处理相关的SQL【收藏】
  19. java求最短距离_计算多点之间的最短距离
  20. 安装纯净版win10系统

热门文章

  1. clint,offset,style的区别
  2. TI逻辑驱动器类族注释
  3. python扫雷游戏实验分析_用python写扫雷游戏实例代码分享
  4. java url地址编码转换_java url编码转换
  5. [翻译] 在 Overleaf 中分享项目
  6. python编写poc_干货分享丨Python从入门到编写POC之读写文件
  7. 白云学院计算机专业好不好,2021年广东白云学院专业排行榜,哪个专业就业比较好...
  8. 计算机网络物理层习题
  9. 中小型微服务系统 硬件设备如何部署,QPS大概多少
  10. 在职计算机应用研究生,攻读计算机应用技术在职研究生可以获得什么学历?