做题遇到虚函数绑定问题,以前学过,记得又不太清楚了

题目

#include <iostream>
using namespace std;

struct A{
    virtual void fun1( ){
        cout << "A:fun1" << endl;
    }
    
    void fun2( ){
        cout << "A:fun2" << endl;
    }
};

struct B : public A{
    virtual void fun1( ){
        cout << "B:fun1" << endl;
    }

    void fun2( ){
        cout << "B:fun2" << endl;
    }
};

int main(){
    A a;
    B b;
    ((A*)(&b))->fun1();
    ((A*)(&b))->fun2();
    ((B*)(&a))->fun1();
    ((B*)(&a))->fun2();
    return 0;
}

这样一个函数,他的输出应该是怎么样的

B:fun1
A:fun2
A:fun1
B:fun2

为什么?

chart GPT回答
这个程序的输出将取决于虚函数的动态绑定和指针的强制类型转换:

**A a; **和 B b; 分别创建了一个类型为 A 和 B 的对象。

** ((A*)(&b))->fun1(); **
通过将 B 类型的对象 b 的地址强制转换为 A 类型的指针,然后调用 fun1。由于 fun1 是虚函数,它会根据实际对象的类型进行动态绑定。因此,它将调用 B 类型对象的 fun1,输出 "B:fun1"。

*((A)(&b))->fun2(); **
类似地,这里也将 B 类型的对象 b 的地址强制转换为 A 类型的指针,但是 fun2 不是虚函数,所以它将在编译时绑定到 A 类型的 fun2,输出 "A:fun2"。

*((B)(&a))->fun1(); **
这里将 A 类型的对象 a 的地址强制转换为 B 类型的指针,然后调用 fun1。由于动态绑定,它将调用 A 类型对象的 fun1,输出 "A:fun1"。这是因为 B 类型对象的 fun1 虽然重写了 A 类型的 fun1,但是由于指针的静态类型是 B 类型,所以编译器会将它绑定到 B 类型的 fun1。

((B)(&a))->fun2();*
类似地,这里将 A 类型的对象 a 的地址强制转换为 B 类型的指针,然后调用 fun2。由于 fun2 不是虚函数,它将在编译时绑定到 B 类型的 fun2,输出 "B:fun2"。

再解释一下关于((B*)(&a))->fun1();

当你执行 ((B*)(&a))->fun1(); 时,你正在尝试将一个 A 类型的对象 a 的地址强制转换为 B 类型的指针,然后调用 fun1 函数。这行代码看起来似乎在尝试调用 B 类型对象的 fun1 函数,但实际情况并非如此,因为 a 是一个 A 类型的对象。

这里的关键是要理解强制类型转换只是告诉编译器将一个指针视为不同的类型,并不改变实际对象的类型。在这种情况下,将 a 强制转换为 B 类型的指针 ((B*)(&a)) 只是编译器的假设,它认为你希望将 a 视为 B 类型的对象。

然而,实际上,a 仍然是一个 A 类型的对象,它的虚函数表指向 A 类型的虚函数。因此,无论如何进行强制类型转换,调用 ((B*)(&a))->fun1(); 实际上仍然调用的是 A 类型对象的 fun1,输出 "A:fun1"。

这是因为强制类型转换只是告诉编译器如何解释指针,但不会改变对象的类型。虚函数的动态绑定仍然基于对象的实际类型,而不是指针的类型。

再解释一下((B*)(&a))->fun2();

当你执行 ((B*)(&a))->fun2(); 时,你将 A 类型的对象 a 的地址强制转换为 B 类型的指针,然后调用 fun2 函数。在这种情况下,虽然 a 是 A 类型的对象,但强制转换将其视为 B 类型的对象。

然而,与虚函数不同,fun2 是非虚函数,它在编译时绑定,而不是动态绑定。由于强制类型转换后的指针类型是 B 类型,编译器将使用 B 类型的 fun2 实现,而不考虑对象的实际类型。

因此,((B*)(&a))->fun2(); 将调用 B 类型对象的 fun2,输出 "B:fun2",尽管实际对象 a 是 A 类型的。这是由于非虚函数 fun2 在编译时绑定到指针的静态类型,而不是动态绑定到对象的实际类型。