我试图理解以下代码:
#include<iostream>
using namespace std;
class Base {
public:
virtual void f(float) { cout << "Base::f(float)\n"; }
};
class Derived : public Base {
public:
virtual void f(int) { cout << "Derived::f(int)\n"; }
};
int main() {
Derived *d = new Derived();
Base *b = d;
d->f(3.14F);
b->f(3.14F);
}
这打印
Derived::f(int)
Base::f(float)
我不确定为什么。
第一次调用 d->f(3.14F) 调用 Derived 中的函数 f。我不是 100% 确定为什么。我看了一下这个(http://en.cppreference.com/w/cpp/language/implicit_cast),上面写着:
浮点类型的纯右值可以转换为任何整数类型的纯右值。小数部分被截断,即小数部分被丢弃。如果该值不适合目标类型,则行为未定义
对我来说,你不能这样做,因为浮点数不适合 int。为什么允许这种隐式转换?
其次,即使我只是接受上述内容没问题,对 b->f(3.14F) 的第二次调用也没有意义。b->f(3.14F)是调用虚函数f,所以这被动态解析为调用b所指向的对象的动态类型关联的f(),这是一个Derived对象。由于我们被允许将 3.14F 转换为 int,因为第一个函数调用表明这是合法的,所以 this(据我的理解)应该再次调用 Derived::f(int) 函数。然而,它调用了 Base 类中的函数。那么这是为什么呢?
编辑:这就是我想出来并向自己解释的方式。
b 是指向 Base 对象的指针,因此我们只能使用 b 访问 Base 对象的成员,即使 b 确实指向某个 Derived 对象(这是标准的 OO/继承内容)。
此规则的唯一例外是当 Base 的成员函数被声明为虚拟时。在这种情况下,派生对象可能会覆盖此函数,通过使用完全相同的签名提供另一种实现。如果发生这种情况,即使我们碰巧通过指向 Base 对象的指针访问成员函数,也会在运行时调用这个 Derived 实现。
现在,在上面的代码片段中,我们没有发生任何覆盖,因为 B::f 和 D::f 的签名不同(一个是浮点数,另一个是整数)。所以当我们调用 b->f(3.14F) 时,唯一考虑的函数就是原来的 B::f,也就是被调用的函数。