- 浏览: 929352 次
- 性别:
- 来自: 北京
-
文章分类
- 全部博客 (537)
- Java SE (114)
- Struts (18)
- Hibernate (25)
- Spring (3)
- Page_Tech (41)
- Others (87)
- Database (29)
- Server (24)
- OpenSource_Tools (15)
- IDE_Tool (22)
- Algorithm (28)
- Interview (22)
- Test (28)
- Hardware (1)
- Mainframe (25)
- Web application (4)
- Linux (3)
- PHP (17)
- Android (1)
- Perl (6)
- ubuntu (1)
- Java EE (9)
- Web Analysis (5)
- Node.js (2)
- javascript (2)
最新评论
-
一键注册:
request.getRequestURL()和request.getRequestURI() -
SuperCustomer:
...
SED的暂存空间和模式空间 -
juyo_ch:
讲得挺好理解的,学习了
java 死锁及解决 -
chinaalex:
最后一题答案正确,但是分析有误.按照如下过程,上一行为瓶,下一 ...
zz智力题 -
liaowuxukong:
多谢博主啦,弱弱的了解了一点。
C++/Java 实现多态的方法(C++)
多态(Polymorphism)是面向对象的核心概念,本文以C++为例,讨论多态的具体实现。C++中多态可以分为基于继承和虚函数的动态多态以及基于模板的静态多态,如果没有特别指明,本文中出现的多态都是指前者,也就是基于继承和虚函数的动态多态。至于什么是多态,在面向对象中如何使用多态,使用多态的好处等等问题,如果大家感兴趣的话,可以找本面向对象的书来看看。
为了方便说明,下面举一个简单的使用多态的例子(From [1] ):
{
protected:
int m_x; // X coordinate
int m_y; // Y coordinate
public:
// Pure virtual function for drawing
virtual void Draw() = 0;
// A regular virtual function
virtual void MoveTo(int newX, int newY);
// Regular method, not overridable.
void Erase();
// Constructor for Shape
Shape(int x, int y);
// Virtual destructor for Shape
virtual ~Shape();
};
class Circle : public Shape
{
private:
int m_radius; // Radius of the circle
public:
// Override to draw a circle
virtual void Draw();
// Constructor for Circle
Circle(int x, int y, int radius);
// Destructor for Circle
virtual ~Circle();
};
Shape::Shape(int x, int y)
{
m_x = x;
m_y = y;
}
// Shape destructor implementation
Shape::~Shape()
{
//...
}
Circle::Circle(int x, int y, int radius) : Shape (x, y)
{
m_radius = radius;
}
// Circle destructor implementation
Circle::~Circle()
{
//...
}
// Circle override of the pure virtual Draw method.
void Circle::Draw()
{
glib_draw_circle(m_x, m_y, m_radius);
}
main()
{
// Define a circle with a center at (50,100) and a radius of 25
Shape *pShape = new Circle(50, 100, 25);
// Define a circle with a center at (5,5) and a radius of 2
Circle aCircle(5,5, 2);
// Various operations on a Circle via a Shape pointer
//Polymorphism
pShape->Draw();
pShape->MoveTo(100, 100);
pShape->Erase();
delete pShape;
// Invoking the Draw method directly
aCircle.Draw();
}
例子中使用到多态的代码以黑体标出了,它们一个很明显的特征就是通过一个基类的指针(或者引用)来调用不同子类的方法。
那么,现在的问题是,这个功能是怎样实现的呢?我们可以先来大概猜测一下:对于一般的方法调用,到了汇编代码这一层次的时候,一般都是使用 Call funcaddr 这样的指令进行调用,其中funcaddr是要调用函数的地址。按理来说,当我使用指针pShape来调用Draw的时候,编译器应该将Shape::Draw的地址赋给funcaddr,然后Call 指令就可以直接调用Shape::Draw了,这就跟用pShape来调用Shape::Erase一样。但是,运行结果却告诉我们,编译器赋给funcaddr的值却是Circle::Drawde的值。这就说明,编译器在对待Draw方法和Erase方法时使用了双重标准。那么究竟是谁有这么大的法力,使编译器这个铁面无私的判官都要另眼相看呢?virtual!!
Clever!!正是virtual这个关键字一手导演了这一出“乾坤大挪移”的好戏。说道这里,我们先要明确两个概念:静态绑定和动态绑定。
1、静态绑定(static bingding),也叫早期绑定,简单来说就是编译器在编译期间就明确知道所要调用的方法,并将该方法的地址赋给了Call指令的funcaddr。因此,运行期间直接使用Call指令就可调用到相应的方法。
2、动态绑定(dynamic binding),也叫晚期绑定,与静态绑定不同,在编译期间,编译器并不能明确知道究竟要调用的是哪一个方法,而这,要知道运行期间使用的具体是哪个对象才能决定。
好了,有了这两个概念以后,我们就可以说,virtual的作用就是告诉编译器:我要进行动态绑定!编译器当然会尊重你的意见,而且为了完成你这个要求,编译器还要做很多的事情:编译器自动在声明了virtual方法的类中插入一个指针vptr和一个数据结构VTable(vptr用以指向VTable;VTable是一个指针数组,里面存放着函数的地址),并保证二者遵守下面的规则:
1、VTable中只能存放声明为virtual的方法,其它方法不能存放在里面。在上面的例子中,Shape的VTable中就只有Draw,MoveTo和~Shape。方法Erase的地址并不能存放在VTable中。此外,如果方法是纯虚函数,如 Draw,那么同样要在VTable中保留相应的位置,但是由于纯虚函数没有函数体,因此该位置中并不存放Draw的地址,而是可以选择存放一个出错处理的函数的地址,当该位置被意外调用时,可以用出错函数进行相应的处理。
2、派生类的VTalbe中记录的从基类中继承下来的虚函数地址的索引号必须跟该虚函数在基类VTable中的索引号保持一致。如在上例中,如果在Shape的VTalbe中,Draw为 1 号, MoveTo 2 号,~Shape为 3 号,那么,不管这些方法在Circle中是按照什么顺序定义的,Circle的VTable中都必须保证Draw为 1 号,MoveTo为 2号。至于 3号,这里是~Circle。为什么不是~Shape啊?嘿嘿,忘啦,析构函数不会继承的。
3、vptr是由编译器自动插入生成的,因此编译器必须负责为其进行初始化。初始化的时间选在对象创建时,而地点就在构造函数中。因此,编译器必须保证每个类至少有一个构造函数,若没有,自动为其生成一个默认构造函数。
4、vptr通常放在对象的起始处,也就是Addr(obj) == Addr(obj.vptr)。
你看,天下果然没有免费的午餐,为了实现动态绑定,编译器要为我们默默干了这么多的脏话累活。如果你想体验一下编译器的辛劳,那么可以尝试用C语言模拟一下上面的行为,【1】中就有这么一个例子。好了,现在万事具备,只欠东风了。编译,连接,载入,GO!当程序执行到 pShape->Draw()的时候,上面的设施也开始起作用了。。
前面已经提到,晚期绑定时之所以不能确定调用哪个函数,是因为具体的对象不确定。好了,当运行到pShape->Draw()时,对象出来了,它由pShape指针标出。我们找到这个对象后,就可以找到它里面的vptr(在对象的起始处),有了vptr后,我们就找到了VTable,调用的函数就在眼前了。。等等,VTable中方法那么多,我究竟使用哪个呢?不用着急,编译器早已为我们做好了记录:编译器在创建VTable时,已经为每个virtual函数安排好了座次,并且把这个索引号记录了下来。因此,当编译器解析到pShape->Draw()的时候,它已经悄悄的将函数的名字用索引号来代替了。这时候,我们通过这个索引号就可以在VTable中得到一个函数地址,Call it!
在这里,我们就体会到为什么会有第二条规定了,通常,我们都是用基类的指针来引用派生类的对象,但是不管具体对象是哪个派生类的,我们都可以使用相同的索引号来取得对应的函数实现。
现实中有一个例子其实跟这个蛮像的:报警电话有110,119,120(VTable中不同的方法)。不同地方的人拨打不同的号码所产生的结果都是不一样的。譬如,在三环外的一个人(具体对象)跟一环内的一个人(另外一个具体对象)打119,最后调用的消防队肯定是不一样的,这就是多态了。这是怎么实现的呢,每个人都知道一个报警中心(VTable,里面有三个方法 110,119,120)。如果三环外的一个人需要火警抢险(一个具体对象)时,它就拨打119,但是他肯定不知道最后是哪一个消防队会出现的。这得有报警中心来决定,报警中心通过这个具体对象(例子中就是具体位置了)以及他说拨打的电话号码(可以理解成索引号),报警中心可以确定应该调度哪一个消防队进行抢险(不同的动作)。
这样,通过vptr和VTable的帮助,我们就实现了C++的动态绑定。当然,这仅仅是单继承时的情况,多重继承的处理要相对复杂一点,下面简要说一下最简单的多重继承的情况,至于虚继承的情况,有兴趣的朋友可以看看 Lippman的《Inside the C++ Object Model》,这里暂时就不展开了。(主要是自己还没搞清楚,况且现在多重继承都不怎么使用了,虚继承应用的机会就更少了)
首先,我要先说一下多重继承下对象的内存布局,也就是说该对象是如何存放本身的数据的。
{
public:
int i;
virtual void cute(){ cout<<"Cute cute"<<endl; }
};
{
public:
int j;
virtual void say(){ cout<<"Pet say"<<endl; }
};
{
public:
int z;
void cute(){ cout<<"Dog cute"<<endl; }
void say(){ cout<<"Dog say"<<endl; }
};
在上面这个例子中,一个Dog对象在内存中的布局如下所示:
Dog |
Vptr1 |
Cute::i |
Vptr2 |
Pet::j |
Dog::z |
也就是说,在Dog对象中,会存在两个vptr,每一个跟所继承的父类相对应。如果我们要想实现多态,就必须在对象中准确地找到相应的vptr,以调用不同的方法。但是,如果根据单继承时的逻辑,也就是vptr放在指针指向位置的起始处,那么,要在多重继承情况下实现,我们必须保证在将一个派生类的指针隐式或者显式地转换成一个父类的指针时,得到的结果指向相应派生类数据在Dog对象中的起始位置。幸好,这工作编译器已经帮我们完成了。上面的例子中,如果Dog向上转换成Pet的话,编译器会自动计算Pet数据在Dog对象中的偏移量,该偏移量加上Dog对象的起始位置,就是Pet数据的实际地址了。
{
Dog* d = new Dog();
cout<<"Dog object addr : "<<d<<endl;
Cute* c = d;
cout<<"Cute type addr : "<<c<<endl;
Pet* p = d;
cout<<"Pet type addr : "<<p<<endl;
delete d;
}
Dog object addr : 0x3d24b0
Cute type addr : 0x3d24b0
Pet type addr : 0x3d24b8 // 正好指向Dog对象的vptr2处,也就是Pet的数据
好了,既然编译器帮我们自动完成了不同父类的地址转换,我们调用虚函数的过程也就跟单继承统一起来了:通过具体对象,找到vptr(通常指针的起始位置,因此Cute找到的是vptr1,而Pet找到的是vptr2),通过vptr,我们找到VTable,然后根据编译时得到的VTable索引号,我们取得相应的函数地址,接着就可以马上调用了。
在这里,顺便也提一下两个特殊的方法在多态中的特别之处吧:第一个是构造函数,在构造函数中调用虚函数是不会有多态行为的,例子如下:
{
public:
Pet(){ sayHello(); }
void say(){ sayHello(); }
virtual void sayHello()
{
cout<<"Pet sayHello"<<endl;
}
};
{
public:
Dog(){};
void sayHello()
{
cout<<"Dog sayHello"<<endl;
}
};
{
Pet* p = new Dog();
p->sayHello();
delete p;
}
Pet sayHello //直接调用的是Pet的sayHello()
Dog sayHello //多态
第二个就是析构函数,使用多态的时候,我们经常使用基类的指针来引用派生类的对象,如果是动态创建的,对象使用完后,我们使用delete来释放对象。但是,如果我们不注意的话,会有意想不到的情况发生。
{
public:
~Pet(){ cout<<"Pet destructor"<<endl; }
//virtual ~Pet(){ cout<<"Pet virtual destructor"<<endl; }
};
{
public:
~Dog(){ cout<<"Dog destructor"<<endl;};
//virtual ~Dog(){ cout<<"Dog virtual destructor"<<endl; }
};
{
Pet* p = new Dog();
delete p;
}
Pet destructor //糟了,Dog的析构函数没有调用,memory leak!
如果我们将析构函数改成virtual以后,结果如下
Dog virtual destructor
Pet virtual destructor // That's OK!
所以,如果一个类设计用来被继承的话,那么它的析构函数应该被声明为virtual的。
Reference:
[1] Comparing C++ and C (Inheritance and Virtual Functions)
[2] C++对象布局及多态实现的探索
[3] Multiple inheritance and the this pointer 讲述多重继承下的类型转换问题
[4] Memory Layout for Multiple and Virtual Inheritance 详细描述了多重菱形多重继承下的对象内存布局以及类型转换
后记:当我完成了本篇%90的时候,我试图提交,谁知道登陆太久没有动作,session超时,让我重新登陆,然后提交的内容就全部不见了,剩下最开始的%10。。。。当时的心情啊,用吕大哥的话来说就是:寻死的心都有了。
发表评论
-
maven 在Mac OS下运行的问题总结
2014-05-16 17:24 851在maven下生成基本的项目结构。 生成eclipse项 ... -
【zz】 java函数参数类型后添加三点的用法
2012-07-02 09:48 1064今天看到一个没见过的函数参数列表test(int... a), ... -
【zz】Java编码的理解和Java加载器的理解
2012-06-08 15:59 772一,我对java中编码的理解1. 编码的产生 对电脑而言 ... -
类加载器入门级应用
2012-06-08 15:17 9511、类加载器负责加载 Ja ... -
ClassLoader详解
2012-06-08 14:23 1268Point One 将J2EE应用程序移植到W ... -
Java静态代理与动态代理
2012-05-29 10:32 965JAVA的静态代理与动态代 ... -
JDK的动态代理深入解析(Proxy,InvocationHandler)(转)
2012-05-29 10:31 5212调用处理器InvocationHandle ... -
zz 动态反射实现AOP的简单原理
2012-05-28 17:46 922其实AOP的意思就是面向切面编程. OO注重的是我们 ... -
理解Java枚举在单例模式的应用
2012-06-05 15:50 12995.3.9 单例和枚举 按照《高效Java 第二版》中的说 ... -
Java 枚举的介绍
2012-05-23 16:50 0一、使用简单程序完成枚举的功能 例:使用简单类完成枚举操作 ... -
枚举类型的用法
2012-06-05 15:50 1453DK1.5引入了新的类型——枚举。在 Java 中它虽然算 ... -
单例模式的七种写法 (包括1.5新特性)
2012-05-23 16:18 0第一种(懒汉,线程不安全): <!--<br / ... -
重写hashCode方法的意义
2012-05-23 16:01 1676Java中的很多对象都override了equ ... -
JDK Log的设计思想
2012-05-23 14:39 1335最近在看《Agile Java》,看到日志一节,收获颇多,所以 ... -
[zz] Synchronized和Static Synchronized区别
2012-05-23 14:07 805通过分析这两个用法的分析,我们可以理解java中锁的概念。一 ... -
双精度、单精度的有效位数
2012-05-22 17:25 5144浮点数7位有效数字。(应该是单精度数) 双精度数16位有效 ... -
DecimalFormat 使用方法
2012-05-22 16:44 1049我们经常要将数字进行格式化,比如取2位小数,这是最常见的。Ja ... -
Java Applet 无法运行的一个问题
2012-04-28 15:09 2560当你用JDK1.6开发出的新功能,在JDK1.6上re ... -
JDK1.5之中的Map相关的类
2012-04-26 10:14 1890java5的java.util包提供了大量集合类。其中最常用的 ... -
设计模式应用场景总结
2012-04-11 16:47 1295在J2EE的开发和测试中,其实不知不觉已经使用了许多设计模式。 ...
相关推荐
### C++和Java多态的区别 #### 一、概述 多态是面向对象编程语言中的一个核心特性,它允许程序员能够使用基类的指针或引用指向派生类的对象,并在运行时根据对象的实际类型来选择合适的方法进行调用。这一特性增强...
《C++/Java代码分析器详解》 在编程领域,理解和分析代码是提升技能的关键步骤。本文将深入探讨“C++/Java代码分析器”,这是一个专为C++和Java编程爱好者设计的工具,旨在帮助他们更好地理解、分析代码,从而提升...
"蓝桥杯历年C/C++/JAVA真题"是一份集合了历年来蓝桥杯大赛中C、C++和Java编程语言竞赛试题的资源。蓝桥杯大赛是中国颇具影响力的计算机编程竞赛之一,旨在促进大学生的创新能力和工程实践能力,尤其在软件和信息技术...
"学习C/C++/JAVA语言的必看经典例题"这个资源集合了这三种语言的经典实例,旨在帮助学习者深入理解语言特性,提升编程能力。 C语言,作为基础的系统级编程语言,它的例题通常涵盖变量、数据类型、运算符、控制结构...
【蓝桥杯竞赛练习题的题解(C/C++/Java)】 蓝桥杯全国软件与信息技术专业人才大赛是一项旨在推动软件和信息技术行业发展、培养创新型工程科技人才的重要赛事。该比赛涵盖了C、C++和Java等多种编程语言,为参赛者提供...
在IT行业的面试中,C++和Java作为两种广泛使用的编程语言,经常出现在笔试和面试的题目中。这两种语言各有特点,适用于不同的应用场景。本篇文章将深入探讨C++和Java的面试知识点,帮助求职者做好充分准备。 对于...
C++和Java都是支持多态的语言,但它们在实现多态的方式上有所不同。本文将深入探讨这两种语言中的继承类多态,并通过实例代码进行解析。 在C++中,多态性主要依赖于虚函数。在上面的示例中,`Animal` 类没有声明...
总的来说,2012年10月的搜狗C++/JAVA校园招聘笔试题旨在考察应聘者的编程基础、问题解决能力和逻辑思维。对于准备这类考试的求职者,建议深入学习并熟练掌握上述知识点,同时不断通过实践来提升自己的编程能力。
- Java 8及以上新特性:Lambda表达式、流API、方法引用来实现函数式编程。 3. **C#**:C#是微软公司推出的一种.NET框架下的编程语言,常用于Windows应用程序开发。面试中可能涉及的C#知识点包括: - 基础语法:类...
继承和多态是面向对象编程的特性,继承允许子类继承父类的属性和方法,多态则允许子类对象替代父类对象。重写是指子类对父类方法的再次实现,回调函数则是在特定事件发生时调用的函数,常用于异步编程和事件驱动模型...
C++ 是 C 语言的扩展,增加了面向对象编程(OOP)的概念,如类、对象、封装、继承和多态,使得它更适合大型软件项目的开发。 知识点包括: 1. C 语言的基本语法:变量、数据类型、运算符、控制结构(如条件语句、...
在程序面试过程中,掌握C,C++,Java以及软件测试的知识是至关重要的。这份资源集合了这四个领域的面试和笔试题目,旨在帮助求职者更好地准备技术面试,提升自身的竞争力。 一、C语言与C++面试题汇总 1. C语言基础...
### 0854考研复试综合面试速成(1):C/C++/Java常识 #### C语言与C++语言的基础对比 - **C语言**: - 面向过程,强调算法和数据结构。 - 程序设计侧重于如何通过一系列步骤处理输入并产生输出。 - 不具备类、...
1. **面向对象**:封装、继承、多态的基本概念及实现方式。 2. **STL**:标准模板库,包括容器(如vector, list, map)、迭代器、算法等。 3. **模板**:函数模板、类模板的使用和理解。 4. **异常处理**:try-catch...
在IT行业中,C、C++和Java是三大基础且重要的编程语言,广泛应用于系统级编程、游戏开发、软件工程以及互联网应用等多个领域。面试时,掌握这些语言的关键知识点和技术是必不可少的。以下是一些根据标题和描述提炼出...
- **继承与多态**:继承的基本概念、基类与派生类、虚函数与抽象类、多态的实现方式。 - **模板**:模板的基本概念、函数模板与类模板的定义与使用。 - **异常处理**:try-catch-finally机制、抛出异常与捕获异常的...
Java的多态主要依赖于方法的覆盖和接口的实现,所有引用类型的方法调用都会在运行时解析到实际对象的方法。 语法和特性差异: 1. C++有指针,可以直接操作内存,而Java没有裸指针,使用引用来代替,内存管理由垃圾...
3. **模板与泛型**:C++的模板提供了静态多态,而Java通过泛型实现了类似功能。转换器需要将模板转换为Java的泛型,但要注意Java泛型的边界和限制。 4. **异常处理**:C++的异常处理与Java的有所不同。转换器需要将...