1. 什么是多态?
答:通俗来说,就是多种形态,具体点就是去完成某种行为,当不同的对象去完成时会产生出不同的状态
2. 什么是重载、重写(覆盖)、重定义(隐藏)?
答:重载:两个函数在同一作用域,函数名相同,参数/返回值不同的函数
重写:两个函数分别在基类和派生类的作用域,函数名/参数/返回值都必须相同(协变除外),两个函数必须是虚函数
重定义(隐藏):两个函数分别在基类和派生类的作用域,函数名相同,只要两个函数不构成重写就是重定义,包括返回值不一样、参数个数不同、不是虚函数。
3. 多态的实现原理?
答:当满足父类指针或引用调用虚函数的时候,传什么对象就调用什么对象的虚函数,这样就实现除了不同对象去完成同一种行为时,展示出不同的形态。达到多态的两个条件:一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。
在满足多态以后的虚函数调用,不是在编译时确定的,是运行起来以后到调用对象中找到的,不满足多态的函数调用时在编译时已经确认好了。
4. inline函数可以是虚函数吗?
答:可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去。需要函数地址,而内联直接把函数展开了,不用放在栈中,没有函数地址。
先把编译器改成内联属性
class A
{
public:
virtual inline void func()
{
cout << "A::void func()" << endl;
}
};
class B :public A
{
public :
virtual inline void func()
{
cout << "B::func()" << endl;
}
};
int main()
{
A aa;
aa.func();
aa.func();
aa.func();
aa.func();
A* p = new B;
p->func();
}
多态情况下:虚函数内联多态情况
看上面这段代码,当A类型对象直接调用函数的时候,转到反汇编,这个函数直接被展开了,没有call函数地址(当调用函数的时候,就会调用函数地址),是被展开了的。但是当用父类指针来调用虚函数的时候,构成了多态,这时候要到虚表去找虚函数。
非多态情况下:虚函数内联非多态情况
总结:虚函数可以是多态,调用时,如果不构成多态,这个函数保持inine属性,如果构成多态,这个函数就没有内联属性了,因为调用是到对象的虚函数表中找到 对象的地址实现调用,无法使用内联属性。
但是有一点要注意的是,在内联属性下,即使虚函数前面没有加inline关键字,这个函数还是会被优化成内联函数,这是类中虚函数的一个属性,类里面定义的成员函数,默认就会被认为内联。如果想要在内联属性下,虚函数不被展开,就让声明在类里面,实现在类外面。
所以只要在类里面定义,只要不是多态,这个函数就是内联
5. 静态成员可以是虚函数吗?
答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。静态成员函数构成不了多态,定义成虚函数没有价值。
6. 构造函数可以是虚函数吗?
答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
答:可以,并且最好把基类的析构函数定义成虚函数。父类指针类型构造子类对象,如果没有virtual虚函数就会调用父类析构函数,这样子类有一部分没释放,造成内存泄漏。只有多态(有虚函数)才能调用子类析构。
8. 对象访问普通函数快还是虚函数更快?
答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
如果不构成多态,都是编译器确定调用函数的地址,那么它们一样快;如果构成多态,那么是虚函数调用运行时,去虚函数表中确定函数地址,普通函数编译时直接确定地址,那么普通函数更快。
9. 虚函数表是在什么阶段生成的,存在哪的?
答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
10. 什么是抽象类?抽象类的作用?
答:在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)