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

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.mrgr.cn/news/29093.html

相关文章:

  • C++核心编程和桌面应用开发 第四天(构造/析构函数)
  • 【python设计模式2】创建型模式1
  • (185)时序收敛--->(35)时序收敛三五
  • C++ 科目二 [dynamic_cast]
  • 企业开发时,会使用sqlalchedmy来构建数据库 结构吗? 还是说直接写SQL 语句比较多?
  • makefile 的语法(7):函数 word wordlist words firstword lastword ;
  • 一种快速遍历二叉树的方法
  • 构建高效、精准的动物情绪分类模型:基于深度学习的技术实践与探索
  • 认知小文3《打破桎梏,编程与人生的基本法则》
  • 程序中类与对象的理解(面向对象思想)
  • kali——foremost的使用
  • 中秋佳节,月圆人团圆
  • 【数据结构篇】~链表算法题3(环形链表)
  • 【时时三省】linux应用层开发经验总结
  • 【计算机基础】关于存储的各种概念
  • 《沈阳体育学院学报》
  • 【每日一题】LeetCode 2332.坐上公交的最晚时间(数组、双指针、二分查找、排序)
  • 笔记:BLIP源码之(2)模型是如何定义的
  • 机器学习、计算机视觉与NLP:从基础到深度学习的综合指南
  • Android 微信,手机文件管理,通过自己软件打开