C++的静态联编和动态联编

所属分类: 软件编程 / C 语言 阅读数: 91
收藏 0 赞 0 分享

最近在看析构函数的内容,看到一些讲的比较好的文章,这里我也有了一些我自己的体会,在这里一并记录一下。

联编是指一个计算机程序自身彼此关联的过程,在这个联编过程中,需要确定程序中的 操作调用(函数调用) 与 执行该操作(函数) 的代码段之间的映射关系。

意思就是这个函数的实现有多种,联编就是把调用和对应的实现进行映射的操作。

按照联编进行的阶段不同,可分为静态联编和动态联编。

静态联编

静态联编工作是在程序编译连接阶段进行的,这种联编又称为早期联编,因为这种联编实在 程序开始运行之前 完成的。在程序编译阶段进行的这种联编在编译时就解决了程序的操作调用与执行该操作代码间的关系。

动态联编

编译程序在编译阶段并不能确切地指导将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切地指导将要调用的函数,要求联编工作在程序运行时进行,这种在 程序运行时进行的 联编工作被称为动态联编。 C++中,动态联编是在虚函数的支持下实现的 。

静态联编和动态联编都是属于多态性的,他们在不同的阶段对不同的实现进行不同的选择。

动态联编需要虚函数的支持,这是因为虚函数的工作原理决定的,而正是因为使用了虚函数来实现动态联编,也让动态联编的效率略低于静态联编。通常,编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员,隐藏成员保存了一个指向函数地址数组的指针 ,这个数组就是虚函数表(virtual function table, vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址,调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表,如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数,如果使用类声明中的第三个虚函数,程序将使用地址位数组中第三个元素的函数。

虚函数这个概念是C++的精华之一。遇到虚函数时要注意:

定义一个函数为虚函数,不代表函数为不被实现的函数(可以有自己的实现)

定义他位虚函数是为了允许用基类的指针来调用子类的这个函数(提供了基类调用子类函数的方式)

定义一个函数为纯虚函数,才代表函数没有被实现(声明后面接=0 virtual func() = 0 此时派生类必须要实现此虚函数)

具有纯虚函数的类是 抽象类 ,不能用于生成对象(即不能实例化),只能派生,他派生的类如果没有实现纯虚函数,那么他的派生类还是抽象函数。

虚析构函数

虚析构函数顾名思义就是将析构函数定义为虚函数。如果我们在派生中分配了内存空间,但是基类的析构函数不是虚析构函数,就会发生内存泄漏。先看一个例子

#include <iostream>

using namespace std;

class Base
{
  public:
    Base(){ data = new char[10];}

    ~Base(){ cout << "destroying Base data[]\n";delete []data;}
  private:
    char *data;
};

class Derive: public Base
{
  public:
    Derive(){ D_data = new char[10];}

    ~Derive(){ cout << "destroying Derive data[]\n";delete []D_data;}
  private:
    char *D_data;
 };

int main()
{
Base *basePtr = new Derive();

delete basePtr;
return 0;
}

输出结果:

$ ./a.out
destroying Base data[]

在这个例子中,派生类的析构函数并没有被调用,这在大的项目中就是一个灾难。究其原因是我们在main函数中定义了一个Base的指针,当我们delete一个动态分配的Base指针时,Base指针此时却指向了Derive类型的对象,但编译器还是按照Base类型调用了析构函数,没有执行Derive类型的虚析构函数。修改Base类的析构函数为虚析构函数即可以确保执行正确的析构函数版本。

最后总结一下关于虚函数的一些常见问题:

  1. 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数,这就是虚函数的基本功能。
  2. 构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还么有构造好,多态此时是被disable的。
  3. 析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。
  4. 将基类中的一个函数定义为纯虚函数,实际上是将这个类定义位抽象类,不能实例化对象。
  5. 纯虚函数通常没有定义体,但也可以拥有。(如果Base的析构函数为纯虚函数,那么在类外定义Base::~Base(){…}的方式来定义其定义体)
  6. 析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
  7. 非纯的虚函数必须有定义体,不然是一个错误。
  8. 派生类的override虚函数定义必须和父类完全一致,除了一个特例,如果父类返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生。如在Base中定义了virtual Base clone();在Derive中可以定义virtual Derive clone()。
更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多