学习完了继承与多态,当然要来点练习题啦~
第一题:
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}
答案:C
解析:先继承的先声明,先声明的先在前。
第二题:
打印的顺序是什么?
class A {
public:
A(const char* s) { cout << s << endl; }
~A() {}
int _a;
};
class B :virtual public A
{
public:
B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
int _b;
};
class C :virtual public A
{
public:
C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
int _c;
};
class D :public B, public C
{
public:
D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2), C(s1, s3), A(s1)
{
cout << s4 << endl;
}
int _d;
};
int main() {
D* p = new D("class A", "class B", "class C", "class D");
delete p;
return 0;
}
答案:
解释:由于我们使用了虚继承,那么就意味着只有一个A,在VS中,这个A一般在对象的末尾。
只有一个A的话那么初始化时只会初始化一次,但是我们的程序中一共调用了3次A的构造,在那个地方初始化呢?注意:我们的A是最先继承的,所以也是最先声明的(由于是虚继承,所以A的位置就不一定放在对象的最前边,由编译器决定),所以在一开始就会先初始化最先声明的,也就是先初始化A,然后是B与C在声明,所以在打印B与C,最后走完D的初始化列表,再打印D。
第三题:
打印的是什么呢?
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;
}
答案:
解析:首先我们对func
函数完成了重写,为什么呢?
子类的virtual
可以不写,不必多说
可能有人会疑惑,参数不同啊,非也非也,我们在多态这说的参数是类型
,并不是数值。
此外,我们虚函数的重写是对实现的重写,对接口是直接继承的,这也是我们重写名字的由来(覆盖是语法层的概念)
现在我们就可以正式的来解决这道问题了。
我们使用p
调用test
函数,注意是继承A的test
函数,所以此时p
发生切片变为A*
,也就是this
指针,我们使用指针父类指针调用,所以此时就构成了多态
!
所以调用的就是B的func,注意我们已经说了我们重写重写的是实现,继承的是接口,所以我们的val为1,所以我们就解决了这道问题!
一些面试题:
-
什么是多态?答:具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
-
什么是重载、重写(覆盖)、重定义(隐藏)?答:
-
多态的实现原理?答:虚表指针与虚函数,由父类指针调用函数时,运行时会去虚表中找对应的虚函数,从而实现不同的行为
-
inline函数可以是虚函数吗?答:
表层
来看:内联函数没有属于自己的地址,而虚函数地址要被放到虚表中,所以不可以是虚函数。深层
:通过编译,我们知道是可以的,首先我们要明确内联只是一个建议:当一个虚函数被声明为内联时,如果没有构成多态,编译器允许的情况下就会展开;如果构成多态,那么就无视inline,转而执行多态的行为 -
静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
-
构造函数可以是虚函数吗?答:我们可以从两个维度理解:
1.
不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。2.
派生类的构造会显示调用父类的构造函数,而若定义为虚函数那么只能只向谁调谁,显然不符合常理,在C++primer中也给这种情况起了名字,具体叫啥忘记了~注意:拷贝也是同理,但是赋值的话对应第二种情况。 -
析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义成虚函数。
-
对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态调用,运行时调用虚函数需要到虚函数表中去查找。(由下图可见并不一定是构成多态才会多态调用,如果没有重写依旧会多态调用)
-
虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
-
什么是抽象类?抽象类的作用?答:抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。
后续也会继续更新!