[C++:类的默认成员函数——Lesson7.const成员函数]
目录
前言:
const成员函数
📖const修饰类的成员函数
核心概念
语法形式
关键特性
作用与意义
❓问题1
❓问题2
🌟🌟🌟针对const成员函数的常考面试题(重点!!)
📖取地址及const取地址操作符重载
✅const成员函数总结
1. 定义与语法
2. 核心特性:this指针的常量性
3. 关键规则
(1)const成员函数的限制
(2)对象与函数的匹配规则
(3)mutable关键字的例外
4. 作用与意义
5. 常见误区
总结
结束语
前言:
在我们前面学习的类中,我们会定义成员变量和成员函数,这些我们自己定义的函数都是普通的成员函数,但是如若我们定义的类里什么也没有呢?是真的里面啥也没吗?如下:
class Date {};
如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成6个默认成员函数。
【默认成员函数概念】:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
其中上次的博客已经详细的讲解了lesson5.构造函数&&析构函数的使用方法与lesson6.拷贝构造函数&&赋值运算符重载,所以本次博客将继续深度的讲解const成员函数问题⭐
1、const成员函数
1.1📖const修饰类的成员函数
在 C++ 中,
const
修饰类的成员函数(称为 const 成员函数)是一种重要的机制,用于保证该成员函数不会修改类的成员变量(静态成员变量除外),从而增强代码的可读性、可靠性和 const 正确性。1.核心概念
当一个成员函数被
const
修饰时,编译器会确保该函数:
- 不能修改类的非静态成员变量(包括直接修改或通过指针 / 引用间接修改)
- 不能调用类的非 const 成员函数(防止间接修改成员变量)
2.语法形式
在成员函数的声明和定义中,将
const
关键字放在参数列表之后、函数体之前(声明时也需保持一致):class MyClass { private:int value; public:// 声明 const 成员函数int getValue() const; // const 放在参数列表后// 非 const 成员函数(可以修改成员变量)void setValue(int v); };// 定义 const 成员函数(必须重复 const 关键字) int MyClass::getValue() const {return value; // 允许读取成员变量// value = 10; // 错误:不能修改成员变量 }void MyClass::setValue(int v) {value = v; // 允许修改 }
3.关键特性
1.const 对象只能调用 const 成员函数
- const 对象的成员变量被视为只读,因此只能调用保证不修改数据的 const 成员函数:
const MyClass obj; // const 对象 obj.getValue(); // 正确:调用 const 成员函数 obj.setValue(5); // 错误:const 对象不能调用非 const 成员函数
2 非 const 对象可以调用所有成员函数
- 非 const 对象既可以调用 const 成员函数,也可以调用非 const 成员函数:
MyClass obj; // 非 const 对象 obj.getValue(); // 正确 obj.setValue(5); // 正确
3.const 成员函数的重载
- 可以根据
const
关键字重载成员函数,即一个类中可以同时存在同名的 const 和非 const 成员函数:class MyClass { public:// 非 const 版本:返回非 const 引用int& getValue() { return value; }// const 版本:返回 const 引用(或值)const int& getValue() const { return value; } };
调用时,编译器会根据对象是否为 const 自动选择对应的版本。
4.作用与意义
- 提高代码可读性:明确告诉开发者该函数不会修改对象状态
- 增强代码可靠性:由编译器保证不修改数据,避免意外修改
- 支持 const 对象操作:使 const 对象能够安全地调用成员函数
- const 正确性:是 C++ 类型安全的重要组成部分,确保对象在不同场景下的正确使用
const 成员函数通常用于访问器(getter) 函数,而非 const 成员函数用于修改器(setter) 函数,这是 C++ 类设计中的常见实践。
2.⭐下面我们通过几个问题来加深理解:
❓问题1
假如我现在有一个日期类,并且有如下的Func函数即调用情况:
class Date { public://构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Printf(){cout << _year << "年" << _month << "月" << _day << "日" << endl;} private:int _year;int _month;int _day; };void Func(const Date& d) {d.Printf(); }int main() {Date d1(2023, 11, 1);d1.Printf();Date d2(2023, 11, 2);Func(d2);return 0; }
此时却出现了报错,这是为什么呢?
- 很明显,这里Func函数d的调用Print()出错了,而d1调用Print()却没出错,为何呢❓
这里涉及到权限问题。我们先把实际调用过程中,隐含的this指针写出来:
如果对 this指针不了解的朋友可以看这篇博客:lesson4.类的封装思想
Print()函数里的const修饰this本身,this不能修改,但是this可以初始化,接着我们要搞清楚&d1和&d的类型分别是啥:
- &d1:Date*
- &d:const Date*
- Date*传给Date* const没有问题,都是可读也可修改,所以d1调用Print()不会出错
- 而const Date* 指向的内容不能被修改,可是当它传给Date*时就出错了,因为Date*是可以修改的,这里传过去会导致权限放大。所以当然d调用Print()函数报错。
⭐解决办法:
- 加上const去保护this指向的内容,也就是在Date*的前面加上const:
void Print(const Date* const this) {cout << _year << "年" << _month << "月" << _day << "日" << endl; }
不过这里又不能直接加上const,因为this指针是隐含的,你不能显示的将const写出来。因此,C++为了解决此问题,允许在函数后面加上const以达到刚才的效果:
void Print() const// 编译器默认处理成:void Print(const Date* const this) {cout << _year << "-" << _month << "-" << _day << endl; }
此时我const Date*传给const Date*就是权限不变,自然不会出错了,同样我Date*传给const Date*就是权限缩小也不会有问题。因为权限不能放大,只能缩小或不变。
正确的代码:
class Date { public://构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Printf() const // void Printf(Date* const this){cout << _year << "年" << _month << "月" << _day << "日" << endl;} private:int _year;int _month;int _day; };void Func(const Date& d) {d.Printf(); // d.Printf(&d); }int main() {Date d1(2023, 11, 1);d1.Printf(); // d1.Printf(&d);Date d2(2023, 11, 2);cout << endl;Func(d2);return 0; }
❓问题2
假如我们遇到如下,自定义类型的比较情况:
class Date { public://构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}bool operator<(const Date& d){if (_year < d._year ||_year == d._year && _month < d._month ||_year == d._year && _month == d._month && _day < d._day){return true;}else{return false;}} private:int _year;int _month;int _day; };int main() {Date d1(2023, 11, 1);const Date d2(2023, 11, 2);cout << endl;d1 < d2;d2 < d1;return 0; }
此时却出现了报错,这是为什么呢?
- 首先对于第一个比较来说
d1
和d2
都是权限的保持
- 接着对于第二个比较来说
d1
传递过去是权限的缩小,本来是可以修改了,现在不能修改;d2
传递过去就变成了【权限的放大】,原本的d2
是const,但是this指针并没有加[const]
做修饰,所以就造成了【权限方法】的问题
- 那要怎么去做一个修改呢?此时就可以使用到我们上面所讲到的【const成员函数】,为当前的隐藏形参
this
加上一个const做修饰,此时就可以做到【权限保持】bool operator<(const Date& d) const
3.🌟🌟🌟针对const成员函数的常考面试题(重点!!)
❓问题1:const对象 可以调用 非const成员函数吗?
这个当然不可以。我们前面已经说过了,若 const对象去调用非const成员函数,会造成【权限放大】的现象,原本在类外const对象的内容是不可以修改的,但是到了函数内部却有可以修改了,这是不被允许的
- 每个成员函数都有一个隐藏的 this 指针,非 const 成员函数的 this 指针类型通常是 “类名 * const”,例如 Date 类的非 const 成员函数中 this 指针类型是 Date* const,它指向的对象内容是可修改的。而 const 对象的类型是 “const 类名 *”,如 const Date*,其指向的对象内容不可修改。当 const 对象调用非 const 成员函数时,会将 const 对象的地址传给 this 指针,就相当于把一个只读权限的对象传递给了一个具有读写权限的指针,从而导致权限放大。
例如以下代码:
class Date { public:void Print1() { _year = 10; cout << _year << "-" << _month << "-" << _day << endl; }void Print2() const { //_year = 10; 报错,const保护了*thiscout << _year << "-" << _month << "-" << _day << endl; } private:int _year;int _month;int _day; };int main() {Date d1(2024, 7, 16);const Date d2(2024, 7, 16);d1.Print2(); // 可以,非const对象调用const成员函数,权限缩小d2.Print1(); // 不行,const对象调用非const成员函数,权限放大return 0; }
- 在上述代码中,const 对象 d2 调用非 const 成员函数 Print1 会导致权限放大,编译器会阻止这种调用。
❓ 问题2:非const对象 可以调用 const成员函数吗?
这个当然是可以的。非const对象本身就是可读可写的,那在函数内部你要去修改或者不修改都不会有影响
非 const 对象可以调用非 const 成员函数,也可以调用 const 成员函数,且在函数内部进行符合函数权限的操作都是允许的。相关原理如下:
- 非 const 对象调用非 const 成员函数:非 const 对象具有可读可写权限,非 const 成员函数可以修改对象状态,其隐藏的 this 指针类型通常是 “类名 * const”。当非 const 对象调用非 const 成员函数时,将对象地址传给 this 指针,权限匹配,不会出现权限问题,在函数内部可以根据需求修改对象成员变量或执行其他操作。
- 非 const 对象调用 const 成员函数:const 成员函数承诺不修改对象的非静态数据成员,其隐藏的 this 指针类型为 “const 类名 * const”。非 const 对象调用 const 成员函数时,相当于将自身的读写权限降级为只读权限,属于权限缩小,这是符合 C++ 权限规则的,因此可以正常调用。
❓问题3:const成员函数内可以调用其它的非const成员函数吗?
不可以,const成员函数内部只能调用const成员函数。因为const成员函数内部的this指针已经具有常属性的,万一这个非const成员函数去修改了成员变量的内容就会出问题了
- 在 C++ 中,const 成员函数通过 const 修饰隐含的 this 指针,来承诺不会修改对象的状态,其 this 指针类型为 “指向常量的常量指针”。而非 const 成员函数可能会修改对象的成员变量,其 this 指针类型通常为 “指向非常量的常量指针”。如果在 const 成员函数中调用非 const 成员函数,就可能会违背 const 成员函数不修改对象状态的承诺,导致对象状态被意外修改,造成权限放大等问题。例如:
#include <iostream> class MyClass { public:void nonConstFunc() {std::cout << "Non - const function, can modify object state." << std::endl;}void constFunc() const {nonConstFunc(); // 编译错误,const成员函数不能调用非const成员函数std::cout << "Const function, cannot modify object state." << std::endl;} }; int main() {MyClass obj;obj.constFunc();return 0; }
- 上述代码中,在 const 成员函数
constFunc
中调用非 const 成员函数nonConstFunc
,会导致编译错误。
❓问题4:非const成员函数内可以调用其它的const成员函数吗?
可以,权限缩小
- 因为 const 成员函数不会修改对象的状态,非 const 成员函数调用它不会产生权限冲突等问题,也不会违背 const 成员函数的常量性原则。例如以下代码:
#include <iostream> class MyClass { public:void nonConstFunc() {std::cout << "Non - const function called." << std::endl;constFunc();}void constFunc() const {std::cout << "Const function called." << std::endl;} };int main() {MyClass obj;obj.nonConstFunc();return 0; }
- 上述代码中,
nonConstFunc
是非 const 成员函数,它内部调用了constFunc
这个 const 成员函数,程序可以正常编译运行。
4.📖取地址及const取地址操作符重载
在 C++ 中,取地址及 const 取地址操作符重载是类的默认成员函数。一般情况下,无需用户显式定义,编译器会自动生成相关函数供使用。但在一些特殊场景下,需要用户自己重载这两个操作符。
语法形式
- 取地址操作符重载函数的语法为
类名* operator&()
,它用于返回普通对象的地址。- const 取地址操作符重载函数的语法为
const 类名* operator&() const
,用于返回 const 对象的地址。其中,函数参数列表后的const
修饰this
指针,表明该函数不会修改对象的状态,返回值用const
修饰是为了保证类型匹配。class Date { public://取地址&重载Date* operator&(){return this;}//const取地址&重载const Date* operator&()const{return this;} private:int _year;int _month;int _day; };
当然,如果我们自己不写&重载,编译器也会默认生成,可以通过打印来看看:
- 作用与应用场景
- 通常,编译器生成的取地址重载函数能满足基本需求,可直接获取对象的地址。但如果不希望使用者获取到对象的真实地址,可显式实现取地址重载,返回空指针或其他特定值。例如,当对象内部存储了敏感信息,不希望外部通过地址直接访问和操作时,可通过这种方式进行限制。
- 当需要自定义对象地址的返回形式,或者结合特定的类功能,需要在获取地址时执行一些额外操作(如记录地址获取的次数、进行权限检查等),也可以通过重载取地址操作符来实现。
5.✅const成员函数总结
在 C++ 中,
const
成员函数是类中一种特殊的成员函数,其核心作用是保证函数不会修改对象的状态,是实现常量正确性(const correctness)的重要机制。以下是关于const
成员函数的总结:1. 定义与语法
const
成员函数在函数声明和定义的参数列表后添加const
关键字,语法如下:class 类名 { public:返回类型 函数名(参数列表) const; // 声明 };// 定义 返回类型 类名::函数名(参数列表) const {// 函数体(不能修改对象的非静态成员) }
2. 核心特性:
this
指针的常量性
- 普通成员函数的隐含
this
指针类型为类名* const
(指向非常量对象的常量指针),允许修改对象成员。const
成员函数的隐含this
指针类型为const 类名* const
(指向常量对象的常量指针),禁止修改对象的非静态成员。3. 关键规则
(1)
const
成员函数的限制
- 不能修改类的非静态数据成员(除非成员用
mutable
修饰)。- 不能在内部调用非
const
成员函数(避免权限放大,防止间接修改对象)。- 可以调用其他
const
成员函数或静态成员函数(静态成员不属于对象,无状态修改风险)。(2)对象与函数的匹配规则
const
对象:只能调用const
成员函数(防止通过函数修改对象)。- 非
const
对象:既可以调用const
成员函数(权限缩小,允许),也可以调用非const
成员函数。(3)
mutable
关键字的例外
- 被
mutable
修饰的成员变量,即使在const
成员函数中也可以被修改(用于记录日志、缓存等不影响对象逻辑状态的场景)。class Test { private:mutable int count = 0; // 可在const函数中修改 public:void print() const {count++; // 合法,mutable成员不受const限制std::cout << "Count: " << count << std::endl;} };
4. 作用与意义
- 保证常量正确性:明确区分 “只读操作” 和 “读写操作”,避免
const
对象被意外修改。- 提高代码可读性:通过
const
关键字,开发者能直观判断函数是否会修改对象状态。- 支持 const 对象调用:
const
对象只能访问const
成员函数,确保其状态不可变。- 函数重载依据:
const
可以作为函数重载的区分条件(const
版本与非const
版本可共存)。class Test { public:int& get() { return data; } // 非const版本,供非const对象调用const int& get() const { return data; } // const版本,供const对象调用 private:int data; };
5. 常见误区
- 认为
const
成员函数 “什么都不能改”:实际可以修改mutable
成员和静态成员。- 混淆
const
对象与const
成员函数的关系:const
对象的限制是 “只能调用const
函数”,而非const
函数本身的限制。- 过度使用
const
:无需保证不修改对象的函数不必加const
,避免冗余。总结
const
成员函数是 C++ 中实现 “只读操作” 的核心机制,通过限制this
指针的权限,确保对象状态不被意外修改。合理使用const
成员函数能提高代码的安全性、可读性和可维护性,是 C++ 常量正确性的重要体现。
结束语
以下就是我对【C++】类的默认成员函数----const成员函数的理解
感谢你的三连支持!!!