携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
-
关于虚表说法正确的是 ( )
A. 一个类只能有一张虚表
B. 基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C. 虚表是在运行期间动态生成的
D. 一个类的不同对象共享该类的虚表
📝 上面的多继承中就有两张虚表,且严格来说虚表不是在类,而是在对象,所以排除 A;不管是否完成重写,父子类的对象都是有独立的虚表,所以排除 B;虚表如果是运行时动态生成,虚表是需要空间的,且运行起来只能在堆上申请,而上面我们通过对比虚表是在常量区或代码段,所以排除 C;
-
假设 A 类中有虚函数,B 继承自 A,B 重写 A 中的虚函数,也没有定义任何虚函数,则 ( )
A. A 类对象的前 4 个字节存储虚表地址,B 类对象前 4 个字节不是虚表地址
B. A 类对象和 B 类对象前 4 个字节存储的都是虚基表的地址
C. A 类对象和 B 类对象前 4 个字节存储的虚表地址相同
D. A 类和 B 类虚表中虚函数个数相同,但 A 类和 B 类使用的不是同一张虚表
📝 A 类有虚函数,A 类对象的前 4 个字节当然是存储虚表地址,只要 B 类继承了 A 类,B 类的前 4 个字节也当然是存储虚表地址,只不过是不同的虚表地址,所以排除 A;注意区分解决菱形继承的虚继承的虚基表,所以排除 B;不管是否重写,父子类的对象都是有独立的虚表,所以排除 C;
-
以下程序输出结果是什么 ( )
class A {
public:
virtual void func(int val = 1){ std::cout << "A->" << val << std::endl; }
virtual void test(){ func();}
};
class B : public A {
public:
void func(int val = 0){ std::cout << "B->" << val << std::endl; }
};
int main(int argc ,char* argv[])
{
B* p = new B;
p->test();
return 0;
}
A. A->0 B. B->1
C. A->1 D. B->0
E. 编译出错 F. 以上都不正确
📝 首先,这里不涉及多态,因为 p 的类型是子类的指针,p 再去调用父类继承下来的 test,但是这里父类中 test 函数的参数中有一个 A* this 的指针,所以调用时就是一个父类的指针指向子类对象,满足多态的条件之一,其次子类重写可以不写 virtual,我们需要重写虚函数,并满足三同或三个例外,但是没有说缺省参数也要相同,标准也基本不会提,我们就认为它构成重写。所以这里 this 调用 func 时符合多态,调用的是子类的 func,所以这里就从 B 和 C 中选。我们又说了普通函数的继承是实现继承,而虚函数的继承是接口继承,接口继承指的是函数的声明,包括函数名、参数、返回值,所以这里把函数的缺省参数也继承下来,而这里重写的是它的实现,跟参数这些无关,所以选择 B 选项。
- 以下两段程序输出结果是什么 ( )
class A
{
public:
virtual void func(int val = 1)
{}
void test()
{}
};
int main()
{
//1
A* p1 = nullptr;
p1->func();
//2
A* p2 = nullptr;
p2->test();
return 0;
}
A. 编译报错 B. 运行崩溃 C. 正常运行
📝 这道题有点类似类和对象上中的一题。我们说了成员函数的地址不在对象中存储,存在公共代码段。这里调用成员函数,不会去访问 p1 和 p2 指向的空间,也就不存在空指针解引用了,这里把 p1 和 p2 传递给隐含的 this 指针,但是 p1 是一个父类的指针,而 func 是 virtual,这里转换必然要去虚表中找,因为从语法识别的角度,编译器看到 p1->func() 时也不知道指向的是哪个对象,所以这里依然对 p1 进行解引用了。所以选择 B、C 选项。