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

C++ 多态详解:从概念到实现原理----《Hello C++ Wrold!》(14)--(C/C++)

C++ 多态详解:从概念到实现原理----《Hello C++ Wrold!》(14)--(C/C++)

文章目录 * 前言 * 多态的概念 * 多态的定义和实现 * 虚函数 * 虚函数的重写(覆盖) * 多态的构成条件 * override 和 final(C++11提出) * final * override * 重载、覆盖(重写)、隐藏(重定义)的对比 * 抽象类 * 接口继承和实现继承 * 多态的原理 * 虚函数表(也叫做虚表) * 引申:虚表的打印 * 多态的原理 * 静态多态和动态多态 * 多继承中的虚函数表 * 作业部分 前言 多态是面向对象编程的三大核心特性(封装、继承、多态)之一,它使得同一接口可以呈现出不同的行为,极大地提升了代码的灵活性和可扩展性。在 C++ 中,多态的实现与虚函数、虚表等机制紧密相关,其底层逻辑涉及编译期与运行期的不同处理方式。 本文将系统梳理 C++ 多态的概念、实现条件、

By Ne0inhk
Java 线程池线程数怎么定?从 IO / CPU / 混合型任务谈起

Java 线程池线程数怎么定?从 IO / CPU / 混合型任务谈起

文章目录 * 1. 按照任务类型对线程池进行分类 * 2. 为 IO 密集型任务确定线程数 * 3. 为 CPU 密集型任务确定线程数 * 4. 为混合型任务确定线程数 在实际开发中,线程池几乎是每个 Java 后端绕不开的组件。但真正让人困惑的往往不是怎么用线程池,而是——线程数到底该怎么配。 有人按 CPU 核数来,有人直接乘 2,还有人干脆拍脑袋设一个固定值。这些做法在某些场景下 “看起来能跑”,但在 IO 较多或混合型任务中,往往会带来性能下降、请求堆积,甚至线程池耗尽的问题。 这篇文章主要面向 Java 后端开发者,结合常见的 IO 密集型、CPU 密集型以及混合型任务,梳理线程池线程数配置的基本思路,并给出可参考的计算方式,帮助你在不同场景下做出更合理的选择。 1. 按照任务类型对线程池进行分类 在讨论线程数之前,首先需要明确一点:线程数的配置和任务类型是强相关的。

By Ne0inhk
Java 大视界 -- Java 大数据在智能医疗电子健康档案数据挖掘与健康服务创新中的应用(350)

Java 大视界 -- Java 大数据在智能医疗电子健康档案数据挖掘与健康服务创新中的应用(350)

Java 大视界 -- Java 大数据在智能医疗电子健康档案数据挖掘与健康服务创新中的应用(350) * 引言: * 正文: * 一、Java 构建的电子健康档案整合系统 * 1.1 多源 EHR 数据融合与隐私保护 * 1.2 跨院 EHR 实时查询(急诊案例) * 二、Java 驱动的 EHR 数据挖掘与健康服务创新 * 2.1 慢性病风险预测(糖尿病案例) * 2.2 健康服务创新场景(全周期干预) * 三、实战案例:从 “档案” 到 “管家” 的蜕变 * 3.1 社区慢性病管理:王大爷的 “少跑腿” 方案 * 3.2

By Ne0inhk
Y20030009基于Java+springboot+MySQL+uniapp框架的待办事项提醒微信小程序的设计与实现 源码 文档 PPT

Y20030009基于Java+springboot+MySQL+uniapp框架的待办事项提醒微信小程序的设计与实现 源码 文档 PPT

待办事项提醒小程序 * 1.摘要 * 2.开发目的和意义 * 3.系统功能设计 * 4.系统界面截图 * 5.源码获取 1.摘要 随着现代人的工作和生活压力越来越大,人们的精力和时间也越来越有限。在这样的情况下,很容易忘记一些很重要的行程,有时会导致严重的后果,如何处理好自己的待办事项,便成为了一个需要特别关注的重要问题,因为只有处理好待办事项,才能让我们的工作和生活更加有序、轻松和高效。因此可以设计一个操作简单的,功能齐全的待办事项管理系统,让用户能够按照优先级、时间、标签等方式对任务进行分类,方便用户管理任务,提高效率。同时还需要提供任务的添加、修改、删除等操作,方便用户随时调整任务。在此基础上添加待办事项提醒功能,来为用户提供一个高效率软件 基于微信的待办事项管理系统小程序主要以Uni-App用为前端框架,利用Uni-App的基础组件库和API、以及UniUI扩展实现基本的小程序功能。采用Springboot作为后端框架。通过MyBatis用为持久层来进行MySQL数据库操作。采用前后端分离的设计原则,前端负责展示和用户交互,后端负责数据处理和业务逻辑实现。

By Ne0inhk