这个世界根本没有什么面向对象!

面向对象可以说是各大语言一个重要的特性了,不过如果我们换个角度,在内存中看看对象的布局,就会发现根本没有什么面向对象,只有面向过程。 

让我们从一个简单的Shape类开始,这个类有两个字段int x, int y, 它们在内存中是这么存放的: 

非常容易理解,对吧?

再来看一下继承, class Circle继承了Shape,增加了一个字段radius, Circle对象在内存中是这样的: 

这也没什么大不了的,但是这里只是字段(x,y,radius), 如果Shape类有个方法:draw(),在内存中该怎么放?  

首先,不能把draw()方法都放在每个对象上,那样就需要复制很多份,太浪费了。 

我们可以把这个draw()方法在内存中生成一份, 然后在每个对象上增加一个指针,指向这个draw()方法就行了。 

 (三个Shape对象,都指向了同一个代码) 

但是这么做也有问题, 如果Shape类又增加了一个方法 move() ,那每个对象都需要记录move方法的指针: 

如果方法很多,对象也很多,还是浪费! 

很明显,我们需要一个中间层, 用这个中间层把所有函数指针都记下来。这个中间层就是所谓的虚函数表: 

每个类,只要维持一个虚函数表就可以了。 

每个对象,只要记录一个虚函数表的地址就可以了。 

当然,也可以在虚函数表中记录一些关于这个类的相关信息,不是本文的重点,就不展开了。 

为什么叫做虚函数表呢?这个概念可能是从C++中来的,在C++中有个关键字virtual ,修饰一个函数的时候,这个函数就会变为虚函数,在调用时就具备了多态的行为。(注:在Java中,一个类的函数默认都是虚函数) 

那多态到底是怎么实现的呢? 

非常简单,只要把虚函数表给设置好就行了。假设子类Circle 也定义了一个move 函数,把父类Shape的move函数覆盖了,在内存将会是这个样子:

当你调用circle.draw()的时候,在虚函数表中找到的还是Shape类的draw()方法。 

但是当调用circle.move()的时候,就会从Circle类的虚函数表中找到Circle.move(),而不是Shape.move(),多态发生了! 

仔细看看上面这张图,在内存中,三个方法和两个对象是分开的,这里没有Class的概念,多态是通过虚函数表实现的。如果我们写程序的时候,写下这样的函数Shape_draw(), Shape_move(), Circle_move(),再写下Shape和Circle这样的数据结构,然后把他们用虚函数表连接到一起。也就实现了面向对象了。 

在内存中,“面向对象”已经褪去漂亮的包装,退化成“面向过程”, 退化成那个最基本的公式:程序 = 数据结构 + 算法。 

当然,在绝大部分情况下,程序员不需要手工地去实现这个虚函数表,这件事情应该交给机器去做。

对于C++,编译器可以在编译期间生成虚函数表。对于Java,编译出的字节码中是没有的,只有invokevirtual这样的指令,虚函数表是在类装入虚拟机的时候创建的。

(完)


关于作者:刘欣,码农翻身公众号作者,畅销书《码农翻身》作者,近 15年以上软件行业从业经验,前 IBM 架构师,领导过多个企业应用架构设计和开发工作,洞察技术本质,用故事讲解技术是拿手好戏。

往期精彩回顾

我是一个线程

我是一个Java Class

面向对象圣经

函数式编程圣经

TCP/IP之大明邮差

CPU阿甘

我是一个网卡

我是一个路由器

一个故事讲完HTTPs

编程语言的巅峰

Java:一个帝国的诞生

JavaScript:一个屌丝的逆袭

负载均衡的原理

阅读源码的三种境界

看到这里的都是老铁,双十一小礼物:

10本签名版码农翻身

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值