C++中重载决议与可访问性检查的顺序

本文通过一个C++示例介绍了重载决议与可访问性检查的顺序。演示了当成员函数重载时,编译器如何先确定最佳匹配函数再检查其访问权限,即使存在类型转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用一个C++的小例子来说明C++中重载决议与可访问性检查的顺序。

 

    对于如下的类:

class ClxECS
{
public:
    
double Test(double dValue) { return dValue * 13; };

private:
    
int Test(int iValue) { return iValue * 13; };
};

    下面的函数输出是什么?

void ECS_test()
{
    
int iValue = 13;

    ClxECS lx;

    cout 
<< lx.Test(iValue) << endl;
}

    如果说你的答案是169,那么你就大错特错了!
    因为上面的函数根本不能通过编译!编译器会给你一个不能访问私有成员的错误。
    惊奇吗?难道编译器不能找到类ClxECS的公有成员函数double Test(double dValue)并把实参iValue类型转换为double吗?
    答案是:不能!其实原因很简单:C++中重载决议是在可访问性检查之前进行的。
    对于上面的例子,编译是按如下顺序进行的:
    首先,编译器进行重载决议,去查找适合的成员函数(而不管查找到的成员函数是否为public的)。这时候,编译器理所当然的选择参数为int类型的Test成员函数。
    然后,编译器进行访问性检查。这个时候,编译器会发现参数类型为int的成员函数Test是类ClxECS的私有成员。那么,编译器就会报错,告诉你无法访问私有成员。

 

 
<think>我们正在讨论C++中的函数重载,特别是常函数(const成员函数)非常函数(非const成员函数)的重载问题。根据用户的问题,我们需要解释这两者的区别以及它们的使用方法。 首先,我们需要明确: 1. 常函数(const成员函数)是在成员函数声明后加上const关键字,表示该函数不会修改类的成员变量(除非成员变量被声明为mutable)。 2. 非常函数(非const成员函数)则没有const修饰,它们可以修改成员变量。 在重载方面,一个类的成员函数可以同时存在const版本和非const版本,这构成了重载。它们的区别在于函数的常量性(即调用对象是常量还是非常量)。 具体来说: - 当通过一个非常量对象调用成员函数时,编译器会优先选择非常量版本的成员函数。 - 当通过一个常量对象调用成员函数时,只能调用常量版本的成员函数。 这种机制使得我们可以根据对象的常量性提供不同的操作,例如,在常量对象上我们只能进行读取操作,而在非常量对象上则可以进行写入操作。 下面我们通过一个例子来说明: ```cpp #include <iostream> using namespace std; class Example { private: int data; public: Example(int d) : data(d) {} // 非常量成员函数,返回对data的引用,允许修改 int& getData() { cout << "non-const getData" << endl; return data; } // 常量成员函数,返回data的副本,不允许修改 int getData() const { cout << "const getData" << endl; return data; } }; int main() { Example obj1(10); const Example obj2(20); // 通过非常量对象调用,调用非常量版本 int a = obj1.getData(); // 调用非常量版本,但这里我们只是读取,所以也可以调用常量版本?但是非常量版本更匹配 // 但是注意:obj1是非常量对象,所以优先匹配非常量版本 // 修改数据 obj1.getData() = 15; // 调用非常量版本,返回引用,可以赋值 // 通过常量对象调用,只能调用常量版本 int b = obj2.getData(); // 调用常量版本 // obj2.getData() = 25; // 错误,常量版本返回的是值,不是引用,且对象是常量,不能修改 return 0; } ``` 在上面的例子中,我们定义了两个`getData`函数,一个是非常量版本,返回`int&`,另一个是常量版本,返回`int`。这样,当我们通过非常量对象调用时,可以使用非常量版本进行修改;而通过常量对象调用时,只能使用常量版本进行读取。 注意:重载决议基于对象的常量性。如果只有常量版本,那么常量对象和非常量对象都可以调用常量版本(非常量对象也可以调用常量版本,但如果有非常量版本,则非常量对象会优先调用非常量版本)。如果只有非常量版本,那么常量对象将无法调用该函数(除非进行强制类型转换,但通常不推荐)。 另外,我们也可以看到,这两个函数的参数列表是相同的(都没有参数),但是它们的常量性不同,所以可以重载。 应用场景: 1. 在常量对象上调用成员函数时,确保不会修改对象的状态。 2. 在非常量对象上,允许修改对象的状态。 3. 例如,在标准库中,`vector`的`operator[]`就有两个版本:常量版本和非常量版本。常量版本返回常量引用,非常量版本返回非常量引用。 引用说明: - 在引用[3]中提到,函数重载的条件是:在同一作用域中,函数名相同,但形参列表(参数个数、类型或类型顺序)不同。而成员函数的常量性也可以作为重载的条件,因为const成员函数实际上有一个隐式的参数:指向对象的指针是常量指针(常量成员函数)还是非常量指针(非常量成员函数)。因此,编译器认为这两个函数是不同的。 关于效率问题,引用[5]展示了传值和传引用的效率对比。在重载中,如果我们需要返回成员变量,常量版本通常返回值(或常量引用)以避免修改,而非常量版本返回引用以允许修改。但是,返回引用时需要小心,确保引用的对象在生命周期内有效。 总结: - 常函数和非常函数可以重载。 - 区别在于:常量对象只能调用常函数,非常量对象优先调用非常函数(如果没有非常函数,也可以调用常函数)。 - 使用场景:根据对象是否是常量,提供不同的操作(读取或修改)。 相关问题: 1. 为什么常量对象不能调用非常量成员函数? 2. 在哪些情况下,非常量对象会调用常量成员函数? 3. 如何设计一个类,使其成员函数既能被常量对象调用,又能被非常量对象调用,且行为不同?</think>### C++ 常函数非常函数重载的区别及使用方法 #### 一、基本概念 1. **常函数(const成员函数)** 在成员函数声明末尾添加 `const` 关键字,表示该函数**不会修改对象状态**。 语法示例: ```cpp class MyClass { public: void display() const; // 常函数 }; ``` 2. **非常函数(非const成员函数)** 普通成员函数,**允许修改对象状态**。 ```cpp class MyClass { public: void modify(); // 非常函数 }; ``` #### 二、重载机制区别 | 特性 | 常函数 | 非常函数 | |--------------------|--------------------------------------------|------------------------------------| | **调用对象** | 常量对象或非常量对象均可调用 | 只能由非常量对象调用 | | **修改权限** | 禁止修改成员变量(除非变量声明为 `mutable`) | 允许修改成员变量 | | **重载决议** | 根据对象的常量性自动选择版本 | 优先匹配非常函数版本 | | **返回值类型** | 通常返回 `const T&` 或值类型 | 通常返回 `T&` 允许修改 | #### 三、使用方法代码示例 ```cpp #include <iostream> using namespace std; class DataContainer { private: int value; public: DataContainer(int v) : value(v) {} // 非常函数版本(修改数据) int& getValue() { cout << "调用非常函数" << endl; return value; // 返回引用可修改 } // 常函数版本(只读访问) int getValue() const { cout << "调用常函数" << endl; return value; // 返回值副本 } }; int main() { DataContainer obj(10); const DataContainer constObj(20); obj.getValue() = 30; // ✅ 调用非常函数,成功修改 cout << constObj.getValue(); // ✅ 调用常函数,输出20 // constObj.getValue() = 40; // ❌ 编译错误!常对象不能调用非常函数 return 0; } ``` **输出结果:** ``` 调用非常函数 调用常函数 20 ``` #### 四、应用场景 1. **常量对象安全访问** 常量对象只能调用常函数,避免意外修改[^3]: ```cpp const DataContainer config(100); int v = config.getValue(); // 安全读取 ``` 2. **API接口设计** 提供双重接口增强灵活性: ```cpp class Vector { public: // 非常量版本支持修改元素 float& operator[](size_t index); // 常量版本保证只读访问 const float& operator[](size_t index) const; }; ``` 3. **性能优化** 常函数可避免深拷贝,返回 `const` 引用提升效率[^5]: ```cpp const std::string& getName() const { return name; // 避免拷贝 } ``` #### 五、重载规则要点 1. **精确匹配优先** - 非常量对象优先匹配非常函数 - 常量对象强制匹配常函数 2. **签名区别** 编译器通过隐含的 `this` 指针类型区分重载: - 常函数:`const DataContainer* this` - 非常函数:`DataContainer* this` 3. **避免二义性** 若只提供常函数版本,非常量对象也可调用它;但若同时存在两个版本,编译器会根据对象常量性精确匹配[^2][^4]。 #### 六、典型错误案例 ```cpp class Logger { mutable int count; // mutable 允许常函数修改 public: // 错误:函数实现逻辑相同但未复用代码 void log() const { count++; // mutable 成员可修改 cout << "Const log\n"; } void log() { cout << "Non-const log\n"; } }; ``` **改进建议:** ```cpp void log() const { count++; _log(); // 复用公共逻辑 } void log() { _log(); } private: void _log() const { cout << "Log message\n"; } // 私有辅助函数 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值