C++中虚函数和纯虚函数的问题总结

简介: C++中虚函数和纯虚函数的问题总结



 

C++中虚函数和纯虚函数的问题总结

虚函数

虚函数的定义

在C++中,在成员函数的声明中加上关键字virtual即可声明该函数为虚函数。

虚函数的目的

虚函数在面向对象的程序设计中实现了多态中的动态绑定问题:

面向对象程序设计中,派生类继承自基类。使用基类指针引用访问派生类对象时。如果派生类覆盖了基类中的方法,通过上述指针或引用调用该方法时,可以有两种结果:

  1. 调用到基类的方法:编译器根据指针或引用的类型决定,称作“静态绑定”;
  2. 调用到派生类的方法:语言的运行时系统根据对象的实际类型决定,称作“动态绑定”。

虚函数的效果属于后者。

如果基类中的函数是“虚”的,则调用到的都是在运行时最终派生类中的函数实现,与指针或引用的类型无关。

如果函数非“虚”,调用到的函数就在编译期根据指针或者引用所指向的类型决定。

例如:

#include <iostream>
using namespace std;
class Base {
public:
    virtual void Max() {
        cout << "Max 1" << endl;
    }
};
class Son : public Base{//继承
public:
    void Max() {
        cout << "Max 2" << endl;
    }
    void Min() {
        cout << "Min" << endl;
    }
};
int main() {
    cout << "----------1------------" << endl;
    Base base;
    base.Max();
    cout << "----------2------------" << endl;
    Base* base1 = new Son;//指针访问
    base1->Max();
    //base1.Min();  //会报错,无法调用非虚函数
    cout << "----------3------------" << endl;
    Son son1;
    Base& base3 = son1; //引用访问实现动态绑定,创建时必须初始化
    base3.Max();
    //base3.Min();  //会报错,无法调用非虚函数
    return 0;
}

结果:

 

注意:引用访问实现动态绑定时,创建对象时必须初始化;指针则可以先声明再初始化。并且只能调用虚函数(调用非虚函数会报错,如例子中的Min()函数所示)。

继承时,虚函数的属性也被继承了,就算子类重写的成员函数中不写“virtual“,系统会默认它是虚函数。

例如:

#include <iostream>
using namespace std;
class Base {
public:
    virtual void Max() {
        cout << "Max 1" << endl;
    }
};
class Son : public Base{
public:
    void Max() {
        cout << "Max 2" << endl;
    }
};
class GrandSon : public Son{//
public:
    void Max() {
        cout << "Max 3" << endl;
    }
};
int main() {
    cout << "----------1------------" << endl;
    Base base;
    base.Max();
    cout << "----------2------------" << endl;
    Base* base1 = new GrandSon;
    base1->Max();
    return 0;
}

其中,Son类中的Max()函数没有标明“virtual”,但是它是重写的Base类中的函数,因此会默认为虚函数。因此在GrandSon类中,Max()函数仍能实现重写操作,能够实现动态绑定。(对重载、重写、隐藏有疑问的可以参考一文彻底解决C++中的重载、重写和隐藏操作

结果:

 

纯虚函数

纯虚函数的定义

纯虚函数只是相当于一个接口名,含有纯虚函数的类不能够实例化。纯虚函数首先是虚函数,其次它没有函数体,取而代之的是用“=0”。

纯虚函数的特点

一个类中如果有纯虚函数的话,称其为抽象类。抽象类不能用于实例化对象,否则会报错。抽象类一般用于定义一些公有的方法。子类继承抽象类也必须实现其中的纯虚函数才能实例化对象。

例如:

#include <iostream>
using namespace std;
class Base {
public:
    virtual void Max() {
        cout << "Max 1" << endl;
    }
    virtual void Min() = 0; //纯虚函数
};
class Son : public Base{//继承
public:
    void Max() {
        cout << "Max 2" << endl;
    }
    void Min() {
        cout << "Min" << endl;//子类实现纯虚函数
    }
};
int main() {
    cout << "----------1------------" << endl;
    //Base base; //报错,含有纯虚函数,不能实例化
    //base.Max();
    cout << "----------2------------" << endl;
    Base* base1 = new Son;//指针访问
    base1->Max();
    base1->Min();  //
    return 0;
}

结果:

 

虚函数相关问题

问题一:基类的虚函数表存放在内存的什么位置,虚表指针vptr的初始化时间

虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),也就是内存模型中的常量区中。而虚函数位于代码区

由于虚表指针vptr跟虚函数密不可分,对于有虚函数或者继承于拥有虚函数的基类,对该类进行实例化时,在构造函数执行时会对虚表指针进行初始化,并且存在对象内存布局的最前面。

问题二:虚函数能否声明为内联函数?

首先,声明为inline的函数,编译器也不一定真实实现内联,还得看函数简不简单。

将虚函数声明为inline,要分情况讨论。因为虚函数可以实现多态(在运行时决定),而inline是在编译时由编译器决定是否内联。所以将虚函数声明为inline分为以下两种情况:

1.当是指向派生类的指针(多态性)调用声明为inline的虚函数时,不会内联展开;

2.当是对象本身调用虚函数时,会内联展开(前提是函数并不复杂的情况下),但是这也并没有实现虚函数的作用。

问题三:构造函数为什么不能为虚函数?析构函数为什么要虚函数?

构造函数:

构造函数不能定义为虚函数。在构造函数中可以调用虚函数,不过此时调用的是正在构造的类中的虚函数,而不是子类的虚函数,因为此时子类尚未构造好。

原因:虚函数对应一个vtable(虚函数表),类中存储一个vptr指向这个vtable。如果构造函数是虚函数,就需要通过vtable调用,可是对象没有初始化就没有vptr,无法找到vtable,所以构造函数不能是虚函数。

析构函数:

析构函数可以为虚函数,并且一般情况下基类析构函数要定义为虚函数。

原因:从上面析构函数的调用顺序可知,首先要调用派生子类的析构函数。因此只有在基类析构函数定义为虚函数时,调用操作符delete销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据。

另外析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。

问题四:构造函数和析构函数可以调用虚函数吗?

在C++中,可以调用,但不提倡;

① 我们调用虚函数,一般就是使用其动态联编的功能。但是构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本;

② 构造函数时,因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化,因此C++不会进行动态联编;

③析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经销毁,也没有使用动态联编。

问题五:静态成员函数static能定义为虚函数吗?常成员函数const呢?

static静态成员函数不能定义为虚函数。

static成员不属于任何类对象或类实例,静态成员函数没有this指针。

虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable。而对于静态成员函数,它没有this指针,所以无法访问vptr。

const成员函数可以是虚函数。这样声明该函数都是“只读”操作。

问题六:哪些函数不能是虚函数?

① 构造函数

② 内联函数

③ 静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。

④ 友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。

⑤ 普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。

目录
相关文章
|
29天前
|
C++
9. C++虚函数与多态
9. C++虚函数与多态
27 0
|
1月前
|
设计模式 编解码 算法
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案(三)
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案
43 0
|
1月前
|
存储 设计模式 编译器
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案(一)
【C/C++ 虚函数以及替代方案】C++ 虚函数的使用开销以及替代方案
62 0
|
3月前
|
存储 C++ 容器
第十四章:C++虚函数、继承和多态详解
第十四章:C++虚函数、继承和多态详解
29 0
|
3月前
|
C++
C++析构函数定义为virtual虚函数,有什么作用?
C++析构函数定义为virtual虚函数,有什么作用?
29 0
|
1月前
|
算法 安全 编译器
【C++ 关键字 override】C++ 重写关键字override(强制编译器检查该函数是否覆盖已存在的虚函数)
【C++ 关键字 override】C++ 重写关键字override(强制编译器检查该函数是否覆盖已存在的虚函数)
27 0
|
1月前
|
算法 Java 编译器
【C++ 关键字 virtual 】C++ virtual 关键字(将成员函数声明为虚函数实现多态
【C++ 关键字 virtual 】C++ virtual 关键字(将成员函数声明为虚函数实现多态
25 0
|
2天前
|
C++
C++虚函数学习笔记
C++虚函数学习笔记
7 0
|
1月前
|
存储 程序员 编译器
【C++ 模板类与虚函数】解析C++中的多态与泛型
【C++ 模板类与虚函数】解析C++中的多态与泛型
46 0
|
1月前
|
存储 算法 编译器
【C++入门到精通】C++入门 —— 多态(抽象类和虚函数的魅力)
多态是面向对象编程中的一个重要概念,指的是同一个消息被不同类型的对象接收时产生不同的行为。通俗来说,**就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态**。
44 0