《吃透 C++ 类和对象(中):const 成员函数与取地址运算符重载解析》

发布于:2025-08-17 ⋅ 阅读:(18) ⋅ 点赞:(0)

🔥个人主页:@草莓熊Lotso

🎬作者简介:C++研发方向学习者

📖个人专栏: 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。

前言:在前面我们学习了4中默认成员函数,这剩下的两种一般都不需要自己去实现,所有在这里仅作了解就可以了,感兴趣的可以更全面的学习一下(本篇博客中博主也通过查询一些资料进行了一些知识的补充,也可以看看)。在 C++ 面向对象编程的知识体系里,const成员函数和取地址运算符重载虽看似基础,实则蕴含诸多关键细节,对代码的安全性、规范性与灵活性影响深远。深入吃透它们,能让我们写出更健壮、更贴合复杂场景需求的类,接下来展开全方位剖析 


目录

一、const 成员函数 

(一)本质与语法逻辑

(二)使用场景与意义

(三)深入代码示例分析

二、取地址运算符重载 

(一)编译器默认行为与原理

(二)手动重载的特殊场景与实现

(三)深入代码示例分析


一、const 成员函数 

(一)本质与语法逻辑

const成员函数,核心是对隐含的this指针进行权限收缩。在 C++ 中,非const成员函数里,this指针类型是类类型* const(指针本身不能改变指向,但可通过指针修改对象内容 );而一旦函数被const修饰(写在参数列表后 ),this指针类型就变为const 类类型* const,这意味着在函数内部,不能通过this指针去修改对象的成员变量(除非成员被声明为mutablemutable修饰的成员可在const成员函数中被修改,常用来处理如线程锁、缓存标记等特殊的 “逻辑只读但实际需修改” 场景 )。

从语法书写看,const要紧跟在成员函数的参数列表之后,它是函数签名的一部分,区分开const成员函数和非const成员函数。比如Date类里的Print函数:

void Print() const;

这清晰表明该函数不会修改Date对象的状态,是一种 “只读” 操作承诺。

(二)使用场景与意义

  1. 保障const对象的操作合法性
    当我们创建const修饰的对象,如const Date d2(2024, 8, 5);,这类对象的成员变量理论上都应是 “只读” 的。C++ 语法规定,const对象只能调用const成员函数,因为非const成员函数可能隐含修改对象的风险(其this指针权限更高 )。所以为const对象提供对应的const成员函数,才能让对象的功能调用完整且安全。像d2.Print();,若Print不是const成员函数,这行代码就会编译报错,const成员函数让const对象能正常执行打印这类 “读操作”。
  2. 明确代码语义,提升可读性与可维护性
    当我们在类的设计中,把不修改对象状态的函数都声明为const成员函数,阅读代码的人(包括自己和团队成员 )能快速识别出函数的行为特征 —— 不会改变对象数据。后续维护代码时,也能更清晰地判断函数调用是否会影响对象状态,降低因误操作修改数据引发 bug 的概率。比如一个复杂的业务类,有大量获取数据、格式化输出的函数,标记为const后,逻辑边界一目了然。

(三)深入代码示例分析

回到Date类的例子:

#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
    {
        // 这里只能访问成员变量,不能修改,若尝试赋值 _year = 2025; 会编译报错
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

main函数中:

int main()
{
    Date d1(2024, 7, 5);
    d1.Print();  // 非const对象调用const成员函数,权限缩小,合法
    const Date d2(2024, 8, 5);
    d2.Print();  // const对象调用const成员函数,保障只读,合法
    return 0;
}

若我们在Print函数里不小心写了修改_year的代码,编译器会直接报错,这就是const成员函数在编译阶段帮我们拦截 “非法修改” 风险,从源头保障代码正确性。而且,非const对象调用const成员函数是允许的,因为非const对象权限更大,调用const成员函数属于 “缩小权限”,符合 C++ 的权限控制逻辑,这也体现出const成员函数设计上的灵活性 —— 能同时服务于const和非const对象,只要是 “只读” 操作需求,都可统一用const成员函数实现。


二、取地址运算符重载 

(一)编译器默认行为与原理

对于一个类,比如Date类,编译器会自动生成两个取地址运算符重载函数,分别应对普通对象和const对象的地址获取:

  • 普通取地址重载:Date* operator&();,作用是返回普通Date对象的地址,默认实现就是简单返回this指针,this指针指向当前对象的起始地址。
  • const取地址重载:const Date* operator&() const;,针对const修饰的Date对象,返回const指针,保障const对象地址获取的 “只读” 特性(获取的地址不能用于修改对象 ),默认实现同样是返回this指针。

大多数时候,编译器生成的这两个函数就能满足我们日常需求,比如调试时打印对象地址、用指针操作对象(配合const规则 )等,所以我们无需手动编写。

(二)手动重载的特殊场景与实现

  1. 场景一:隐藏真实地址,保护对象隐私
    在一些对安全性要求极高的场景,比如类封装的是敏感数据(像加密密钥管理类、用户隐私信息类 ),我们不希望外部直接获取到对象的真实内存地址(防止通过地址做内存窥探、恶意篡改等非常规操作 ),就可以手动重载取地址运算符,返回一个经过处理的 “虚假” 地址。例如:
    class SecretData
    {
    public:
        SecretData* operator&()
        {
            // 故意返回一个不在对象真实地址的指针,这里简单示例返回nullptr,实际可更复杂
            return nullptr; 
            // 或者返回一个全局的“ dummy ”对象地址,混淆视听
        }
    
        const SecretData* operator&() const
        {
            return nullptr;
        }
    
    private:
        // 假设存储敏感密钥等数据
        string _key; 
    };

    这样,当外部代码尝试获取SecretData对象地址时,拿到的不是真实地址,一定程度上增加了数据安全性(当然,这种方式更多是 “障眼法”,配合其他安全机制才能更好保护数据,但体现了取地址重载的定制能力 )。

  2. 场景二:地址相关的特殊逻辑处理
    比如在一些内存池管理、对象池设计的场景中,对象的地址可能不是简单的 “自身地址”,而是关联到内存池的某个索引、映射地址。这时,手动重载取地址运算符,就能将对象地址获取逻辑与内存池管理逻辑结合。举个简化例子:
    class PoolObject
    {
    public:
        PoolObject* operator&()
        {
            // 假设内存池有一个全局映射,将对象地址转换为池内管理地址
            return MemoryPool::MapToPoolAddress(this); 
        }
    
    private:
        // 对象数据...
        int _data; 
    };

    通过这种方式,把对象地址获取和内存池的地址映射、管理流程整合,让类的地址操作更贴合整体框架需求,提升代码架构的内聚性。

  3. 场景三:调试与日志中的地址定制
    在大型项目调试时,我们可能希望对象地址打印更具辨识度,比如带上对象的标识信息(像对象编号、类型标记 ),手动重载取地址运算符,返回一个包含这些信息的 “伪装” 地址(实际是自定义结构体或特殊指针,配合日志打印解析 ),方便在日志中快速定位、区分不同对象。不过这种场景实现较复杂,需要结合自定义的地址解析、日志系统等一起工作。

(三)深入代码示例分析

Date类手动重载为例,看看特殊实现的写法(简单演示返回乱序地址思路 ):

class Date
{
public:
    Date* operator&()
    {
        // 这里随意返回一个地址,实际可根据复杂逻辑生成
        return reinterpret_cast<Date*>(0x12345678); 
    }

    const Date* operator&() const
    {
        return reinterpret_cast<const Date*>(0x87654321); 
    }

private:
    int _year;
    int _month;
    int _day;
};

但要特别注意,手动重载取地址运算符后,外部代码用指针操作对象时,必须严格遵循新的地址逻辑,否则极易引发内存访问错误(比如拿到虚假地址后解引用,会导致程序崩溃 )。所以,除非有明确的特殊需求,且能把控后续地址使用逻辑,否则不建议轻易手动重载,优先用编译器默认生成的版本更稳妥。

总结:const成员函数是守护对象数据安全的 “门神”,把控着成员函数对对象的修改权限;取地址运算符重载是地址获取的 “调节阀”,常规场景依赖编译器默认,特殊场景可定制地址逻辑。吃透这两个知识点,能让我们在 C++ 类设计与面向对象编程中,精准把控代码的安全性与灵活性,写出更专业、更可靠的代码,为应对复杂项目开发筑牢基础 。


往期回顾:

《吃透 C++ 类和对象(上):封装、实例化与 this 指针详解》

《吃透 C++ 类和对象(中):构造函数与析构函数的核心逻辑》

《吃透 C++ 类和对象(中):拷贝构造函数与赋值运算符重载深度解析》

结语:吃透这两个知识点,让我们在 C++ 世界里,既能守好代码安全的底线,又能解锁定制化编程的新可能,以更成熟的姿态,奔赴复杂项目开发的挑战,让每一个类的设计,都成为高效、安全代码的基石 。如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。


网站公告

今日签到

点亮在社区的每一天
去签到