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

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

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

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

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

静态联编

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

动态联编

编译程序在编译阶段并不能确切地指导将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切地指导将要调用的函数,要求联编工作在程序运行时进行,这种在 程序运行时进行的 联编工作被称为动态联编。 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()。
(0)

相关推荐

  • C++ 中静态成员函数与非静态成员函数的区别

    静态成员函数与非静态成员函数的区别 数据成员: 静态数据成员是类的一部分,为类的所有实例共享(静态区):非静态数据成员,类的每个实例都有一份拷贝(动态区). 静态数据成员的访问: 静态数据成员是类的一部分,在产生任何实例之前已经存在,通过类名::静态成员变量名访问. 函数成员(都在代码区): 静态函数成员与非静态函数成员都为类所有,对象并不存在函数的拷贝.静态成员函数和非静态成员函数的根本区别在于非静态函数由对象名.或者对象指针->调用,调用时编译器会向函数传递this指针:静态成员函数则有类名

  • 深入解析C++编程中的静态成员函数

    C++静态成员函数 与数据成员类似,成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数.如 static int volume( ); 和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分. 如果要在类外调用公用的静态成员函数,要用类名和域运算符"::".如 Box::volume( ); 实际上也允许通过对象名调用静态成员函数,如 a.volume( ); 但这并不意味着此函数是属于对象a的,而只是用a的类型而已. 与静态数据成员不同,静态成

  • 详解C++的JSON静态链接库JsonCpp的使用方法

    JsonCpp部署方法: 在http://sourceforge.net/projects/jsoncpp/中下载最新版本的jsoncpp库源码. 之后将jsoncpp-src-版本号-tar.gz解压出来,打开makefiles中的jsoncpp.sln进行编译,之后build文件夹下的vs71\debug\lib_json中会有一个.lib静态链接库. JsonCpp主要包含三种类型的class:Value Reader Writer. jsoncpp中所有对象.类名都在namespace

  • C++静态成员函数不能调用非静态成员变量(详解)

    其实我们从直观上可以很好的理解静态成员函数不能调用非静态成员变量这句话因为无论是静态成员函数还是静态成员变量,它们 都是在类的范畴之类的,及在类的整个生存周期里始终只能存在一份.然而非静态成员变量和非静态成员函数是针对类的对象而言. 然而从本质上来说类的静态成员函数的函数形参中没有默认的this指针,导致不能调用具体实例对象的成员. 下面我们来测试一下: 先在静态成员函数中调用静态成员变量: #include <iostream> using namespace std; class vpoe

  • C++利用静态成员或类模板构建链表的方法讲解

    直接上代码了,说明看注释就可以: 利用静态成员构建链表 #include <IOSTREAM.H> class Node { public: Node(int val, Node* next):val(val),next(next){} //~Node(){cout<<"del "<<val<<endl;} static void showAll();//打印全部节点的值 static void insertHead(int);//头插

  • C++ 静态成员的类内初始化详解及实例代码

    C++ 静态成员的类内初始化详解及实例代码 一般来说,关于C++类静态成员的初始化,并不会让人感到难以理解,但是提到C++ 静态成员的"类内初始化"那就容易迷糊了. 我们来看如下代码: //example.h #include<iostream> #include<vector> using namespace std; class Example{ public: static double rate = 6.5; static const int vecSi

  • c++静态局部变量和静态函数示例

    在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存.但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效.但有的时候我们需要在两次调用之间对变量的值进行保存.通常的想法是定义一个全局变量来实现.但是这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便.静态局部变量正好可以解决这个问题.静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值.该变量在全局数据区内分配内存:静态局部变量在程序执行到该对象的声明

  • C++静态成员变量和静态成员函数的使用方法总结

    一.静态成员变量: 类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员.和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则.同时,静态数据成员还具有以下特点: 1.静态数据成员的定义. 静态数据成员实际上是类域中的全局变量.所以,静态数据成员的定义(初始化)不应该被放在头文件中. 其定义方式与全局变量相同.举例如下: xxx.h文件 class base{ private: static const int _i;//

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

    最近在看析构函数的内容,看到一些讲的比较好的文章,这里我也有了一些我自己的体会,在这里一并记录一下. 联编是指一个计算机程序自身彼此关联的过程,在这个联编过程中,需要确定程序中的 操作调用(函数调用) 与 执行该操作(函数) 的代码段之间的映射关系. 意思就是这个函数的实现有多种,联编就是把调用和对应的实现进行映射的操作. 按照联编进行的阶段不同,可分为静态联编和动态联编. 静态联编 静态联编工作是在程序编译连接阶段进行的,这种联编又称为早期联编,因为这种联编实在 程序开始运行之前 完成的.在程

  • C++的静态联编和动态联编详解

    一.概述: 通常来说联编就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数调用分配内存地址,并且对外部访问也分配正确的内存地址,它是计算机程序彼此关联的过程.按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编. 静态联编是指在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定,在编译阶段就必须了解所有的函数或模块执行所需要检测的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型,C语言中,所有的联编都是静态联编,并且任何一种编译器

  • C++动态联编介绍

    目录 一.静态联编和动态联编 1.指针和引用类型的兼容性 2.虚函数的工作原理 文章转自:公众号:Coder梁(ID:Coder_LT) 一.静态联编和动态联编 当我们使用程序调用函数的时候,究竟应该执行哪一个代码块呢?将源代码中的函数调用解释为执行特定的函数代码块这个过程被称为函数名联编(binding). 在C语言当中,这非常简单,因为每个函数名都对应一个不同的函数.而在C++当中,由于支持了函数重载,使得这个任务变得更加复杂.编译器必须要查看函数的参数以及函数名才能确定.好在函数的选择以及

  • JavaWeb开发之使用jQuery与Ajax实现动态联级菜单效果

    写在前面,在笔者完成这个demo的时候,笔者发现现在大家已经不用Ajax来完成联级菜单了,实际上笔者这个demo也并不是为了完成这个,笔者主要的学习方向是JavaWeb后台的业务逻辑开发.但是做后台呢还是需要对前端有所了解,尤其是像Ajax这种异步提交数据的技术需要了解并掌握.所以这里笔者这里用了一个联级菜单来练习Ajax异步提交,当然后续还会写几个异步提交表单的demo. 笔者的后台是用的spring+SpringMVC的框架,这里不对这部分进行解释,重点在jQuery和Ajax. 第一,下载

  • Flutter iOS开发OC混编Swift动态库和静态库问题填坑

    目录 引言 OC接入Swift 插件 静态库和 Framework 区别 新的问题: non-modular heade 不能在Framework Module中使用非Modular 的 Header 引言 Flutter 在 iOS 上的编译问题相信大家多多少少遇到过,不知道大家在搜索这方便的问题时,得到的答案是不是让你 clean 或者 install 多几次,很多时候就算解决完问题,也是处于薛定谔的状态,所以本篇也简单记录下 Flutter 开发中,OC 混编 Swift 遭遇动态库和静态

  • Java反射之静态加载和动态加载的简单实例

    静态加载: package com.imooc.加载类; public class Office_Static { public static void main(String[] args) { //new 创建对象,是静态加载类,在编译时刻就需要加载所有的可能使用到的类 if("Word".equals(args[0])){ Word w = new Word(); w.start(); } if("Excel".equals(args[0])){ Excel

  • C++中静态初始化数组与动态初始化数组详解

    静态初始化的数组的长度必须是在程序中确定的常数,不能是由用户输入的变量 例子: int a[10];//正确 Student stud[10];//正确:Student是一个学生类 int n;cin>>n;int a[n];//错误 int n;cin>>n;Student stud[n];//错误:Student是一个学生类 动态初始化数组可以使用用户输入的变量作为数组的长度. 例子: int n; cin>>n; int *a=new int[n];//这样整数数

  • C语言编程gcc如何生成静态库.a和动态库.so示例详解

    目录 一.什么是静态库和动态库 二.gcc生成.a静态库和.so动态库 1.生成静态库(.a) 1.1编辑生成例子程序hello.h.hello.c和main.c 1.2将hello.c编译成.o文件 1.3由.o文件创建静态库 1.4在程序中使用静态库 1.5验证静态库的特点 2.生成动态库(.so) 2.1由.o文件创建动态库文件 2.2在程序中使用动态库 三.实例 1.实例1 1.1代码 1.2 静态库.a文件的生成与使用 1.3 动态库.so文件的生成与使用 2.实例2 2.1代码 2.

  • Android应用开发中Fragment的静态加载与动态加载实例

    1.Fragment的静态使用 Fragment是作为Activity的UI的一部分,它内嵌在Activity中,多个Fragment可以把一个Activity分成多个部分,这在大屏幕手机或者平板电脑中会比较多的用到,这样就不用使用多个Activity来切换这么麻烦了.当然Fragment也可以不显示,只在后台处理一些数据,这篇文章中就暂时不谈到这个.以下来看怎么静态地在Activity的布局文件中添加Fragment. 自定义的Fragment通常要继承Fragment这个类,也有一些特殊的是

  • php 静态页面中显示动态内容

    最近在做一个站点时,需要生成静态页面,但是生成的静态页面中有些内容是需要动态获取的,怎不能每天生成一下吧.. 最后上网查了一下,再加上个要总结,呵....终于实现了..发出来,大家一起研究..呵... <span class="STYLE1">应用一</span>:文章计数,获取动态内容 计数页:count.php 复制代码 代码如下: <?php require_once './global.php'; $DB->query("updat

随机推荐