c++常考笔记

一 什么是深拷贝,什么是浅拷贝?c++

浅拷贝(Shallow Copy)

  • 浅拷贝在C++中是默认的复制方式,它复制对象的所有成员变量,但对于指针成员变量,仅复制指针的值(即指向的地址),而不复制指针指向的内容。这样一来,两个对象中的指针成员将指向同一块内存区域。
#include <iostream>class Shallow {
public:int* data;// 指向动态分配的内存的指针Shallow(int d) {data = new int(d);//在堆上分配一个int,并用d的值初始化}// 默认的拷贝构造函数实现浅拷贝Shallow(const Shallow& source) : data(source.data) {std::cout << "Shallow copy constructor" << std::endl;}~Shallow() {delete data;std::cout << "Destructor freeing data" << std::endl;}
};int main() {Shallow obj1(10);Shallow obj2 = obj1;  // 浅拷贝std::cout << *obj1.data << std::endl;  // 输出 10std::cout << *obj2.data << std::endl;  // 输出 10return 0;
}

在这个例子中,obj2是通过浅拷贝构造函数创建的,两个对象中的data指针指向同一块内存区域。如果对象obj2的生命周期结束,析构函数将释放内存,但obj1data指针将变成悬挂指针。

深拷贝(Deep Copy) 

  • 深拷贝在C++中涉及到对包含动态分配内存的成员变量进行复制,即分配新的内存,并复制内容,而不是仅复制指针的值。
  • #include <iostream>class Deep {
    public:int* data;Deep(int d) {data = new int(d);}// 深拷贝构造函数Deep(const Deep& source) {data = new int(*source.data);// 分配新的内存,并复制值std::cout << "Deep copy constructor" << std::endl;}~Deep() {delete data;std::cout << "Destructor freeing data" << std::endl;}
    };int main() {Deep obj1(10);Deep obj2 = obj1;  // 深拷贝std::cout << *obj1.data << std::endl;  // 输出 10std::cout << *obj2.data << std::endl;  // 输出 10*obj2.data = 20;std::cout << *obj1.data << std::endl;  // 输出 10std::cout << *obj2.data << std::endl;  // 输出 20return 0;
    }

    在这个例子中,obj2是通过深拷贝构造函数创建的。两个对象各自持有一份独立的内存区域,修改obj2的数据不会影响obj1的数据。

关键点

  • 浅拷贝:仅复制指针的值,多个对象共享同一块内存。
  • 深拷贝:分配新的内存,并复制指针指向的内容,多个对象拥有独立的内存。

实现深拷贝的细节

  1. 自定义拷贝构造函数
  2. 自定义赋值运算符(需要处理自赋值问题)。
  3. 释放动态内存的析构函数。

完整示例如下:

#include <iostream>class Deep {
public:int* data;Deep(int d) {data = new int(d);}// 深拷贝构造函数Deep(const Deep& source) {data = new int(*source.data);std::cout << "Deep copy constructor" << std::endl;}// 深拷贝赋值运算符Deep& operator=(const Deep& source) {if (this == &source)  // 检查自赋值return *this;delete data;  // 释放旧内存data = new int(*source.data);  // 分配新内存并复制数据std::cout << "Deep assignment operator" << std::endl;return *this;}~Deep() {delete data;std::cout << "Destructor freeing data" << std::endl;}
};int main() {Deep obj1(10);Deep obj2 = obj1;  // 深拷贝构造函数obj2 = obj1;  // 深拷贝赋值运算符std::cout << *obj1.data << std::endl;  // 输出 10std::cout << *obj2.data << std::endl;  // 输出 10*obj2.data = 20;std::cout << *obj1.data << std::endl;  // 输出 10std::cout << *obj2.data << std::endl;  // 输出 20return 0;
}

这个示例展示了如何在C++中正确地实现深拷贝,确保对象完全独立,并正确管理动态内存。 

二 什么时候产生默认拷贝构造函数

        在C++中,如果没有为类显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。默认拷贝构造函数执行的是浅拷贝也就是说,它逐个成员地复制对象,包括对指针和其他对象成员只复制其地址或引用,而不复制其指向的内容。这种行为在某些情况下可能会导致问题,特别是当类中有动态分配的内存或资源时。

1.什么时候会生成默认拷贝构造函数

  1. 显式调用拷贝构造函数
    1. 当你显式地使用拷贝构造函数创建一个对象时:
      MyClass obj1;
      MyClass obj2 = obj1;  // 这里调用了拷贝构造函数
      
  2. 函数参数传递
    1. 当一个对象通过值传递给函数时,拷贝构造函数会被调用
      void myFunction(MyClass obj) {// do something
      }
      MyClass obj;
      myFunction(obj);  // 这里调用了拷贝构造函数
      

  3.  函数返回值
    1. 当一个对象通过值返回时,拷贝构造函数会被调用:
      MyClass myFunction() {MyClass obj;return obj;  // 这里调用了拷贝构造函数
      }
      MyClass obj = myFunction();
      

  4. 类对象的成员初始化
    1. 当类中包含其他类的对象成员时,包含类的拷贝构造函数会调用这些成员对象的拷贝构造函数。

2.默认拷贝构造函数的行为

默认拷贝构造函数执行的是逐成员复制,即:

  1. 对于内置类型的成员(如intchar等),直接复制其值。
  2. 对于指针类型的成员,仅复制指针的值(即地址),不复制指针指向的内容。
  3. 对于类类型的成员,调用其拷贝构造函数(这意味着如果成员类也没有定义拷贝构造函数,编译器会为它们生成默认的拷贝构造函数)。

以下是一个使用默认拷贝构造函数的示例:

#include <iostream>class MyClass {
public:int value;int* ptr;MyClass(int val) : value(val) {ptr = new int(val);}// 默认拷贝构造函数将会逐成员复制// MyClass(const MyClass& source) = default;~MyClass() {delete ptr;}
};int main() {MyClass obj1(10);MyClass obj2 = obj1;  // 调用默认拷贝构造函数// 修改obj2中的指针指向的值*obj2.ptr = 20;// 查看obj1和obj2中的值std::cout << "obj1 value: " << obj1.value << ", obj1 ptr value: " << *obj1.ptr << std::endl;std::cout << "obj2 value: " << obj2.value << ", obj2 ptr value: " << *obj2.ptr << std::endl;return 0;
}
obj1 value: 10, obj1 ptr value: 20
obj2 value: 10, obj2 ptr value: 20

在这个示例中,obj2通过默认拷贝构造函数从obj1创建,两个对象中的ptr成员指向同一块内存。因此,修改obj2.ptr的值也会影响obj1.ptr的值。

3.避免默认拷贝构造函数的问题 

如果类中包含动态分配的内存或其他需要独立管理的资源,应该显式定义拷贝构造函数来实现深拷贝,以避免共享同一块资源导致的问题。显式定义拷贝构造函数的示例如下:

class MyClass {
public:int value;int* ptr;MyClass(int val) : value(val) {ptr = new int(val);}// 深拷贝构造函数MyClass(const MyClass& source) {value = source.value;ptr = new int(*source.ptr);}~MyClass() {delete ptr;}
};

通过显式定义深拷贝构造函数,obj2obj1将各自持有独立的内存区域,修改obj2的内容不会影响obj1

三 虚析构函数的作用?

在C++中,虚析构函数(virtual destructor)对于正确管理多态基类对象的析构操作至关重要特别是在使用继承和多态的情况下,虚析构函数确保派生类对象在通过基类指针删除时,其析构函数能够被正确调用。

作用和原因

  1. 正确析构派生类对象:如果一个基类的析构函数不是虚的,通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类中动态分配的资源(如内存、文件句柄等)没有被正确释放,造成资源泄漏。
  2. 保证析构顺序:在继承层次中,虚析构函数确保删除对象时会按照从派生类到基类的顺序调用析构函数,先析构派生类,再析构基类。这确保了对象的资源被正确地释放。

示例 

以下是一个展示虚析构函数作用的简单示例:

#include <iostream>class Base {
public:Base() {std::cout << "Base constructor\n";}virtual ~Base() {  // 虚析构函数std::cout << "Base destructor\n";}
};class Derived : public Base {
public:int* data;Derived() {data = new int(42);std::cout << "Derived constructor\n";}~Derived() {delete data;std::cout << "Derived destructor\n";}
};void deleteObject(Base* obj) {delete obj;  // 通过基类指针删除对象
}int main() {Base* obj = new Derived();deleteObject(obj);  // 正确删除对象return 0;
}
Base constructor
Derived constructor
Derived destructor
Base destructor

详细说明

  1. 构造函数
    1. 当创建 Derived 对象时,会先调用基类 Base 的构造函数,然后调用派生类 Derived 的构造函数。    
  2. 虚析构函数
    1. 当通过基类指针 Base* 删除派生类对象 Derived 时,由于基类的析构函数是虚函数,首先会调用派生类 Derived 的析构函数,释放 Derived 类中分配的资源。
    2. 然后调用基类 Base 的析构函数,完成基类部分的资源释放。
  3. 非虚析构函数的情况
    1. 如果 Base 的析构函数不是虚函数,删除对象时只会调用 Base 的析构函数,派生类 Derived 的析构函数不会被调用,导致派生类中动态分配的资源没有被释放。
    2. #include <iostream>class Base {
      public:Base() {std::cout << "Base constructor\n";}~Base() {  // 非虚析构函数std::cout << "Base destructor\n";}
      };class Derived : public Base {
      public:int* data;Derived() {data = new int(42);std::cout << "Derived constructor\n";}~Derived() {delete data;std::cout << "Derived destructor\n";}
      };void deleteObject(Base* obj) {delete obj;  // 通过基类指针删除对象
      }int main() {Base* obj = new Derived();deleteObject(obj);  // 错误删除对象return 0;
      }
      

Base constructor
Derived constructor
Base destructor

 可以看到,没有调用 Derived 的析构函数,导致 data 没有被释放,从而造成内存泄漏。

总结:

虚析构函数在多态基类中至关重要,用于:

  1. 确保派生类对象被正确析构,释放所有资源,防止资源泄漏。
  2. 维持正确的析构顺序,从派生类到基类,确保对象的完整析构。

  因此,当类可能被继承并通过基类指针或引用操作时,应将析构函数声明为虚函数。              

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/31314.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IPython的使用技巧

1、解释说明 IPython是一个强大的Python交互式shell&#xff0c;它提供了丰富的功能&#xff0c;如自动补全、历史记录、内置帮助等。IPython使得在命令行下编写和测试Python代码变得更加方便和高效。 2、使用示例 安装IPython&#xff1a; pip install ipython启动IPython…

亲测5个电脑浏览器高效技巧,保证让你搜索效率倍增!

虽然我们每个人的电脑基本每天都会用到浏览器&#xff0c;但你会发现有的人用起浏览器就是噼里啪啦的&#xff0c;找他要个什么网站他都能快速找到&#xff0c;而有的人&#xff0c;经常打开的是广告搜索的网页&#xff0c;找不到搜索的答案非常慢。小编今天就来跟你分享一下我…

LeetCode 热题100 --哈希

哈希 哈希&#xff0c;有限空间映射一个无限的空间。在空间内&#xff0c;有序化进行快速查询。 用空间换时间。 1.两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组…

深度神经网络:开启人工智能的新篇章

在人工智能的浩瀚星空中&#xff0c;深度神经网络&#xff08;Deep Neural Networks, DNNs&#xff09;无疑是那颗最为璀璨夺目的星辰。自2006年深度学习的概念被重新发掘以来&#xff0c;深度神经网络凭借其强大的模式识别能力和卓越的数据处理效率&#xff0c;引领了人工智能…

【面试干货】Java中的访问修饰符与访问级别

【面试干货】Java中的访问修饰符与访问级别 1、public2、protected3、默认&#xff08;没有访问修饰符&#xff09;4、private &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;访问修饰符用于控制类、变量、方法和构造器…

MATLAB 中,fopen 和 fgetl 函数用于文件操作

逐行读文件内容 fopen 和 fgetl 读结构体 在 MATLAB 中&#xff0c;fopen 和 fgetl函数用于文件操作。 fopen 用于打开一个文件并返回一个文件标识符&#xff0c;而 fgetl 用于从该文件中读取一行文本。 对于 MATLAB R2018b 版本&#xff0c;这些函数的用法没有显著变化&a…

使用 BroadcastChannel 进行跨页面通信

在现代 Web 应用程序中&#xff0c;有时候我们需要在不同的页面之间进行通信&#xff0c;例如在一个页面上的操作需要更新另一个页面上的内容。这时候&#xff0c;BroadcastChannel 可以成为一个非常有用的工具。BroadcastChannel 允许我们在不同的浏览器标签页或者不同的窗口之…

哈尔滨高校哪些系统需要做等保

高校需要进行等保的系统类别 高校的信息系统安全等级保护工作是根据《网络安全法》和相关政策法规要求进行的&#xff0c;目的是保护信息化发展、维护国家信息安全&#xff0c;确保信息系统的安全稳定运行。根据等保2.0标准&#xff0c;高校的信息系统可以分为不同的安全等级&…

Java中的测试驱动开发(TDD)实践

Java中的测试驱动开发&#xff08;TDD&#xff09;实践 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 引言 测试驱动开发&#xff08;TDD&#xff09;是一种…

分享:MoneyPrinterTurbo只需一个视频主题或关键词全自动生成一个高清的短视频

MoneyPrinterTurbo是基于原有的MoneyPrinter项目进行优化和重构后推出的新版本。它利用先进的AI技术&#xff0c;通过用户提供的视频主题或关键词&#xff0c;全自动生成视频文案、素材、字幕以及背景音乐&#xff0c;并最终合成高清的短视频。 功能特性 AI智能文案生成&…

问题解决:Problem exceeding maximum token in azure openai (with java)

问题背景&#xff1a; Im doing a chat that returns queries based on the question you ask it in reference to a specific database. For this I use azure openai and Java in Spring Boot. 我正在开发一个聊天功能&#xff0c;该功能根据您针对特定数据库的提问返回查询…

学习新语言方法总结(一)

随着工作时间越长&#xff0c;单一语言越来越难找工作了&#xff0c;需要不停地学习新语言来适应&#xff0c;总结一下自己学习新语言的方法&#xff0c;这次以GO为例&#xff0c;原来主语言是PHP &#xff0c;自学GO 了解语言特性&#xff0c;知道他是干嘛的 go语言&#xff0…

Spring 面试题,静态代理和动态代理的区别是什么?什么是 AOP 编程?

在编程领域&#xff0c;代理模式是一种非常常见的设计模式&#xff0c;它的主要思想就是通过引入一个中介来控制对象的访问。而在Java的世界里&#xff0c;我们通常会遇到两种代理模式&#xff0c;也就是静态代理和动态代理。 首先&#xff0c;我们来理解一下静态代理。静态代理…

Golang | Leetcode Golang题解之第171题Excel列表序号

题目&#xff1a; 题解&#xff1a; func titleToNumber(columnTitle string) (number int) {for i, multiple : len(columnTitle)-1, 1; i > 0; i-- {k : columnTitle[i] - A 1number int(k) * multiplemultiple * 26}return }

PyTorch下的5种不同神经网络-一.AlexNet

1.导入模块 导入所需的Python库&#xff0c;包括图像处理、深度学习模型和数据加载 import osimport torchimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import Dataset, DataLoaderfrom PIL import Imagefrom torchvision import models, transf…

怎么添加网页到桌面快捷方式?

推荐用过最棒的学习网站&#xff01;https://offernow.cn 添加网页到桌面快捷方式&#xff1f; 很简单&#xff0c;仅需要两步&#xff0c;接下来以chrome浏览器为例。 第一步 在想要保存的网页右上角点击设置。 第二步 保存并分享-创建快捷方式&#xff0c;保存到桌面即可…

Docker定位具体占用大量存储的容器

监控告警生产环境的服务器磁盘分区使用率大于90%&#xff0c;进入服务器查看Docker 的 overlay2 存储驱动目录中占用很大&#xff0c;很可能是某个容器一直在打印日志&#xff0c;所以需要定位到是哪个容器&#xff0c;然后进行进一步排查。 然后进入到overlay2中查看是哪个目录…

【第13章】进阶调试思路:如何安装复杂节点IP-Adapter?(安装/复杂报错/节点详情页/精读)ComfyUI基础入门教程

🎈背景 IP-Adapter这个名字,大家可能听说过,可以让生成的结果从参考图中学习人物、画风的一致性,在目前是比较实用的一个节点,广泛的用于照片绘制、电商作图等方面。 但同时,这个节点也是比较难安装的一个节点。 所以,这节课,我们就通过一个案例,来学习如何在Comf…

MySQL----彻底卸载(附带每一步截图)

停止mysql服务 打开任务管理器&#xff0c;点击服务&#xff0c;找到mysql服务&#xff0c;这里我的是MySQL57&#xff0c;找到mysql服务后选中&#xff0c;点击右键选择停止服务 删除mysql服务 winR打开命令框&#xff0c;输入cmd打开cmd控制台或者电脑左下角输入cmd搜索&…

算法导论 总结索引 | 第四部分 第十五章:数据结构的扩张

1、动态规划&#xff08;dynamic programming&#xff09;与分治方法相似&#xff0c;都是通过组合子问题的解 来求解原问题 分治方法 将问题划分为互不相交的子问题&#xff0c;递归地求解子问题&#xff0c;再将它们的解组合起来。求出原问题的解 与之相反&#xff0c;动态规…