面试中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.析构函数什么时候需要写成虚析构函数
源代码
运行结果
可以发现:当用一个基类指针指向堆上的一个派生类对象,析构时派生类无法析构,原因是析构函数不是虚函数,
编译器在编译时就根据p是Base* 类型,而确定要调用base类的析构函数。
解决办法:是将基类析构函数写成虚构函数,动态绑定,运行阶段,会根据p指向的是Derive类,derive类的虚函数表
中的析构覆盖了Base的析构,所以在调用时会调用Derive类的析构,达到两个两个类的正常释放
2.基类无虚函数,派生类中有虚函数会出现的问题
1题代码中派生类对象的内存分布:
p指针指向vfptr那一行的地址,内存释放正常。而当基类无虚函数,派生类有时,内存布局如下:
因为在无vbptr时,vfptr都会在对象内存的前四个字节。p指针指向mb那一行,delete p时程序就会崩,这种问题无
解决方式,这种方式的书写本身就是错误的,我们要避免。
3.在基类的构造函数中写对虚函数表的清空函数,会不会影响到派生类的虚函数表的生成
运行结果如下:
分析:我们发现并没有影响到,因为虚函数的生成实在编译阶段,而清空函数的运行是在运行阶段,在此之前,虚函数表已复制完毕。
4.派生类函数参数的默认值为什么一般没用
我们发现第三个问题中,运行结果第三行i的值是10.函数的传参和调用检验在编译阶段,就算把派生类的show函数写成private也能编译过,因为p是Base类型的指针,p调用show函数,编译器只会去鉴定Base中的show函数,虽然是virtual函数,但是编译器它不知道运行时会发生什么,所以他只能如此检验,所以在写有继承时的代码时,尽量避免参数默认值。
基类的私有成员变量,派生类虽能继承,但不能访问,是不可见的。
基类的保护成员和派生类的唯一不同就是作用域。
除了析构和所有的构造不可继承外,其他都可继承
基类和派生类成员方法的关系:
重载:(同一作用域),方法名相同,返回值类型,参数列表不同。
隐藏:(使用前提:派生类对象调用继承于基类的方法)同名的方法就隐藏了。(函数名,不管参数)
覆盖:(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++中私有继承后虚函数的访问权限与私有继承后多态的问题
- 关于继承和虚函数的问题
- 关于C++虚函数默认参数的问题。Effective C++ 条款38: 决不要重新定义继承而来的缺省参数值
- 关于C++的private继承问题
- 面试中遇到的C++问题(一)
- C++中继承中遇到的构造函数问题
- 关于MFC中数据库操作中遇到的问题:Microsoft C++ 在xxx内存处出现问题_com_error的另一解决方案
- 关于c#调用c++的dll遇到的问题
- 关于c++中的public继承,private继承,以及protect继承的问题
- 关于C++继承中的几个问题
- c#过程中遇到的关于构造函数和继承的问题
- 一道关于C++ 继承/虚函数 笔试题
- 关于虚函数重载遇到的怪问题 -- 为什么经常调用了基类的函数
- 关于c++多重继承下的函数调用注意的问题
- 一道关于C++ 继承/虚函数 笔试题
- 面试里经常遇到的一个关于StringBuffer和final的问题
- c++一些 面试可能遇到的问题
- 面试中关于C++中的类,结构体,enum,字符变量等所占内存空间问题总结
- 有关于面试常遇到的问题回答,以及解决方案。
- 关于面试遇到的一些问题