当前位置:首页 » 编程软件 » 多态性编译器机制

多态性编译器机制

发布时间: 2023-01-15 05:01:53

① 什么叫做多态性,在c++中是如何实现多态的

C++中的多态(虽然多态不是C++所特有的,但是C++中的多态确实是很特殊的)分为静多态和动多态(也就是静态绑定和动态绑定两种现象),静动的区别主要在于这种绑定发生在编译期还是运行期,发生在编译期的是静态绑定,也就是静多态;发生在运行期的则是动态绑定,也就是动多态。

静多态可以通过模板和函数重载来实现(之所说C++中的多态主要还是因为模板这个东西),下面举两个例子:
1)函数模板
template <typename T>
T max(const T& lsh, const T& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}
返回两个任意类型对象的最大值(对象),前提是该类型能够使用>运算符进行比较,并且返回值是bool类型。
使用:
int a = 3; int b = 4;
cout << max(a, b) << endl;
float c = 2.4; float d = 1.2;
cout << max(c, d) << endl;
输出结果为:
4
2.4
这种绑定发生在编译期,这是由于模板的实例化是发生在编译期的,即在编译时编译器发现你调用max(a, b)时就自动生成一个函数
int max(const int& lsh, const int& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}
即将所有的T替换成int;
当你调用max(c, d)时就自动生成一个函数
float max(const float& lsh, const float& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}
之所以说开始的函数定义是函数模板,就是因为他就像个模子似的,你可以用铝作为原料也可以用石膏或者铜。
2)函数重载:
int max (int a, int b)
{
return (a > b) ? a : b;
}
int max (int a, int b, int c)
{
return max(max(a, b), c);
}
两个函数名称一样,参数类型或个数不完全相同,返回值一样(这个不重要)。
使用:
int a = 3, b = 4, c = 5;
cout << max(a, b) << endl;
cout << max(a, b, c) << endl;
输出结果为:
4
5
确定函数的过程也发生在编译器,当你使用max(a, b),编译器发现只有两个参数,那么就调用只有两个参数的函数版本,当使用max(a, b, c)时,编译器则使用有3个参数的版本。
通过上面的两个例子,你还可以使用更为方便的模板函数重载:
template <typename T>
T max(const T& lsh, const T& rhs)
{
return (lsh > rhs) ? lsh : rhs;
}

template <typename T>
T max(const T& a, const T& b, const T& c)
{
return max(max(a, b), c);
}
使用
float a = 3.6, b = 1.2, c = 7.8;
cout << max(a, b, c) << endl;
输出:
7.8
通过参数个数和类型,编译器自动生成和调用对应得函数版本!

动多态则是通过继承、虚函数(virtual)、指针来实现。
class A {
public:
virtual void func() const {
coust << “A::func()” << endl;
}
}

class B : public A {
public:
virtual void func() const {
coust << “B::func()” << endl;
}
}
使用:
A a* = B();
a->func();
输出:
B::func()
编译期是不调用任何函数的,编译器编译到a->func()时只是检查有没有语法问题,经过检查没有。编译器并不知道调用的是A版本的func()还是B版本的func(),由于a是一个指向B对象的指针,所以a只知道它指向的是一个A类型(或者能转换成A类型)的对象。通常集成体系就说明了(由于是公有继承)B是一种A。在运行期,a要调用a所指向对象的func()函数,就对它指向的对象下达调用func()的命令,结果a所指向的是一个B对象,这个对象就调用了自己版本(B版)的func()函数,所以输出时B::func()

总结:
在编译期决定你应该调用哪个函数的行为是静态绑定(static-binding),这种现象就是静多态。
在运行期决定应该调用哪中类型对象的函数的行为是动态绑定(dynamic-binding),这种现象就是动多态!

注:由于这是我花了有限的时间总结的,语言应用能力比较差,还有比如类模板(静多态和动多态组合的情况)都没有说,最近比较忙,请见谅!

如果还不是很懂,我建议你看C++Primer 4th Edition,讲的比较清晰,但是比较零散!

② 一个关于C++多态性的问题

多态性(polymorphism)是面向对象编程的基本特征之一。而在C++中,多态性通过虚函数(virtual function)来实现。我们来看一段简单的代码:

#include <iostream>
using namespace std;

class Base
{
int a;
public:
virtual void fun1() {cout<<"Base::fun1()"<<endl;}
virtual void fun2() {cout<<"Base::fun2()"<<endl;}
virtual void fun3() {cout<<"Base::fun3()"<<endl;}
};

class A:public Base
{
int a;
public:
void fun1() {cout<<"A::fun1()"<<endl;}
void fun2() {cout<<"A::fun2()"<<endl;}
};

void foo (Base& obj)
{
obj.fun1();
obj.fun2();
obj.fun3();
}

int main()
{
Base b;
A a;
foo(b);
foo(a);
}

运行结果为:
Base::fun1()
Base::fun2()
Base::fun3()
A::fun1()
A::fun2()
Base::fun3()

仅通过基类的接口,程序调用了正确的函数,它就好像知道我们输入的对象的类型一样!

那么,编译器是如何知道正确代码的位置的呢?其实,编译器在编译时并不知道要调用的函数体的正确位置,但它插入了一段能找到正确的函数体的代码。这称之为晚捆绑(late binding)或运行时捆绑(runtime binding)技术。

通过virtual关键字创建虚函数能引发晚捆绑, 编译器在幕后完成了实现晚捆绑的必要机制。它对每个包含虚函数的类创建一个表(称为VTABLE),用于放置虚函数的地址。在每个包含虚函数的类中,编译器秘密地放置了一个称之为vpointer(缩写为VPTR) 的指针,指向这个对象的VTABLE。所以无论这个对象包含一个或是多少虚函数,编译器都只放置一个VPTR即可。VPTR由编译器在构造函数中秘密地插入的代码来完成初始化,指向相应的VTABLE,这样对象就“知道”自己是什么类型了。VPTR都在对象的相同位置,常常是对象的开头。这样,编译器可以容易地找到对象的VTABLE并获取函数体的地址。

如果我们用sizeof查看前面Base类的长度,我们就会发现,它的长度不仅仅是一个int的长度,而是增加了刚好是一个void指针的长度(在我的机器里面,一个int占4个字节,一个void指针占4个字节,这样正好类Base的长度为8个字节)。

每当创建一个包含虚函数的类或从包含虚函数的类派生一个类时,编译器就为这个类创建一个唯一的VTABLE。在VTABLE中,放置了这个类中或是它的基类中所有虚函数的地址,这些虚函数的顺序都是一样的,所以通过偏移量可以容易地找到所需的函数体的地址。假如在派生类中没有对在基类中的某个虚函数进行重写(overriding),那么还使用基类的这个虚函数的地址(正如上面的程序结果所示)。

至今为止,一切顺利。下面,我们的试验开始了。

就目前得知的,我们可以试探着通过自己的代码来调用虚函数,也就是说我们要找寻一下编译器秘密地插入的那段能找到正确函数体的代码的足迹。

如果我们有一个Base指针作为接口,它一定指向一个Base或由Base派生的对象,或者是A,或者是其它什么。这无关紧要,因为VPTR的位置都一样,一般都在对象的开头。如果是这样的话,那么包含有虚函数的对象的指针,例如Base指针,指向的位置恰恰是另一个指针——VPTR。VPTR指向的 VTABLE其实就是一个函数指针的数组,现在,VPTR正指向它的第一个元素,那是一个函数指针。如果VPTR向后偏移一个Void指针长度的话,那么它应该指向了VTABLE中的第二个函数指针了。

这看来就像是一个指针连成的链,我们得从当前指针获取它指向的下一个指针,这样我们才能“顺藤摸瓜”。那么,我来介绍一个函数:

void *getp (void* p)
{
return (void*)*(unsigned long*)p;
}

我们不考虑它漂亮与否,我们只是试验。getp() 可以从当前指针获取它指向的下一个指针。如果我们能找到函数体的地址,用什么来存储它呢?我想应该用一个函数指针:

typedef void (*fun)();

它与Base中的三个虚函数相似,为了简单我们不要任何输入和返回,我们只要知道它实际上被执行了即可。

然后,我们负责“摸瓜”的函数登场了:

fun getfun (Base* obj, unsigned long off)
{
void *vptr = getp(obj);

unsigned char *p = (unsigned char *)vptr;
p += sizeof(void*) * off;

return (fun)getp(p);
}

第一个参数是Base指针,我们可以输入Base或是Base派生对象的指针。第二个参数是VTABLE偏移量,偏移量如果是0那么对应fun1(),如果是1对应fun2()。getfun() 返回的是fun类型函数指针,我们上面定义的那个。可以看到,函数首先就对Base指针调用了一次getp(),这样得到了vptr这个指针,然后用一个 unsigned char指针运算偏移量,得到的结果再次输入getp(),这次得到的就应该是正确的函数体的位置了。

那么它到底能不能正确工作呢?我们修改main() 来测试一下:

int main()
{
Base *p = new A;

fun f = getfun(p, 0);
(*f)();
f = getfun(p, 1);
(*f)();
f = getfun(p, 2);
(*f)();
delete p;
}

激动人心的时刻到来了,让我们运行它!

运行结果为:
A::fun1()
A::fun2()
Base::fun3()

至此,我们真的成功了。通过我们的方法,我们获取了对象的VPTR,在它的体外执行了它的虚函数。

③ 编译时的多态性和运行时的多态性在实现方法上有何不同

我不知道你哪本书上看到的,但是,只要不是后绑定就不能称为多态,前绑定只能称为代码重用,比如函数的重载、覆盖以及一般的类继承。
多态的关键特点就是:在运行时虚基类指针指向派生类对象地址,而将派生类对象地址赋值给基类指针,这就是所谓的后绑定,编译时绑定称为前绑定,因此多态另一个特点就是“动态“。换句话说,如果是后绑定,编译器事先是不知道在运行时指针将指向哪一种派生类的对象,因此基类指针必须是“虚“的,虚基类中不能有任何实现只有定义,此时虚基类的作用就是一个类接口,这样才能在编译时“模糊”掉类型匹配原则,基类的作用只是个约定,定义了函数调用格式,而只在运行时才确定指针具体指向哪一个对象。
而所谓编译时的多态性根本不存在,如果编译器能确定基类指针指向哪一个派生类对象地址,就不是多态,哪怕你采用重载覆盖或者继承,这些编译器已经可以预知的事情,一旦编译完成就固定了,运行时无法更改的,比如你不能在不重新编译的情况下增加一个重载,这就制约了程序运行时的灵活性以及可扩充性。而多态完全可以实现“热“更新,更多的是便于程序的可扩充性。你完全可以将派生类编译在DLL中,每当更新程序时,只要替换掉DLL而不用重新编译全部代码。

④ 怎么理解java中的多态性

一、基本概念

多态性:发送消息给某个对象,让该对象自行决定响应何种行为。
通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。

java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

1. 如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类。
2. 如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例。

二、Java多态性实现机制

SUN目前的JVM实现机制,类实例的引用就是指向一个句柄(handle)的指针,这个句柄是一对指针:
一个指针指向一张表格,实际上这个表格也有两个指针(一个指针指向一个包含了对象的方法表,另外一个指向类对象,表明该对象所属的类型);
另一个指针指向一块从java堆中为分配出来内存空间。

The Java Virtual Machine does not require any particular internal structure for objects. In Sun 's current implementation of the Java Virtual Machine, a reference to a class instance is a pointer to a handle that is itself a pair of pointers: one to a table containing the methods of the object and a pointer to the Class object that represents the type of the object, and the other to the memory allocated from the Java heap for the object data. (jvm规范中关于对象内存布局的说明)

三、总结

1、通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。

DerivedC c2=new DerivedC();
BaseClass a1= c2; //BaseClass 基类,DerivedC是继承自BaseClass的子类
a1.play(); //play()在BaseClass,DerivedC中均有定义,即子类覆写了该方法

分析:
* 为什么子类的类型的对象实例可以覆给超类引用?
自动实现向上转型。通过该语句,编译器自动将子类实例向上移动,成为通用类型BaseClass;
* a.play()将执行子类还是父类定义的方法?
子类的。在运行时期,将根据a这个对象引用实际的类型来获取对应的方法。所以才有多态性。一个基类的对象引用,被赋予不同的子类对象引用,执行该方法时,将表现出不同的行为。

在a1=c2的时候,仍然是存在两个句柄,a1和c2,但是a1和c2拥有同一块数据内存块和不同的函数表。

2、不能把父类对象引用赋给子类对象引用变量

BaseClass a2=new BaseClass();
DerivedC c1=a2;//出错

在java里面,向上转型是自动进行的,但是向下转型却不是,需要我们自己定义强制进行。
c1=(DerivedC)a2; 进行强制转化,也就是向下转型.

3、记住一个很简单又很复杂的规则,一个类型引用只能引用引用类型自身含有的方法和变量。
你可能说这个规则不对的,因为父类引用指向子类对象的时候,最后执行的是子类的方法的。
其实这并不矛盾,那是因为采用了后期绑定,动态运行的时候又根据型别去调用了子类的方法。而假若子类的这个方法在父类中并没有定义,则会出错。
例如,DerivedC类在继承BaseClass中定义的函数外,还增加了几个函数(例如 myFun())

分析:
当你使用父类引用指向子类的时候,其实jvm已经使用了编译器产生的类型信息调整转换了。
这里你可以这样理解,相当于把不是父类中含有的函数从虚拟函数表中设置为不可见的。注意有可能虚拟函数表中有些函数地址由于在子类中已经被改写了,所以对象虚拟函数表中虚拟函数项目地址已经被设置为子类中完成的方法体的地址了。

⑤ C++中多态是怎样实现的

多态是一种不同的对象以单独的方式作用于相同消息的能力,这个概念是从自然语言中引进的。例如,动词“关闭”应用到不同的事务上其意思是不同的。关门,关闭银行账号或关闭一个程序的窗口都是不同的行为;其实际的意义取决于该动作所作用的对象。 大多数面向对象语言的多态特性都仅以虚拟函数的形式来实现,但C++除了一般的虚拟函数形式之外,还多了两种静态的(即编译时的)多态机制: 2、模板:例如,当接受到相同的消息时,整型vector对象和串vector对象对消息反映是不同的,我们以关闭行为为例: vector < int > vi; vector < string > names; string name("VC知识库"); vi.push_back( 5 ); // 在 vector 尾部添加整型 names.push_back (name); // 添加串和添加整型体现差别的潜在的操作 静态的多态机制不会导致与虚拟函数相关的运行时开。此外,操作符重载和模板两者是通用算法最基本的东西,在STL中体现得尤为突出。 那么接下来我们说说以虚函数形式多态: 通常都有以重载、覆盖、隐藏来三中方式,三种方式的区别大家应该要很深入的了解,这里就不多说了。 许多开发人员往往将这种情况和C++的多态性搞混淆,下面我从两方面为大家解说: 1、 编译的角度 C++编译器在编译的时候,要确定每个对象调用的函数的地址,这称为早期绑定(early binding)。2、 内存模型的角度为了确定对象调用的函数的地址,就要使用迟绑定(late binding)技术。当编译器使用迟绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器采用迟绑定,就要在基类中声明函数时使用virtual关键字(注意,这是必须的,很多开发人员就是因为没有使用虚函数而写出很多错误的例子),这样的函数我们称为虚函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual。 那么如何定位虚表呢?编译器另外还为每个类的对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。 正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数。那么虚表指针在什么时候,或者说在什么地方初始化呢? 答案是在构造函数中进行虚表的创建和虚表指针的初始化。还记得构造函数的调用顺序吗,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否后还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。 要注意:对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。总结(基类有虚函数):1、 每一个类都有虚表。2、虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。3、派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。

⑥ c++ 的特点是什么c++ 的特点是什么谈谈它的多态性。

C++是以C语言为基础,支持数据抽象和面向对象的程序设计语言。C++对C语言的扩充部分汲取了
许多着名语言中最优秀的特征,如从Algo168中吸取了操作符重载机制等。由于C++语言具有与C语言一

样的高执行效率,并容易被熟悉C语言的软件人员接受,因而很快得以流行。但这种混合型面向对象的

程序设计语言是一种新的程序设计语言,人们对它许多潜在的性能(封装、继承、多态等)还没有充分

地理解和应用,没有充分发挥其优势。多态性是面向对象系统的重要概念之一,它指的是同样的消息

能被发送到父类的对象和它的子类的对象。本文重点讨论多态性在程序设计中的应用。

1 多态性的实现形式

从广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++语言中,这种多态性可以

通过强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。类型参数化多态和包含多态统

称为一般多态性,用来系统地刻画语义上相关的一组类型。重载多态和强制多态统称为特殊多态性,

用来刻画语义上无关联的类型间的关系。
包含多态是指通过子类型化,1个程序段既能处理类型T的对象,也能够处理类型T的子类型S的对

象,该程序段称为多态程序段。公有继承能够实现子类型。在包含多态中,1个对象可以被看作属于不

同的类,其间包含关系的存在意味着公共结构的存在。包含多态在不少语言中存在,如整数类型中的

子集构成1个子类型。每一个子类型中的对象可以被用在高一级的类型中,高一级类型中的所有操作可

用于下一级的对象。在C++中公有继承关系是一种包含多态,每一个类可以直接公有继承父类或多个父

类,如语句class D�public P1,public P2{……};表示类D分别是类P1和类P2的子类型。
类型参数化多态是指当1个函数(类)统一地对若干类型参数操作时,这些类型表现出某些公共的语

义特性,而该函数(类)就是用来描述该特性的。在类型参数化多态中,1个多态函数(类)必须至少带有

1个类型参数,该类型参数确定函数(类)在每次执行时操作数的类型。这种函数(类)也称类属函数(类)

。类型参数化多态的应用较广泛,被称为最纯的多态。
重载是指用同一个名字命名不同的函数或操作符。函数重载是C++对一般程序设计语言中操作符重

载机制的扩充,它可使具有相同或相近含义的函数用相同的名字,只要其参数的个数、次序或类型不

一样即可。例如:
int min(int x,int y); //求2个整数的最小数
int min(int x,int y,int z); //求3个整数的最小数
int min(int n,int a〔〕); //求n个整数的最小数
当用户要求增加比较2个字符串大小的功能时,只需增加:

char*min(char*,char*);

而原来如何使用这组函数的逻辑不需改变,min的功能扩充很容易,也就是说维护比较容易,同时也提

高了程序的可理解性,“min”表示求最小值的函数。
强制是指将一种类型的值转换成另一种类型的值进行的语义操作,从而防止类型错误。类型转换

可以是隐式的,在编译时完成,如语句D=I把整型变量转换为实型;也可以是显式的,可在动态运行

时完成。
从总体上来说,一般多态性是真正的多态性;特殊多态性只是表面的多态性。因为重载只允许某

一个符号有多种类型,而它所代表的值分别具有不同的、不相兼容的类型。类似地,隐式类型转换也

不是真正的多态,因为在操作开始前,各值必须转换为要求的类型,而输出类型也与输入类型无关。

相比之下,子类与继承却是真正的多态。类型参数化多态也是一种纯正的多态,同一对象或函数在不

同的类型上下文中统一地使用而不需采用隐式类型转换、运行时检测或其它各种限制。

2 多态性应用

2.1 包含多态
C++中采用虚拟函数实现包含多态,虚拟函数为C++提供了更为灵活的多态机制,这种多态性在程

序运行时才能确定,因此虚拟函数是多态性的精华,至少含有一个虚拟函数的类称为多态类。包含多

态在程序设计中使用十分频繁。
派生类继承基类的所有操作,或者说,基类的操作能被用于操作派生类的对象,当基类的操作不

能适应派生类时,派生类需重载基类的操作,见下例中的void circle::showarea()。
#include <iostream.h>
class point //屏幕上的点类
� {int x,y;
public;
point(int x1,int y1)
{x=x1;y=y1;}
void showarea()
{cout<<〃Area of point is:〃<<0.0<<endl;}
};

class circle:public point//圆类
{int radius;
public:
circle(int x,int y,int r):point(x,y){ radius=r;}
void showarea(){cout<<〃Area of circle is:〃<<3.14
*radius*radius<<endl;}
};

void disparea(const point*p) //多态程序段
{p->showarea();}
void main()
{circle c1(1,1,1);disparea(&c1);

程序的运行结果为0.0(正确结果应为3.14),出错的原因是:表达式p->showarea()中的函数调

用在编译时被束定到函数体上,使得这个表达式中的函数调用执行point类的showarea()。为此,当程

序员在实现一个派生类而变动了基类中的操作实现时,C++提供的虚函数机制可将这种变动告诉编译器

,即将关键字virtual放在类point中该函数的函数说明之前(virtual void showarea()),程序其它部

分保持不变(circle::showarea()自动地成为虚函数),编译器就不会对函数调用p->showarea()进

行静态束定(在编译/连接时进行的束定)而产生有关的代码,使函数调用与它所应执行的代码的束定

工作在程序运行时进行,这样上述程序的运行结果即为3.14。在程序运行时进行的束定被称为动态束

定。
利用虚函数,可在基类和派生类中使用相同的函数名定义函数的不同实现,从而实现“一个接口

,多种方式”。当用基类指针或引用对虚函数进行访问时,软件系统将根据运行时指针或引用所指向

或引用的实际对象来确定调用对象所在类的虚函数版本。
C++语言还增加了纯的虚函数机制用来更好地设计包含多态性。对于如图1(a)所示结构的类层次,

假如每个类中都有一个函数“void display(void);”,那么,怎样对它们按多态性进行统一处理呢

?对这类问题应先设计一个抽象的类,使它成为所有类的祖先类,如图1(b)所示。设置类A的目的是由

它说明统一使用该层次中的display()函数的方法(赋值兼容规则从语法上保证了A的子孙类可按A说明

的方式使用display()函数;多态性则从语义上保证了在执行时,根据实际的对象访问相应对象类中的

display()函数)。

为了保证在类A中设置的display()函数是抽象动作,并能说明类A是一个抽象的类,在C++中,可用纯

的虚函数语言机制在类A中声明1个成员函数“virtual void display(void)=0;”。请注意,在类A

的子孙类中要么给出display()的定义,要么重新将该函数声明为纯的。
从上面的分析可以看出,类A的设计尽管是用继承性语法表达的,但它的主要目的不是为代码共享而设

计的,而是为了提高多态性而设计的,它是另一个维度的抽象。
2.2 类型参数化多态
参数化多态又称非受限类属多态,即将类型作为函数或类的参数,避免了为各种不同的数据类型

编写不同的函数或类,减轻了设计者负担,提高了程序设计的灵活性。
模板是C++实现参数化多态性的工具,分为函数模板和类模板二种。
类模板中的成员函数均为函数模板,因此函数模板是为类模板服务的。类模板在表示数组、表、

矩阵等类数据结构时,显得特别重要,因为这些数据结构的表示和算法的选择不受其所包含的元素的

类型的影响。下面是一个通用数组类模板的定义。
template <class T,int N>
class array
{T elem〔N〕;
public:
array(){for(int j=0;j<N;j++)elem〔j〕=0;}
T& operator〔〕(int index){return elem〔index〕;}
void modi(int index,T value){elem〔index〕=value;}
};
其中,T是类型参数,N是常量参数。T和N的实际值是在生成具体类实例时指定的。类模板的< >

可以包括任意个类型参数或常量参数,但至少应有一个参数。在类模板定义中,可在程序中通常使用

类型指定的任何地方使用类型参数,可在通常使用特定类型常量表达式的任何地方使用常量参数。
成员函数模板可放在类模板中定义,也可放在类外定义,例如:
template <class T,int N>
T& array<T,N>::operator〔〕(int index){return elem〔index〕;}
当由类模板生成一个特定的类时,必须指定参数所代表的类型(值)。例如,1个元素类型为int、

长度为100的数组类使用类型表达式array<int,100>来表示,这个类型表达式被用于说明数组类对

象。例如:
array<int,100> a: //生成特定类的对象a
a.modi(1,34); //对象a访问成员函数
类模板一旦生成了对象和指定了参数表中的类型,编译器在以后访问数据成员和调用成员函数时

完全强制为这些类型。
在C++中可以重载定义多个同名的函数模板,也可以将1个函数模板与1个同名函数进行重载定义。

例如:
template <class T> T min(T a,T b){return a<b?a:b;}
template <class T>
T min(T a,T b,T c){T x=min(a,b);return min(x,c);}
int min(int a,int b)〔return a<b?a:b;}
调用min(3,7),则调用第3个函数;调用min(3.8.5.9),编译器将根据带2个参数的模板生成新函

数min(double,double);调用min(4,90,76),则编译器根据带3个参数的模板生成新函数min(int,

int,int);而调用min(56.3,48,71),编译将给出错误信息,说明无法从上面的模板中生成函数

min(double,double,double),因为编译器在类型推导时,不存在类型强制。
模板描述了1组函数或1组类,它主要用于避免程序员进行重复的编码工作,大大简化、方便了面

向对象的程序设计。
2.3 重载多态
重载是多态性的最简形式,而且把更大的灵活性和扩展性添加到程序设计语言中,它分成操作符

重载和函数重载。
C++允许为类重定义已有操作符的语义,使系统预定义的操作符可操作类对象。C++语言的一个非

常有说服力的例子是count对象的插入操作(<<)。由于其类中定义了对位左移操作符“<<”进行重

载的函数,使C++的输出可按同一种方式进行,学习起来非常容易。并且,增加一个使其能输出复数类

的功能(扩充)也很简单,不必破坏原输出逻辑。
C++规定将操作符重载为函数的形式,既可以重载为类的成员函数,也可以重载为类的友员函数。

用友员重载操作符的函数也称操作符函数,它与用成员函数重载操作符的函数不同,后者本身是类中

成员函数,而它是类的友员函数,是独立于类的一般函数。注意重载操作符时,不能改变它们的优先

级,不能改变这些操作符所需操作数的个数。
重定义已有的函数称为函数重载。在C++中既允许重载一般函数,也允许重载类的成员函数。如对

构造函数进行重载定义,可使程序有几种不同的途径对类对象进行初始化。还允许派生类的成员函数

重载基类的成员函数,虚函数就属于这种形式的重载,但它是一种动态的重载方式,即所谓的“动态

联编(束定)”。
2.4 强制多态
强制也称类型转换。C++语言定义了基本数据类型之间的转换规则,即:
char->short->int->unsigned->long->unsigned long->float->double->long

double
赋值操作是个特例,上述原则不再适用。当赋值操作符的右操作数的类型与左操作数的类型不同

时,右操作数的值被转换为左操作数的类型的值,然后将转换后的值赋值给左操作数。
程序员可以在表达式中使用3种强制类型转换表达式:①static_cast<T>(E);②T(E);③(T)E

。其中任意一种都可改变编译器所使用的规则,以便按自己的意愿进行所需的类型强制。其中E 代表

一个运算表达式,T代表一个类型表达式。第三种表达形式是C语言中所使用的风格,在C++中,建议不

要再使用这种形式,应选择使用第一种形式。例如,设对象f的类型为double,且其值为3.14。则表达

式static_cast<int>(f)的值为3,类型为int。
通过构造函数进行类类型与其它数据类型之间的转换必须有一个前提,那就是此类一定要有一个

只带1个非缺省参数的构造函数,通过构造函数进行类类型的转换只能从参数类型向类类型转换,而想

将一个类类型向其它类型转换是办不到的。类类型转换函数就是专门用来将类类型向其它本类型转换

的,它是一种类似显式类型转换的机制。转换函数的设计有以下几点要特别注意:①转换函数必须是

类的成员函数;②转换函数不可以指定其返回值类型;③转换函数其参数行不可以有任何参数。
强制使类型检查复杂化,尤其在允许重载的情况下,导致无法消解的二义性,在程序设计时要注

意避免由于强制带来的二义性。

⑦ c#多态的运行机理

描述一下C++的,应该差不多

1、编译器发现一个类中有虚函数,编译器会立即为此类生成虚拟函数表 vtable(后面有对vtable的分析)。虚拟函数表的各表项为指向对应虚拟函数的指针。

2、编译器在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)。

有一个办法可以让你感知这个隐含指针的存在,虽然你不能在类中直接看到它,但你可以比较一下含有虚拟函数时的类的尺寸和没有虚拟函数时的类的尺寸,你能够发现,这个指针确实存在。

class cnovirtualfun
{
private:
long lmember;
public:
long getmembervalue();
} class chavevirtualfun
{
private:
long lmember;
public:
virtual long getmembervalue();
}

cnovirtualfun obj;
sizeof(obj) -> == 4;
chavevirtualfun obj;
sizeof(obj) -> == 8;

3、在调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable。这就将类与此类的vtable联系了起来。

4、在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,从而实现了多态性。在此时才能真正与函数体进行连接,这就是动态联编。

⑧ c++中什么是多态!

在C++中接口的多种不同的实现方式就是多态。

多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。允许将子类类型的指针赋值给父类类型的指针。多态性在ObjectPascal和C++中都是通过虚函数实现的。

赋值之后,父类型的引用就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。

(8)多态性编译器机制扩展阅读

多态的使用

#include<iostream>

usingnamespacestd;

classA{

public:

virtualvoidPrint(){cout<<"A::Print"<<endl;}

};

classB:publicA{

public:

voidPrint(){cout<<"B::Print"<<endl;}

};

voidPrintlnfo(A&r){//传参相当于:r=a(指向父类A的Print()虚函数);或者r=b(指向子类B的Print()虚函数);

r.Print();//多态,调用哪个Print,取决于r引用了哪个类的对象

}

intmain()

{

Aa;Bb;

Printlnfo(a);//输出A::Print

Printlnfo(b);//输出B::Print

return0;

}

⑨ c++和java的多态性的区别

多态是指用父指针指向不同子类对象时,调用其共有的函数,不同的子类会有不同的行为。虽然C++和Java都具有多态机制,但是他们的实现不同,使用时的效果也会略有不同

在C++中

  • 普通函数调用:具体调用哪个方法在编译时就可以决定(通过查找编译器的符号表),同时在使用标准过程调用机制基础上增加一个表示对象身份的指针(this指针)。

  • 虚函数调用:函数调用依赖于对象的实际类型,一般地说,对象的实际类型只能在运行时间才能确定。实现机制是使用virtual table(vtbls)和virtual table pointers(vptrs)。

  • vtbl 是由函数指针构成的数组或链表,程序中每一个class凡声明(或继承)虚函数者,都有一个自己的vtbl,其中的条目就是该class的各个虚函数实现的指针。因此必须为每一个class消耗一个vtbl空间,其大小视虚函数的个数确定。

  • 凡声明有虚函数的class,其对象都有一个隐藏的data member,用来指向class的vtbl。

  • 当多态发生时,编译器首先根据对象vptr找出其vtbl,然后找出vtbl内对应的函数指针,最后调用函数指针指向的函数。从而实现多态。

  • 在Java中

    1.C++中VTable和vptr是在编译阶段由编译器自动生成的,也就是说,在C++程序载入内存以前,在.obj(.o)文件中已经有这些结构的信

    息;Java中的方法表是由JVM生成的,因此,使用javac命令编译后生成的.class文件中并没有方法表的信息。只有等JVM把.class文件
    载入到内存中时,才会为该.class文件动态生成一个与之关联的方法表,放置在JVM的方法区中。
    2.
    C++中某个方法在VTable的索引号是在编译阶段已经明确知道的,并不需要在运行过程中动态获知;Java中的方法初始时都只是一个符号,并不是
    一个明确的地址,只有等到该方法被第一次调用时,才会被解析成一个方法表中的偏移量,也就是说,只有在这个时候,实例方法才明确知道自己在方发表中的偏移
    量了,在这之前必须经历一个解析的过程。

  • 因此在构造函数是Java会发生多态,即使子类此时还没有构造完全(一个极难发现的bug)。而C++则不会发生多态,待父类构造完全,在构造子类。
热点内容
java返回this 发布:2025-10-20 08:28:16 浏览:645
制作脚本网站 发布:2025-10-20 08:17:34 浏览:936
python中的init方法 发布:2025-10-20 08:17:33 浏览:632
图案密码什么意思 发布:2025-10-20 08:16:56 浏览:821
怎么清理微信视频缓存 发布:2025-10-20 08:12:37 浏览:731
c语言编译器怎么看执行过程 发布:2025-10-20 08:00:32 浏览:1066
邮箱如何填写发信服务器 发布:2025-10-20 07:45:27 浏览:299
shell脚本入门案例 发布:2025-10-20 07:44:45 浏览:160
怎么上传照片浏览上传 发布:2025-10-20 07:44:03 浏览:852
python股票数据获取 发布:2025-10-20 07:39:44 浏览:763