美文网首页C++复习
C++重新理解虚函数

C++重新理解虚函数

作者: 凉拌姨妈好吃 | 来源:发表于2018-05-24 13:13 被阅读11次

1. 虚函数的定义

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

1.1 动态绑定(动态联编)

函数的运行版本由实参决定,直到运行的时候才知道调用了哪个版本的虚函数。
动态绑定只有通过指针或引用调用虚函数时才会发生

Quote base = derived;
base.net_price(20);
//在这里只调用Quote的net_price

switch、if也属于动态联编

2. 虚函数的构造

2.1 派生类中虚函数的构造

派生类中虚函数的参数列表函数名必须相同,返回类型在大多情况下是相同的(当返回类型为基类时,派生虚函数返回类型为自己的派生类)

2.2 override/final说明符

使用override关键字来修饰派生类中的虚函数,表示该函数并没有覆盖已存在的虚函数,如果用户强制覆盖会报错。

struct B{
  virtual void f1() const;
  virtual void f2();
};
struct D:B{
  void f1() const override;
  void f2(int)  override;//编译器会报错,因为B没有f2(int)这样的函数
}

如果用final关键字修饰虚函数,那么它的派生类如果要覆盖该函数会出错

struct B{
  virtual void f2() final;
};
struct D:B{
void f2();//出错,因为f2已经声明为final
}
2.2.1 重载/重写/覆盖
  • 重载(overload):同一个访问区内被声明的几个具有不同参数列的同名函数,在传入数据时,根据实参选择相应的形参的函数,不关心返回值类型
class A{
public:
  void test(int i);
  void test(double i);
  void test(int i, double j);
  void test(double i, int j);
};
  • 重写(override)派生类中需要重写的函数,它的返回值,参数列表,函数名都必须与被重写的基类相同,只有函数体不同。并且基类中的被重写函数必须加上virtual。派生类在调用该函数时,就只会调用自己重写的函数,不会调用基类的函数(其实也相当于覆盖了)
class A{
public:
  virtual void fun3(int i){
    cout << "A::fun3() : " << i << endl;
  }
 
};
class B : public A{
public:
  virtual void fun3(double i){
    cout << "B::fun3() : " << i << endl;
  }
};
  • 覆盖:派生类的函数屏蔽了与其同名的基类函数。注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏
class A{
public:
  void fun1(int i, int j){
    cout << "A::fun1() : " << i << " " << j << endl;
  }
 
};
class B : public A{
public:
    //隐藏
  void fun1(double i){
    cout << "B::fun1() : " << i << endl;
  }
};
2.3 回避虚函数机制

有时候我们在派生类的虚函数的函数体中想要调用基类的虚函数,那么这时候就一定要加上作用符限定,否则就会调用派生类的虚函数,造成无限循环

3. 纯虚函数

当设计者不希望创建一个基类对象,因为基类里面的函数是没有意义的,那么可以将基类的该函数定义为纯虚函数,那么我们将这些含有纯虚函数的基类称为抽象基类

3.1 抽象基类
3.1.1 为什么有抽象基类

因为纯虚函数不能被调用,所以包含纯虚函数的类是无法实例化的,那么这时候就出现了一个抽象类,它作为多个子类的共同基类,就相当于给多个子类提供一个公共的接口,我们可以通过定义这个公共接口的指针或引用,指向派生类的某个对象,这样就可以通过它来访问派生类对象中的虚函数

3.1.2 抽象基类的几个要点
  • 抽象基类负责定义接口,后续派生类可以覆盖接口,实现该接口。
  • 抽象基类无法实例化。
  • 如果基类定义多个纯虚函数,子类没有一一将纯虚函数实现,那么子类依旧也会被认为是抽象类。

4. 虚函数表

4.1 虚函数表是如何实现的

先思考一个问题,编译器是在什么时候实现不同对象能调用同名函数绑定关系的?

在创建对象的时候,编译器偷偷给对象加了一个vptr指针。只要我们类中定义了虚函数,那么在实例化对象的时候,就会给对象添加一个vptr指针,类中创建的所有虚函数的地址都会放在一个虚函数表中,vptr指针就指向这个表的首地址。

4.2 在构造函数中定义虚函数会出现什么情况?

看以下代码,思考一下此时虚函数的调用

class Parent{
public:
    Parent(int a=0){
            this->a = a;
            print();}
    virtual void print(){cout<<"Parent"<<endl;}
private:
    int a;
};
class Son:public Parent{
    Son(int a=0,int b=0):Parent(a){
        this->b = b;
        print();}
    virtual void print(){cout<<"Son"<<endl;}
};
void main(int argc, char const *argv[]){
        Son s;
        return 0;
}

两个类中构造函数中,都只会调用自己类中的print()函数
为什么呢?因为Son对象在实例化时,先调用基类构造函数,存在虚函数,将vptr指向基类的虚函数表,调用派生类构造函数,存在虚函数,将vptr指向派生类的虚函数表。所以都只会调用自己类中的虚函数。

如果子类重写了父类的某一虚函数,那么父类的该虚函数就被隐藏,无论以后怎么调用,调用同名虚函数调用的都是子类虚函数

重写前
重写后

为什么析构函数经常定义为虚析构函数

虚析构函数:只有当一个类被定义为基类的时候,才会把析构函数写成虚析构函数。
为什么一个类为基类,析构函数就需要写成虚析构?
假设现在有一个基类指针,指向派生类。此时释放基类指针,如果基类没有虚析构函数,此时只会看指针的数据类型,而不会看指针指向的数据类型,所以此时会发生内存泄露。

4.3 虚继承
4.3.1 为什么会使用虚继承

当类D继承于类B与类C,而类B与类C又继承于公共基类类A,为了避免基类多重拷贝,我们就让类B与类C虚继承于类A

4.3.2 底层原理

每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)
vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间


一个非常容易错的地方!
class parent  
{  
    public:  
    virtual void output();  
};  
void parent::output()  
{  
    printf("parent!");  
}  
       
class son : public parent  
{  
    public:  
    virtual void output();  
};  
void son::output()  
{  
    printf("son!");  
}

son s; 
memset(&s , 0 , sizeof(s)); 
parent& p = s; 
p.output(); 

猜一猜上面会输出什么呢?
编译出错!!!
为什么呢?

memset会将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值
虚函数链表地址也清空了, 所以p.output调用失败。 output函数的地址编程0

相关文章

  • C++重新理解虚函数

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

  • 理解C++虚函数

    title: 理解C++虚函数date: 2018-11-11 15:31:26categories:- 概念理解...

  • C++ 虚函数

    C++ 虚函数 虚函数 基类中使用virtual关键字声明的函数,称为虚函数。虚函数的实现,通过虚函数表来实现的。...

  • C++学习笔记

    C++面对对象 实函数,虚函数,纯虚函数,函数重写 虚函数:需要进行子类的重写时。virtual void 函数名...

  • (Boolan) 面向对象高级编程(下)第四周笔记

    一、虚函数表 对C++ 虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)...

  • 查漏补缺

    C++虚函数: 多态: 静态多态(重载)、动态多态(虚函数) 虚函数 虚函数表:编译器为每个类创建了一个虚函数表...

  • 理解 C++ 虚函数表

    引言 虚表是 C++ 中一个十分重要的概念,面向对象编程的多态性在 C++ 中的实现全靠虚表来实现。在聊虚表之前我...

  • C++ 虚函数本质

    C++ 虚函数本质

  • pwnable.kr之uaf && c++虚函数

    c++的逆向还是要熟悉下。 一、关于c++虚函数 虚函数使得c++能够实现多态,每个类有一个虚表,每个对象在实现的...

  • (Boolan) 面向对象高级编程(下)第四周笔记

    一、虚函数表 对C++虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来...

网友评论

    本文标题:C++重新理解虚函数

    本文链接:https://www.haomeiwen.com/subject/tvwjjftx.html