

🔥个人主页:小张同学****
🎬作者简介:C++研发方向学习者
📖**个人专栏: 《C语言》 《数据结构》 《C++深度剖析:从入门到深耕》**
⭐️人生格言:无视中断,不弃热枕,方得坚持之道。
前言:
在前面我们学习了4种默认成员函数,这剩下的两种取地址重载一般都不需要自己去实现,仅作了解即可,感兴趣的可以更全面的学习一下。下面我们就来深入学习一下这两种取地址重载:普通对象取地址和const对象取地址。
目录
一、const成员函数
• 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
• const实际修饰该成员函数隐含的this指针 ,表明在该成员函数中不能对类的任何成员进行修改 。const 修饰Date类的Print成员函数,Print隐含的this指针由Date* const this变为 const Date* const this。
1.本质概念与函数书写:
const成员函数,核心是对隐含的this指针进行权限收缩。在 C++ 中,非const成员函数里,this指针类型是 类类型* const (指针本身不能改变指向,但可通过指针修改对象内容 );而一旦函数被const修饰(写在参数列表后 ),this指针类型就变为 const 类类型* const,这意味着在函数内部,不能通过this指针去修改对象的成员变量(除非成员被声明为mutable,mutable修饰的成员可在const成员函数中被修改,常用来处理如线程锁、缓存标记等特殊的 "逻辑只读但实际需修改" 场景 )。
cpp
// Date* const this
void Print()
{
......
}
// 变为const成员函数
// void Print(const Date* const this) const
void Print() const
{
......
}
从语法书写看,const要紧跟在成员函数的参数列表之后,它是函数签名的一部分,区分开const成员函数和非const成员函数。比如Date类里的Print函数;
这清晰表明该函数不会修改Date对象的状态,是一种 "只读" 操作承诺。
2.运用场景和意义:
保障const对象的操作合法性
当我们创建const修饰的对象,如const Date d2(2024, 8, 5);,这类对象的成员变量理论上都应是 "只读" 的。C++ 语法规定,const对象只能调用const成员函数,因为非const成员函数可能隐含修改对象的风险(其this指针权限更高 )。所以为const对象提供对应的const成员函数,才能让对象的功能调用完整且安全。像d2.Print();,若Print不是const成员函数,这行代码就会编译报错,const成员函数让const对象能正常执行打印这类 "读操作"。
明确代码语义,提升可读性与可维护性
当我们在类的设计中,把不修改对象状态的函数都声明为const成员函数,阅读代码的人(包括自己和团队成员 )能快速识别出函数的行为特征 ------ 不会改变对象数据。后续维护代码时,也能更清晰地判断函数调用是否会影响对象状态,降低因误操作修改数据引发 bug 的概率。比如一个复杂的业务类,有大量获取数据、格式化输出的函数,标记为const后,逻辑边界一目了然。
3.代码说明:
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// void Print(const Date* const this) const
void Print() const
{
// 这里只能访问成员变量,不能修改,若尝试赋值 _year = 2025; 会编译报错
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩
Date d1(2024, 7, 5);
d1.Print();// 非const对象调用const成员函数,权限缩小,合法
const Date d2(2024, 8, 5);
d2.Print();// const对象调用const成员函数,保障只读,合法
return 0;
}
二、取地址运算符重载
取地址运算符重载分为普通取地址运算符重载 和const取地址运算符重载 ,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,返回空或者胡乱返回一个地址。
1.编译器默认生成的函数:
对于一个类,编译器会自动生成两个取地址运算符重载函数,分别应对普通对象和const对象的地址获取:
1)普通取地址重载 :Date* operator&(); 作用是返回普通Date对象的地址,默认实现就是简单返回this指针,this指针指向当前对象的起始地址。
2)const取地址重载 :const Date* operator&() const; 针对const修饰的Date对象,返回const指针,保障const对象地址获取的 "只读" 特性(获取的地址不能用于修改对象 ),默认实现同样是返回this指针,但是返回的地址不能再次进行修改。
大多数时候,编译器生成的这两个函数就能满足我们日常需求,比如调试时打印对象地址、用指针操作对象(配合const规则 )等,所以我们无需手动编写。
2.特殊场景需要手动重载:
1)场景一:保护对象隐私,不让对象被读取地址
在一些对安全性要求极高的场景,比如类封装的是敏感数据(像加密密钥管理类、用户隐私信息类 ),我们不希望外部直接获取到对象的真实内存地址(防止通过地址做内存窥探、恶意篡改等非常规操作 ),就可以手动重载取地址运算符,返回一个经过处理的 "虚假" 地址。例如:
cpp
class SecretData
{
public:
SecretData* operator&()
{
// 故意返回一个不在对象真实地址的指针,这里简单示例返回nullptr,实际可更复杂
return nullptr;
// 或者返回一个全局的" dummy "对象地址,混淆视听
}
const SecretData* operator&() const
{
return nullptr;
}
private:
// 假设存储敏感密钥等数据
string _key;
};
2)场景二:地址相关的特殊逻辑处理
比如在一些内存池管理、对象池设计的场景中,对象的地址可能不是简单的 "自身地址",而是关联到内存池的某个索引、映射地址。这时,手动重载取地址运算符,就能将对象地址获取逻辑与内存池管理逻辑结合。举个简化例子:
cpp
class PoolObject
{
public:
PoolObject* operator&()
{
// 假设内存池有一个全局映射,将对象地址转换为池内管理地址
return MemoryPool::MapToPoolAddress(this);
}
private:
// 对象数据...
int _data;
};
感兴趣的朋友们可以自行搜索进一步了解。
3)场景三:调试与日志中的地址定制
在大型项目调试时,我们可能希望对象地址打印更具辨识度,比如带上对象的标识信息(像对象编号、类型标记 ),手动重载取地址运算符,返回一个包含这些信息的 "伪装" 地址(实际是自定义结构体或特殊指针,配合日志打印解析 ),方便在日志中快速定位、区分不同对象。不过这种场景实现较复杂,需要结合自定义的地址解析、日志系统等一起工作。
3.代码说明:
cpp
class Date
{
public:
Date* operator&()
{
return this;
// return nullptr;
// return (Date*)0x0012ff40;
}
const Date* operator&()const
{
return this;
// return nullptr;
// return (const Date*)0x0012ff40;
}
private:
int _year; // 年
int _month; // ⽉
int _day; // ⽇
};
注意:手动重载取地址运算符后,外部代码用指针操作对象时,必须严格遵循新的地址逻辑,否则极易引发内存访问错误(比如拿到虚假地址后解引用,会导致程序崩溃 )。所以,除非有明确的特殊需求,且能把控后续地址使用逻辑,否则不建议轻易手动重载,优先用编译器默认生成的版本更稳妥。
本篇博客的完整原代码:
往期回顾:
C++ 类和对象(三):拷贝构造函数与赋值运算符重载之核心实现-CSDN博客
C++ 类和对象(二):实例化、this指针、构造函数、析构函数详解-CSDN博客
结语:
const成员函数对数据对象进行安全保护,把控成员函数对对象的修改权限,保障了数据安全;取地址运算符重载这两个默认成员函数同意也能保护对象,但是这两个函数一般不用自己重新定义,使用编译器自动生成的就行了,除非有特殊要求。
