ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

c++多态学习小记

2021-05-22 22:33:42  阅读:207  来源: 互联网

标签:虚表 函数 联编 fish 多态 c++ animal 指针 小记


联编

首先先了解啥是联编,先看书上的概念
联编: 是指确定函数调用和函数代码段之间的映射关系。
静态联编:是只在编译时确定了函数调用的具体操作对象;
动态联编:是只在程序运行过程中动态确定函数调用的具体对象。

换句话说
联编就是找到应该调用哪个函数的过程;
看个例子

#include <iostream>
using namespace std;

class animal{
public:
    void breathe() {
        cout << "animal breathe" << endl;
    }
};

在编译以上代码后,编译器会记住函数 breathe() 的地址,每次对animal的实例调用该函数时,编译器都会按照固定的调用路径执行,为静态联编,即在编译时就已确定函数的调用路径。例子1就是静态联编。

多态的引入

多态的目的即一种接口,多种实现,如下例子1,

#include <iostream>
using namespace std;

class animal{
public:
    void breathe() {
        cout << "animal breathe" << endl;
    }
};

class fish: public animal{
    void breathe(){
        cout<<"fish bubble"<<endl;
    }
};
int main() {
    fish f;
    animal *p = &f;
    p->breathe();
    return 0;
}
//输出:animal breathe

上例的本意是,通过父类指针 去访问子类的成员方法,即应输出 “fish bubble”,但其结果仍调用的是父类的成员方法。
原因在于:
在编译器编译时,需要确定每个对象调用函数的地址,称为早期绑定。在构造fish类的对象时,先要调用animal的构造函数,再调用fish类的构造函数。二者拼成一个完整的fish对象。
当将fish类的对象f 的地址赋给animal类的指针p时,需要进行类型转换(可以类比 int a = 1.5),即p此时会指向animal的地址,调用的就是animal的breathe()函数了。

多态的实现

1.函数重载–静态联编

看下面例子2

#include <iostream>
using namespace std;

class point{
private:
    int x;
    int y;
public:
    point(int xx, int yy)
    {
        x = xx;
        y = yy;
    }
    double area() {
        return 0.0;
    }
};

class circle: public point{
private:
    int r;
public:
    circle(int xx, int yy, int rr):point(xx,yy){
        r = rr;
    }
    double area() {
        return 3.14 * r * r;
    }
};
int main() {
   point p(1,1);
   circle c(2,2,2);
   cout<<p.area()<<endl;
    cout<<c.area()<<endl;
    cout<<c.point::area()<<endl;
    return 0;
}
输出
0
12.56
0

这里,采用跳转指令,告诉了编译器该调用哪个类的实例的成员方法。

2 虚函数-动态联编

同样是还是上面的例子2修改如下

#include <iostream>
using namespace std;

class animal{
public:
   virtual void breathe() {
        cout << "animal breathe" << endl;
    }
};

class fish: public animal{
    void breathe(){
        cout<<"fish bubble"<<endl;
    }
};
int main() {
    fish f;
    animal *p = &f;
    p->breathe();
    return 0;
}
输出 fish bubble

只是声明基类的成员方法为虚函数,即实现了我们想要的功能,即:通过基类指针访问子类的成员变量和成员函数。注意,如果不声明为虚函数,则基类指针只能访问子类的成员变量,并不能访问成员方法。

虚函数的实现方法:

当brearhe() 被声明为虚函数时,编译器在编译时会为每个包含虚函数的类创建一个虚表(为1维数组),这个虚表里会存放虚函数的地址,并为每个类提供一个虚表指针,指向各自对应的虚表。进而,在程序运行的时候,编译器根据对象的类型去调用各自对应的函数。对于上例,当fish类的实例f创建后,其内部的虚表指针会指向fish类的虚表,进而会调用fish类的breathe方法。
上述描述刚看起来可能有点不好理解,借用别人博客上的一张图:
在这里插入图片描述
以base基类和derive子类为例,当类中有虚函数时,一个对象的构成会包括一个虚表指针vfptr 和这个对象本身,32位编译器下这个指针占4个字节。这个虚表指针会指向某一个虚表, 虚表是一个一维数组,里面包括各个虚函数的地址。当调用虚函数时,编译器首先会查看对象中的虚表指针,然后转到相应的虚函数地址表。这里需要注意, 无论类中有多少个虚函数,只需要在类中添加一个虚表指针即可,只是这个虚表的大小不同了。,可以想到,对于多重继承,会存在多个虚表指针和多个虚表,每个虚表指针指向各自对应的虚表。

由上可见,在使用虚函数时也会带来一定的成本,包括:
1)每个对象都会增大,增大一个存储地址的空间;
2 )创建类的时候会额外创建一个虚表;
3) 调用函数时会增加额外的在虚表中查找地址的操作。

总结,对于虚函数的调用来说,每个对象内部都会有一个虚表指针,被初始化为指向本类的虚表。不管对象类型如何去转换,该虚表指针是固定的,因此实现了动态的对象函数的调用。

基类的构造函数不允许定义为虚函数

在这里插入图片描述
自己测试如上。这个问题网上有很多回答:包括,虚函数的调用需要虚函数表指针,而该指针存放在对象的内容空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数
我个人比较认同的一个回答是:
假如基类的构造函数是虚的,那么我们在实例化一个子类对象时,由于虚函数的特性,子类会调用子类自己的构造函数。又由于继承机制,子类实例化时,会先调用基类的构造函数,再调用子类的构造函数。这样下来二者冲突。故 这样做没啥意义。

析构函数一般定义为虚函数

如下例子

#include <iostream>
using namespace std;

class animal{
public:
     animal(){
         cout<<"animal"<<endl;
     };
    ~ animal(){
        cout<<"~animal"<<endl;
    };
};
class fish: public animal{
public:
    fish(){
        cout<<"fish"<<endl;
    };
    ~ fish(){
        cout<<"~fish"<<endl;
    };
};
int main() {
    fish f;
    animal *p = &f;
    delete p;
    return 0;
}
输出:
animal
fish
~animal

如果析构函数不采用虚函数,释放指针p只是释放了基类的内存,没有释放子类的内存;这样会导致内存泄漏。原因同静态联编。

为什么c++ 默认的类不采用虚析构函数呢,因为虚函数会导致额外的开销,对于不需要继承的类,声明为虚函数会浪费内存,故默认没有采用。。
由上可见,当需要该类作为基类时,一般采用虚析构函数;这样会导致内存泄漏。

标签:虚表,函数,联编,fish,多态,c++,animal,指针,小记
来源: https://blog.csdn.net/qq_35027690/article/details/117154412

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有