精华内容
下载资源
问答
  • 本文阐述了静态联编和动态联编的概念区别,通过具体实例分析了实现动态联编的条件,指出了虚函数是实现动态联编的基础。
  • 像这种,在编译阶段,就已经确定函数的调用地址的情况,就叫静态联编 那么问题来了,我们应该如何实现执行animal.speak()时,最终调用的cat对象中的doSpeak()呢? 答案很简单,只需要在Animal的speak()方法前加个...

    先看下面的代码

    class Animal
    {
    public:
    	    void speak()
    	{
    		cout << "动物在说话" << endl;
    	}
    
    
    };
    
    class Cat :public Animal
    {
    public:
    	void speak()
    	{
    		cout << "小猫在说话" << endl;
    	}
    
    };
    void doSpeak(Animal & animal) 
    {
    	animal.speak();
    }

    接着调用如下代码测试

    Cat cat;
    	doSpeak(cat);

    运行结果如下:

    可能对于大多数人而言,他们会认为输出结果是:小猫在说话。

    然而事实上却不是,出现这样的原因是,在编译的时候,编译器已经将animal.speak();这行代码转化为了具体的函数地址调用,因此这行代码并不受外边参数的影响。像这种,在编译阶段,就已经确定函数的调用地址的情况,就叫静态联编

    那么问题来了,我们应该如何实现执行animal.speak()时,最终调用的cat对象中的doSpeak()呢?

    答案很简单,只需要在Animal的speak()方法前加个virtual,具体如下

    class Animal
    {
    public:
    	virtual void speak()
    	{
    		cout << "动物在说话" << endl;
    	}
    
    };

    此时,你再调用下边的代码

    Cat cat;
    	doSpeak(cat);

    打印的结果就是:小猫在说话。

    由此可看出,virtual的作用就是告诉编译器,不要在编译阶段就确定speak()的调用地址,只在运行的时候,才根据参数决定speak()调用地址。像这种情况就叫做动态联编,在其他的编程语言里,动态联编也被叫做多态。

    C++ 动态联编(多态)原理剖析

    展开全文
  • 静态联编和动态联编 将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编. 静态联编: 在编译过程中进行联编, 又叫早期联编 动态联编:编译器必须生成能够在程序运行时选择正确的虚方法的的方式被称为...

    静态联编和动态联编

    将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编.

    静态联编: 在编译过程中进行联编, 又叫早期联编

    动态联编:编译器必须生成能够在程序运行时选择正确的虚方法的的方式被称为动态联编(dynamic binding), 又称为晚期联编(late binding).

    将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting), 这使得共有继承不需要进行显示类型转换.

    向上强制转换是可以传递的, 也就是说, 如果BrassPlus派生出BrassPlusPlus, 那么Brass指针或引用可以引用Brass对象, BrassPlus对象或BrassPlusPlus对象.

    将基类指针或引用转换为派生类指针或引用被称为向下强制转换(downcasting). 如果不使用显示类型转换, 则向下强制转换是不允许的.

    看下面这段代码, 假设每个函数都调用虚方法ViewAcct():

    // 引用传递
    void fr(Brass & rb);
    // 指针传递
    void fp(Brass * pb);
    // 值传递
    void fv(Brass b)
    
    int main()
    {
    	Brass b("Tom", 213, 10000.0);
    	BrassPlus bp("Jack", 23333, 25000.0);
    	// 调用的是Brass::ViewAcct()
    	fr(b);
    	// 调用的是BrassPlus::ViewAcct()
    	fr(bp);
    	// 调用的是Brass::ViewAcct()
    	fp(b);
    	// 调用的是BrassPlus::ViewAcct()
    	fp(b);
    	// 调用的是Brass::ViewAcct()
    	fv(b);
    	// 调用的是Brass::ViewAcct()
    	fv(b);
    }

    按值传递导致只将BrassPlus对象的Brass部分传递给函数fv(). 但随引用和指针发生的隐式向上转换导致函数fr和fp分别为Brass对象和BrassPlus对象使用Brass::ViewAcct()和BrassPlus::ViewAcct(). 隐式向上强制转换使基类指针或引用可以指向基类对象或派生类对象, 因此需要动态联编, c++使用虚成员函数来满足这种需求

     

    编译器对非虚方法使用静态联编

    编译器对虚方法使用动态联编.

    由于静态联编的效率更高, 因此C++默认选择静态联编(如果基类没有使用虚方法, 或派生类不重新定义基类的任何方法).

    注意: 如果要在派生类中重新定义基类的方法, 则将其设置为虚方法, 否则应设置为非虚方法.

    虚函数的工作原理:

    编译器处理虚函数的方法是: 给每个对象添加一个隐藏成员. 隐藏成员中保存了一个指向函数地址数组的指针. 这种数组称为虚函数表(virtula function table). 虚函数表中存储了为类对象进行声明的虚函数的地址. 例如, 基类对象包含一个指针, 该指针指向基类中所有虚函数的地址表. 派生类对象将包含一个指向独立地址表的指针. 如果派生类提供了虚函数的新定义, 该虚函数表将保存新函数的地址; 如果派生类没有重新定义虚函数, 该虚函数表将保存函数原始版本的地址. 如果派生类定义了新的虚函数, 则该函数的地址也将被添加到虚函数表中. 注意无论勒种包含的虚函数是1个还是10个, 都只需要在对象中添加一个地址成员, 只是表的大小不同而已.

    使用虚函数时, 程序将查看存储在对象中的虚函数表地址, 然后转向相应的函数地址表.

    使用虚函数时, 在内存和执行速度方便有一定的成本:

    1.每个对象都将增大, 增大量为存储地址的空间;

    2.对于每个类, 编译器都创建一个虚函数地址表(数组);

    3.对于每个函数调用, 都需要执行一项额外的操作, 即到表中查找地址.

    有关虚函数注意事项:

    1.在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚的.

    2.如果使用指向对象的引用或指针来调用虚方法, 程序将使用对象类型定义的方法, 而不是用为引用或指针类型定义的方法. 这称为动态联编.

    3.如果定义的类被用作基类, 则应将那些需要在派生类中重新定义的方法声明为虚的.

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

    5.析构函数应当是虚函数, 除非类不用做基类.

    例如: Employee是基类, Singer是派生类, 并添加一个char* 成员, 该成员指向由new分配的内存. 当Singer对象过期时, 必须调用~Singer析构函数来释放内存.

    Employee * pe = new Singer;
    ...
    delete pe;

    如果使用默认的静态联编, delete语句将调用Employee()析构函数, 这将释放由Singer对象中的Employee部分指向的内存, 但不会释放新的类成员指向的内存. 但如果析构函数是虚的, 则上诉代码将先调用~Singer析构函数释放由Singer组件指向的内存, 然后调用~Employee()析构函数来释放由Employee组件指向的内存.

    提示: 通常给基类提供一个虚析构函数, 即使它并不需要析构函数.

    6.友元不能是虚函数.

    7.如果派生类没有重新定义函数, 将使用该函数的基类版本. 如果派生类位于派生链中, 则将使用最新的虚函数版本, 例外的情况是基类版本是隐藏的.

    8.重新定义将隐藏方法:

    假设创建了如下所示的代码:

    class Dwelling
    {
    public:
    	virtual void showperks(int a) const;
    	...
    };
    
    class Hovel : public Dwelling
    {
    public:
    	virtual void showperks() const;
    	...
    };

    这将导致如下的问题:

    Hovel trump;
    
    // 正确调用
    trump.showperks()
    
    // 不正确调用
    trump.showperks(5);

    派生类中新定义将showperks()定义为一个不接受任何参数的函数, 重新定义不会生成函数的两个重载版本, 而是隐藏了接收一个int参数的基类版本. 总之, 重新定义继承的方法并不是重载, 如果在派生类中重新定义函数, 将不是使用相同的函数特征列表覆盖基类声明, 而是隐藏同名的基类方法, 不管参数特列表标如何.

    这引出了两条经验规则:

    第一.如果重新定义继承的方法: 应确保与原来的原型完全相同, 但如果返回类型是基类引用或指针, 则可以修改为指向派生类的应用或指针, 这种特征被称为返回类型协变(covariance of return type), 因为允许返回类型随类类型的变化而变化:

    class Dwelling
    {
    public:
    	virtual Dwelling & build(int n);
    };
    
    class Hovel: public Dwelling
    {
    public:
    	virtual Hovel & build(int n);
    }

    注意, 这种例外只适用于返回值, 而不适用于参数.

    第二, 如果基类声明被重载了, 则应在派生类中重新定义所有的基类版本:

    class Dwelling
    {
    public:
    	// 三个重载的虚方法
    	virtual void showperks(int a) const;
    	virtual void showperks(double a) const;
    	virtual void showperks() const;
    };
    
    class Hovel : public Dwelling
    {
    public:
    	// 三个重新定义的showperks()
    	virtual void showperks(int a) const;
    	virtual void showperks(double a) const;
    	virtual void showperks() const;
    };

    如果只重新定义一个版本, 则另外两个版本将被隐藏, 派生类对象将无法使用它们. 注意, 如果不需要修改, 则新定义可只调用基类版本:

    void Hovel::showperks() const {Dwelling::showperks();}

     

    展开全文
  • 主要介绍了C++静态联编和动态联编详解,对于深入理解C++编译运行原理有很大帮助,需要的朋友可以参考下
  • C++静态联编和动态联编

    千次阅读 2016-09-07 16:00:53
    C++静态联编和动态联编标签(空格分隔): c++C静态联编和动态联编 指针引用类型的兼容性 虚拟成员和动态编联 为什么C中默认的编联方式是静态编联 动态编联如何工作 注意事项 友元 没有重定义 重新定义将隐藏...

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

    标签(空格分隔): c++



    在程序中如何确定应该调用哪一个函数呢?在C++中,将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。在编译过程中进行的联编被称作静态联编(static binding),又称为早期联编(early binding)。然而对于虚拟成员来说,编译就就无法确定具体应该调用哪一个方法了。于是C++引入了另一种联编机制——动态联编(dynamic binding),也叫作晚期联编(late binding)1

    指针和引用类型的兼容性


    在C++中,不允许一种类型的指针指向另一种类型。然而,指向基类的引用或者指针却可以引用派生类对象,而不必进行显示的类型转换。这种由基类指针指向派生类对象的转换方式被称作向上类型转换(upcasting),这使得公有继承不需要进行显示的类型转换。与之相反的过程——将基类指针或引用转换为派生类指针或者引用——被称作向下类型转换(downcasting)。如果不适用强制类型转换则向下类型转换是不被允许的。

    虚拟成员和动态编联


    如果在基类没有将成员声明为virtual,则方法调用时将根据指针类型选择。因为指针类型在编译时已知,因此编译器编译时会之间关联到非虚拟的成员方法。然而,如果基类将一个成员声明为virtual,则编译器就无法在编译时确定到底指针当前指向的是哪一个对象了。

    为什么C++中默认的编联方式是静态编联


    这里从两个方面考虑:
    首先考虑效率,为了使程序能够在运行时期进行决策,必须采取一些方法来跟踪基类指针或引用指向的对象类型,这增加了额外的处理开销。所以采取静态编联能够显著的提高效率。这也符合C++的设计原则:不为了不使用的特性付出代价。
    其次考虑概念模型,在类设计时,可能包含一些不在派生类中重新定义的成员函数。这样能够显著提升运行效率。

    动态编联如何工作


    通常,编译器处理虚拟函数的方法是:给每一个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数数组的指针。这种指针被称为虚函数表(virtual function table,vtbl)。虚拟函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有的虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数将保存虚函数的地址;如果派生类没有重新定义虚拟函数,该vtbl将保存函数原始版本的地址。

    注意事项


    友元

    友元函数不能是虚函数,只有成员才能是虚函数。

    没有重定义

    如果派生类没有重定义函数,将使用该函数的基类版本,如果位于派生链中,将使用最新的虚函数版本。

    重新定义将隐藏方法

    如果派生类重写了基类的方法并且修改了参数列表,则基类的方法将不可见。

    class Base{
    public:
        void print(int size);
    };
    class Imp : Base{
    public:
        void print();
    };

    这种不严谨的代码编写方式在一些要求严格的编译器上回造成编译失败。如果能够编译通过,则基类的带参数的show方法将会不可见。

    总结


    • 每个对象都将会增大存储空间的地址;
    • 对于每个类,编译器都创建一个虚函数地址表(数组);
    • 对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址;
    • 在基类中声明的virtual函数,在整个继承链中都是virtual类型的;
    • 如果重新定义继承的方法,应该确保函数定义与基类声明保持一致。但是,如果返回的是某一种基类的指针,则可以重写为派生类的指针。这种特性被称为返回类型协变(covariance of return type);
    • 如果基类声明被重载了,应该在派生类中重定义所有的基类版本。

    1. Stephen Prata.C++ Primer Plus 6th.人民邮电出版社 2016.3 501~507
    展开全文
  • c++ 静态联编和动态联编 首先我们得明白一个概念:什么是联编? 联编(binding)又称绑定,就是将模块或者函数合并在一起生成 可执行代码的处理过程,同时对每个模块或函数分配内存地址,并且对外部访问也分配正确...

    c++ 静态联编和动态联编

    首先我们得明白一个概念:什么是联编?

    联编(binding)又称绑定,就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或函数分配内存地址,并且对外部访问也分配正确的内存地址。
    在编译阶段就将函数实现和函数调用绑定起来称为静态联编(static binding)。静态联编在编译阶段就必级了解所有的出数模块执行所需要的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型。C语言中,所有的联编都是静态联编,C++中一般情况下联编也是静态联编。

    在程序运行的时候才进行函数实现和函数调用的绑定称为动态联编(dynamic binding) 。c++中进行动态联编就是使用了虚函数,然后用基类的指针指向派生类地址或者将派生类对象赋给基类的引用

    静态联编举例:

    #include <iostream>
    using namespace std;
    class Point { //Point类表示平面上的点
    double x,y; //坐标值
    public:
    Point(double x1=0, double y1=0) : x(x1),y(y1) { }//构造函数
    double area() return 0; }
    };
    class Circle: public Point { //Circle类表示圆
    double r;//半径
    public:
    Ccircle(double x.double v.double r1 ):Point(x.v).r(r1) 
    double area(){return 3.1415926*r*r}
    };
    
    int main( )
    { Point a(2.5,2.5); Circle c(2.5,2.51);//Point基类对象,Circle派生类对象
    cout<<"Point area="<<a.area()<<endl; //基类对象
    cout<<"Circle area="<<c.area( )<<endl;//派生类对象
    Point *pc=&c , &rc=c;//基类指针、引用指向或引用派生类对象
    cout<<"Circle area="<<pc->area( )<<endl;//静态联编基类调用
    cout<<"Circle area="<<rc.area( )<<endl;1//静态联编基类调用
    return 0;
    }
    

    结果:
    Point area=0
    Circle area=3.14
    Circle area=0
    circle area=0

    动态联编举例1-----类多态:

    #include <iostream>
    using namespace std;
    class Point { //Point类表示平面上的点
    double x,y; //坐标值
    public:
    Point(double x1=0, double y1=0) : x(x1),y(y1) { }//构造函数
    virtual double area() return 0; }//虚函数只能是类中的成员函数,不能是静态的,virtual必须在类体里面使用!
    };
    class Circle: public Point { //Circle类表示圆
    double r;//半径
    public:
    Ccircle(double x.double v.double r1 ):Point(x.v).r(r1) 
    double area(){return 3.1415926*r*r}
    };//继承同名的虚函数(参数个数,参数类型,返回类型一致),在派生类中自动变成virtual,所以可写virtual也可不写
    
    int main( )
    { Point a(2.5,2.5); Circle c(2.5,2.51);//Point基类对象,Circle派生类对象
    cout<<"Point area="<<a.area()<<endl; //基类对象
    cout<<"Circle area="<<c.area( )<<endl;//派生类对象
    Point *pc=&c , &rc=c;//基类指针、引用指向或引用派生类对象
    cout<<"Circle area="<<pc->area( )<<endl;//动态联编派生类调用
    Point *pb=new Circle;
    cout<<"Circle area="<<pb->area( )<<endl;//动态联编派生类调用
    delete pb;
    cout<<"Circle area="<<pc->Point::area( )<<endl;//强制使用静态联编
    cout<<"Circle area="<<rc.area( )<<endl;1//动态联编派生类调用
    return 0;
    }
    

    结果:
    Point area=0
    Circle area=3.14
    Circle area=3.14
    Circle area=0
    Circle area=3.14

    动态联编举例2-----类成员函数多态:

    #include <iostream>
    using namespace std;
    class Base {
    public: virtual void print() { cout<<"Base "<<endl; }//虚函数};
    };
    class Derived: public Base {
    public: void print() { cout<<"Derived" <<endl; }//虚函数};
    void display ( Base *p , void( Base::*pf)( )){ (p->*pf)(); }
    };//Base::*pf指向Base成员函数的指针
    int main()
    {
    Derived d;Base b;
    display(&d ,&Base::print); //输出Derived
    display (&b ,&Base::print);//输出Base
    return 0;
    }
    
    

    虚函数的调用规则是:根据当前对象,优先调用对象本身的虚成员函数。这和名字支配规律类似,不过虚函数是动态联编的,是在运行时(通过虚函数表中的函数地址)“间接"调用实际上欲联编的函数。

    展开全文
  • 先不论动态还是静态,首先需要明确的是关于“联编”的含义,书中有这样的说明: 将源代码中的函数调用解释为执行特定函数代码块被称为函数名联编(binding) 通俗些说,就是指明调用函数的语句调用的究竟是哪一个...
  • 按照联编所进行的阶段不同,可分为静态联编和动态联编。 例如A类中有fun这个函数, B类中也有fun这个函数,现在我在类外的main函数里面调用fun 函数。那么main函数就是函数调用,调用fun函数,而A...
  • 静态联编和动态联编

    2019-05-05 15:49:57
    静态联编和动态联编 1、联编是指一个程序模块、代码之间互相关联的过程。 2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。 重载函数使用静态联编。(没有加virtual) 3、...
  • 静态联编:在编译过程中进行联编被称为静态联编,又称早期联编 动态联编:使用虚函数时,程序使用那一个函数是不能在编译时确定的,因为编译器不知道用户选择的那种类型对象。 所以,编译器必须能够在程序运行时选择...
  • 静态联编和动态联编1、联编是指一个程序模块、代码之间互相关联的过程。 2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。 重载函数使用静态联编。 3、动态联编是指程序联编...
  • C++虚函数与静态动态联编个人理解
  • 静态联编:在编译时所进行的这种联编又称静态束定,在编译时就解决了程序中的操作调用与执行该操作代码间的关系。(范围很大,比静态多态大)。 动态联编:编译程序在编译阶段并不能确切知道将要调用的函数,只有在...
  • C++ 静态联编

    2018-08-15 22:12:24
    �� 静态联编:编联工作出现在编译连接阶 段,这种联编又称为早期联编,因为联编 过程是在程序开始之前完成的 �� 动态联编:编译程序在编译阶段并不能确 切知道将要调用的函数,只有在程序运行 时才能确定...
  • 静态联编和动态联编 、虚函数和动态联编 、虚函数的工作原理 、有关虚函数的注意事项 RTTI 运行时类型识别
  • 静态联编多态性(编译时的多态性)通过函数,运算符的重载实现的(系统根据形参的个数来实现编译的多态性) 动态联编多态性(运行时的多态性)通过继承,虚函数(当运行时才能实现对象与函数的联编)C++规定:动态联...
  • C++ 静态联编 VS 动态联编 在编译过程中进行联编被称为静态联编。 在运行过程中进行联编被称为动态联编,包含虚函数的需要进行动态联编,编译器需要在程序运行时选择正确的虚方法的代码。 ...
  • 按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。 1. 静态联编        静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行...
  • c++动态联编与静态联编

    万次阅读 多人点赞 2011-11-04 00:29:25
    摘要】:本文阐述了静态联编和动态联编的概念区别,通过具体实例分析了实现动态联编的条件,指出了虚函数是实现动态联编的基础。 【关键词】:静态联编动态联编;虚函数 在C++中,联编是指一个计算机程序的...
  • 程序调用函数时,将使用哪个可执行代码块呢? 编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。 在C语言中,这非常简单,...在编译过程中进行联编被称为静态联编...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,208
精华内容 4,083
关键字:

c++静态联编和动态联编

c++ 订阅