ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

类和对象(2)

类和对象(2)

默认成员函数:⽤⼾没有显式实现,编译器⾃动⽣成的成员函数。

类中如果不写任何实现,编译器会默认生成6 个成员函数:

构造函数、析构函数、拷贝构造函数、拷贝赋值重载。这些涉及对象的创建、销毁和拷贝,是资源管理的核心,需要重点学习。

取地址重载(普通取地址和const取地址)。简单了解即可。

C++11 又增加了2 个默认成员函数:移动构造和移动赋值。

需要掌握的是我们不写时,编译器默认⽣成的函数是什么,是否满⾜我们的需求。

编译器默认⽣成的函数不满⾜我们的需求时,如何⾃⼰实现?

一、构造函数(应写尽写)

构造函数的主要任务不是开空间创建对象(对象空间在栈帧创建时已分配),而是实例化时初始化对象。

其本质是替代 C 语言中手动调用的Init初始化函数,利用“对象实例化时系统自动调用”的特性,让初始化更自动化、更安全。

(一)特点:

  1. 函数名与类名相同。

  2. 无返回值(不需写void,这是 C++ 的强制规定)。

  3. 对象实例化时系统自动调用。

  4. 支持函数重载(可提供多种初始化方式)。

  5. 什么是“默认构造函数”?不传实参就可以调用的构造函数。包含三类:

    1. 我们写的无参构造函数。

    2. 我们写的全缺省构造函数(所有参数都有默认值)。

    3. 编译器自动生成的默认构造函数。

    4. 注意:这三者只能存在一个。因为无参和全缺省虽构成重载,但调用时(如Date d1;)编译器无法区分,会产生歧义报错。

  6. 如果没写任何构造函数,编译器会自动生成一个无参默认构造;如果写了任意构造函数(无论是有参、无参还是缺省),编译器不再生成。

  7. 编译器生成的默认构造做了什么?

C++ 区分类型来处理成员变量:

  • 内置类型(int、char、指针等):不要求初始化,其初始值通常是随机值(取决于编译器)。

  • 自定义类型(类/结构体对象):必须调用该成员变量自身的默认构造函数来初始化。

    • 若该自定义类型成员没有默认构造函数(即没有无参构造/全缺省构造,且编译器也未生成),则会编译报错。

    • 解决方案:需使用初始化列表。

(二)代码示例

  • 对象定义陷阱:调用无参构造创建对象时,对象后面不要跟括号(即写Date d1;)。若写成Date d3();,编译器会将其识别为函数声明,而不是创建对象。

  • 自动调用传递:如示例中的MyQueue类,它包含两个Stack自定义成员。即使MyQueue没写构造函数,编译器生成的默认构造也会自动调用Stack的构造函数,完美完成嵌套对象的初始化。

  • class Date { public: // 1.⽆参构造函数 //Date() //{ // _year = 1; // _month = 1; // _day = 1; //} // 2.带参构造函数 //Date(int year, int month, int day) //{ // _year = year; // _month = month; // _day = day; //} // 3.全缺省构造函数 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() { // 如果留下三个构造中的第⼆个带参构造,第⼀个和第三个注释掉 // 编译报错:error C2512 : “Date”:没有合适的默认构造函数可⽤ Date d1;// 调⽤默认构造函数 Date d2(2025, 1, 1); // 调⽤带参的构造函数 // 注意:如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,否则编译器⽆法区分这⾥是函数声明还是实例化对象 // warning C4930: “Date d3(void)”: 未调⽤原型函数(是否是有意⽤变量定义的? ) Date d3(); d1.Print(); d2.Print(); return 0; }
    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; } // ... private: STDataType * _a; size_t _capacity; size_t _top; }; // 两个Stack实现队列 class MyQueue { public: //编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化 private: Stack pushst; Stack popst; }; int main() { MyQueue mq; return 0; }

    二、析构函数

  • 析构函数不是销毁对象本身(局部对象占用的栈帧随函数结束自动释放,不需我们管)。

  • 在对象销毁时自动调用,用于完成对象中资源的清理释放(如动态内存、文件句柄等)。

  • 等价于 C 语言中 Stack 的Destroy函数;而像Date类这种没有资源申请的,严格来说不需要析构函数。

(一)特点:

  1. 函数名:类名前加~(如~Stack())。

  2. 参数与返回值:无参数、无返回值(不需要写void)。

  3. 唯一性:一个类只能有一个析构函数(不可重载)。

  4. 触发时机:对象生命周期结束时,系统自动调用。

  5. 编译器自动生成的析构函数做了什么?内置类型(int、指针等):不做处理(不释放资源);自定义类型成员(其他类对象):自动调用该成员自身的析构函数。

  6. 即使你显式写了析构函数,类中的自定义类型成员依然会自动调用其自身的析构函数

  7. 何时该写,何时不用写?

  • 不用写(使用编译器默认生成):

    • 类中没有申请资源(如Date)。

    • 默认生成的析构够用,如MyQueue,它只包含Stack对象,默认析构会自动调用Stack的析构,释放干净。

  • 必须显式写:

    • 类中动态申请了资源(如Stack中用了malloc开空间)。如果不写,默认析构不会释放_a指向的内存,会造成内存泄漏。

8.局部对象的析构顺序:同一个作用域内有多个对象时,遵循后定义、先析构的原则(构造顺序的逆序)。

(二)代码示例:

  • Stack:动态申请了空间,显式写了析构,内部free(_a),防止泄漏。

  • MyQueue:仅包含两个Stack对象,无需显式写析构,编译器生成的默认析构会自动调用Stack的析构,正确完成嵌套资源的释放。

  • 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: //编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源 // 显⽰写析构,也会⾃动调⽤Stack的析构 /*~MyQueue() {}*/ private: Stack pushst; Stack popst; }; int main() { Stack st; MyQueue mq; return 0; }

    三、拷贝构造函数

  • 定义:第一个参数是自身类类型的引用(通常为const修饰),且其余参数均有默认值的构造函数。

  • 本质:构造函数的一种重载形式,用于用已存在的对象初始化同类型的新对象。

(一)特点

1.参数必须为引用:若使用传值方式(如Date(Date d)),编译器会报错,(C++规定传值传参会调用拷贝构造,导致无限递归。)(传值调用拷贝构造,调用拷贝构造需要传值,传值又要调用拷贝构造)

为什么会发生无限递归?
假设我们写了错误的拷贝构造:Date(Date d),并执行代码 Date d2(d1);(用 d1 拷贝构造 d2)。

编译器和运行时的逻辑推演是这样的:

调用起点:我们要用 d1 拷贝构造 d2,所以需要调用拷贝构造 Date(Date d)。

传值困境(第一次递归):这个函数的参数是 Date d,它是一个值传递的参数。C++ 规定,值传递自定义类型时,形参 d 需要用实参 d1 来拷贝初始化。

触发新拷贝:为了完成“把 d1 拷贝给形参 d”这个动作,编译器必须再次调用拷贝构造函数。

回到起点:再次调用的拷贝构造,依然是 Date(Date d),它的参数依然是值传递。

死循环:为了进入第二次调用,又得完成它的传值传参,于是又触发第三次调用……如此层层嵌套,永远不会到达函数体内部,直到编译器栈溢出或直接报错(C++标准规定编译器必须拒绝这种代码)。

为什么改成引用 (const Date& d) 就解决了?
因为引用是别名,不是值。

当你写 Date(const Date& d) 时,形参 d 只是实参 d1 的一个别名(类似于给 d1 起了个小名)。传递引用不需要拷贝对象,它只是传递了一个地址。因此,编译器不需要调用任何拷贝构造就能完成传参,直接进入了函数体内部,递归链条就此打断。

可以有额外参数,但必须提供默认值(如Date(const Date& d, int n = 0))。

调用时机:自定义类型对象的拷贝行为(传值传参、传值返回、显式拷贝初始化)都会触发拷贝构造。

2.编译器自动生成的拷贝构造

若用户未显式定义,编译器会自动生成一个默认拷贝构造。

默认行为:

对内置类型成员:执行浅拷贝(逐字节复制)。(比如日期类就不需要我们显式的写拷贝构造函数)

对自定义类型成员:调用该成员对应的拷贝构造。

何时需要显式定义:

类成员指向动态分配的资源(如 Stack 中的 _a),浅拷贝会导致多个对象共享同一资源,比如析构时重复释放同一块空间 → 程序崩溃,入栈时互相覆盖对方资源。此时必须显式实现深拷贝(为指针成员重新分配空间并复制内容)。

经验技巧:若类显式定义了析构函数来释放资源,则通常也需要显式定义拷贝构造(以及拷贝赋值)。

3. 深拷贝 vs 浅拷贝

浅拷贝:仅复制指针值,不复制指向的资源。

深拷贝:复制指针所指向的资源内容,确保每个对象拥有独立的资源副本。

典型场景:

Date(成员均为内置类型,无资源)→ 无需显式拷贝构造,默认即可。

Stack(含动态数组)→ 必须显式深拷贝。

MyQueue(成员为 Stack 对象)→ 编译器自动生成拷贝构造时会调用 Stack 的深拷贝,因此无需再显式实现。

4. 传值返回与引用返回

传值返回:会产生一个临时对象,调用拷贝构造。性能开销较大。

传引用返回:返回对象的别名(引用),避免拷贝,效率高。

⚠️ 关键风险:绝不能返回局部对象的引用(函数结束局部对象销毁,引用成为“野引用”)。只有确保返回对象在函数结束后仍存在(如静态、全局、或通过参数传入的引用)时,才可使用引用返回。

(二)代码示例

// 正确的拷贝构造(const 引用) Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } // 错误的写法(值传递,报错) // Date(Date d) { ... } // 深拷贝示例(Stack) Stack(const Stack& st) { _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity); memcpy(_a, st._a, sizeof(STDataType) * st._top); _top = st._top; _capacity = st._capacity; }
class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // 编译报错:error C2652 : “Date”:⾮法的复制构造函数:第⼀个参数不应是“Date” //Date(Date d) Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } Date(Date* d) { _year = d->_year; _month = d->_month; _day = d->_day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; void Func1(Date d) { cout << &d << endl; d.Print(); } // Date Func2() Date& Func2() { Date tmp(2024, 7, 5); tmp.Print(); return tmp; } int main() { Date d1(2024, 7, 5); // C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥传值传参要调⽤拷⻉构造 // 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉,传引⽤传参可以较少这⾥的拷⻉ Func1(d1); cout << &d1 << endl; // 这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造 Date d2(&d1); d1.Print(); d2.Print(); //这样写才是拷⻉构造,通过同类型的对象初始化构造,⽽不是指针 Date d3(d1); d2.Print(); // 也可以这样写,这⾥也是拷⻉构造 Date d4 = d1; d2.Print(); // Func2返回了⼀个局部对象tmp的引⽤作为返回值 // Func2函数结束,tmp对象就销毁了,相当于了⼀个野引⽤ Date ret = Func2(); ret.Print(); return 0; }

四、运算符重载

1. 什么是运算符重载?

给类类型的对象使用运算符时(如d1 == d2),编译器会将其转换为调用对应的operator函数。如果没有重载,编译会报错。

2. 重载的语法规则

  • 函数名:operator后加上运算符类似于函数名,例如operator==。

  • 参数个数:与运算符操作数数量一致。

    • 一元运算符(如++):1个参数。

    • 二元运算符(如+、==):2个参数,左侧操作数传给第1个参数,右侧传给第2个。

  • 成员函数特例:如果重载为类的成员函数,第一个操作数会默认传给隐藏的this指针,所以参数列表少一个。

3. 重要限制(高频考点)

  • 不能创建新运算符:如operator@是不允许的。

  • 以下5个运算符不能重载(死记硬背):

    • .*::sizeof?: .

  • 必须有一个类类型参数:不能改变内置类型的运算含义(比如你不能重载int + int)。

  • 优先级和结合性不变:重载后,运算符的优先级依然和内置类型一样。

6. 访问私有成员的问题

如果重载为全局函数,直接访问对象的私有成员会报错。解决办法:

  1. 把成员变量设为公有(不推荐)。

  2. 提供公有的GetXXX接口。

  3. 使用友元函数(常用)。

  4. 直接重载为成员函数(省去友元,但要注意<<不能用此法)。

7、赋值运算符重载(核心重点)

赋值运算符重载是一个特殊的默认成员函数,用于两个已经存在的对象之间的拷贝赋值。

7.1 必须遵守的三大特点

  • 7.必须重载为成员函数。

  • 参数建议:写成const 类名&(传引用避免拷贝,加 const 防止修改右侧对象)。

  • void operator=(Date d)//这样也可以,不过传值传参会有拷贝,会出现额外开销,但不会出现嵌套调用拷贝构造 {}
  • 返回值建议:写成类名&(返回当前对象的引用),目的是支持连续赋值(如d1 = d2 = d3)。

  • 传值返回会创建临时对象,调用拷贝构造,产生拷贝开销。
    传引用返回不产生拷贝,返回的是对象的别名,但必须确保返回的对象在函数结束后依然有效(如全局变量、静态变量、成员变量或调用方传入的引用);若返回的是函数内的局部对象,则会导致野引用,危险。

    简言之:引用返回高效但需保证对象生命周期,局部对象不可用引用返回。

7.2 编译器自动生成的默认行为

  • 如果没写,编译器会自动生成一个。

  • 默认行为:(与拷贝构造类似)

    • 对内置类型成员:完成浅拷贝(逐字节复制)。

    • 对自定义类型成员:调用该成员的赋值运算符重载。

7.3 什么时候需要自己实现?(关键判断技巧)

  • 像Date类(只有内置类型,不指向资源):默认生成的完全够用,不需要自己写。

  • 像Stack类(有动态申请的_a指针):默认浅拷贝会导致两个对象指向同一块内存,析构时崩溃。必须自己实现深拷贝。

  • 像MyQueue类(成员全是Stack类型):因为编译器会调用Stack的赋值重载,所以MyQueue不需要自己写。

  • 技巧:如果一个类显式写了析构函数来释放资源,那么这个类大概率也需要显式写赋值运算符重载(和拷贝构造)。

7.4"赋值" vs "拷贝构造"

比较维度拷贝构造赋值重载
发生时机对象还未创建,用已有对象初始化新对象。对象已经存在,用另一个对象覆盖它。
代码示例Date d2 = d1;(d2 还没创建)d1 = d3;(d1 已经创建好了)
是否生成新对象是(创建新对象)否(修改已有对象)
Date d1(2026, 1, 1); Date d2(2026, 1, 2); //赋值运算符重载 d2 = d1; //拷贝构造 Date d3(d2); Date d4 = d2;

代码示例:日期类

Date.h

#pragma once #include<iostream> using namespace std; class Date { public: //友元函数声明,加在public,或者public,private之外的位置(即private)也可以,加在哪儿都可以 friend ostream& operator<<(ostream& out, const Date& d);//支持连续的输出需要加引用返回ostream& friend istream& operator>>(istream& in, Date& d); int getMonthDay(int year, int month)//获取当前月份天数 { static int MonthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))//闰年的2月有29天,其他年份2月为28天 //后两个条件是一个整体,必须加括号,(4年一润百年不润 或 四百年一润) { return 29; } return MonthDayArray[month]; } Date(int year = 1900, int month = 1, int day = 1); void Print() const; //检查日期是否合法 bool CheckDate(); // d1 + 天数 Date operator+(int day) const; Date& operator+=(int day); //Date operator-(int day); //d1-天数 Date& operator-=(int day); Date operator-(int day) const; //++ Date& operator++();//++d1 Date operator++(int);//d1++,加一个参数区分前置还是后置加加 //-- Date& operator--();//--d1 Date operator--(int);//d1-- //比较大小 bool operator==(const Date& d) const; bool operator<(const Date& d) const; bool operator<=(const Date& d) const; bool operator>(const Date& d) const; bool operator>=(const Date& d) const; bool operator!=(const Date& d) const; int operator-(const Date& d) const; //ostream& operator<<(ostream& out)//支持连续的输出需要加引用返回ostream& //{ // out << _year << "年" << _month << "月" << _day << "日" << endl; // //二元运算符,左操作数传递给第一个对象,右操作数传递给第二个对象 // //d1<<cout这样才对,d1传给this指针,cout传给out // //但这样可读性比较差,只能被迫重载为全局函数 // return out; //} ////属于默认成员函数,不显示式写,会默认生成(库里面有,可以不写) //Date* operator&()//与底下函数构成重载,返回值与参数(隐含的this指针)都不同,一个是普通对象Date*,一个是const对象const Date* //{ // return this; // // return nullptr; //} //const Date* operator&()const //{ // return this; // // return nullptr; //} private: int _year; int _month; int _day; }; //重复声明没事 ostream& operator<<(ostream& out, const Date& d);//支持连续的输出需要加引用返回ostream& istream& operator>>(istream& in, Date& d);//d是形参,Date& d加引用形参d改变,外面也改变;提取的日期类值要放在对象d中,d得可写,不能加const,

Date.cpp

#include"Date.h" bool Date::CheckDate() { if(_month < 1 || _month > 12 || _day <1 || _day > getMonthDay(_year, _month)) { return false; } else { return true; } } Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; if (!CheckDate())//if(!(this->CheckDate())) { cout << "非法日期" << *this <<endl; } } void Date::Print() const { cout << _year << "-" << _month << "-" << _day << endl; } Date Date::operator+(int day) const { Date tmp = *this; tmp += day;//直接用写好的+=重载 //Date tmp = *this; //tmp._day += day; //while (tmp._day > getMonthDay(tmp._year, tmp._month)) //{ // tmp._day -= getMonthDay(tmp._year, tmp._month); // tmp._month++; // if (tmp._month == 13) // { // tmp._month = 1; // tmp._year++; // } //} return tmp; } Date& Date::operator+=(int day) { if (day < 0)//d1 += -100(处理这种情况) { return *this -= -day; } _day += day; while (_day > getMonthDay(_year, _month)) { _day -= getMonthDay(_year, _month); _month++; if (_month == 13) { _month = 1; _year++; } } return *this; } //日期-天数 //2025.4.26 26-100=-74 //2025.4.-74 -74+31=-43 //2025.3.-43 -43+28=-15 //20252.-15 -15+31=16 //2025.1.16 Date& Date::operator-=(int day) { if (day < 0) { return *this += -day; } _day -= day;//26-100=-74 while (_day <= 0)//4.0那就不对了 { --_month; if (_month == 0) { _month = 12; --_year; } _day += getMonthDay(_year, _month);//加上减去的那个月天数 } return *this; } Date Date::operator-(int day) const { Date tmp(*this); tmp -= day; return tmp; } Date& Date::operator++()//++d1,出了作用域还在,可以用引用返回 { *this += 1; return *this; } Date Date::operator++(int)//d1++,出了作用域tmp不在,不能用引用返回 { Date tmp(*this); *this += 1; return tmp; } Date& Date::operator--()//--d1 { *this -= 1; return *this; } Date Date::operator--(int)//d1-- { Date tmp(*this); *this -= 1; return tmp; } bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; } bool Date::operator<(const Date& d) const { if (_year < d._year) { return true; } else if(_year == d._year && _month < d._month) { return true; } else if (_year == d._year && _month == d._month && _day < d._day) { return true; } return false; } //实现<,实现==,其它<=,>,>=,!=都可以复用(任何涉及到比较的类都可以用这套复用的逻辑) bool Date::operator<=(const Date& d) const { return *this < d || *this == d; } bool Date::operator>(const Date& d) const { return !(*this <= d);//<=取反 } bool Date::operator>=(const Date& d) const { return !(*this < d); } bool Date::operator!=(const Date& d) const { return !(*this == d); } int Date::operator-(const Date& d) const//把小的那个日期一天一天往大的加,加了多少次就差多少天 { Date max = *this; Date min = d; int flag = 1; if (*this < d) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min; ++n; } return n*flag;//d1<d2,flag=1,天数为正;d1>d2,flag-1,天数为负 } //类外想访问类内私有成员变量,用友元函数,加在哪里都可以,这里放在声明位置 ostream& operator<<(ostream& out, const Date& d)//支持连续的输出需要加引用返回ostream& { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } istream& operator>>(istream& in, Date& d) { while(1) { cout << "请依次输入年月日"; in >> d._year >> d._month >> d._day; if (d.CheckDate()) { break; } else { cout << "非法日期,请重新输入" << endl; } } return in; }

Test.cpp

#include"Date.h" void TestDate1()//测试+=,+ { Date d1(2026, 8, 1); Date d2(2026, 8, 2); d1.Print(); Date d3(d1 + 10000); d1 += 10000; d3.Print(); d1.Print(); d1 += -100; d1.Print(); } void TestDate2()//测试-=,- { Date d1(2026, 10, 22); Date d2 = d1 -= 10000; d1.Print(); d2.Print(); Date d3(2026, 10, 22); Date d4 = d3 - 10000; d4.Print(); d1 -= -100; d1.Print(); } void TestDate3()//测试++,-- { Date d1(2026, 10, 22); Date d2 = d1++; //Date d2 = d1.operator++(1);//显式调用,一般不这样用 d1.Print(); d2.Print(); Date d3(2026, 10, 22); Date d4 = ++d2; d3.Print(); d4.Print(); } void TestDate4() { Date d1(2026, 10, 22); Date d2(2026, 11, 22); cout << (d1 < d2) << endl;//必须要加括号 cout << (d1 > d2) << endl; cout << (d1 == d2) << endl; } void TestDate5() { Date d1(2025, 4, 26); Date d2(2025, 7, 1); cout << d1 - d2 << endl;//d1 - d2不需要加括号,-的优先级比较高 } void TestDate6() { Date d1(2025, 4, 26); Date d2(2025, 7, 1); //int i = 1; //double d = 1.1; //cout << i << " " << d << endl; //之所以可以自动识别输出变量类型,是因为iostream库中把<<重载好了 //cout.operator<<(i);//等价于调用这个函数 //流插入运算符 cout << d1;//operator<< operator <<(cout, d1); cin >> d1 >> d2; cout << d1 << d2; cout << d1 - d2 << endl; } void TestDate7() { Date d1(2025, 4, 26); d1.Print(); const Date d2(2025, 7, 1); //d2.Print();错误 } void TestDate8() { Date d1(2025, 4, 26); Date d2(2025, 7, 1); cout << &d1 << endl; cout << &d2 << endl; } int main() { //TestDate1(); //TestDate2(); //TestDate3(); //TestDate4(); //TestDate5(); //TestDate7(); TestDate8(); return 0; }

8.流插入运算符

int i = 1; double d = 1.1; cout << i << " " << d << endl; //之所以可以自动识别输出变量类型,是因为iostream库中把<<重载好了 //cout.operator<<(i);//等价于调用这个函数

9.const成员函数

将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后 ⾯。

const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。 const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this

const Date* const this 那么这两个this有什么区别呢?

写法名字能不能改变指针指向?能不能改变指向的内容?比喻
Date* const this常量指针不能能

默认this指针都是这样,因为如果this的指向可以更改

this = nullptr; // 灾难!

this->_year = 2026; // 程序直接崩溃 (空指针解引用)

const Date* p指针常量 (指向常量的指针)能不能
p指向的内容只能读,不能写

const Date* const p

两者皆是不能不能

10.取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动 ⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景。

//属于默认成员函数,不显示式写,会默认生成(库里面有,可以不写) Date* operator&()//与底下函数构成重载,返回值与参数(隐含的this指针)都不同,一个是普通对象Date*,一个是const对象const Date* { return this; // return nullptr; } const Date* operator&()const { return this; // return nullptr; }
void TestDate8() { Date d1(2025, 4, 26); Date d2(2025, 7, 1); cout << &d1 << endl; cout << &d2 << endl; }
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进