当前位置: 首页 > article >正文

C++第六节课 - 拷贝构造函数

一、复习构造函数

一般情况下,构造函数都需要我们自己去写!

但是有两种情况自己可以不用去写构造函数:

  • 内置类型成员都有缺省值,且初始化符合我们的要求;
  • 全是自定义类型的构造,且这些类型都定义默认构造;

且对于不同的编译器来说,有的可能也会对内置类型的成员变量进行初始化!

下面分别是在VS2013和VS2019中的结果:

二、复习析构函数

return之后调用析构函数!

注意点:假如当前有一个成员变量如下:

这里的_a[100]需不需要使用析构函数来释放?

答案是:不需要,析构函数用于释放动态申请的资源,例如下面所示,对于静态的资源(在栈上),不需要我们去手动的释放,出了作用域会自动销毁!(如果定义的是全局对象或者静态对象->不在堆上不需要自己手动释放的,都不需要用析构函数来释放!这些程序结束后会自动销毁!)

结论:什么时候需要写析构函数,什么时候不写析构函数?

  • 一般情况下,有动态申请资源,就需要显示写析构函数释放资源;
  • 没有动态申请的资源,不需要写析构;
  • 需要释放资源的成员都是自定义类型,不需要写析构;

 例如下面两种不需要构建析构函数:

三、拷贝构造函数

补充知识点:

C++规定:

  • 内置类型可以直接拷贝;
  • 自定义类型必须调用拷贝构造函数实现拷贝;
  • 执行func(Date d)这一函数,实际上是先去Date这个类中执行拷贝构造函数,再运行函数体内的代码;

不仅仅是在传参上面,正常的赋值也是这样的;

假设这里不传入引用:如果我们传入d1,因为d1是自定义类型,传入d1需要调用拷贝构造函数,而调用拷贝构造函数需要传入d1......此时引起无限循环!

拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。 

因此,传入引用或者指针都可以!

但是一般我们传引用,因为引用比指针用着更加方便;

此时this就是d2,d在这里就是d1(d是d1的别名)

拷贝构造函数传引用调用的时候一般最好加上const!

加上const之后,此时d的值不能别修改,如果我们意外将数据传反了,编译器会主动提示,方便我们检查! (权限可以缩小,不能方法!)

注意点:权限的private的变量只是在类外的实例化对象不能使用,但是在类内还是能调用的!

例如这里的312行的_year和320的_year是同一个_year吗?

答案:不是的!320的_year只是变量的声明,312中的_year是this的year!

一个是d2的_year,一个是d1的year!

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

  • 内置类型成员完成值拷贝/浅拷贝;(类似于memcpy,一个字节一个字节的值拷贝过去)
  • 自定义类型成员会调用他的拷贝构造;

例如下面:

将d1进行初始化赋值,d2会按照字节拷贝d1;

这里可以发现,日期类可以不写,默认生成的拷贝构造函数就够用

但是有的情况下我们必须要自己写拷贝构造函数:

如果使用默认的拷贝构造函数,当st1销毁的时候,会调用析构函数来清理,其实st2也会调用析构函数来清理:会造成同一块空间被清理两次!

且对于这种来说,后定义的会先析构:st2会先进行析构

因此,此时就需要自己实现深拷贝!(关于深拷贝我们后面再讲)

且如果此时st1.push(x)在st2中也有! 两者产生影响!

自己实现的析构函数完成深度拷贝的代码如下:

总结:为什么有的时候不能使用浅拷贝?

  • 会析构两次,导致报错;
  • 对一个对象进行修改会影响另一个!

这里Data和Queue都不需要我们自己写:(分别对应左边两条准则),但是stack需要自己完成实现!

思考下面这种情况:

void func(Date& d)
{}

void func(Stack st)
{}

int main()
{

    Date d1;
    func(d1);
    Stack st1;
    func(st1);
}
  • 这里d是d1的别名,通过引用直接对d1进行操作,比较方便;
  • 如果stack这里已经实现深拷贝,如果Stack进行传值调用,要进行拷贝构造,如果stack过大,开辟的空间会非常大;
  • 如果此时传递的是对象的引用,则不会调用拷贝构造函数。函数将直接操作原始对象。

如果返回的对象出作用域不会被销毁,我们此时可以返回引用(不会调用拷贝构造函数);

如果返回的对象出作用域后被销毁,我们此时使用带引用的返回值!

如果你从一个函数返回一个对象的引用,而该对象在函数返回后超出了作用域,那么返回的引用将指向一个已经被销毁的对象。这种情况会导致未定义行为。

1. 按值返回

  • 拷贝构造:当函数返回一个类类型的对象时,通常会调用拷贝构造函数来创建返回值的副本。这意味着返回的对象是原始对象的一个拷贝,函数结束后,原始对象的生命周期不会受到影响。
  • 示例
class MyClass {  
public:  
    int value;  
    MyClass(int v) : value(v) {}  
    MyClass(const MyClass &obj) : value(obj.value) { // 拷贝构造函数  
        // 其他初始化  
    }  
};  

MyClass createObject() {  
    MyClass obj(10); // 局部对象  
    return obj;      // 返回对象的副本  
}  

int main() {  
    MyClass myObj = createObject(); // 调用拷贝构造函数  
    // myObj.value 现在是 10  
}

2. 返回值优化 (RVO)

  • 返回值优化:现代 C++ 编译器通常会应用返回值优化(Return Value Optimization, RVO),以避免不必要的拷贝。在这种情况下,编译器会直接在调用者的上下文中构造返回对象,从而省略拷贝构造的调用。
  • 示例
    MyClass createObject() {  
        return MyClass(10); // 可能会直接在调用者的上下文中构造对象  
    }

但是注意:引用的返回值不能是出了函数域就销毁的!如果是这种情况只能使用传值返回!

上面一种可以使用引用返回,下面一种不可以!

总结:拷贝构造函数典型调用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象


http://www.kler.cn/a/316886.html

相关文章:

  • 如何在Puppeteer中实现表单自动填写与提交:问卷调查
  • 一文详解java的数据类型
  • 《TCP/IP网络编程》学习笔记 | Chapter 11:进程间通信
  • 沃飞长空郭亮博士荣获中国航空航天月桂奖
  • C# 集合与泛型
  • Vue中优雅的使用Echarts的三种方式
  • go语言 数组和切片
  • vue 页面常用图表框架
  • C++ std::find函数 容器元素查找
  • 机器学习-梯度下降实验一
  • 探索味蕾新境界,品味嘴尚绝卤味的健康之旅
  • 摩尔-彭罗斯伪逆(pinv)
  • spring 的启动过程
  • Kotlin 智能类型转换与 when 表达式(八)
  • 828华为云征文 | 云服务器Flexus X实例,Docker集成搭建Redis集群
  • 实战19-详情页UI4等分
  • 浅谈C#之SynchronizationContext
  • Fyne ( go跨平台GUI )中文文档- 架构 (八)完结
  • Openpyxl 插入数据添加数据
  • leetcode 437.路径总和III
  • Gitlab runner的简单使用(一)
  • 【Redis】之Geo
  • 红黑树:强大的数据结构之插入详解,附图
  • yolov8多任务模型-目标检测+车道线检测+可行驶区域检测-yolo多检测头代码+教程
  • Spring IOC容器Bean对象管理-注解方式
  • 每日一题——第九十四题