C++类的构造函数、析构函数、拷贝构造函数、赋值函数和移动构造函数详细总结
1. 五种函数介绍
构造函数:负责对象的初始化工作,构造函数可以重载,但不可以在构造函数前加virtual
析构函数:负责在撤销对象前,完成清理工作(释放内存),析构函数不可以重载,一个类中有且只有一个析构函数
拷贝构造函数:一种特殊的构造函数,用同类的对象去构造和初始化另一个对象。函数名和类名一致,只有一个参数,这个参数是一个被const修饰的本类型引用变量
赋值构造函数:当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数,就是重载了=操作符,去完成对应的对象赋值操作(这里涉及深浅拷贝问题)
移动构造函数:使用一个右值来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。
2. 左值&右值怎么区分?怎么看?
判断方法: 可以取地址的变量就是左值,不可以的就是右值
| 类型 | 含义 |
|---|---|
| 泛左值 (glvalue) | 一个表达式,其值可确定某个对象或函数的标识 |
| 纯右值 (prvalue) | 符合下列之一的表达式: ① 计算某个运算符的操作数的值(这种纯右值没有结果对象) ② 初始化某个对象(称这种纯右值有一个结果对象) |
| 亡值 (xvalue) | 代表它的资源能够被重新使用的对象或位域的泛左值 (通过移动构造函数) |
| 左值 (lvalue) | 非亡值的泛左值 |
| 右值 (rvalue) | 纯右值或亡值 |
注意:亡值就是将亡值,同一个概念。
3. 匿名对象的3种使用情况
情况1:没有被对象接收,执行完所在行之后立刻调用析构函数,释放其所占内存。
情况2:用来初始化对象,用匿名对象来初始化新对象时,可以这么理解:匿名对象被创建,但被用来初始化新对象,相当于匿名对象变成有名对象。因此只存在拷贝构造。
情况3:用来赋值给对象,用匿名对象来赋值给已存在对象时,此时会发生赋值构造,是会调用赋值函数的,同样执行完该行之后匿名对象所占内存被释放。
针对三种情况举例测试:
点击展开[匿名对象]测试代码
class Person{
public:
// 析构函数
~Person() { std::cout << "destroy...\n";}
// 构造函数
Person() { std::cout << "default constructor...\n"; }
// 赋值函数
Person& operator=(const Person& p) {
std::cout << "assign function...\n";
return *this;
}
};
Person f() { return Person(); }
int main()
{
// 情况一:匿名对象没有被对象接收
Person();
// 情况二:用建匿名对象来初始化对象
Person p_2 = Person(); // 等价于 Person p_2 = f();
// 情况三:用匿名对象来赋值给已存在对象
Person p_3;
p_3 = f();
return 0;
}
4. 代码详细验证每个函数调用情况
代码详细简单,每一步都做过注释,很好理解。
点击展开[函数调用]测试代码
class Person
{
public:
// 析构函数
~Person() { std::cout << "destroy...\n";}
// 默认构造函数
Person() { std::cout << "default constructor...\n"; }
// 拷贝构造函数
Person(const Person& p) { std::cout << "copy constructor...\n"; }
// 移动构造函数
//Person(Person&& p) { std::cout << "move constructor...\n"; }
// 赋值函数
Person& operator=(const Person& p) {
std::cout << "assign function...\n";
return *this;
}
};
// 1.调用拷贝构造函数
void f_1(Person p) {}
// 2.不会调用拷贝构造函数
void f_2(Person& p) {}
// 3.调用默认构造函数
Person f_3() {
Person p;
return p;//注意p它是一个将亡值,如果此时存在移动构造是会调用移动构造的
}
// 4.调用默认构造函数
Person f_4() { return Person(); }
4.1 测试 f_1 函数(函数形参测试 -- 值传递)
void f_1(Person p) {}
测试代码:
Person p_1;
f_1(p_1);
运行结果:

结果分析:
第一个默认构造:声明 [对象p_1] 时调用;
第二个拷贝构造:[对象p_1] 传值给 [形参对象p] 时调用;
第三个析构函数:函数结束,形参对象p 占用内存被释放。
4.2 测试 f_2 函数(函数形参测试 -- 引用传递)
void f_2(Person& p) {}
测试代码:
Person p_2;
f_2(p_2);
运行结果:

结果分析:
第一个默认构造:声明 [对象p_2] 时调用;
注意:因为函数形参是该类型的对象引用,所以不存在拷贝构造,函数执行完后也就不存在内存被释放。
4.3 测试 f_3 函数(函数返回值测试 -- 具名对象)
Person f_3() {
Person p;
return p;//注意p它是一个将亡值,如果此时存在移动构造是会调用移动构造的
}
4.3.1 测试代码-1(初始化新对象)
Person p_3 = f_3();
运行结果:

结果分析:
第一个默认构造:函数体中声明 [对象p] 时调用;
第二个拷贝构造:函数体中创建的 [对象p] 被返回,然后赋值给 [对象p_3] 时调用(注意:由于函数体中的对象不是 [匿名对象],无法直接转化为有名 [对象p_3],需要调用拷贝构造去将[对象p]的数据拷贝给[对象p_3]);
第三个析构函数:是执行完拷贝构造之后,将在函数体中创建的 [对象p] 析构时调用。
重点: 如果此时类中存在 移动构造函数(把注释去掉),那么是不会调用拷贝构造函数的,而是调用 移动构造函数(避免大量数据的拷贝),结果如下:

4.3.2 测试代码-2(赋值给已存在对象)
Person p_3;
p_3 = f_3();
运行结果:

结果分析:
第一次构造函数:声明 [对象p_3] 时调用;
第二次构造函数:函数体中声明 [对象p] 时调用;
第三次拷贝构造:将函数体中声明的 [对象p] 的数据拷贝给一个 [临时对象] 时调用(如果把类中 移动构造函数的注释去掉 ,那么是不会调用拷贝构造函数的,而是调用移动构造函数);
第四次析构函数:函数体中声明的 [对象p] 被析构时调用;
第五次赋值函数:是将 [临时变量] 的数据复制给 [对象p_3] 时调用;
第六次析构函数:[临时变量] 完成拷贝构造之后,调用析构函数,释放所占内存。
为什么不直接调用拷贝构造就完事了呢?
在 【4.3.1 测试代码-1】中可知,用函数返回的对象来初始化新对象时只需要调用一次拷贝构造或移动构造就行,是不存在复制函数被调用的情况,那为什么4.3.2 测试代码-2就需要调用?
原因剖析:
原因一:执行Person p_3导致创建了 [对象p_3] ,而函数返回的也是一个被创建的对象(不是匿名函数),在两个已存在对象之间使用=是赋值操作,是不会调用拷贝构造或移动构造的。
原因二:由于原因一,导致函数返回的 [对象p] 没用被使用,函数返回的 [对象p] 会被析构函数释放其所占内存。
因为原因二会导致函数返回的 [对象p] 会被析构,注意此时都还没有赋值给 [对象p_3] 咧,所以 [对象p] 在析构之前需要把 [对象p] 的数据拷贝给一个 [临时对象] (调用拷贝构造),完成拷贝之后 [对象p] 就被析构。最后把 [临时对象] 的数据赋值给已存的 [对象p_3] 即可 (调用赋值函数),完成赋值之后 [临时对象] 就被析构。
至此上面的所有步骤分析完成。
4.4 测试 f_4 函数(函数返回值测试 -- 匿名对象)
Person f_4() { return Person(); }
测试代码:
Person p_4 = f_4();
运行结果:

结果分析:
声明 [对象p_4] 的同时直接使用 [匿名对象] 去初始化,此时 [匿名对象] 会直接转化成 [有名对象p_4] (匿名对象使用情况2),所以就这个情况而已不会调用拷贝构造函数和移动构造函数。
如果是如下分开情况(先声明,再用匿名对象赋值):
Person p_4;
p_4 = f_4();
运行结果:

结果分析:
第一个默认构造:声明 [对象p_4] 时调用;
第二个默认构造:函数体中的 [匿名对象] 被创建时调用的;
第三个赋值函数:[匿名对象] 赋值给已存在的 [对象p_4] 时调用(匿名对象使用情况3,用匿名对象来赋值给已存在对象时,此时会发生赋值构造,是会调用赋值函数的,同样执行完该行之后匿名对象所占内存被释放);
第四个析构函数:完成赋值之后,[匿名对象] 所占内存被释放时调用。
5. 完整测试代码
点击展开完整测试代码
#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::string;
class Person
{
public:
// 析构函数
~Person() { std::cout << "destroy...\n"; }
// 默认构造函数
Person() { std::cout << "default constructor...\n"; }
// 拷贝构造函数
Person(const Person& p) { std::cout << "copy constructor...\n"; }
// 移动构造函数
//Person(Person&& p) { std::cout << "move constructor...\n"; }
// 赋值函数
Person& operator=(const Person& p) {
std::cout << "assign function...\n";
return *this;
}
};
// 1.调用拷贝构造函数
void f_1(Person p) {}
// 2.不会调用拷贝构造函数
void f_2(Person& p) {}
// 3.调用默认构造函数
Person f_3() {
Person p;
return p;//注意p它是一个将亡值(右值的一种)
}
// 4.调用默认构造函数
Person f_4() { return Person(); }
int main()
{
//Person();
//Person p = Person();
cout << "------测试f_1函数------\n";
Person p_1;
f_1(p_1);
cout << "-----------------------\n\n";
cout << "------测试f_2函数------\n";
Person p_2;
f_2(p_2);
cout << "----------------------\n\n";
cout << "------测试f_3函数------\n";
Person p_3;
p_3 = f_3();
//Person p_3 = f_3();
cout << "----------------------\n\n";
cout << "------测试f_4函数------\n";
Person p_4 = f_4();
cout << "----------------------\n\n";
return 0;
}
C++类的构造函数、析构函数、拷贝构造函数、赋值函数和移动构造函数详细总结的更多相关文章
- c++类大四个默认函数-构造函数 析构函数 拷贝构造函数 赋值构造函数
每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数).对于任意一个类A,如果不编写上述函数,C++编译器将自动为A 产生四个缺省的函数,例如: A ...
- C++初阶(类的访问权限以及封装+this指针+构造函数+析构函数+拷贝构造函数+参数列表+友元+内部类)
面向过程与面向对象 C语言是面向过程的,关注的是过程(函数),分析出求解问题的步骤,通过函数调用逐步解决问题. C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成. ...
- C++(1)C++类四个默认函数---构造函数、析构函数、拷贝函数、赋值函数
C++构造函数和析构函数 默认构造函数指不带参数或者所有参数都有缺省值的构造函数!!! (1)构造函数.析构函数与赋值函数 构造函数.析构函数与赋值函数是每个类最基本的函数.它们太普通以致让人容易麻痹 ...
- C++类构造函数、拷贝构造函数、复制构造函数、复制构造函数、构造函数显示调用和隐式调用
一. 构造函数是干什么的 class Counter { public: // 类Counter的构造函数 // 特点:以类名作为函数名,无返回 ...
- C++构造函数和拷贝构造函数详解
构造函数.析构函数与赋值函数是每个类最基本的函数.它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险. 每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含 ...
- C++编译器将自动为类产生四个缺省的函数
构造函数.析构函数与赋值函数是每个类最基本的函数.它们太普通以致让人容易麻痹大意, 其实这些貌似简单的函数就象没有顶盖的下水道那样危险. 每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包 ...
- CPP_类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数
类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数 // person.h #ifndef _PERSON_H_ #define _PERSON_H_ class Person{ public : ...
- C/C++面试题:编写类String的构造函数、析构函数和赋值函数。
转https://www.cnblogs.com/alinh/p/9636500.html 考点:构造函数.析构函数和赋值函数的编写方法出现频率:☆☆☆☆☆已知类String的原型为: ...
- C++中构造函数,拷贝构造函数和赋值函数的区别和实现
C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法.下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象 ...
- C++中的构造函数,拷贝构造函数,赋值函数
C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法.下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象 ...
随机推荐
- java mybatisplus+springboot服务器跨域问题
项目本地增删改测试正常,上传到 阿里服 页面出现了 跨域报错问题! 解决方案:添加一个过滤器 package com.rl;import org.springframework.stereotyp ...
- 基础vue的一些知识补充
一.:disabled 该属性能接受布尔值,可以用于元素的使用.当值为true时,该元素将无法被使用,如button的disabled属性被设置为true后,将无法被点击,input的disabled ...
- xshell和xftp绿色版下载
下载地址:https://www.xshell.com/zh/free-for-home-school/ 点击后页面如下,输入自己的姓名和邮箱然后点击下载即可.登录自己的邮箱获取下载链接.
- FastReport和RDLC报表
最近在做报表的时候第一次接触到RDLC报表,对比于之前使用的FastReport报表来说,在使用体验上个人目前感觉RDLC灵活性相对较差,尤其是表格的格式多样的时候,不易修改.RDLC应用于格式简单的 ...
- SaltStack学习笔记
SaltStack三大功能: 1. 远程执行 2. 配置管理 (状态) 3.云管理 运维三板斧:监控.执行.配置 四种运行方式: 1.Local 2. Minion/Master C/S架构 3 ...
- GPS数据处理
GPS数据处理 题目内容: NMEA-0183协议是为了在不同的GPS(全球定位系统)导航设备中建立统一的BTCM(海事无线电技术委员会)标准,由美国国家海洋电子协会(NMEA-The Nationa ...
- VOIP(SIP)呼叫环境及流程试验
宿主机:win11 IP: .1 PHONE: 102 虚拟机: v11 IP: .129 SIP SERVER 虚拟机: v10 IP: .128 ...
- go tour 笔记 day1
go get 访问github太慢需要配置代理,设置环境变量 http_proxy=http://127.0.0.1:xxxx 算是比较方便的一种 ref: https://blog.csdn.net ...
- 关于 'vue-cli-service' 不是内部或外部命令,也不是可运行的程序 或批处理文件 的处理
关于 npm run serve 之后 'vue-cli-service' 不是内部或外部命令,也不是可运行的程序 或批处理文件 一.安装node.js 去官网安装Node.js(地址:https:/ ...
- mybatis的xml中#{}和${}区别
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入.初步编译后的sql语句是" ...