您的位置:首页 > 编程语言 > C语言/C++

面试中c++中单继承关于虚函数常遇到的4个问题

2017-06-29 11:22 447 查看
在讲到虚函数之前,先附一张表(如果急切,直接翻到说虚函数的部分即可)



  基类的私有成员变量,派生类虽能继承,但不能访问,是不可见的。

  基类的保护成员和派生类的唯一不同就是作用域。

  除了析构和所有的构造不可继承外,其他都可继承

  

基类和派生类成员方法的关系:

重载:(同一作用域),方法名相同,返回值类型,参数列表不同。

隐藏:(使用前提:派生类对象调用继承于基类的方法)同名的方法就隐藏了。(函数名,不管参数)

覆盖:(virtual)(使用前提:动多态,基类指针,指向不同的派生类对象,且要调用虚函数):即虚函数表中覆 盖,返回值,参数列表,函数名都相同,基类的方法是虚函数,派生类的自动成为虚函数,两方法是覆盖关 系。

基类对象 =》 派生类对象      不行

派生类对象 =》基类对象      (由下都上) ok

基类指针或引用=》派生类对象    ok,只能访问派生类继承来的成员

派生类指针或引用 =》基类对象   不行

虚函数基础讲解

虚函数的作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的 同名函数

虚函数的使用:

1:基类的方法前面加上virutual,派生类的同返回值,同方法名,同参数的方法也自动成为虚函数。根据派生类的 需求,重写此函数。

2:定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。

3:通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。(原因与动态绑定有关,下面 会详细说)

纯虚函数:在基类中声明的虚函数,它在基类中没有定义(无函数体),但要求任何派生类都要定义自己的实现方法。 在基类中实现纯虚函数的方法是在函数原型后加“=0”(eg:virtual void funtion()=0 ),拥有纯虚函数 的类是抽象类。抽象类不能实例化对象.

静多态(编译时多态):重载,模板

动多态(运行时多态):继承

静态绑定(早绑定):在编译时期,即还没有运行,就可以确定函数的入口地址就已加载到内存。

动态绑定(晚绑定):如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

虚函数指针(vfptr):含有虚函数的类生成的对象有虚函数指针,一个对象有一个虚函数指针vfptr,每个对象的vfptr都在对象内存的前四个字节。如果基类有虚函数指针,则派生类中的虚函数指针是由基类继承来的

注意:

静态函数不能写成虚函数(无this指针)

构造函数不能写成虚函数。(对象还未生成)

析构函数可以写成虚函数。(基类指针指向堆上的派生类对象)

虚函数表(vftable):(放在只读数据段,编译时期生成)虚函数表中放的虚函数地址。 一个类型对应一个虚函数表。同类型对象对应一个虚函数表。

虚函数表图解:



静态函数没有虚函数指针

构造函数没有虚函数指针,不能写成虚函数。

析构函数可以写成虚函数。(基类指针指向堆上的派生类对象)

         虚函数方面在面试中的常见问题

下面的代码中Base是基类,Derive是派生类

1.析构函数什么时候需要写成虚析构函数

源代码  

#include<iostream>
using namespace std;
class Base
{
public:
Base(){cout<<"Base()"<<endl;}
virtual ~Base(){cout<<"~Base()"<<endl;}
private:
int ma;
};
class Derive : public Base
{
public:
Derive():Base(){cout<<"Derive()"<<endl;}
~Derive(){cout<<"~Derive()"<<endl;}
private:
int mb;
};
int main()
{
Base *p = new Derive;
delete p;
}


  运行结果


 

  可以发现:当用一个基类指针指向堆上的一个派生类对象,析构时派生类无法析构,原因是析构函数不是虚函数,

编译器在编译时就根据p是Base* 类型,而确定要调用base类的析构函数。

  解决办法:是将基类析构函数写成虚构函数,动态绑定,运行阶段,会根据p指向的是Derive类,derive类的虚函数表

中的析构覆盖了Base的析构,所以在调用时会调用Derive类的析构,达到两个两个类的正常释放

2.基类无虚函数,派生类中有虚函数会出现的问题

1题代码中派生类对象的内存分布:



  p指针指向vfptr那一行的地址,内存释放正常。而当基类无虚函数,派生类有时,内存布局如下:



因为在无vbptr时,vfptr都会在对象内存的前四个字节。p指针指向mb那一行,delete p时程序就会崩,这种问题无

解决方式,这种方式的书写本身就是错误的,我们要避免。

3.在基类的构造函数中写对虚函数表的清空函数,会不会影响到派生类的虚函数表的生成

#include<iostream>
using namespace std;
class Base
{
public:
Base(int a=10):ma(a)
{
cout<<"Base()"<<endl;
clear();
}
void clear(){memset(this, 0, sizeof(*this));}
virtual ~Base(){cout<<"~Base()"<<endl;}
virtual void show(int i=10)
{
cout<<"Base::show() i:"<<i<<endl;
}
protected:
int ma;
};

class Derive : public Base
{
public:
Derive(int data=10):Base(data), mb(data)
{
cout<<"Derive()"<<endl;
}
~Derive(){cout<<"~Derive()"<<endl;}

private:void show(int i=20)
{
cout<<"Derive::show() i:"<<i<<endl;
}
int mb;
};
int main()
{
Base *p2 = new Derive(20);
p2->show();
delete p2;

return 0;
}


运行结果如下:



分析:我们发现并没有影响到,因为虚函数的生成实在编译阶段,而清空函数的运行是在运行阶段,在此之前,虚函数表已复制完毕。

4.派生类函数参数的默认值为什么一般没用

  我们发现第三个问题中,运行结果第三行i的值是10.函数的传参和调用检验在编译阶段,就算把派生类的show函数写成private也能编译过,因为p是Base类型的指针,p调用show函数,编译器只会去鉴定Base中的show函数,虽然是virtual函数,但是编译器它不知道运行时会发生什么,所以他只能如此检验,所以在写有继承时的代码时,尽量避免参数默认值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ 虚函数