精华内容
下载资源
问答
  • 静态联编
    2022-05-05 08:41:57

    一、什么是联编

    • 联编:将源代码中的函数调用解释为执行特定的函数代码块被称为函数名的联编(binding)。
    • 静态联编: 在编译过程中进行联编被称为静态联编,又称为早期联编。
    • 动态联编:编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编,又称为晚期联编。

    二、为什么有两种类型的联编以及为什么默认为静态联编?

    • 效率
      动态联编效率低。因为要跟踪基类指针或引用指向的对象类型,增加了额外开销。
      如果类不需要作为基类,则不需要动态联编。
      如果派生类不需要重新定义类的任何方法,不需要动态联编。

      - 概念
      在设计类时,可能包含一些不在派生类重新定义的成员函数。不将这些函数设置为虚函数,有两方面的好处:首先效率更高;其次指出不需要重新定义该函数。

    三、虚函数工作原理

    • 虚函数表:虚函数表是一个存储了类中虚函数地址的数组,虚函数表是在编译阶段生成的,每个类有一个虚函数表。

    通常编译器处理虚函数的方法是:

    • 给每个对象添加一个隐藏成员,这个隐藏成员指向虚函数表,被称为虚表指针,虚表指针是在运行时(创建对象时)生成的。
    • 基类对象的虚表指针指向基类的虚函数表,派生类对象的虚表指针,指向派生类的虚表。
    • 基类的虚表和派生类的虚表是两个独立的虚表。构建派生类的虚表时,如果派生类提供了虚函数的新定义,则虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,则保存的是基类中的该虚函数的地址;如果派生类定义了新的虚函数,则该函数的地址被添加到虚表中。

    四、虚函数注意事项

    • 构造函数不能为虚函数。因为:1)创建派生类时要调用派生类的构造函数,而不是基类的构造函数,这和虚函数实现多态的目标不符。2)不同于继承基值,派生类不会继承基类的构造函数,而是调用派生类的构造函数需要先调用基类的构造函数。
    更多相关内容
  • 本文阐述了静态联编和动态联编的概念和区别,通过具体实例分析了实现动态联编的条件,指出了虚函数是实现动态联编的基础。
  • 静态联编是指在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定,在编译阶段就必须了解所有的函数或模块执行所需要检测的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型,C语言中,...
  • 动态联编和静态联编

    千次阅读 2021-10-23 10:08:16
    一、动态联编和静态联编的基本概念 1.基本概念 将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。在C语言中这个步骤更简单,因为C语言的函数名不允许重复,即没有函数重载这一说,每个函数名都对应...

    一、动态联编和静态联编的基本概念

    将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。在C语言中这个步骤更简单,因为C语言的函数名不允许重复,即没有函数重载这一说,每个函数名都对应着一个函数代码块。但是在C++中要复杂,因为C++中允许函数重载,必须得根据函数名和形参列表来对应一个函数代码块。C/C++编译器在编译过程就完成了这种联编。在编译过程中进行联编被称为静态联编早期联编)。
    但是虚函数的产生使得静态联编变的困难,因为父类的虚函数允许被子类重写。当我们用一个父类指针指向一个子类对象的时候,编译器编译阶段可以知道父类指针的类型,但是它不能够直接用父类指针的类型中的虚函数作为本次调用的函数代码块。因为可能子类对虚函数进行重写了,这种情况下用户明显是想要调用重写后的函数。那么可能要说了,那编译器通过对象类型调用该类型中的重写后的函数不就可以实现编译阶段早期绑定了?然而,通常情况下,只有在运行时才能确定对象的模型。对于虚函数,编译器要通过动态联编晚期联编)的方式确定对应的函数代码块。即在运行时,通过对象类型确定调用的虚函数的函数代码块。重写了,就调用在对象类型中重写的函数对应的函数代码块,没有重写,那么就调用父类虚函数对应的函数代码块。
    总结:
    对于普通成员函数,如果通过对象调用普通成员函数,编译器直接调用该对象类型中的该函数对应的函数代码块;如果通过指针调用普通成员函数,那么编译器会直接根据指针类型调用该类型中的该函数对应的函数代码块。这些都是在编译阶段确定的,都是静态联编
    对于虚函数,如果通过对象调用虚函数,编译器会直接通过对象的类型如果是通过指针或者引用的方式调用虚函数,那么编译器将无法确定该指针类型中的虚函数对应的代码块是否是用户想要调用的。因为如果是父类指针指向子类对象的话,当子类没有对父类虚函数重写,我们意思肯定是调用父类的虚函数,如果重写了,我们意思肯定是调用子类重写后的函数,这个时候编译器不能直接说因为是父类指针,就直接去调用父类中的虚函数对应的代码块。所以有了虚函数指针和虚函数表的概念,通过运行时查虚函数表的方式,确定要调用的函数的代码块的地址,因为如果子类没有重写这个虚函数的话,虚函数表中放的是父类的虚函数,如果子类对父类的虚函数重写了,那么重写后的函数的地址会覆盖掉父类虚函数的地址,调用的就是重写后的函数了。(对于虚函数指针和虚函数表,请点击此处

    有一种说法,说对于调用普通成员函数来说,都是静态联编,这个没问题。但是说对于虚函数来说,如果是通过对象调用虚函数,不会经过查虚函数表,是静态联编;如果是通过指针调用虚函数,就会经过查虚函数表,是动态联编

    二、指针和引用类型的兼容性

    父类指针可以指向子类对象。指向子类对象的父类指针的使用请点击此处。将子类对象的类型转化为父类对象,为向上强制转换,编译器可以直接隐式转换,而父类对象的类型转换为子类对象的类型,为向下强制转换,必须显示转换。
    隐式向上转换是基类指针或者基类引用可以指向基类对象或派生类对象,因此需要动态联编。

    class A 
    {
    public:
    	virtual void fun() { cout << "A fun()" << endl; }
    };
    
    class B :public A
    {
    public:
    	void fun() { cout << "B fun()" << endl; }
    };
    
    void test1(A* a)
    {
    	a->fun();
    }
    
    void test2(A& a)
    {
    	a.fun();
    }
    
    void test3(A a)
    {
    	a.fun();
    }
    
    int main()
    {
    	B b;
    	test1(&b);
    	test2(b);
    	test3(b);
    	cout << "/***********************************/" << endl;
    	A a;
    	test1(&a);
    	test2(a);
    	test3(a);
    	
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述
    对于test1和test2很好理解。参考指向子类对象的父类指针的使用
    对于test3而言,形参是值拷贝的临时对象,这个值拷贝不针对虚函数指针,也就是说用哪个类创建的对象,这个对象的虚函数指针就指向哪个类的虚函数表,所以对于test3不论是传A的对象还是B的对象,利用拷贝构造函数创建出来的形参是临时对象,且对象隐藏的虚函数指针指向的都是A类的虚函数表。所以调用的都是A类的虚函数中的fun函数,而不是B类的虚函数表中被重写的fun函数。

    三、静态联编和动态联编的效率问题

    既然动态联编这么好用,为什么还要存在静态联编呢?
    (1)效率方面
    静态联编是在编译期间就执行好的,而动态联编是运行期间才开始。不仅如此,动态联编还需要通过查虚函数表,找到虚函数地址,再去这个地址里找虚函数。而且动态联编需要生成虚函数指针(存在对象中),还需要生成虚函数表。所以说动态联编的步骤比静态联编的步骤复杂,而且还需要生成虚函数指针虚函数表。时间和空间都比静态联编消耗的多。
    (2)概念模型方面
    虚函数一般是为了子类涉设计的,预期子类需要重写这个函数,所以将这个函数写成虚函数。然而,有些函数并不需要被子类重新定义,那么父类就没必要将这个函数写成虚函数。效率提高了,还告诉子类我没有把这个设置成虚函数,就是为了告诉你不要重新定义这个函数。

    四、有关虚函数的注意事项

    1.编译器不允许将构造函数设置成虚函数

    构造函数不能是虚函数。因为如果构造函数放到虚函数表中,那么子类创建对象会调用父类的构造函数,然而事实上是子类先调用父类的构造函数,再用自己的构造函数。所以这个不符合继承构造函数调用的逻辑。

    2.有继承关系时,析构函数尽量设置成虚函数

    有继承关系时,特别是如果子类有指针成员变量,我们需要在子类的析构函数中判断指针是否指向堆空间,是的话,我们需要在子类的析构函数中对堆空间进行释放。那么如果不将父类析构函数设置成虚析构函数的话,那么如果用父类指针指向子类对象,父类指针将无法调用子类的析构函数,无法将申请的堆空间释放。(对于为什么父类析构函数写成虚函数,指向子类对象的父类指针就可以调用子类的析构函数,而不把父类析构函数写成虚函数,指向子类对象的父类指针不可以调用子类的析构函数,请点击此处查看指向父类对象的父类指针的使用)
    所以有继承关系时,尽量将析构函数写成虚函数,哪怕这个类不需要用析构函数做什么。

    3.友元不能是虚函数

    虚函数必须是对类的成员函数而言的,不可以将友元函数设置成虚函数。

    4.重新定义问题

    如果派生类重新定义父类的成员函数,那么父类的所用同名的成员函数都被隐藏。包括虚函数。如下代码:

    class A 
    {
    public:
    	virtual void fun(int a) {}
    	void fun() {}
    };
    
    class B :public A
    {
    public:
    	void fun() {}
    };
    
    int main()
    {
    	B b;
    
    	//b.fun(10);//error
    	b.fun();//调用的是子类的成员函数
    	return 0;
    }
    
    

    子类写了一个fun函数,父类的两个fun函数读背隐藏了(注意只要是函数同名,就被隐藏,和函数重载无关)。
    如果再调用父类的fun函数,需要显示调用。
    如下:
    b.A::fun();
    b.A::fun(10);
    如果重新定义继承的方法,应该确保与原来的原型完全相同(函数名,形参列表)。当然,可以将函数返回类型修改。
    (1)只要子类的函数名和形参列表与父类的虚函数相同,编译器就会认为这个是对父函数的虚函数的重写,这个时候如果返回类型和父类不一样,编译器会报错。(除去唯一一个例外:返回类型可以协变,比如父类虚函数返回类型是父类指针(引用)形式,子类重写父类的虚函数,允许将函数的返回类型改为子类指针(引用)形式)。
    (2)如果子类重写父类的虚函数,那么子类的其他重名函数(不论是不是虚函数)也会被隐藏。
    总的来说,只要父类的函数名和子类的函数名相同,都会被隐藏。
    如下示例:

    class A 
    {
    public:
    	virtual void fun() { cout << "A fun()" << endl; }
    	void fun(int a) { cout << "A fun(int)" << endl; }
    };
    
    class B :public A 
    {
    public:
    	int fun(int a, int b) { cout << "B fun(int)" << endl; return 0; }
    	
    	//下面语句错误,由于函数名和形参列表与父类的虚函数一样,
    	// 编译器会认为子类是对父类的虚函数重写
    	// 但是由于返回类型不同,编译器会报错:重写后的函数的返回类型与被重写的虚函数的返回类型不同,而且也不是协变
    	//int fun() {}
    };
    
    int main()
    {
    	B b;
    	b.fun(10,20);
    	//以下两种语句错误
    	// 本来想使用父类的fun函数,但是由于子类有同名函数,父类的所有同名函数都被隐藏,所以找不到,编译器报错
    	//b.fun();//想要调用父类的fun()函数
    	// b.fun(10);想要调用父类的fun(int)函数
    	//调用父类被隐藏的函数,需要告诉编译器这些函数在哪里,如下
    	b.A::fun();    //告诉编译器是调用A类的被隐藏fun()函数
    	b.A::fun(10);//告诉编译器是调用A类的被隐藏的fun(int)函数
    
    	A* aptr = &b; //父类指针指向子类对象,调用的函数都是父类的(还有子类对父类的虚函数重写的函数)
    	aptr->fun();    //A类的fun()
    	aptr->fun(10);//A类的fun(int)
    	return 0;
    }
    
    展开全文
  • C++静态联编和动态联编 一、简介 程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数 代码块被称为函数名联编。 在C语言中,这非常简单,因为每个函数名...

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

    一、简介

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

    在C语言中,这非常简单,因为每个函数名都对应一个不同的函数。在C++中,由于函数重载的缘故,这项任务更复杂。

    编译器必须查看函数参数及函数名才能确定使用哪个函数。然而,C/C++编译器可以在编译过程完成这种联编。

    静态联编: 在编译过程中进行联编被称为静态联编,也称为早期联编。

    动态联编: 虚函数的使用使函数名联编这项工作变的困难。使用哪一个函数是不能再编译时确定的,因为编译器不知道用户将选择哪种类型的对象。所以,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编,也称为晚期联编

    二、指针与引用类型的兼容性

    2.1 通常使用方式

    在C++中,动态联编与通过指针和引用调用方法相关,从某种程度上说,这是由继承控制的。公有继承建立is-a关系的一种方法是如何处理指向对象的指针和引用。通常,C++不允许将一种类型的地址赋给另一种类型的指针,也不允许一种类型的引用指向另一种类型。举例如下:

    double x = 3.2;
    int *pi = &x;		// 无效赋值,不匹配的指针类型
    long &rl = x;		// 无效赋值,不匹配引用类型
    

    2.2 继承使用方式

    指向基类的引用或指针可以引用派生类对象,而不必进行显示类型转换。举例如下:

    BrassPlus tom("Test", 1234, 1234.56);
    Brass *pb = &tom;		// 正确使用
    Brass &rb = tom;		// 正确使用
    

    2.3 继承类型转换

    向上强制转换: 将派生类引用或指针转换为基类引用或指针被称为向上强制转换,这种公有继承不需要进行显示类型转换,该规则是is-a关系的一部分。

    向下强制转换: 将基类指针引用或指针转换为派生类引用或指针,被称为向下强制转换,如果不使用显示类型转换,则向下强制转换是不允许的,原因是is-a关系通常是不可逆的。

    2.4 向上类型转换说明

    对于使用基类引用或指针作为参数的函数调用,将进行向上强制转换。举例如下:

    // 假设示例中的每个函数的代码块都调用参数的ViewAcct()函数
    void fr(Brass &rb);		// 调用rb.ViewAcct()
    void fp(Brass *pb);		// 调用pb->ViewAcct()
    void fv(Brass b);		// 调用b.ViewAcct()
    
    int main()
    {
        Brass b("Test1", 1234, 1234.56);
        BrassPlus bp("Test2", 5678, 5678.89);
        
        fr(b);				// 调用Brass::ViewAcct()
        fr(bp);				// 调用BrassPlus::ViewAcct()
        
        fp(&b);				// 调用Brass::ViewAcct()
        fp(&bp);			// 调用BrassPlus::ViewAcct()
        
        fv(b);				// 调用Brass::ViewAcct()
        fv(bp);				// 调用Brass::ViewAcct()
    }
    

    上述示例代码详解:

    • 按值传递导致只将BrassPlus对象的Brass部分传递给fv()函数。

    • 引用和指针发生的隐式向上转换导致fr()函数和fp()函数,分别为Brass对象和BrassPlus对象使用Brass::ViewAcct()和BrassPlus::ViewAcct()。

    • 隐式向上强制转换使基类指针或引用可以指向基类对象或派生类对象,因此需要动态联编。C++使用虚成员函数来满足这种需求

    三、虚成员函数和动态联编

    根据如下代码示例,回顾使用引用或指针调用方法的过程:

    Brass *b;
    BrassPlus bp;
    b = &bp;
    b->ViewAcct();
    

    如果在基类中没有将ViewAcct()函数声明为虚函数,则b->ViewAcct()将根据指针类型(Brass*)调Brass::ViewAcct()。

    指针类型在编译时已知,因此编译器在编译时,可以将ViewAcct()关联到Brass::ViewAcct()。总之,编译器对非虚方法使用静态联编

    如果在基类中将ViewAcct()函数声明为虚函数,则b->ViewAcct()根据对象类型(BrassPlus)调用BrassPlus::ViewAcct()。

    对象类型只有在运行程序时才能确定。所以编译器生成的代码将在程序执行时,根据对象类型将ViewAcct()关联到Brass::ViewAcct()或BrassPlus::ViewAcct()。总之,编译器对虚方法使用动态联编

    3.1 为什么有两种类型的联编

    如果动态联编能够让开发人员能够重新定义方法,而静态联编在这方面很差,为什么不摒弃静态联编呢?

    解:原因有两个,效率和概念模型

    效率: 为使程序能够在运行阶段进行决策,必须采取一些方法来跟踪基类指针或引用指向的对象类型,这增加了额外开销。例如,如果类不用作基类,则不需要动态联编。如果派生类不重新定义基类的任何方法,也不需要使用动态联编。

    概念模型: 在设计类时,可能包含一些不在派生类重新定义的成员函数。不将函数设置为虚函数,有两方面的好处:首先效率更高;其次,指出不要重新定义该函数。仅将那些预期将被重新定义的方法声明为虚方法。

    注: 如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置为非虚方法。

    3.2 为什么默认为静态联编

    基类方法不声明为虚方法,或派生类不重新定义基类任何方法,则使用静态联编更合理,效率也更高。由于静态联编的效率更高,因此被设置为C++的默认选择。

    3.3 虚函数的工作原理

    编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表

    虚函数表: 虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。具体如下:

    • 如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;
    • 如果派生类没有重新定义虚函数,该虚函数表将保存函数原始版本的地址。
    • 如果派生类定义了新的虚函数,则该函数的地址也将被添加到虚函数表。
    • 无论类中包含的虚函数是1个或者多个,都只需要在类对象添加1个地址成员,只是表的大小不同而已。
    3.3.1 虚函数调用过程

    调用虚函数时,程序将查看存储在对象中的隐藏成员保存的虚函数表地址,然后转向相应的虚函数地址表。具体调用过程说明如下:

    • 如果使用类声明中定义的第一个虚函数,则程序将使用虚函数表中的第一个函数地址
    • 如果使用类声明中定义的第三个虚函数,则程序将使用地址为虚函数表中第三个元素的函数

    3.4 使用虚函数成本

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

    • 每个对象都将增大,增大量为存储地址的空间
    • 对于每个类,编译器都将创建一个虚函数地址表(数组)
    • 对于每个函数调用,都需要执行一项额外的操作,即到虚函数表中查找地址

    虽然非虚函数的效率比虚函数稍高,但不具备动态联编功能。

    四、虚函数注意事项

    虚函数的一些要点如下:

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

    (2)如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不是使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。

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

    4.1 构造函数

    构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类构造函数,所以将类构造函数声明为虚的没什么意义。

    4.2 析构函数

    析构函数应当是虚函数,除非类不用作基类。例如,假设Employee是基类,Singer是派生类,并添加一个char*成员,该成员指向由new分配的内存。当Singer对象过期时,必须调用~Singer()析构函数来释放内存。示例代码如下:

    Employee *pe = new Singer;
    delete pe;		// 使用~Employee()或~Singer()
    

    如果使用默认的静态联编(析构函数为非虚函数),delete语句将调用~Employee()析构函数。这将释放由Singer对象中的Employee部分指向的内存(应为delete pe;调用的是Employee类的析构函数),但不会释放新的类成员指向的内存。

    如果析构函数是虚函数,则delete pe;将先调用Singer()析构函数释放由Singer指向的内存,然后调用Employee()析构函数来释放由Employee组件指向的内存。

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

    4.3 友元函数

    友元函数不能是虚函数,因为友元函数不是类成员函数,而只有成员函数才能使虚函数。如果由于这个原因引起设计问题,可以通过让友元函数使用虚成员函数来解决。

    4.4 没有重新定义

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

    4.5 重新定义将隐藏方法

    假设有如下示例代码:

    // 基类
    class Base
    {
    public:
    	virtual void showperks(int a) const;    
    };
    
    // 派生类
    class Derived : public Base
    {
    public:
        virtual void showperks() const;
    };
    
    // 调用
    Derived der;
    der.showperks();		// 有效的调用
    der.showperks(5);		// 无效的调用
    

    新定义将showperks()定义为一个不接受任何参数的函数。重新定义不会生成函数的两个重载版本,而是隐藏了接受一个int参数的基类版本。总之,重新定义继承的方法并不是重载。

    如果在派生类中重新定义函数,将不只是使用参数列表相同的函数覆盖基类声明,无论参数列表是否相同,该操作将隐藏所有的同名基类方法

    这引出两条经验规则:

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

    // 基类
    class Base
    {
    public:
        virtual Base& build(int a);
    };
    
    // 派生类
    class Derived : public Base
    {
    public:
        // 返回类型协变的派生类方法
        virtual Derived& build(int a);
    };
    

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

    (2)如果基类中声明的函数被重载了,则应在派生类中重新定义所有的基类重载函数。示例代码如下:

    // 基类
    class Base
    {
    public:
        // 3个showperks()重载函数
        virtual void showperks(int a) const;
        virtual void showperks(double b) const;
        virtual void showperks() const;
    };
    
    // 派生类
    class Derived : public Base
    {
    public:
        // 3个重新定义的showperks()重载函数
        virtual void showperks(int a) const;
        virtual void showperks(double b) const;
        virtual void showperks() const;
    };
    

    注: 如果只重新定义一个版本,则另外两个版本将被隐藏,派生类对象将无法使用它们。如果重新定义的基类中重载的某个函数不需要对函数行为进行修改,则派生类新定义的函数可只调用被新定义的基类函数,示例代码如下:

    void Derived::showperks() const
    {
        // 不更改showperks()行为,只需调用基类的相同版本函数即可
        Base::showperks();
    }
    
    展开全文
  • 静态联编

    2019-05-30 16:55:52
    静态联编 静态联编支持的多态性,我们将其称为编译时的多态性,又称为静态的多态性。 静态联编是在程序开始执行之前进行的。即在编译,连接的过程中,系统可以根据类型匹配等特征确定在什么地方调用什么函数。 ...

    静态联编

    1. 静态联编支持的多态性,我们将其称为编译时的多态性,又称为静态的多态性。
    2. 静态联编是在程序开始执行之前进行的。即在编译,连接的过程中,系统可以根据类型匹配等特征确定在什么地方调用什么函数。
    3. 那么可见,编译时的多态性可以通过函数重载来实现。函数重载的意义在于它可以用同一个名字访问一组相关的函数,能使用户为某一类操作取一个通用的名字。编译程序在编译时决定选择具体的函数段执行,这种方式也利于解决程序的复杂性。静态联编函数调用速度快,效率高,但是编程不灵活。
    4. C++中通过两种工作方式实现编译的多态性:函数重载和运算符重载。它们都属于静态联编。
    5. 分析该程序的运行结果可知,由于程序中完成的是静态联编,在程序编译阶段,基类指针p对于area方法的调用只能绑定到基类的area方法上,虽然程序运行时p指向了不同的派生类对象,但绑定已在编译阶段完成,所以无论运行时p指向什么类型的对象,始终调用的都是基类中的area方法。有时候这种联编方式输出的结果可能并不是我们期望的结果。
    6. 关于类型兼容原则,在实际应用时应注意以下几个方面:
      (1).基类的指针可以指向它的公有派生类对象,但是不允许指向它的私有或保护派生类的对象。
      (2).派生类的指针不允许指向它基类的对象。
      (3).基类的指针指向它的公有派生类对象时,只能用它来直接访问派生类中从基类继承的成员,而不能直接访问公有派生类中定义的新成员。
    #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;
    		}
    		float 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++中,由于函数重载的缘故,这项任务变得十分复杂。...
  • 动态联编和静态联编、以及多态

    千次阅读 2022-03-12 14:42:04
    动态联编和静态联编 动态联编和静态联编的存在是为了支持C++的多态性。 静态联编:编译器在编译期就把对象与其在申明时采用的类型绑定起来,就确定函数的调用地址 class A { } class B:public A { } class D:...
  • 动态联编与静态联编的区别

    千次阅读 多人点赞 2019-03-18 14:40:57
    摘要】:本文阐述了静态联编和动态联编的概念和区别,通过具体实例分析了实现动态联编的条件,指出了虚函数是实现动态联编的基础。 【关键词】:静态联编;动态联编;虚函数 在C++中,联编是指一个计算机程序的...
  • 先不论动态还是静态,首先需要明确的是关于“联编”的含义,书中有这样的说明: 将源代码中的函数调用解释为执行特定函数代码块被称为函数名联编(binding) 通俗些说,就是指明调用函数的语句调用的究竟是哪一个...
  • 静态联编与动态联编

    千次阅读 2020-03-08 16:02:43
    在C++中,由于重载的缘故,编译器必须查看函数参数以及函数名才能确定使用的是哪个函数,然而睡佳佳编译器可以在编译过程中完成这种两边在编译过程中进行联编,被称为静态联编。虚函数是这项工作变得更加困难,使用...
  • c++ 静态联编和动态联编 首先我们得明白一个概念:什么是联编? 联编(binding)又称绑定,就是将模块或者函数合并在一起生成 可执行代码的处理过程,同时对每个模块或函数分配内存地址,并且对外部访问也分配正确...
  • 一、静态联编 定义:由于函数重载,编译器必须查看函数参数以及函数名就能确定使用哪个函数;这种C/C++编译器可以在编译过程中完成的联编,被称为静态联编 函数重载:在同一作用域中,可以有一组具有相同函数名,...
  • 静态联编:在编译时所进行的这种联编又称静态束定,在编译时就解决了程序中的操作调用与执行该操作代码间的关系。(范围很大,比静态多态大)。 动态联编:编译程序在编译阶段并不能确切知道将要调用的函数,只有在...
  • 静态联编:在编译阶段 就将函数的实现与函数的调用绑定起来 动态联编:在程序运行时 将函数的实现与函数的调用绑定起来 例如: #include<iostream> using namespace std; class base{ public:print(){cout<...
  • 按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。  1. 静态联编 静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在...
  • 静态联编说的是在编译时就已经确定好了调用和被调用两者的关系 动态联编说的是程序在运行时才确定调用和被调用者的关系.这个主要是虚函数实现的多态性. 1.静态联编 静态联编是指联编工作在编译阶段完成的,...
  • 本文阐述了静态联编和动态联编的概念和区别,通过具体实例分析了实现动态联编的条件,指出了虚函数是实现动态联编的基础。在C++中,联编是指一个计算机程序的不同部分彼此关联的过程。按照联编所进行的阶段不同,可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,182
精华内容 6,872
关键字:

静态联编