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