文章目录
一、问答题
- 什么是菱形继承?菱形继承的问题是什么?
- 什么是菱形虚拟继承?如何解决数据冗余和二义性的。
- 继承和组合的区别?什么时候用继承?什么时候用组合?
- 什么是多态?
- 什么是重载、重写(覆盖)、重定义(隐藏)?
- 多态的实现原理?
- inline函数可以是虚函数吗?
- 静态成员可以是虚函数吗?
- 构造函数可以是虚函数吗?
- 析构函数可以是虚函数吗?
- 对象访问普通函数快还是虚函数更快?
- 虚函数表是在什么阶段生成的,存在哪的?
- C++菱形继承的问题?虚继承的原理?
- 什么是抽象类?抽象类的作用?
二、概念题
- 下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象
- ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
- 面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现
- 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化对象
B:声明纯虚函数的类是虚基类
C:子类必须实现基类的纯虚函数
D:纯虚函数必须是空函数
- 关于虚函数的描述正确的是( )
A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型
B:内联函数不能是虚函数
C:派生类必须重新定义基类的虚函数
D:虚函数可以是一个static型的函数
- 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表
- 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B:A类对象和B类对象前4个字节存储的都是虚基表的地址
C:A类对象和B类对象前4个字节存储的虚表地址相同
D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
- 下面程序输出结果是什么? ()
cpp
#include<iostream>
using namespace std;
class A{
public:
A(char *s) { cout<<s<<endl; }
~A(){}
};
class B:virtual public A
{
public:
B(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class C:virtual public A
{
public:
C(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};
class D:public B,public C
{
public:
D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1)
{ cout<<s4<<endl;}
};
int main() {
D *p=new D("class A","class B","class C","class D");
delete p;
return 0;
}
A:class A class B class C class D
B:class D class B class C class A
C:class D class C class B class A
D:class A class C class B class D
- 多继承中指针偏移问题?下面说法正确的是( )
cpp
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;
}
A:p1 == p2 == p3
B:p1 < p2 < p3
C:p1 == p3 != p2
D:p1 != p2 != p3
- 以下程序输出结果是什么()
cpp
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: 以上都不正确
三、答案与解析
问答题
-
什么是菱形继承?菱形继承的问题是什么?
解析:
菱形继承是多继承延伸出来的问题,可概括为"一子多父,多父共一父",这样导致了数据冗余和二义性问题,最关键的在于数据冗余,如果数据量过大,则会导致内存资源浪费的问题,二义性可通过指定作用域来解决。
-
什么是菱形虚拟继承?如何解决数据冗余和二义性的。
解析:
从概念上来讲,菱形虚拟继承是在菱形继承的基础上,在父类继承其基类时,前加virtual的现象,从原理上来讲,通过引用虚基表和虚基表指针,加以改变对象的存储模型,使最后一个位置存的是基类,原来存基类的位置换成了虚基表指针,虚基表指针指向的是虚基表,虚基表存放的是偏移量,第一个位置存放的是虚基表指针的地址相对于this指针的偏移量,计算方式:this指针 - 虚表指针的地址(通常为0或者-4),第二个位置存放的是虚基表指针的地址相对于基类的this指针 的偏移量,第三个位置存放的可能是相较于其它基类的偏移量。最后一个位置存放的是结束位置,VS下为0。这只是一个元素要存放的信息。通过如上操作,虚表和虚基表指针来达到节省内存空间的值,总的来说还是浪费几个字节的,不过相较于数据量很大的来说就忽略不计了,其次这样设计是为了考虑通用性,一个类直接用一张统一的虚基表和虚表指针即可。
-
继承和组合的区别?什么时候用继承?什么时候用组合?
解析:
在实现类的多态时,我们就不得不使用继承了,因为只有继承才能实现类的多态。在大多数情况下,能考虑用组合就用组合,因为组合更加符合高内聚低耦合的概念,使代码之间更加地完整和独立。
-
什么是多态?
解析:
从概念上来讲,就是一种事物对应不同种形态,符合 has_a的关系,从分类上来讲,多态分为函数重载和动态绑定,从实现角度上来讲,主要是通过基类的指针和引用以及虚函数和重写,来达到通过父类的指针或引用指向子类时,能够调用子类的虚函数而不是父类的虚函数。
-
什么是重载、重写(覆盖)、重定义(隐藏)?
一张图理解:
-
多态的实现原理?
解析:从实现角度上来讲,主要是通过基类的指针和引用以及虚函数和重写,来达到通过父类的指针或引用指向子类时,能够调用子类的虚函数而不是父类的虚函数。
-
inline函数可以是虚函数吗?
解析:
这里我们要明确两个概念的区别内联函数和被inline关键字修饰的函数,被inline修饰的函数最终是不是内联是我们说了不算的,最终要靠编译器来决定的,虚函数前加inline并不是内联,因为如果是内联,那虚函数的地址就不会被存放在虚表中,而我们都知道虚函数是要存放在虚表中的,与概念相悖,因此虚函数可以被inline修饰,但是其不是内联函数。
-
静态成员可以是虚函数吗?
解释:
不是,因为静态成员没有this指针,仅通过对象和指定作用域调用即可,而多态调用是要传this指针,通过this指针来判断是否构成多态的条件,从而实现多态,因此不可能是虚函数。
-
构造函数可以是虚函数吗?
解释:
不可以,从语法上编译器就禁掉了,从原理层上来看,我们要考虑先有鸡还是先有蛋的问题,即先初始化虚表指针(蛋)再调用构造函数(鸡)。这是不现实的,因此只能通过构造函数初始化函数指针,在通过虚函数指针调用虚函数。
-
析构函数可以是虚函数吗?
解释:
可以,我们也推荐加上virtual,在实现多态时,如果不是虚函数,从而导致指向子类的父类指针,在进行delete时,只调用了父类的析构而没有调用子类的析构函数,这样可能会导致内存泄漏,因此可以,并强烈建议加上。
-
对象访问普通函数快还是虚函数更快?
解释:
普通函数在调用时,是直接call对应的函数地址,而虚函数需要先找到虚函数指针,再通过虚函数指针找到对应的地址,再进行调用,因此普通函数更快,但综合下来只在函数过多的情况下考虑这种问题,一般来说差别不大。
-
虚函数表是在什么阶段生成的,存在哪的?
解释:
虚函数表是在编译期间生成的,至少在VS下是存在常量区的。
-
什么是抽象类?抽象类的作用?
解释:
我们把具有纯虚函数的类称为抽象类,抽象类在运用角度来说,强制子类必须重写虚函数,否则无法进行示例化,因为继承了纯虚函数是无法示例化的。
概念题
- A 2. D 3. C 4. A 5. B
- D 7. D 8. A 9. C 10. B