C++六大默认成员函数
C++六大默认成员函数
- 默认构造函数
- 默认析构函数
- RAII技术
- RAII的核心思想
- 优点
- 示例
- 应用场景
- 默认拷贝构造
- 深拷贝和浅拷贝
- 默认拷贝赋值运算符
- 移动构造函数(C++11起)
- 默认移动赋值运算符(C++11起)
- 取地址及const取地址操作符重载
- 取地址操作符重载
- 常量取地址操作符重载
- 示例代码
- 输出结果
- 注意事项
- 扩展:前置++和后置++重载
- 重载规则
C++中的六大默认成员函数是编译器在特定条件下自动生成的成员函数,用于管理对象的生命周期和资源操作。它们分别是:
默认构造函数
-
作用:初始化对象,当类没有显式定义任何构造函数时生成。
-
生成条件:用户未定义任何构造函数。
-
注意:若类有其他构造函数(如带参数的构造函数),需显式使用 = default 声明默认构造函数。
class Person
{
public://Person()//{//} 不写的话默认自动生成void GetAge(){std::cout << _age << std::endl;}
private:int _age;
};int main()
{Person p;p.GetAge();
}
其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 编译器生成默认的构造函数会对自定类型成员调用的它的默认成员
函数。C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
默认析构函数
-
作用:释放对象资源,默认析构函数调用成员变量的析构函数。
-
生成条件:用户未定义析构函数。
-
注意:若类管理动态资源(如堆内存),需自定义析构函数以避免内存泄漏
class Person
{
public://Person()//{//} 不写的话默认自动生成void GetAge(){std::cout << _age << std::endl;}~Person(){}
private:int _age;
};int main()
{Person p;p.GetAge();
}
RAII技术
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种管理资源的编程技术。它通过将资源的生命周期与对象的生命周期绑定在一起,利用C++的构造函数和析构函数来自动管理资源,从而避免了手动分配和释放资源可能带来的问题,如内存泄漏、资源未正确释放等。
RAII的核心思想
- 资源在对象构造时获取:当一个对象被创建时,它的构造函数负责获取所需的资源(例如,动态内存分配、文件打开、网络连接等)。
- 资源在对象销毁时释放:当对象离开作用域或被显式删除时,其析构函数会自动释放之前获取的资源。
优点
- 异常安全性:由于资源管理由构造和析构函数自动处理,即使程序中抛出了异常,也能确保资源得到正确释放。
- 简化代码:开发者不需要手动跟踪每个资源的状态,并且可以在不使用显式的
try-finally
块的情况下保证资源的释放。 - 防止资源泄露:只要对象被正确地创建并最终销毁,资源就会被正确释放。
示例
以下是一个简单的例子,展示了如何使用RAII来管理动态分配的内存:
#include <iostream>class ResourceHandler {
private:int* data;
public:// 构造函数:资源获取ResourceHandler() {data = new int(10); // 分配资源std::cout << "Resource acquired." << std::endl;}// 析构函数:资源释放~ResourceHandler() {delete data; // 释放资源std::cout << "Resource released." << std::endl;}void showData() const {std::cout << "Data: " << *data << std::endl;}
};void useResource() {ResourceHandler handler;handler.showData();// 不需要手动释放资源,handler离开作用域时会自动调用析构函数
}int main() {useResource();return 0;
}
在这个例子中,ResourceHandler
类负责管理一个整数类型的动态分配内存。构造函数在对象创建时分配资源,而析构函数在对象销毁时释放这些资源。这样就确保了无论函数useResource
如何退出(正常结束或因异常退出),资源都会被正确释放。
应用场景
RAII不仅限于内存管理,还可以应用于其他资源类型,如文件句柄、网络套接字、数据库连接等。标准库中的智能指针(如std::unique_ptr
、std::shared_ptr
)、锁机制(如std::lock_guard
、std::unique_lock
)都是RAII原则的实际应用案例。通过使用这些工具,可以有效地减少资源管理错误,提高代码的安全性和可靠性。
默认拷贝构造
-
声明形式:ClassName(const ClassName&)
-
作用:通过已有对象初始化新对象,默认执行浅拷贝。
-
生成条件:用户未定义拷贝构造函数。
-
注意:若类包含指针或动态资源,需自定义深拷贝防止重复释放。
class Person
{
public:Person(){}Person(const Person& person){this->_age = person._age;}~Person(){}void GetAge(){std::cout << _age << std::endl;}
private:int _age;
};int main()
{Person p;p.GetAge();
}
深拷贝和浅拷贝
class Stack
{
public://初始化Stack(){_array = new int[20];}//默认生成拷贝构造//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);
}
这里我没有写实际的拷贝构造函数,这里s2调用的默认的拷贝构造,所以s2_array的地址就是s1中_array的地址,这就叫浅拷贝:
这样代码就会有问题,因为一个地址会被析构两次:
正确的方法应该是给s2的array开辟一块新的空间:
class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);
}
这样的拷贝我们称为深拷贝,再次运行程序:
默认拷贝赋值运算符
-
声明形式:ClassName& operator=(const ClassName&)
-
作用:将已有对象的值赋给另一个对象,默认浅拷贝。
-
生成条件:用户未定义拷贝赋值运算符。
-
注意:需处理自赋值问题,并在资源管理时实现深拷贝。
class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}Stack& operator=(const Stack& st){if (this != &st){_array = new int[10];_size = st._size;_capacity = st._capacity;}return *this;}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2;s2 = s1;
}
移动构造函数(C++11起)
-
声明形式:ClassName(ClassName&&)
-
作用:通过右值引用“窃取”资源,避免深拷贝开销。
-
生成条件:用户未定义拷贝操作、移动操作或析构函数。
-
注意:移动后源对象应处于有效但未定义状态(如空指针)。
class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}Stack& operator=(const Stack& st){if (this != &st){_array = new int[10];_size = st._size;_capacity = st._capacity;}return *this;}void swap(Stack& st){std::swap(_array, st._array);std::swap(_size, st._size);std::swap(_capacity, st._capacity);}//移动构造函数Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0){swap(st);}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(std::move(s1));
}
默认移动赋值运算符(C++11起)
-
声明形式:ClassName& operator=(ClassName&&)
-
作用:通过右值引用转移资源所有权。
-
生成条件:同移动构造函数。
-
注意:需正确处理自移动赋值。
class Stack
{
public://初始化Stack(){_array = new int[20];}Stack(const Stack& st){_array = new int[10];_size = st._size;_capacity = st._capacity;}Stack& operator=(const Stack& st){if (this != &st){_array = new int[10];_size = st._size;_capacity = st._capacity;}return *this;}void swap(Stack& st){std::swap(_array, st._array);std::swap(_size, st._size);std::swap(_capacity, st._capacity);}//移动构造函数Stack(Stack&& st):_array(nullptr), _size(0), _capacity(0){swap(st);}//移动复制构造Stack& operator=(Stack&& st){swap(st);st._array = nullptr;st._size = 0;st._capacity = 0;return *this;}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2;s2 = std::move(s1);
}
在C++中,前置++
和后置++
运算符可以通过成员函数或非成员函数的形式进行重载。两者的主要区别在于参数列表和返回值:
- 前置
++
:增加对象的值,并返回增加后的对象引用。 - 后置
++
:首先保存当前对象的状态,然后增加对象的值,最后返回之前保存的对象的副本。
取地址及const取地址操作符重载
在C++中,取地址操作符(&
)和常量取地址操作符(const &
)通常不需要显式地重载,因为编译器提供了默认的实现,它们分别返回对象或常量对象的内存地址。然而,在某些特定情况下,你可能想要自定义这些操作符的行为。
取地址操作符重载
当你重载取地址操作符时,你通常是为了改变其默认行为,例如返回一个代理对象的地址而不是原始对象的地址。不过这种情况非常少见,大多数时候并不需要这样做。
常量取地址操作符重载
类似地,重载常量版本的取地址操作符也是为了提供特定的行为,但同样,这并不是常见的需求。
示例代码
尽管不常见,这里还是给出如何重载这两种操作符的基本示例:
#include <iostream>class MyClass {
private:int value;
public:MyClass(int val) : value(val) {}// 重载取地址操作符int* operator&() {std::cout << "非const取地址操作符被调用" << std::endl;return &value;}// 重载const取地址操作符const int* operator&() const {std::cout << "const取地址操作符被调用" << std::endl;return &value;}
};int main() {MyClass obj(10);const MyClass constObj(20);int* addr = &obj; // 调用非const版本const int* constAddr = &constObj; // 调用const版本std::cout << "*addr: " << *addr << std::endl;std::cout << "*constAddr: " << *constAddr << std::endl;return 0;
}
输出结果
非const取地址操作符被调用
const取地址操作符被调用
*addr: 10
*constAddr: 20
在这个例子中,我们为MyClass
类重载了取地址操作符和常量取地址操作符。当通过非常量对象调用operator&()
时,会调用非常量版本的操作符,并打印一条消息。而当通过常量对象调用operator&()
时,则调用常量版本的操作符,并打印另一条不同的消息。
注意事项
- 谨慎使用:一般情况下,不需要也不建议重载这两个操作符,除非有特别的需求。这是因为它们改变了标准语义,可能会导致混淆或者不可预期的行为。
- 保持一致性:如果你决定重载这些操作符,请确保它们的行为符合逻辑且一致,避免引入错误。
- 理解限制:需要注意的是,即使你重载了取地址操作符,也无法阻止使用内置的取地址操作来获取对象的实际地址。例如,
&obj
总是可以获得obj
的实际地址,除非你完全隐藏了对象的访问方式。
总的来说,重载取地址操作符和常量取地址操作符是一种高级技巧,适用于特定场景下的特殊需求。在大多数日常编程任务中,这种重载是不必要的。
扩展:前置++和后置++重载
重载规则
- 前置
++
只需要一个参数(即调用该运算符的对象本身),并且通常返回一个指向修改后的对象的引用。 - 后置
++
需要两个参数:第一个是调用该运算符的对象本身,第二个是一个int类型的占位参数,用于区分前置和后置形式。后置++
返回的是操作前对象的一个副本(通常是通过值返回)。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class Count
{
public://重载后置++Count operator++(){++_count;return *this;}//后置++Count operator++(int){Count temp = *this;++_count;return temp;}int getCount() const {return _count;}
private:int _count = 0;
};int main()
{Count myCounter;std::cout << "Initial count: " << myCounter.getCount() << std::endl;++myCounter; // 调用前置++std::cout << "After prefix increment: " << myCounter.getCount() << std::endl;Count d;d = myCounter++; // 调用后置++std::cout << "After postfix increment: " << d.getCount() << std::endl;return 0;
}
相关文章:

C++六大默认成员函数
C六大默认成员函数 默认构造函数默认析构函数RAII技术RAII的核心思想优点示例应用场景 默认拷贝构造深拷贝和浅拷贝 默认拷贝赋值运算符移动构造函数(C11起)默认移动赋值运算符(C11起)取地址及const取地址操作符重载取地址操作符重…...

基于springboot校园点歌系统
基于Spring Boot的校园点歌系统是一种专为校园场景设计的音乐点播平台,它能够丰富学生的校园生活,提升学生的娱乐体验。以下是对该系统的详细介绍: 一、系统背景与意义 在校园环境中,学生们对于音乐有着浓厚的兴趣,传…...

pycharm 中的 Mark Directory As 的作用是什么?
文章目录 Mark Directory As 的作用PYTHONPATH 是什么PYTHONPATH 作用注意事项 Mark Directory As 的作用 可以查看官网:https://www.jetbrains.com/help/pycharm/project-structure-dialog.html#-9p9rve_3 我们这里以 Mark Directory As Sources 为例进行介绍。 这…...

【Elasticsearch】文本分类聚合Categorize Text Aggregation
响应参数讲解: key (字符串)由 categorization_analyzer 提取的标记组成,这些标记是类别中所有输入字段值的共同部分。 doc_count (整数)与类别匹配的文档数量。 max_matching_length (整数)从…...
算法随笔_40: 爬楼梯
上一篇:算法随笔_39: 最多能完成排序的块_方法2-CSDN博客 题目描述如下: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入:n 2 输出:2 解释&am…...

【Linux探索学习】第二十七弹——信号(一):Linux 信号基础详解
Linux学习笔记: https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言: 前面我们已经将进程通信部分讲完了,现在我们来讲一个进程部分也非常重要的知识点——信号,信号也是进程间通信的一…...

【数学】矩阵、向量(内含矩阵乘法C++)
目录 一、前置知识:向量(一列或一行的矩阵)、矩阵1. 行向量2. 列向量3. 向量其余基本概念4. 矩阵基本概念5. 关于它们的细节 二、运算1. 转置(1)定义(2)性质 2. 矩阵(向量࿰…...
设置git区分大小写
设置git区分大小写 1.全局设置 (影响全部仓库): git config --global core.ignorecase false2.仓库级别设置 (影响当前仓库): git config core.ignorecase false3.已经提交了大小写不一致的文件处理: git mv -f OldName newName # 强制重命名 git commit -m "Fix cas…...

排序算法与查找算法
1.十大经典排序算法 我们希望数据以一种有序的形式组织起来,无序的数据我们要尽量将其变得有序 一般说来有10种比较经典的排序算法 简单记忆为Miss D----D小姐 时间复杂度 :红色<绿色<蓝色 空间复杂度:圆越大越占空间 稳定性&…...
Github 2025-01-31Java开源项目日报 Top10
根据Github Trendings的统计,今日(2025-01-31统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目10C项目1Kotlin项目1Bazel:快速、可扩展的多语言构建系统 创建周期:3564 天开发语言:Java协议类型:Apache License 2.0Star数量:2…...

Java进阶笔记(中级)
-----接Java进阶笔记(初级)----- 目录 集合多线程 集合 ArrayList 可以通过List来接收ArrayList对象(因为ArrayList实现了List接口) 方法:接口名 柄名 new 实现了接口的类(); PS: List list new ArrayList();遍历…...

2025游戏行业的趋势预测
一、市场现状 从总产值的角度来看,游戏总产值的增长率已经放缓,由增量市场转化为存量市场,整体的竞争强度将会加大,技术水平不强(开发技术弱、产品品质低、开发效率低)的公司将会面临更大的生存的困难。 从…...
4-ET框架demo的运行
开始操作 开始运行报错 编译Unity项目 状态同步demo运行 1-设置构建参数 CodeMode:ClientServer EPlayMode:EditorSimulateMode 点击ReGeneratoePerojectFiles调整代码结构 2-编译 在Unity.sln下编译项目 3-运行 以帧同步模式运行 1-合端运行 1.1 修改帧同步标记 …...
kamailio源文件modules.lst的内容解释
在执行make cfg 后,在kamailio/src目录下有一个文件modules.lst,内容如下: # this file is autogenerated by make modules-cfg# the list of sub-directories with modules modules_dirs:modules# the list of module groups to compile cf…...
亚远景-从SPICE到ASPICE:汽车软件开发的标准化演进
一、SPICE标准的起源与背景 SPICE,全称“Software Process Improvement and Capability dEtermination”,即“软件流程改进和能力测定”,是由国际标准化组织ISO、国际电工委员会IEC、信息技术委员会JTC1联合发起制定的ISO 15504标准。该标准旨…...
vue3 + ElementPlus 封装列表表格组件包含分页
在前端开发中,封装组件是必不可少的。今天就来封装一个通用的列表表格组件,包含分页功能,可以提高代码的复用性和可维护性。 1. 组件设计 Props: tableData:表格数据。columns:表格列配置。totalÿ…...

挑战项目 --- 微服务编程测评系统(在线OJ系统)
一、前言 1.为什么要做项目 面试官要问项目,考察你到底是理论派还是实战派? 1.希望从你的项目中看到你的真实能力和对知识的灵活运用。 2.展示你在面对问题和需求时的思考方式及解决问题的能力。 3.面试官会就你项目提出一些问题,或扩展需求…...

Med-R2:基于循证医学的检索推理框架:提升大语言模型医疗问答能力的新方法
Med-R2 : Crafting Trustworthy LLM Physicians through Retrieval and Reasoning of Evidence-Based Medicine Med-R2框架Why - 这个研究要解决什么现实问题What - 核心发现或论点是什么How - 1. 前人研究的局限性How - 2. 你的创新方法/视角How - 3. 关键数据支持How - 4. 可…...
Oh3.2项目升级到Oh5.0(鸿蒙Next)具体踩坑记录(一)
目录 1.自动修复部分 Cause: The project structure and configuration require an upgrade. Solution: 1. Use Migrate Assistant to auto-upgrade the project structure and configuration. 2. Manually upgrade the project structure and configuration by following th…...

【自动化办公】批量图片PDF自定义指定多个区域识别重命名,批量识别铁路货物运单区域内容改名,基于WPF和飞桨ocr深度学习模型的解决方案
项目背景介绍 铁路货运企业需要对物流单进行长期存档,以便后续查询和审计。不同的物流单可能包含不同的关键信息,通过自定义指定多个区域进行识别重命名,可以使存档的图片文件名具有统一的规范和明确的含义。比如,将包含货物运单…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...

Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...