🍬 mooridy-CSDN博客
🧁C++专栏(更新中!)
目录
1. 多态的概念
2. 多态的定义及实现
2.1 多态的构成条件
2.2 虚函数的重写/覆盖
2.3 虚函数重写的⼀些其他问题
2.4 override 和 final关键字
2.5 重载/重写/隐藏的对比
3. 纯虚函数和抽象类
4. 多态的原理
4.1 虚函数表指针
4.2 多态是如何实现的
4.3 动态绑定与静态绑定
4.4 虚函数表
5.多态面试题总结
1. 多态的概念
2. 多态的定义及实现
2.1 多态的构成条件
#include <iostream>
using namespace std;
class Person {
public:
virtual void BuyTicket() {
cout << "全价票" << endl;
}
};
class Student:public Person {
public:
virtual void BuyTicket() {
cout << "学生票" << endl;
}
};
void func(Person& p) {
p.BuyTicket();
}
int main() {
Student s;
func(s);
return 0;
}
2.2 虚函数的重写/覆盖
2.3 虚函数重写的⼀些其他问题
2.4 override 和 final关键字
2.5 重载/重写/隐藏的对比
3. 纯虚函数和抽象类
//抽象类
class Person {
public:
virtual void BuyTicket() = 0;//纯虚函数
};
class Student :public Person {
public:
virtual void BuyTicket() {
cout << "学生票" << endl;
}
};
void func(Person& p) {
p.BuyTicket();
}
int main() {
Student s;
func(s);
return 0;
}
4. 多态的原理
4.1 虚函数表指针
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
protected:
int _b = 1;
char _ch = 'x';
};
int main()
{
Base b;
cout << sizeof(b) << endl;
return 0;
}
4.2 多态是如何实现的
4.3 动态绑定与静态绑定
4.4 虚函数表
5.多态面试题总结
1.以下程序输出结果是什么()
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;
}
正确答案:B B->1
解析:
(1) 由于B类中没有覆盖(重写)基类中的虚函数test(),因此会调用基类A中的test();
(2)注意调用基类A中的test()时,由于this指针是A*类型的,在调用时就满足了基类指针指向子类对象的条件。
(3) A中test()函数中继续调用虚函数 fun(),因为虚函数执行动态绑定,p此时的动态类型(即目前所指对象的类型)为B*,因此此时调用虚函数fun()时,执行的是B类中的fun();所以先输出“B->”;
(4) 缺省参数值是静态绑定,即此时val的值使用的是基类A中的缺省参数值,其值在编译阶段已经绑定,值为1,所以输出“1”;
最终输出“B->1”。
结论:(1)绝不重新定义继承而来的缺省参数值!
(2)重写本质即重写函数体,函数头以基类中的虚函数为准。
2.
关于虚函数说法正确的是( )
A.被virtual修饰的函数称为虚函数
B.虚函数的作用是用来实现多态
C.虚函数在类中声明和类外定义时候,都必须加虚拟关键字
D.静态虚成员函数没有this指针
答案:B
解析:
A:被virtual修饰的成员函数称为虚函数
C:虚函数不能在类外定义实现
D:虚函数不可以是static类型,因为虚函数需要this指针调用,而static类型的函数无this指针。
静态成员函数:不属于类中的任何一个对象,属于类共有的一个函数。它无法用this指针来访问,因为this指针指向的是每一个对象和实例。
virtual虚函数:使用this指针。this指针调用vptr指针,指向虚函数列表,通过虚函数列表找到需要调用的虚函数的地址。总体来说虚函数的调用关系是:this指针->vptr(4字节)->虚函数列表 ->virtual虚函数。
因此,static静态函数没有this指针,就不能是虚函数。
3.
以下哪项说法时正确的( )
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;}
};
A.基类和子类的f1函数构成重写
B.基类和子类的f3函数没有构成重写,因为子类f3前没有增加virtual关键字
C.基类引用引用子类对象后,通过基类对象调用f2时,调用的是子类的f2
D.f2和f3都是重写,f1是重定义
答案:D
解析:
对于f1,由于基类中的f1没有被virtual关键字修饰,不是虚函数。所以不满足重写条件。但因为函数名相同,且在继承体系中,满足隐藏即重定义。
对于f2,f3,基类中的f2,f3被virtual关键字修饰,是虚函数。至于子类当中的f2,f3无论是否被virtual修饰,都已经是虚函数。满足重写条件。
4.
关于抽象类和纯虚函数的描述中,错误的是 ( )
A.纯虚函数的声明以“=0;”结束
B.有纯虚函数的类叫抽象类,它不能用来定义对象
C.抽象类的派生类如果不实现纯虚函数,它也是抽象类
D.纯虚函数不能有函数体
答案:D
解析:
A纯虚函数的声明以“=0;”结束
B有纯虚函数的类叫抽象类,它不能用来定义对象
C子类不实现父类所有的纯虚函数,则子类还属于抽象类,仍然不能实例化对象
D纯虚函数能有函数体,只是没有意义而已。因为抽象类无法实例化,根本不能调用纯虚函数。
5.
关于虚表说法正确的是( )
A.一个类只能有一张虚表
B.基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C.虚表是在运行期间动态生成的
D.一个类的不同对象共享该类的虚表
答案:D
解析:
A一个类中有几个虚函数就有几个虚表
B基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类仍然会有一张属于自己的虚表,内容与基类虚表相同
C虚函数表里的内容是在编译时期由编译器生成的
D.一个类的不同对象共享该类的虚表
6.
下面函数输出结果是( )
class A
{
public:
virtual void f()
{
cout<<"A::f()"<<endl;
}
};
class B : public A
{
private:
virtual void f()
{
cout<<"B::f()"<<endl;
}
};
A* pa = (A*)new B;
pa->f();
A.B::f()
B.A::f(),因为子类的f()函数是私有的
C.A::f(),因为强制类型转化后,生成一个基类的临时对象,pa实际指向的是一个基类的临时对象
D.编译错误,私有的成员函数不能在类外调用
答案:A
解析:
B.虽然子类函数为私有,但是多态仅仅是用子类函数的地址覆盖虚表,最终调用的位置不变,只是执行函数发生变化
C.不强制也可以直接赋值,因为赋值兼容规则作出了保证
D.编译正确
7.
以下程序输出结果是( )
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;
}
A.1 0
B.0 1
C.0 1 2
D.2 1 0,
答案:C
解析:
多态中,派生类初始化顺序如下:
1.构造父类;
2. 将子类虚函数表地址给子类对象;
3. 初始化列表进行初始化;
4. 构造函数本体;
(1)创造新的B对象时,先调用A的构造函数,在test函数中又调用了func函数,这个时候由于派生类的构造函数初始化列表还没走完,所以没有虚表指针不构成多态,只能调用A类中的func打印0。
(2)然后进入B类的构造函数的函数体中调用test函数,由于B中无test函数只能去父类A中调用,在A类中的test函数体中调用func函数,这个时候因为派生类的初始化列表已经走完了,虚表指针也形成了,并且func被子类重写由this指针也就是A*父类指针调用func满足多态,所以在B类中的func中先让mval++变成1然后打印1
(3)接下来由父类指针P主动调用test函数,同样满足多态调用B类中的func函数,mval++变成2然后打印2,所以答案是0 1 2.
8.
假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A.A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B.A类对象和B类对象前4个字节存储的都是虚表的地址
C.A类对象和B类对象前4个字节存储的虚表地址相同
D.A类和B类中的内容完全一样,但是A类和B类使用的不是同一张虚表
答案:B
解析:
A.A类对象和B类对象前4个字节存储的都是虚表的地址
C.A类和B类是不同的类如果都有虚函数各自有各自的虚表,地址不同
D.A类和B类不是同一个类,内容一定不同