C++ 类和对象(二):默认成员函数详解

C++ 类和对象(二):默认成员函数详解

        在 C++ 面向对象编程中,类的默认成员函数是非常重要的概念。当我们没有显式实现某些成员函数时,编译器会自动生成它们,这些函数被称为默认成员函数。本文将详细介绍 C++ 类的 6 个默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载以及取地址运算符重载。

一、默认成员函数概述

默认成员函数是指用户没有显式实现,编译器会自动生成的成员函数。一个类在我们不写任何成员函数的情况下,编译器会默认生成以下 6 个默认成员函数:构造函数析构函数拷贝构造函数赋值运算符重载普通取地址运算符重载const 取地址运算符重载

        其中前 4 个是我们需要重点掌握的,后两个在大多数情况下使用编译器自动生成的即可。另外,C++11 以后还增加了两个默认成员函数:移动构造和移动赋值,本文暂不讨论。

二、构造函数

        构造函数是一种特殊的成员函数,其作用是在对象实例化时初始化对象,替代了我们以前手动调用的Init函数,并且会自动调用。

构造函数的特点:函数名与类名相同无返回值(不需要写void对象实例化时系统会自动调用对应的构造函数可以重载若未显式定义,编译器会生成无参的默认构造函数;一旦用户显式定义,编译器不再生成无参构造函数、全缺省构造函数、编译器默认生成的构造函数都称为默认构造函数(不传实参即可调用),且这三者不能同时存在编译器默认生成的构造函数对内置类型成员变量的初始化不确定,对自定义类型成员变量会调用其默认构造函数

构造函数示例

#include <iostream> using namespace std; class Date { public: // 无参构造函数 Date() { _year = 1; _month = 1; _day = 1; } // 带参构造函数 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } // 全缺省构造函数(不能与无参构造同时存在) // Date(int year = 1, int month = 1, int day = 1) // { // _year = year; // _month = month; // _day = day; // } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; // 调用无参构造函数 Date d2(2025, 1, 1); // 调用带参构造函数 // 注意:以下写法是函数声明,不是对象实例化 // Date d3(); return 0; }

        大多数情况下,我们需要自己实现构造函数。只有少数情况,如类的成员都是自定义类型且这些自定义类型有合适的默认构造函数时(如用两个 Stack 实现队列),编译器自动生成的构造函数才够用。

三、析构函数

        析构函数的功能并不是销毁对象本身(对象在生命周期结束时会自动销毁),而是完成对象中资源的清理释放工作,类比我们之前实现的Destroy函数。

析构函数的特点析构函数名是在类名前加上~无参数无返回值(不需要写void一个类只能有一个析构函数,若未显式定义,系统会自动生成对象生命周期结束时,系统会自动调用析构函数编译器自动生成的析构函数对内置类型成员不做处理,对自定义类型成员会调用其析构函数即使显式定义了析构函数,自定义类型成员的析构函数也会被自动调用没有申请资源的类(如 Date)可以不写析构函数;有资源申请的类(如 Stack)必须自己写析构函数,否则会造成资源泄漏局部域的多个对象,后定义的先析构

析构函数示例

#include<iostream> using namespace std; typedef int STDataType; class Stack { public: Stack(int n = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } // 析构函数:释放资源 ~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } private: STDataType* _a; size_t _capacity; size_t _top; }; // 两个Stack实现队列 class MyQueue { public: // 编译器默认生成的析构函数会调用Stack的析构函数 ~MyQueue() { cout << "~MyQueue()" << endl; } private: Stack pushst; Stack popst; }; int main() { Stack st; MyQueue mq; // 析构顺序:~MyQueue() -> ~Stack()(popst) -> ~Stack()(pushst) -> ~Stack()(st) return 0; }

四、拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,用于用一个已存在的对象初始化一个新创建的对象。

拷贝构造函数的特点是构造函数的一个重载第一个参数必须是自身类类型对象的引用,使用传值方式会引发无穷递归调用;可以有多个参数,但后续参数必须有缺省值C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造(如传值传参、传值返回)若未显式定义,编译器会生成默认拷贝构造函数,对内置类型成员进行值拷贝 / 浅拷贝,对自定义类型成员调用其拷贝构造成员全是内置类型且无资源的类(如 Date)不需要显式实现;有资源的类(如 Stack)需要显式实现深拷贝,否则会导致双重释放传值返回会产生临时对象并调用拷贝构造;传引用返回可减少拷贝,但需确保返回对象在函数结束后仍存在

拷贝构造函数示例

#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // 拷贝构造函数 Date(const Date& d) // 使用const引用避免修改原对象 { _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2025,11,30); d1.Print(); // 2025/11/30 // 拷贝构造 Date d2(d1); d2.Print(); // 2025/11/30 Date d3 = d2; // 也是拷贝构造 d3.Print(); // 2025/11/30 return 0; }

深拷贝实现(以 Stack 为例)

// 栈的深拷贝构造 Stack(const Stack& st) { // 对指针指向的资源重新申请空间并复制数据 _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity); if (nullptr == _a) { perror("malloc申请空间失败!!!"); return; } memcpy(_a, st._a, sizeof(STDataType) * st._top); _top = st._top; _capacity = st._capacity; }
深 / 浅拷贝区别浅拷贝:按字节拷贝,对于指针成员只拷贝地址,不拷贝资源,可能导致多个对象共享同一份资源深拷贝:不仅拷贝指针本身,还对指针指向的资源重新申请空间并复制数据,每个对象拥有独立的资源

五、赋值运算符重载

        赋值运算符重载用于完成两个已存在对象之间的拷贝赋值,与拷贝构造的区别是:拷贝构造用于用已有对象初始化新对象,而赋值重载用于两个已存在对象之间的赋值。

运算符重载基础运算符重载是具有特殊名字的函数,形式为operator运算符重载运算符的参数个数与运算对象数量一致,一元运算符 1 个参数,二元运算符 2 个参数若为成员函数,第一个参数为隐式的this指针,因此参数比运算对象少一个不能重载的运算符:.*::sizeof?:.重载操作符至少有一个类类型参数前置 ++ 和后置 ++ 的区分:后置 ++ 重载时增加一个int形参<<>>建议重载为全局函数,以符合使用习惯

赋值运算符重载的特点必须重载为成员函数参数建议为const当前类类型引用,避免传值拷贝返回值建议为当前类类型引用,支持连续赋值未显式实现时,编译器会生成默认赋值重载,行为与默认拷贝构造类似(内置类型浅拷贝,自定义类型调用其赋值重载)有资源的类需要显式实现深拷贝的赋值重载

赋值运算符重载示例

// 赋值运算符重载 Date& operator=(const Date& d) { // 避免自己给自己赋值 if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; // 支持连续赋值 } // 使用示例 int main() { Date d1(2025, 11, 30); Date d2(2025, 12, 1); d1 = d2; // 赋值重载,两个已存在对象 Date d3(d2); // 拷贝构造,用d2初始化新对象d3 Date d4 = d2; // 拷贝构造,不是赋值 return 0; }

六、取地址运算符重载

        取地址运算符重载分为普通取地址和 const 取地址两种,一般情况下编译器自动生成的即可满足需求,不需要显式实现

const成员函数

        const 成员函数是指用 const 修饰的成员函数,const 放在参数列表后面,实际修饰的是隐含的this指针,表明在该函数中不能修改类的任何成员。

// const成员函数示例 void Print() const // 实际为void Print(const Date* const this) { cout << _year << "-" << _month << "-" << _day << endl; }

取地址运算符重载示例

class Date { public : // 普通取地址运算符重载 Date* operator&() { return this; // return nullptr; // 可以自定义返回值 } // const取地址运算符重载 const Date* operator&()const { return this; // return nullptr; // 可以自定义返回值 } private : int _year; // 年 int _month; // 月 int _day; // 日 };

Read more

多组学因果推断实操指南:孟德尔随机化 + 中介效应建模(含 R/Python 因果验证代码)

多组学因果推断实操指南:孟德尔随机化 + 中介效应建模(含 R/Python 因果验证代码)

一、引言:从关联到因果 —— 多组学研究的核心挑战 在精准医学时代,多组学技术(基因组学、转录组学、蛋白质组学、代谢组学等)已成为解析复杂疾病机制的核心工具。通过整合不同生物分子层面的数据,研究者能构建从基因到表型的分子调控网络,但传统分析往往止步于变量间的相关性描述,难以区分因果关系与混杂干扰。例如,在肥胖与 2 型糖尿病的研究中,炎症因子水平升高与两者均相关,但无法确定是炎症导致糖尿病,还是肥胖同时驱动了两者变化。 孟德尔随机化(Mendelian Randomization, MR)借助遗传变异的 "自然随机分配" 特性,为解决因果推断难题提供了新思路。其核心逻辑是:遗传变异在受孕时随机分配,不受后天环境和疾病状态影响,可作为暴露因素的工具变量(Instrumental Variable, IV),有效规避混杂偏倚与反向因果问题。而中介效应建模能进一步拆解因果通路,识别多组学分子在暴露 - 结局关系中的中间传导角色,例如揭示某蛋白质如何介导基因变异对疾病的影响。 本指南将系统梳理多组学背景下 MR 与中介效应建模的整合分析框架,结合真实案例与可复现代码,助力研究者实现从

By Ne0inhk
[全网首发] Sora2Pro API 逆向接入指南:如何用 Python 实现 0.88元/次 的 4K 视频生成?(附源码)

[全网首发] Sora2Pro API 逆向接入指南:如何用 Python 实现 0.88元/次 的 4K 视频生成?(附源码)

2025年10月,OpenAI 终于开放了 Sora2 API,但 $5/次的昂贵成本和严苛的企业资质认证劝退了 90% 的开发者。本文将分享一种“曲线救国”的方案——通过 小镜AI开放平台 接入 Sora2Pro。实测单次成本降低 98%(低至 0.88元),且独创“成功才计费”模式。本文包含完整的 Python 接入流程、错误处理机制及并发优化策略。 1. 行业痛点:为什么官方 API 是“开发者的噩梦”? 根据 Statista 2025 Q3 报告,78% 的开发者在对接官方 Sora2 API 时遭遇滑铁卢。核心原因在于 OpenAI 的企业级定位对个人和中小团队极不友好: * 成本黑洞: 官方单次调用

By Ne0inhk
从零开始:Python与Jupyter Notebook中的数据可视化之旅

从零开始:Python与Jupyter Notebook中的数据可视化之旅

目录 * **理解数据与数据可视化的基本流程** * **了解Python与其他可视化工具** * **掌握Anaconda、Jupyter Notebook的常用操作方法** * **原理** * 环境配置 * 1. **安装Anaconda软件,创建实验环境** * 2. **安装Jupyter Notebook** * 3. **创建第一个Jupyter Notebook文本** * (1)**更改保存路径、重命名文件** * (2)**创建代码单元和Markdown单元** * 实验1-1:鸢尾花数据集可视化练习 * 1. **安装scikit-learn库** * 2. **导入鸢尾花数据集并绘制表格** * 代码步骤: * 绘制特征之间的散点图 * 绘制饼图 * 绘制散点图 * 条形图:展示每种鸢尾花品种的平均特征值,例如平均花萼长度。 * 通过鸢尾花的目标(种类)创建类别列 * 计算每个品种的

By Ne0inhk
Python快速入门专业版(十四):变量赋值的“陷阱”:浅拷贝与深拷贝(用代码看懂内存地址)

Python快速入门专业版(十四):变量赋值的“陷阱”:浅拷贝与深拷贝(用代码看懂内存地址)

目录 引言:为什么改了b,a也跟着变? 1.赋值的本质:不是值传递,而是引用传递 1.1 用id()函数看穿内存地址 场景1:不可变对象的赋值(无副作用) 场景2:可变对象的赋值(有副作用) 1.2 不可变对象的“特殊情况”:小整数池与字符串驻留 2.浅拷贝(Shallow Copy):只复制“外层壳子” 2.1 浅拷贝的4种实现方式 代码示例:列表的浅拷贝 2.2 浅拷贝的“隐形陷阱”:内层对象仍共享 代码演示:浅拷贝的内层共享问题 2.3 浅拷贝的适用场景 3.深拷贝(Deep Copy):复制“所有层级”

By Ne0inhk