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

类和对象深层回顾:(内含面试题)拷贝构造函数,传值返回和传引用返回区别

🎬 胖咕噜的稞达鸭:个人主页

🔥 个人专栏: 《数据结构》《C++初阶高阶》《算法入门》

⛺️技术的杠杆,撬动整个世界!

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

本文的学习你将深入理解:

问题一:构造函数的本质是什么,为什么需要构造函数?

问题二:拷贝构造函数和构造函数的区别,已经有构造函数了,为什么还需要拷贝构造函数?

面试题插入:问题三:传值返回和传引用返回的区别,优劣分析?

通过拷贝构造函数的特点理解:

  1. 特点一:拷贝构造函数是构造函数的一个重载

怎么理解:

问题一:构造函数的本质是什么,为什么需要构造函数?

构造函数的本质就是替代我们之前实现stack和Date类中的Init功能,构造函数自动调用就可以替代Init.
比如说红黑树中迭代器,list容器的迭代器要实现遍历,红黑树的一个节点和它的左右子树需要有联系才能实现 “ 迭代器 operator++()“”迭代器 operator --() ",这就需要对迭代器定义构造函数,同理list迭代器要遍历,但是链表的节点是不连续的,不像数组那样可以靠下标来访问下一个位置的元素。

问题二:拷贝构造函数和构造函数的区别,已经有构造函数了,为什么还需要拷贝构造函数?

拷贝构造函数的作用就是用已有的对象初始化新的对象,同时解决了默认拷贝带来的问题,

默认构造是有缺陷的,编译器【对于自定义类型成员变量会调用它的拷贝构造】【对于内置类型自动生成默认拷贝构造,但默认实现是 “浅拷贝”(逐字节复制,值拷贝)】。而当类包含堆内存、文件句柄、网络连接等 “需要手动管理的资源” 时,浅拷贝会导致严重问题

资源重复释放:两个对象的指针指向同一块堆内存,析构时会两次调用 delete,导致程序崩溃;
野指针:一个对象修改了堆内存中的数据,另一个对象会受影响;一个对象析构释放内存后,另一个对象的指针变成野指针。

此时必须手动实现拷贝构造函数,通过== “深拷贝”(不仅复制指针,还复制指针指向的资源)==解决这些问题,保证对象拷贝的安全性。

重载的核心要求是 “参数列表不同”(参数类型、数量或顺序不同)。

class Date {
public:// 重载1:无参构造Date() : _year(0), _month(0), _day(0) {}// 重载2:带参构造Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}// 重载3:拷贝构造(构造函数的特殊重载)Date(const Date& d) : _year(d._year), _month(d._month), _day(d._day) {}
private:int _year, _month, _day;
};
  1. 特点二:拷贝构造函数是构造函数的一种特殊重载,具体体现在拷贝构造函数的第一个参数必须是类类型对象的引用(Date(const Date& d)

如果使用Date(Date d)就会报错,如下图,所以不可以传值方式写一个拷贝构造的参数,会形成无穷递归调用
在这里插入图片描述
3. 特点三:如果一个类实现了析构并且释放资源,那么就需要显示写拷贝构造,否则不需要。

全是内置类型没有指向什么资源的,编译器自动生成的拷贝构造就可以实现拷贝,不需要显示写拷贝构造;

有内置类型但是指向了一个资源,或者内部有内存的申请自动生成的拷贝构造(浅拷贝,值拷贝)不符合我们的需要,只拷贝了值,没有拷贝指向的资源,所以要显示实现拷贝构造。

一个自定义类型,里面都是内置类型,而且没有指向资源,那么编译器自动生成的拷贝构造就可以实现拷贝,不需要显示实现拷贝构造。

  1. 特点四:传值返回和传引用返回的区别(传值返回产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝。)

面试题插入:问题三:传值返回和传引用返回的区别

返回类型传值返回传引用返回
产生拷贝了吗产生一个临时对象调用拷贝构造贝引用返回,返回的是返回对象的别名(引用),没有产生拷贝
受生命周期影响临时对象不受原函数生命周期影响如果返回对象是局部域的局部对象,受生命周期影响,函数结束销毁,此时的引用就是野指针野引用
适用于哪种场景内置类型没有指向资源返回静态对象、全局对象或堆上持久存在的对象

这里给一道题:传值返回和传引用返回认识的巩固
假设有一个类,它的构造和析构函数如下:

class Buffer {
private:char* data; // 指向堆内存的指针size_t size;
public:Buffer(size_t s) : size(s) {data = new char[size];cout << "Buffer 构造,分配 " << size << " 字节" << endl;}~Buffer() {delete[] data;cout << "Buffer 析构,释放 " << size << " 字节" << endl;}// 为了简化,省略拷贝构造、赋值运算符等
};

现在用两个成员函数来调用,并指出有什么问题?

// 函数1:传值返回
Buffer createBuffer1(size_t s) {Buffer buf(s);return buf;
}// 函数2:传引用返回
Buffer& createBuffer2(size_t s) {Buffer buf(s);return buf;
}int main() {// 场景1Buffer b1 = createBuffer1(1024);// 场景2Buffer& b2 = createBuffer2(2048);return 0;
}

这里我们转入visual stdio来编译一下:
在这里插入图片描述

可以编译出结果,但是程序是有问题的,

传值返回+重复析构问题的解决:

此时函数1传值返回,默认是浅拷贝,产生一个临时对象调用拷贝构造拷贝,函数在构造的时候没有指向额外的资源,所以浅拷贝一个一个字节进行拷贝的时候可以完成拷贝,也就可以打印出图中的结果,如果指向额外的资源,
默认的浅拷贝后的buffer,临时对象,b1的data会同时指向一块空间,会发生 “三次析构(buf→临时对象→b1)、三次delete[]”。
第一次析构是buffer的析构,第二次析构是临时对象的析构,重复析构,第三次析构是(b1)析构,重复析构问题。
所以要有一个深拷贝构造函数,确保拷贝时复制堆内存资源,而非仅复制指针

 Buffer(const Buffer& other) :size(other.size){data = new char[size];memcpy(data, other.data, size);cout << "Buffer 深拷贝,构造" << size << "字节" << endl;}

在这里插入图片描述
传引用返回+野引用 问题的解决:

此时函数2传引用返回,返回的是返回对象的别名(引用),没有法生拷贝,但是此时的buf(s)是函数2的局部域的局部对象,出了作用域就会销毁,此时的引用就像一个野引用,函数析构的时候会析构掉buf,里面就全是野指针,返回会返回野指针。所以为了避免出现这个问题,将返回对象设置为不可以被析构的,在Buffer前加上static。静态全局对象,函数结束后不析构。

Buffer& createBuffer2(size_t s) {static Buffer buf(s);// 静态对象,函数结束后不析构return buf;
}

在这里插入图片描述

这里我们再用内置类型来演示一下传值返回+重复析构问题:
重点观察stack类型的输出打印:

#include<iostream>using namespace std;
typedef int STDataType;
class stack
{
private:STDataType* _a;size_t _capacity;size_t _top;
public:stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr) { perror("malloc failed"); return; }_capacity = n;_top = 0;}~stack(){cout << "~stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}};
class Myqueue
{
public:
private:stack pushst;stack popst;
};int main()
{stack st1;stack st2 = st1;
}

在这里插入图片描述
所以要加上深拷贝:

stack(const stack& other)
{_a = (STDataType*)malloc(sizeof(STDataType) * other._capacity);if (_a == nullptr) { perror("malloc failed"); return; }memcpy(_a, other._a, sizeof(STDataType) * other._top);_top = other._top;_capacity = other._capacity;
}

other._capacity 是被拷贝对象(other)的容量,保证新栈的容量与原栈一致。
sizeof(STDataType) * other._capacity 计算所需内存总字节数(STDataType 是栈中元素的类型,如 int)。
malloc 动态分配堆内存,并强制转换为 STDataType* 类型(与 _a 的指针类型匹配)。
目的:通过新开辟内存,避免与 other 共享同一块堆空间(深拷贝的核心)。
memcpy(_a, other._a, sizeof(STDataType) * other._top);将other._a中的元素拷贝到_a中,仅仅拷贝有效个元素,other._top*sizeof(STDataType), 实现元素的深拷贝。
在这里插入图片描述
在这里插入图片描述

#include<iostream>using namespace std;
typedef int STDataType;
class stack
{
private:STDataType* _a;size_t _capacity;size_t _top;
public:stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr) { perror("malloc failed"); return; }_capacity = n;_top = 0;}stack(const stack& other){_a = (STDataType*)malloc(sizeof(STDataType) * other._capacity);if (_a == nullptr) { perror("malloc failed"); return; }memcpy(_a, other._a, sizeof(STDataType) * other._top);_top = other._top;_capacity = other._capacity;}~stack(){cout << "~stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}};
class Myqueue
{
public:
private:stack pushst;stack popst;
};int main()
{Myqueue my1;Myqueue my2 = my1;stack st1;stack st2 = st1;
}

在这里插入图片描述
总结:传值返回传引用返回的区别分析:

返回方式说明优势(使用场景)劣势(不适用场景)
传值返回传值返回产生一个临时对象调用拷贝构造返回的是临时对象,不受生命周期限制传值返回是浅拷贝,仅复制指针,内部有资源申请,会引发重复析构,需要深拷贝
传引用返回返回对象的别名(引用)不产生拷贝受到局部域生命周期影响,函数结束后会对象销毁,析构后返回对象里面都是野引用,需要加static

最后来做一道题:题目:
设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为?( )

C c;int main(){A a;B b;static D d;return 0}

1、类的析构函数调用一般按照构造函数调用的相反顺序进行调用,但是要注意static对象的存在, 因为static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象
2、全局对象先于局部对象进行构造
3、局部对象按照出现的顺序进行构造,无论是否为static
4、所以构造的顺序为 c a b d
5、析构的顺序按照构造的相反顺序析构,只需注意static改变对象的生存作用域之后,会放在局部对象之后进行析构
6、因此析构顺序为B A D C
先定义的后析构。

在这里插入图片描述

http://www.dtcms.com/a/545379.html

相关文章:

  • Rust环境搭建
  • 潍坊做网站价格个人网页设计软件
  • LeetCode 刷题【138. 随机链表的复制】
  • 做可转债好的网站wordpress不用邮件确认
  • Rust 中的减少内存分配策略:从分配器视角到架构设计 [特殊字符]
  • MySQL8.0.30 版本中redo log的变化
  • 0430. 扁平化多级双向链表
  • 网站关键词多少合适icp备案服务码
  • TypeScript声明合并详解二
  • 做网站组织架构my77728域名查询
  • 深度学习------图像分割项目
  • 【深度学习2】线性回归的从零开始实现
  • LeetCode第2题:两数相加及其变种(某大厂面试原题)
  • Java 字符编码全解析:从乱码根源到 Unicode 实战指南
  • SpringBoot 高效工具类大全
  • 自己做网站用软件wordpress电商优秀
  • 百度网站建设中的自由容器网站用哪个数据库
  • 入侵检测系统——HIDS和NIDS的区别
  • C语言多进程创建和回收
  • 仓颉编程语言:控制流语句详解(if/else)
  • 专利撰写与申请核心要点简报
  • AI搜索引擎num=100参数移除影响深度分析:内容标识与准确性变化
  • NJU-SME 人工智能(三) -- 正则化 + 分类 + SVM
  • 【数据库】表的设计
  • 深圳制作网站建设推广第一网站ppt模板
  • 点网站建设广州专业网站建设哪家公司好
  • 仓颉语言构造函数深度实践指南
  • DTAS 3D-尺寸公差分析定制化服务与解决方案的专家-棣拓科技
  • 永康营销型网站建设wordpress自定义作者连接
  • linux NFS(网络文件系统)挂载完整指南