学习之前,建议观看:【C++】多态:深度剖析(多态、虚函数、抽象类、底层原理)_c++ 多态和虚函数,虚函数的实现原理-CSDN博客https://blog.csdn.net/2301_80555259/article/details/142178677?spm=1001.2014.3001.5501
一.概念题
1.关于虚函数说法正确的是( )
A.被virtual修饰的函数称为虚函数
B.虚函数的作用是用来实现多态
C.虚函数在类中声明和类外定义时候,都必须加虚拟关键字
D.静态虚成员函数没有this指针
答案:B
对于A,必须是被virtual修饰的成员函数;
对于C,虚函数不能在类外定义;
对于D,不存在静态虚函数这种说法,static和virtual不能同时修饰一个成员函数
2.关于不能设置成虚函数的说法正确的是( )
A.友元函数可以作为虚函数,因为友元函数出现在类中
B.成员函数都可以设置为虚函数
C.静态成员函数不能设置成虚函数,因为静态成员函数不能被重写
D.析构函数建议设置成虚函数,因为有时可能利用多态方式通过基类指针调用子类析构函数
答案:D
对于A,友元函数不属于成员函数,因此友元函数不能被修饰为虚函数;
对于B,静态成员函数不可以设置为虚函数;
对于C,原因错误,静态成员函数之所以不能设置为虚函数是因为 静态成员函数属于整个类,没有this指针,从而无法利用this指针找到类中的虚表指针,从而找到虚表中的虚函数地址
3.关于多态,说法不正确的是( )
A.C++语言的多态性分为编译时的多态性和运行时的多态性
B.编译时的多态性可通过函数重载实现
C.运行时的多态性可通过模板和虚函数实现
D.实现运行时多态性的机制称为动态绑定
答案:C ,运行时的多态性不能使用模板实现,模板属于编译时多态
4.要实现多态类型的调用,必须( )
A.基类和派生类原型相同的函数至少有一个是虚函数即可
B.假设重写成功,通过指针或者引用调用虚函数就可以实现多态
C.在编译期间,通过传递不同类的对象,编译器选择调用不同类的虚函数
D.只有在需要实现多态时,才需要将成员函数设置成虚函数,否则没有必要
答案:D
对于A,只能是基类中为虚函数;
对于B,完整应该是 通过基类(父类)的指针或者引用来调用虚函数;
对于C,多态是运行时多态,不是在编译期间。编译期间编译器主要是检查语法错误,此时无法得知基类指针具体指向哪个对象,只有在运行时,才知道指向的具体对象,从而调用其虚表中的函数,实现多态
5.关于重载、重写和重定义的区别说法正确的是( )【不定项选择】
A.重写和重定义都发生在继承体系中
B.重载既可以在一个类中,也可以在继承体系中
C.它们都要求原型相同
D.重写就是重定义
E.重定义就是重写
F.重写比重定义条件更严格
G.以上说法全错误
答案:A、F ,
对于B,重载的两个函数必须在同一作用域,不能在两个不同的类中;
对于C,重载要求函数名相同,参数不同。重写要求函数名,参数,返回值都必须相同,即原型相同。而重定义仅要求函数名相同;
对于D、E ,很明显不相同,下图是具体区别图
6. 关于抽象类和纯虚函数的描述中,错误的是 ( )
A.纯虚函数的声明以“=0;”结束
B.有纯虚函数的类叫抽象类,它不能用来定义对象
C.抽象类的派生类如果不实现纯虚函数,它也是抽象类
D.纯虚函数不能有函数体
答案:D,纯虚函数可以拥有函数体,只是这意义不大,需要被重写
7.关于虚表说法正确的是( )
A.一个类只能有一张虚表
B.基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C.虚表是在运行期间动态生成的
D.一个类的不同对象共享该类的虚表
答案:D
对于A,若一个类继承了多个父类,那么将拥有多个虚表
对于B,并不是共用一张虚表,而是子类的虚表中依旧是基类的虚表内容,还没被覆盖而已
对于C,虚表是在编译时生成的
8.假设D类先继承B1,然后继承B2,B1和B2基类均包含虚函数,D类对B1和B2基类的虚函数重写了,并且D类增加了新的虚函数,则:( )
A.D类对象模型中包含了3个虚表指针
B.D类对象有两个虚表,D类新增加的虚函数放在第一张虚表最后
C.D类对象有两个虚表,D类新增加的虚函数放在第二张虚表最后
D.以上全部错误
答案:B , D继承了两个父类,故有两个虚表指针,同时,子类新增的虚函数会只会放在第一个虚表的最后,其他父类虚表不需要储存
二.程序分析题
1.以下哪项说法时正确的( )
A.基类和子类的f1函数构成重写
B.基类和子类的f3函数没有构成重写,因为子类f3前没有增加virtual关键字
C.基类引用引用子类对象后,通过基类对象调用f2时,调用的是子类的f2
D.f2和f3都是重写,f1是重定义
class A
{
public:
void f1(){cout<<"A::f1()"<<endl;}
virtual void f2(){cout<<"A::f2()"<<endl;}
virtual void f3(){cout<<"A::f3()"<<endl;}
};
class B : public A
{
public:
virtual void f1(){cout<<"B::f1()"<<endl;}
virtual void f2(){cout<<"B::f2()"<<endl;}
void f3(){cout<<"B::f3()"<<endl;}
};
答案:D
对于A,由于父类A中f1没有添加关键字virtual,不构成重写
对于B,虽然子类B中f3没有关键字virtual,但是继承了父类中该函数的虚函数性质,因此仍然构成重写,但是这种写法是不规范,不推荐的
对于C,“通过基类对象调用f2”是错误的,直接通过对象来进行调用函数,就不会通过虚表以多态的形式调用了(其实这个选项有点抠字眼,前一句引用很容易误导)
2.下面函数输出结果是( )
A.B::f()
B.A::f(),因为子类的f()函数是私有的
C.A::f(),因为强制类型转化后,生成一个基类的临时对象,pa实际指向的是一个基类的临时对象
D.编译错误,私有的成员函数不能在类外调用
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();
}
答案:A
尽管该重写的函数是私有成员,但多态的本质是虚表中函数地址的覆盖,该虚函数在子类中重写后,子类虚表中该函数地址就被重写后的函数的地址覆盖,调用时,也是直接根据函数地址调用,因此能够成功调用private内的成员函数,编译成功,运行成功
3.以下程序输出结果是( )
A.1 0
B.0 1
C.0 1 2
D.2 1 0
E.不可预期
F. 以上都不对
class A
{
public:
A() :m_iVal(0) { test(); }
virtual void func() { std::cout << m_iVal << " "; }
void test() { func(); }
public:
int m_iVal;
};
class B : public A
{
public:
B() { test(); }
virtual void func()
{
++m_iVal;
std::cout << m_iVal << " ";
}
};
int main(int argc, char* argv[])
{
A* p = new B;
p->test();
return 0;
}
答案:C
首先,new B子类时,要先调用父类的构造函数,再调用子类的构造函数。因此用0初始化了m_iVal并打印,而后调用子类的构造函数,此时父类已经构造完成,虚表也已经生成了,B调用继承来的test(),继承的test中的this是A*类型,即基类指针,满足多态的所有条件,因此调用子类的func,进行+1并打印,而后的p->test()再重复一次多态的过程,故最终结果为:
0 1 2