C++中的virtual概念
此虚非彼虚
0. 目录
- 虚函数
- 纯虚函数和抽象基类
- 虚析构函数
- 虚函数和类作用域
1. 虚函数
在类的继承中引入虚函数(virtual function)的概念。如果基类中定义了虚成员函数,在派生类中可以为该函数定义派生类自己的版本。
注意:
- 基类中的虚成员函数声明时加virtual关键字,但是如果虚函数在类外定义则不能加virtual关键字。
- 派生类中对应的函数可以不加virtual关键字,但派生类中的函数也是虚函数,即使没有virtual关键字,它的虚属性从基类中继承过来了。
- 要求派生类中定义的函数与基类中对应的函数的类型相同——返回类型、函数名、参数等。有个例外如果基类中函数返回值类型是基类,那么允许派生类中对应的函数的返回值类型是派生类类型。
- 如果函数类型不一致,不能看成是派生类对基类函数的改写(override),而是看成重载,那么派生类中的函数会覆盖基类中的函数。
- override:在派生类中显示定义,这个函数是改写基类中的函数.override写在参数列表或者const关键字之后。
1 |
|
- final:该关键字用在基类成员函数上,显式声明这个函数不能在派生类中改写。
1
2
3
4
5class child:public parent{
public:
void func1() const final;
void func2() final;
};
成员函数如果没有定义成虚函数,那么函数解析发生在编译阶段;而虚函数的解析发生在运行时,也就是动态绑定,也叫运行时绑定。
1 |
|
输出
1 |
|
从上面输出可以看出两个showT1()函数调用的都是基类对象的show()函数,这是因为基类中的show()函数不是虚函数,则在函数showT1()中的show()函数在编译阶段进行解析,解析成基类对象t的show()函数。
当虚函数通过引用或者指针调用时,编译器产生的代码到运行时才能确定调用那个版本的函数
将基类中的show()函数定义成虚函数,加上virtual关键字
1 |
|
输出
1 |
|
可以将共有派生类对象绑定到基类对象的引用或者指针上
1 |
|
输出
1 |
|
这里将showT1()的参数定义成基类对象,而不是引用或者指针,这样在调用showT1()函数时,如果参数是派生类,会将派生类对象转换成基类对象,这样在showT1(t2)调用的是基类的show()函数。
2. 纯虚函数和抽象基类
纯虚函数无须定义,如下所示。当然也可以为其定义,但是必须在类外。
1 |
|
含有纯虚函数的类是抽象基类,抽象基类负责定义接口,而它的派生类覆盖这些接口。
不能为抽象基类创建对象
3. 虚析构函数
通常基类中的析构函数定义成虚析构函数。这是为了确保对象能够正确析构。
1 |
|
有的时候我们将Base*类型绑定到Derived派生类对象上,释放派生类对象时需要执行派生类对象自己版本的析构函数,所以要将基类的析构函数定义成虚函数。
析构函数的属性会被继承
4. 虚函数和类作用域
前文中讲了派生中的虚函数要和基类中的虚函数参数列表一致,这是因为在使用将基类对象的指针或引用绑定到派生类对象上这个规则的时候,对象的静态对象(变量声明时决定的)和动态对象(运行时决定)是不同的,静态对象是基类类型,动态对象是派生类类型。虚函数是动态类型绑定,所以即使将派生类对象绑定到了基类的引用或指针上,虚函数的对象类型依旧是派生类。
1 |
|
派生类的作用域在基类作用域内。根据上面的自理,在编译阶段现在派生类作用域内按名字查找函数名memfun,找到memfun(int)函数,停止查找,但对于基类对象引用或指针,他没有memfun(int)这个函数,所以test(d)语句会报错。
事实上派生类中的memfun(int)函数隐藏了基类中的memfun()函数。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!