题目:下面函数输出结果是( )

class A 
{
public:
		virtual void f() 
        {
			cout << "A::f()" << endl;
		}
};

class B : public A 
{
private:
		virtual void f() 
        {
			cout << "B::f()" << endl;
		}
};
int main()
{
	A* pa = (A*)new B;
	pa->f();
	return 0;
}

A .B::f()

B .A::f(),因为子类的f()函数是私有的

C .A::f(),因为强制类型转化后,生成一个基类的临时对象,pa实际指向的是一个基类的临时对象

D .编译错误,私有的成员函数不能在类外调用


题目分析

B类继承A类,即使不强转也可以直接赋值,因为赋值兼容规则作出了保证。

赋值兼容规则补充

将派生类对象 赋值给基类对象/指针/引用

Base* p = new Derive;// 派生类对象指针 -> 基类指针
Derive d; 
Base& r = d;// 派生类对象 赋值给 基类引用
Base b = d;// 派生类对象 赋值给 基类对象

第一句代码:

A* pa = (A*)new B;

C的说法很离谱啊:为甚么这么说呢?

new B确实会初始化一个B的对象,new后返回B的对象的地址,这个地址原本指向的是B的对象。但是该地址赋值给pa。pa向上转型成为基类地址,那么pa只会指向B的对象中的基类对象的部分。

故排除C。

第二句代码:

pa->f();

继上篇文章,我们知道首先判断是不是多态:基类指针调用(多态条件一满足),调用虚函数(多态条件二满足)。于是f()的具体表现,应该是B类中的f()。

但问题的关键是:

图1 B::f()被private限定,恰恰A::f()又是public

那问题的关键是:B 将重写的虚函数声明为 private,是否会影响通过基类指针的调用,进而影响多态的结果;又或者根本编译不过去?

访问权限与多态的关系

在 C++ 中,虚函数的访问权限是由调用者的静态类型(即指针或引用的类型)决定的,而不是由动态类型(即实际对象的类型)决定的

好理解:访问权限在编译阶段检查(起效),编译器看到f()通过A*调用,立即就去A类作用域检查A::f()的访问权限:

  • 如果是private,那么对外就不应该访问,编译报错。
  • 如果是public,那么允许类外调用,编译通过。

而我们说的多态,则是起效在运行阶段。

  • 运行时,由于f()是虚函数,且pa实际指向的是B对象,因此会调用B::f()。(从底层来说,编好的指令里,call的函数地址,是从B的对象的虚表里取到的函数地址)

所以,这个场景就好比:

一天小明的父母要出差,李华一个人在家,就给李华饭钱,并问李华打算晚餐吃什么——(这个“问”,就是编译阶段)

李华回答:“买一点菜自己下厨。”(下厨,是基类函数定义)

父母表示很满意,放心地走了(编译通过)

正到了饭点,这个饭钱的使用者是李华,李华直接点外卖(点外卖,虚函数执行,产生多态效果)——bot原创😎读者看个乐呵就行

所以回到这道题:只要在编译阶段,检查到调用的函数对应的静态类型作用域(A:: )里函数的限定符为public。它的编译就能通过,不会去影响运行阶段调用实际指向对象的虚函数。

因此本题选A。

图2:f()能否访问,看的是pa的类型作用域是否允许

所以,即使派生类里写private,影响不了多态的行为。而会影响什么?

int main()
{
    B b;
    b.f(); // 这句编译就报错了,这就是派生类里对虚函数加私有访问的结果。派生类对象自己访问不了
    return 0;
}

致谢D老师的摘要:
摘要:题目考察C++中虚函数的访问权限与多态行为当基类指针指向派生类对象时,虚函数调用权限由指针的静态类型(基类)决定,而实际执行则由动态类型(派生类)决定。虽然派生类B将虚函数f()设为private,但由于基类A中的f()是public,通过A*调用f()能编译通过,运行时仍会调用B::f(),输出B::f()。因此正确答案是A。访问权限检查在编译阶段进行,不影响运行时的多态行为。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐