我正在阅读Smalltalk的介绍.在C++中,类中声明的函数可以由该类的对象调用,类似地,在Smalltalk中,一个关键字(称为message)与对象的名称相邻.(不知道多少,但也想在这里询问是否在回复消息时,是否有执行的独特方法?)
基本上,对于我天真的想法,这似乎只是语法风格的差异.但是,我想知道在内部编译或内存结构方面,这种调用的差异是否具有任何意义.
提前致谢.
PS:我向你们所有人倾诉你的时间和答案.非常感谢.
Ber*_*erg 15
根本区别在于,在Smalltalk中,消息的接收者可以完全控制消息的处理方式.它是一个真正的对象,而不是具有在其上运行的函数的数据结构.
这意味着在Smalltalk中,您可以向任何对象发送任何消息.编译器对此没有任何限制,它们都在运行时处理.在C++中,您只能调用编译器知道的函数.
此外,Smalltalk消息只是符号(唯一字符串),而不是C++中的内存中的函数地址.这意味着可以通过交互方式或通过网络连接轻松发送消息.有一种perform:方法可以让您发送给定字符串名称的消息.
对象甚至接收它未实现的消息.虚拟机检测到该情况并创建一个Message对象,然后发送该messageNotUnderstood:消息.同样,对象是如何处理该未知消息的唯一责任.大多数对象只是继承了引发错误的默认实现,但是对象也可以自己处理它.例如,它可以将这些消息转发到远程对象,或将它们记录到文件等.
Uko*_*Uko 12
您在C++中调用函数是因为在编译期间您知道将调用哪个函数(或者至少您在类层次结构中定义了一组有限的函数.
Smalltalk是动态类型和后期绑定的,所以在编译期间你不知道要评估哪个方法(如果有的话).因此,您发送一条消息,如果该对象具有该选择器的方法,则会对其进行评估.否则,引发"消息不理解"异常.
这里已经有了很好的答案.让我添加一些细节(最初,部分内容在评论中).
在普通的C中,每个函数调用的目标是在链接时确定的(除非你使用函数指针).C++添加了虚函数,调用时将调用的实际函数在运行时确定(动态调度,后期绑定).函数指针在某种程度上允许自定义调度机制,但您必须自己编程.
在Smalltalk中,动态调度所有消息发送.在C++术语中,这大致意味着:所有成员函数都是虚拟的,并且没有独立的函数(总有一个接收器).因此,Smalltalk编译器永远不会*决定消息发送将调用哪个方法.相反,调用的方法在运行时由实现Smalltalk的虚拟机确定.
实现虚函数调度的一种方法是虚函数表.Smalltalk中的近似等价物是方法词典.但是,这些字典是可变的,与典型的虚函数表不同,后者由C++编译器生成,并且在运行时不会更改.所有Smalltalk行为(Behavior作为超类Class)都有这样的方法字典.正如@ aka.nice在他的回答中指出的那样,可以查询方法词典.但是,当Smalltalk系统运行时,也可以添加(或删除)方法.当Smalltalk VM调度消息send时,它会搜索接收者超类链的方法字典以获取正确的方法.通常有缓存可以避免查找的重复成本.
另请注意,消息传递是对象在Smalltalk中进行通信的唯一方法.两个对象无法访问彼此的实例变量,即使它们属于同一个类.在C++中,您可以编写打破此封装的代码.因此,消息发送是Smalltalk的基础,而在C++中,它基本上是一个可选功能.
在C++,Java和类似语言中,还有另一种称为函数重载的调度形式.它仅在编译时发生,并根据调用站点上声明的参数类型选择一个函数.你不能在运行时影响它.Smalltalk显然不提供这种形式的调度,因为它没有静态类型的变量.尽管如此,仍然可以使用诸如双重调度之类的习语来实现.其他语言,例如Common Lisp的CLOS或Groovy,提供了更通用的多分派,这意味着将根据接收者的类型和所有参数的运行时类型选择方法.
*一些特殊的消息,例如ifTrue: ifFalse: whileTrue:通常直接编译到条件分支和字节码中的跳转,而不是消息发送.但在大多数情况下,它不会影响语义.
以下是您在C++中找不到的一些示例
在Smalltalk中,您可以通过发送消息(根据方言取决于超类或命名空间)来创建新类.
在Smalltalk中,您通过向编译器发送消息来编译新方法.
在Smalltalk中,通过发送消息来打开调试器以响应未处理的异常.所有异常处理都是在发送消息方面实现的.
在Smalltalk中,您可以查询类的方法,或通过发送消息来收集其所有实例.
更简单地说,所有控制结构(分支,循环,......)都是通过发送消息来执行的.
它的消息一直在下降.