精华内容
下载资源
问答
  • 文章目录C++的动态联编虚函数前言指针和引用的兼容性静态联编动态联编动态联编的缺点虚函数工作原理虚函数注意项总结 前言 函数名联编(binding):将源代码中的函数调用解释为执行特定的函数代码块的过程。 ...

    C++的动态联编与虚函数



    前言

    函数名联编(binding):将源代码中的函数调用解释为执行特定的函数代码块的过程。

    静态联编:在编译过程中进行联编叫作静态联编。

    动态联编:程序运行时才选择需要执行的代码叫作动态联编。

    指针和引用的兼容性

    派生类引用或指针转换为基类引用或指针,称为向上强制转换,可隐式。示例如下:

    class Animal {  /* 基类 */
    };
    
    class Dog: public Animal {  /* 派生类 */
    };
    
    int main() {
        Animal* dogOne = new Dog();
        /* `new Dog()`返回派生类指针,但可以赋值给基类指针`dogOne` */
    
        Dog dogTwo;
        Animal& dogTwoRef = dogTwo;  /* 基类引用指向了派生类对象 */
        return 0;
    }
    

    上述代码中,new Dog()返回派生类指针,但可以赋值给基类指针dogOne;作为基类引用的dogTwoRef指向了派生类对象dogTwo。编译这段代码不会报错,这是因为C++允许这样,本质上是发生了向上强制转换,体现了指针和引用的兼容性。

    基类指针或引用转换为派生类指针或引用被称为向下强制转换,要求必须显式地转换。

    静态联编与动态联编

    设计一个类时,可以将成员函数设计为虚函数(virtual)和非虚函数,函数“虚不虚”,直接影响了编译器对代码的处理方式。这里有一个结论:编译器对非虚函数使用静态联编;对虚函数使用动态联编。

    通过一个简单示例,我们看看里边区别:

    class Animal {
    public:
        void run() { cout << "not implemented." << endl; }  /* 非虚函数 */
        virtual void fly() { cout << "not implemented." << endl; }  /* 虚函数 */
    };
    
    class Dog: public Animal {
    public:
        void run() { cout << "can run." << endl; }
        void fly() { cout << "can not fly." << endl; }
    };
    
    int main() {
        Animal* dog = new Dog();  /* 用基类指针指向派生类对象地址 */
        /* 注意run()与fly()的输出 */
        dog->run();
        dog->fly();
        return 0;
    }
    // 输出:
    not implemented.
    can not fly.
    

    由于还不知道作为派生类的具体动物(狗、猫、鱼、鸟等)会不会飞,会不会跑,所以让基类Animal中的run()fly() 直接打印“未完成”,希望后面的设计人员来设计。

    现在设计了一个狗(派生类Dog),它会跑但不会飞,如上述代码那样。最后用基类指针Animal*管理派生类对象new Dog(),调用run()和fly()。可以从输出结果中看到,dog->run()调用的是基类版的run(),所以输出 “not implemented.” ;而dog->fly()调用的是派生类版的fly,因此打印 “can not fly.”

    会出现这种结果,是因为基类Animal中的run()是非虚函数,编译器静态联编,此时它将根据定义类型寻找方法,定义类型是Animal*,所以找到了基类版的run()。而fly()是虚函数,编译器动态联编,当程序执行到调用语句,它会根据对象类型寻找方法,对象类型是Dog*(new Dog()的返回对象),所以找到了派生类的fly()。

    大多时候,为让代码具有多态特性,通过基类指针或引用 管理 派生类对象是常见手段。同时要让代码清晰明辨,一般会把派生类中重新定义的虚函数也标志为virtual,上述代码最好写成这样:

    class Dog: public Animal {
    public:
        void run() { cout << "can run." << endl; }
        virtaul void fly() { cout << "can not fly." << endl; }
    };
    

    动态联编的缺点

    尽管动态联编看上去优质,然而动态联编和静态联编同时存在C++中,设计是有讲究的,主要基于以下两点:

    • 效率:动态联编需要跟踪 基类指针或引用 指向的对象类型,这将增加额外的处理开销。C++的指导原则之一是:不要为不使用的特性付出代价。并非所有的基类方法都需要多态,所以如果全部采取动态联编一定会损耗性能。
    • 概念模式:(virtual)标记出需要重新定义的函数,让代码呈现更清晰的意图。

    虚函数工作原理

    虚函数的一种实现机制:

    • 编译器给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向虚函数表的指针;
    • 虚函数表中存储了 为类对象 进行声明的 虚函数的地址;
    • 如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;
    • 如果派生类定义了新的虚函数,该函数的地址也将添加到虚函数表中;
    • 调用虚函数时,程序会找到对象的虚函数表,然后查找相应的函数的地址,最后执行。

    执行虚函数带来的成本:

    • 每个对象都将增大,增大的是存储地址的空间;
    • 对于每个类,编译器都会创建一个虚函数地址表;
    • 对于每个函数调用,都需要执行额外操作,即到虚函数表中查询函数地址。

    虚函数注意项

    虚函数的好处显而易见,但有些地方仍要留心注意:

    • (1)基类中用virtual修饰方法,可使该方法在基类及其所有派生类中都是虚的;
    • (2)构造函数不能是虚函数。这是因为派生类不继承基类的构造函数,所以将构造函数声明为虚函数没有意义;
    • (3)友元不能是虚函数,因为友元不是类成员,只有类成员才能使虚函数;
    • (4)最好为每一个基类提供一个虚析构函数,即便它并不需要析构函数。如果析构函数非虚,此时基类指针管理派生类对象,对该指针做delete,只会执行基类的析构函数,而不会执行派生类的,可能造成内存泄露:
    class Animal {
    public:
        ~Animal() { cout << "~Animal() called" << endl; }
    };
    
    class Dog: public Animal {
    public:
        ~Dog() { cout << "~Dog() called" << endl; }
    };
    
    int main() {
        Animal* dog = new Dog();  /* 基类指针管理派生类对象 */
        delete dog;
        return 0;
    }
    // 输出:
    ~Animal() called  /* 不会执行 ~Dog() */
    
    • (5)如果派生类没有重新定义函数,使用该函数的基类版本;
    • (6)重新定义不会生成函数的两个重载版本,而是隐藏该方法的基类版本。比方基类中是void run() {...} 而派生类中是void run(bool isFast) {...}。对派生类对象来说,如果直接调用run()是会报错的,因为void run()已经被隐藏了,能被调用的是void run(bool isFast)

    由第6点引出的两条经验:

    • 如果重新定义继承的方法,应确保与原来的原型完全相同。但返回类型协变除外。即,如果存在某个虚方法需要返回数据的类型是类:
    class Animal {
    public:
        virtual Animal get_kind();
    };
    

    此时允许返回类型跟随类改变:

    class Dog: public Animal {
    public:
        virtual Dog get_kind();
    };
    
    • 如果基类声明被重载了,则应该在派生类中重新定义所的基类版本(否则派生类指针管理派生类对象时,无法使用被重载了的、其他版本的方法):
    class Animal {
    public:
    	/* 因为重载,而存在许多版本的greet() */
        virtual void greet();
        virtual void greet(string who);
        virtual void greet(int count);
        ...
    };
    

    比较好的方式是,如果其他版本的greet()没有改动,那么直接调用基类对应版本的greet():

    class Dog: public Animal {
    public:
        virtual void greet() { ... }
        virtual void greet(string who) { Animal::greet(who); }
        virtual void greet(int count) { Animal::greet(count); }
        ...
    };
    

    总结

    • 需要被重新定义的基类的方法,应该被声明为虚函数;
    • 虚函数根据对象类型找方法,非虚函数根据定义类型找方法;
    • 最好为每个类都声明一个虚析构函数。
    展开全文
  • 静态联编动态联编虚函数动态联编虚函数的工作原理 、有关虚函数的注意事项 RTTI 运行时类型识别

    静态联编和动态联编

    程序调用的函数时,将使用哪个可执行代码块?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编

    在C语言中,这非常简单,因为每个函数名都对应不同的函数。但在C++中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数和函数名才能确定使用哪个函数。 然而,C/C++编译器可以在编译过程完成这种联编。

    在编译过程中进行联编被称为静态联编(static binding),又称为静态绑定、早期联编。然而,虚函数使这项工作变得更加困难,使用哪一个函数是不能在编译时期确定的,因为编译器不知道用户将选择哪种类型的对象。所以,编译器必须能够在程序运行时选择正确的虚函数的代码,这被称为动态联编(dynamic binding),又称为动态绑定、晚期联编。<

    展开全文
  • 动态联编虚函数

    2019-05-30 17:44:03
    虚函数是实现动态联编的基础,它是一种动态的重载方式,它允许在运行时建立函数调用与函数体之间的关系。 虚函数是一个在基类中声明为virtual的函数,并在一个或多个派生类中被重新定义的成员函数,虚函...

    动态联编

    • 有时候我们并不希望某些环节在编译阶段完成,而需要它在程序运行时才可以确定将要调用的函数,称为动态联编。动态联编提高了编程的灵活性和程序的易维护性,但与静态联编相比,函数调用速度慢。

    • 虚函数是实现动态联编的基础,它是一种动态的重载方式,它允许在运行时建立函数调用与函数体之间的关系。

    • 虚函数是一个在基类中声明为virtual的函数,并在一个或多个派生类中被重新定义的成员函数,虚函数的声明格式如下:
      virtual <返回值类型> <函数名>(<参数表>);

    • 基类中声明为virtual的函数一般在派生类中需要重新定义,在重新定义时,参数的类型和个数必须相同,一旦一个函数被声明为虚函数,则无论声明它的类继承了多少层,在每一层派生类中该函数都继续保持虚函数的特性。

    • ps:虚函数与重载不同,虚函数的参数类型个数完全相同。

    #include <iostream>
    
    using namespace std;
    
    //动态联编-虚函数 
    class Shape{
    	int x;
    	int y;
    	public:
    		Shape(int px,int py):x(px),y(py)
    		{
    			cout<<"construct Shape"<<endl;
    		}
    		virtual float area() //基类中的area方法声明为虚函数
    		{
    			return 0;
    		}
    };
    
    class Rectangle:public Shape{
    	int w;
    	int h;
    	public:
    		Rectangle(int px,int py,int pw,int ph):Shape(px,py),w(pw),h(ph)
    		{
    			cout<<"construct Rectangle"<<endl;
    		}
    		float area()
    		{
    			return w*h;
    		}
    };
    
    class Circle:public Shape{
    	int r;
    	public:
    		Circle(int px,int py,int pr):Shape(px,py),r(pr)
    		{
    			cout<<"construct Circle"<<endl;
    		}
    		float area()
    		{
    			return 3.14*r*r;
    		}
    };
    int main()
    {
    	Rectangle a(30,40,4,8);
    	Circle b(30,40,4);
    	Shape *p=&a;
    	cout<<a.area()<<endl;
    	cout<<p->area()<<endl;
    	p=&b;
    	cout<<b.area()<<endl;
    	cout<<p->area()<<endl;
    	return 0;
    }
    

    运行结果:
    /

    展开全文
  • 一、静态联编 定义:由于函数重载,编译器必须查看函数参数以及函数名就能确定使用哪个函数;这种C/C++编译器可以在编译过程中完成...1、虚函数 定义:定义非常简单,只需要在成员函数原型前加一个关键字virtual即可 特

    一、静态联编
    定义:由于函数重载,编译器必须查看函数参数以及函数名就能确定使用哪个函数;这种C/C++编译器可以在编译过程中完成的联编,被称为静态联编
    函数重载:在同一作用域中,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数

    二、动态联编
    定义:使用哪个函数是不能在编译时确定的,因为编译器不知道用户将要选择哪种类型的对象。所以,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这就是动态联编。
    1、虚函数
    定义:定义非常简单,只需要在成员函数原型前加一个关键字virtual即可
    特点:如果一个基类的成员函数定义为虚函数,那么它在派生类中也保持为虚函数;即使在派生类中省略了virtual关键字,也仍然是虚函数。
    重定义的格式要求:
    ①与基类的虚函数有相同的参数个数
    ②与基类的虚函数有相同的参数类型
    ③与基类的虚函数有相同的返回类型

    2、虚函数的访问
    (1)对象名调用虚函数:和普通函数一样,虚函数一样可以通过对象名来调用,此时编译器采用的是静态联编。
    通过对象名访问虚函数时,调用哪个类的函数取决于定义对象名的类型。
    对象类型是基类时,就调用基类的函数;对象类型是子类时,就调用子类的函数
    (2)指针访问:
    ①:使用指针访问非虚函数时,编译器根据指针本身的类型决定要调用哪个函数,而不是根据指针指向的对象类型
    ②:使用指针访问虚函数时,编译器根据指针所指对象的类型决定要调用哪个函数(动态联编),而与指针本身的类型无关
    (3)引用访问:与指针类似;不同的是,引用一经声明后,引用变量本身无论如何改变,其调用的函数就不会再改变,始终指向其开始定义时的函数。
    3、触发动态绑定的条件
    (1)只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定
    (2)必须通过基类类型的引用或者指针进行函数调用
    4、虚函数的工作原理
    编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。
    这种数组称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。
    无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员(表的地址),只是表的大小不同而已
    虚函数机制:
    在这里插入图片描述
    5、有关虚函数的注意事项
    (1)构造函数:构造函数不能是虚函数
    根据继承的性质,构造函数执行的顺序是:基类的构造函数->派生类的构造函数
    但是如果基类的构造函数是虚函数,且派生类中也出了构造函数,
    那么当下应该会只执行派生类的构造函数,不执行基类的构造函数,那么基类的构造函数就不能构造了
    (2)析构函数:析构函数应当是虚函数,除非类不用做基函数。比如:假设Employee是基类,Singer是派生类,并添加一个char *成员,该成员指向由new分配的内存,然后,调用~Singer()析构函数来释放内存。
    代码如下:

    Employee * pe = new Singer;//true  Employee是Singer的基类
    delete pe;//~Employee()or ~Singer()?
    

    如果使用默认的静态联编,delete语句将会调用~Employee()析构函数。
    这将释放Singer对象中的Employee部分指向的内存,但不会释放新的成员指向的内存。
    但是如果析构函数是虚的,则上述代码将先调用Singer析构函数释放由Singer组件指向的内存,然后,调用Employee()析构函数来释放由Employee组件指向的内存。
    最后,给类的析构函数定义析构函数没有错,即使这个类不做基类
    (3)友员函数:友元函数不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数
    一个简单例子:

    #include<iostream>
    using namespace std;
    class Base {
    public:
    	virtual void disp() {
    		cout << "hello,base1" <<endl;
    	}
    	void disp2() {
    		cout << "hello,base2" << endl;
    	}
    };
    class Child1 :public Base {
    public:
    	void disp() {
    		cout << "hello,child1" << endl;
    	}
    	void disp2() {
    		cout << "hello,child2" << endl;
    	}
    };
    void main(){
    	Child1 child1;
    	Base * base = &child1;
    	base->disp();
    	base->disp2();
    	system("pause");
    }
    

    输出:hello,child1
    hello,base2

    展开全文
  • 在程序运行时选择正确的虚函数代码的方法称为动态联编,又称为晚期联编。编译器对虚方法使用动态联编。 使用虚函数的缺点 1 每个对象都将增大,在内存和执行速度方面有一定的成本。 2 对于每个类
  • 静态联编动态联编 程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问 题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数联编 (binding)。在C语言中,这非常简单,因为每个函数...
  • 动态联编:在程序运行时 将函数的实现与函数调用绑定起来 例如: #include<iostream> using namespace std; class base{ public:print(){cout<<"this is base's print"<<endl; } }; //派生类...
  • 在C++中,联编是指一个计算机程序的不同部分彼此关联的过程...要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静
  • 多态性的概念 参考mooc魏英老师《c++程序设计》 虚函数是多态性的精华 联编与静态联编 ...动态联编 这样就实现了同一个指针指向不同的对象调用的成员函数是所指对象的成员函数。 ...
  • //动态联编虚函数的简单应用#include &lt;iostream&gt;using namespace std;class Base{public:#if 1 //if 1则fun()为虚函数virtual #endifvoid fun()//成员函数 { cout&lt;&lt;"in base ...
  • 前面我写了几篇关于继承的博文,分别为: c++继承详解之一——继承的三种方式、派生类的对象模型 C++继承详解之二——派生...这几篇博文只涉及到了继承的知识,没有加入虚函数没有涉及到多态的知识,从这篇开始我会更
  • 文章目录联编静态联编static binding or 早期联编early binding动态联编dynamic binding or 晚期联编late binding用对象的指针和引用去调用方法(成员方法)先复习C++对类型一致性的严格要求(类型转换)向上强制...
  • 文章目录静态联编动态联编静态联编的多态——根据指针类型确定执行方法静态联编的多态——泛型编程 模版动态联编的多态——虚函数参考链接 多态(Polymorphism)按字面的意思就是“多种状态”。  在面向对象语言...
  • C++联编虚函数

    2019-04-15 00:20:58
    C++联编虚函数表 1.联编 联编:将源代码中的函数调用解释为执行特定的函数代码块叫函数...程序运行期间,决定调用哪个函数,虚函数采用动态联编 2.虚函数机制实现——虚函数表 I>虚函数表的内容 虚函数表...
  • 在C++中,联编是指一个计算机程序的不同部分彼此关联的...要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联
  • 通常,编译器处理虚函数的方法是:给每一个对象添加一个隐藏成员。隐藏成员中保存了一个只想函数地址数组的指针。这种数组成为虚函数表(vtable)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如基类对象...
  • 构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。 原因分析: (1)不要在构造函数中调用虚函数的原因:因为父类...
  • C++虚函数动态联编

    千次阅读 2013-04-30 21:58:56
    调用是相对于实调用而言的,它的本质是动态联编(后面我们会讲到)。 实调用:在发生函数调用的时候,如果函数的地址是在编译阶段确定的,就是实调用。反之,函数的入口地址要在运行时通过查 询虚函数表的方式获得...
  • C++笔记——虚函数动态联编 动态联编?静态联编? 程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。在C语言中...
  • 动态联编虚函数,重写,纯虚函数,C++对象模型,继承,虚表,
  • 编译器必须能够在程序运行时选择正确的虚函数的代码,这被称为动态联编 虚函数动态联编 虚函数:主要是实现了多态的机制 多态:用基类型别的指针指向其派生类的实例,然后通过基类的指针调用实际派生类的成员...
  • C++ 虚函数、静态联编动态联编

    千次阅读 2016-01-05 17:12:04
    本文将对c++虚函数做个总结。 虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数! 若在子类中重写成员函数,将不是使用相同的函数特征标覆盖基类声明,...
  • 本笔记总结了C++11的多态性、静态联编动态联编的区分方法、虚函数的用法、覆写与final操作。 也可以直接看第6大点总结,较为简洁和明了。
  • 联编的定义:将源码中将函数调用解释为特定的函数代码块称为函数联编 ...动态联编:编译器必须生成能够在程序运行时选择正确的方法的代码,这种联编称为动态联编。又叫晚期联编。 我给大...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,934
精华内容 3,973
关键字:

动态联编调用虚函数