当前位置: 首页 > news >正文

[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 修饰时,编译器会确保该函数:

  1. 不能修改类的非静态成员变量(包括直接修改或通过指针 / 引用间接修改)
  2. 不能调用类的非 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*
  1. Date*传给Date* const没有问题,都是可读也可修改,所以d1调用Print()不会出错
  2. 而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;
}

此时却出现了报错,这是为什么呢?

  • 首先对于第一个比较来说d1d2都是权限的保持
  • 接着对于第二个比较来说d1传递过去是权限的缩小,本来是可以修改了,现在不能修改;d2传递过去就变成了【权限的放大】,原本的d2const,但是this指针并没有加[const]做修饰,所以就造成了【权限方法】的问题

  •    那要怎么去做一个修改呢?此时就可以使用到我们上面所讲到的【const成员函数】为当前的隐藏形参this 加上一个const做修饰,此时就可以做到【权限保持】
bool operator<(const Date& d) const

3.🌟🌟🌟针对const成员函数的常考面试题(重点!!)

❓问题1const对象 可以调用 非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成员函数的理解

感谢你的三连支持!!!

http://www.dtcms.com/a/395080.html

相关文章:

  • 园区3D可视化数字孪生管理平台与 IBMS 智能化集成系统:打造智慧园区新范式​
  • 【Javaweb】Restful开发规范
  • 【C++】深入理解const 成员函数
  • 使用vscode自带指令查找有问题的插件
  • JAVA算法练习题day18
  • springboot3 exception 全局异常处理入门与实战
  • spring简单入门和项目创建
  • lVS 负载均衡技术
  • 【论文阅读】OpenDriveVLA:基于大型视觉语言动作模型的端到端自动驾驶
  • Redis 缓存更新策略与热点数据识别
  • 新手小白——Oracle新建表完成题目
  • 如何让百度快速收录网页如何让百度快速收录网页的方法
  • Bugku-1和0的故事
  • 微硕WINSOK N+P MOSFET WSD3067DN56,优化汽车智能雨刷系统
  • DeviceNet 转 Profinet:西门子 S7 - 1500 PLC 与欧姆龙伺服电机在汽车焊装生产线夹具快速切换定位的通讯配置案例
  • 探索鸿蒙应用开发:构建一个简单的音乐播放器
  • 人脸识别(具体版)
  • 4.10 顶点光源
  • 深度学习---PyTorch 神经网络工具箱
  • 第九篇:静态断言:static_assert进行编译期检查
  • 第10讲 机器学习实施流程
  • tablesample函数介绍
  • 机器学习-单因子线性回归
  • android pdf框架-14,mupdf重排
  • 借助VL模型实现一个简易的pdf书签生成工具
  • 78-数据可视化-折线图
  • 静默安装 Oracle Database 21c on CentOS 7.9
  • DINOv3详解+实际下游任务模型使用细节(分割,深度,分类)+ Lora使用+DINOv1至v3区别变换分析(可辅助组会)
  • Linux编译SRS并测试RTMP流
  • 【完整源码+数据集+部署教程】遥感温室图像分割系统: yolov8-seg-slimneck