C++ 多态和析构虚函数

  面试被问道具体名词解释,知道啥意思,表述不清。难受。


C++ 多态和析构虚函数

注意:

1.一个类如果使用了虚函数,请把他的析构函数也改为虚函数。
2.保证至少有一个虚函数不是内联实现的。

1. 多态含义

  多态,字面上看多态就是多种形态。类存在继承关系且多种层次时,会用到多态。多态具体指:调用成员函数时,根据当前的类型执行不同的函数。(动态调用,程序跑起来后才知道调用什么)
  如果一个语言无法实现多态,只能算是“基于对象的程序设计语言”而非“面向对象的程序设计语言”。
  多态诞生目的:降低程序运行效率,提高开发人员效率。

2. 如何实现多态-虚函数

  在类中使用virtual关键字修饰函数。

class A {
  public:
    virtual void Execute() {
        cout << "A::Execute" << endl;
    }
};
class B : public A {
  public:
    virtual void Execute() {
        cout << "B::Execute" << endl;
    }
};

int main() {
    A  a;
    B b;
    A *pa = &a;
    B *pb = &b;
    pa->Execute();
    pa = pb;
    pa->Execute();
    return 0;
}

3. 纯虚函数

  需要在基类中定义虚函数,以便派生时更好使用。但是基类有没什么好写的,就可以使用纯虚函数。

  virtual void Execute() = 0;

4. 析构函数

  正常逻辑是 delete应该调用类本身的析构函数,而不是基类的虚函数。如果析构函数不用虚函数的话,会有各种BUG。

  • 引用计数,因为调用错了析构函数导致delete后计数错误
  • 类内动态分配的内存,调用错误的析构函数就内存泄露了

  c++ 派生类会自动调用基类的析构函数。

一个类如果使用了虚函数,请把他的析构函数也改为虚函数。

5. 虚函数必须在外部实现

保证有一个虚函数不是内联实现的就行了。

  析构虚函数如果定义在内部,会发生警告

class A {
  public:
    virtual ~A(){}
    virtual void Execute() {
        cout << "A::Execute" << endl;
    }
};
 warning: 'A' has no out-of-line virtual method definitions; 
its vtable will be emitted in every translation unit

  定义在外部则正常。

class A {
  public:
    virtual ~A();
    virtual void Execute() {
        cout << "A::Execute" << endl;
    }
};
A::~A() {}

  原因就设计到虚函数是如何实现的了,定义了虚函数编译器会自动生成虚函数表,用来对应虚函数。如果虚函数所有方法是内联的,编译器不知道在那个cpp文件中生成虚函数表,他会在所有用到这个类的cpp中搞一个副本并链接。增大了.o文件。

6. 构造函数不能是虚函数

  从使用角度来说:构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用。具体实现看下文。

7. 虚函数实现原理

7.1.1. 虚函数类的构造

  类的实例化就是给每一个实例在内存中分配一块地址。空类的大小是0,实例化时编译器会给一个字节。

class A {
  public:
    virtual ~A();
};
A::~A() {}

class AA {
  public:
    ~AA() {}
};
int main() {
    std::cout << sizeof(A) << ", " << sizeof(AA) << std::endl;
    // 结果 8, 1 (gcc 64)
}

  看到如果类中有虚函数,多8个字节(gcc 64)。任何有虚函数的类,都会多一个地址的大小,存放虚函数表的位置。为了高效,这个放地址的位置在这个类的存储空间最前面。

  每一个有虚函数的类都有一个虚函数表,这个类的所有对象都放着这个表的指针。

编译器自动添加到构造函数中的,所以构造函数不能使虚函数

  程序编译时候会给每个有虚函数的类生成一个虚函数表放在.o里,程序运行后拷贝到内存里。

class A {
  public:
    int i;
    virtual ~A();
};
A::~A() {}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ok9yLkCj-1611056634521)(vx_images/4010501208390.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkEfBxGR-1611056634523)(vx_images/3047937218390.png)]

图片来源 https://www.cnblogs.com/phpandmysql/p/10853354.html

7.1.2. 虚函数类的调用
  1. 取a指针指向的地址的前8个字节,如果a指向的是类A这个地址指向位置就是类A的虚函数表,如果a指向的是类B这个地址指向的位置就是类B的虚函数。
  2. 在虚函数表里找到要调用函数的地址。
  3. 调用函数。
class A {
  public:
    virtual ~A();
    virtual void fun() {
        std::cout << "A::fun" << std::endl;
    }
};
A::~A() {}

class B: public A {
  public:
    virtual ~B();
    virtual void fun() {
        std::cout << "B::fun" << std::endl;
    }
};
B::~B() {}

int main() {
    A *a;
    B b;
    a = new A;
    a->fun();
    a = &b;
    a->fun();
    a = new A;
    a->fun();
    return 0;
}

/* 结果
A::fun
B::fun
A::fun
*/

参考
https://www.zhihu.com/question/425173545/answer/1520796610

单继承时:

  如何知道a指向的是类A还是类B?

  • 每个虚函数在虚函数表中有个索引
  • 虚函数调用被编译器改成了:(*p->vptr[x])(p)。(其中pthisx是这个虚函数在虚函数表中位置)

  这样就可以做到在程序运行是,调用那个函数取决于vptr[x]具体指向哪里。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhEBuFt8-1611056634526)(vx_images/793438775913.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x2KwgWSf-1611056634528)(vx_images/2145947565005.png)]

图片来源 https://www.cnblogs.com/phpandmysql/p/10853354.html

  可以看到有继承关系的所有虚函数的x都是一样的。


多继承时:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beyond欣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值