logo
天地变化的道理
使用率很高网站
生活要常常分享
您身边百科全书
免费为您秀产品
Smalltalk
Smalltalk Smalltalk是一种动态类型、反射式的面向对象编程语言。Smalltalk由艾伦·凯、Dan Ingalls、Ted Kaehler、Adele Goldberg等于1970年代在施乐帕罗奥多研究中心开始开发。 Smalltalk对其它众多的程序设计语言的产生起到了极大的推动作用,特别是Objective-C、CLOS、Python和Ruby等。1990年代涌现的许多软件开发思想都得益于Smalltalk,例如设计模式、敏捷编程和代码重构等。 概述. Smalltalk和许多程序设计语言不同,它不仅仅是一门语言。下面从几个不同的角度来解释Smalltalk。 历史. 最早的Smalltalk原型由艾伦·凯于1970年代初提出。类(来自Simula 67)、海龟绘图(来自LOGO)以及图形用户界面(来自Sketchpad等先驱系统)等概念的有机组合,构成了Smalltalk的最初的蓝图。 在1971年到1975年之间,艾伦·凯在Xerox PARC的小组,在Xerox Alto计算机上,设计并实现了第一个真正的Smalltalk语言系统,编译器由Dan Ingalls负责主要实作。这个系统被称为Smalltalk-71与Smalltalk-72,具有以下几个技术创新: 开发环境的革新相当迅速。虽然当时的位图显示器十分昂贵,但是艾伦·凯却说服了PARC,让他使用这些位图显示器,这使得艾伦·凯和他的小组,能够实现不同大小和字体的文字,使用多窗口环境,以及一些对图像处理的高端支持。Smalltalk-72影响了演员模型的发展,它的语法和执行模型,与现代的Smalltalk变体有着显著的差异。 在1975到1976年间,艾伦·凯小组认识到应当对执行效率和规模进行优化。于是他们在许多重要方面重新设计了Smalltalk系统,被称为Smalltalk-76,它在语言上: 前述的所有Smalltalk系统,都是在特殊的硬件上实现的,直到1977年至1978年,Bruce Horn和Ted Kaehler把Smalltalk-76移植到上,它是由Intel 8086处理器和自定显示器所组成的硬件环境。虽然这种硬件环境只生产了10台,但是它证明了在通常的处理器上实现Smalltalk的可能性。 在1979至1980年,部分受NoteTaker项目的影响,Smalltalk小组的注意力转移到Smalltalk的销售可行性上。小组设计并实现了新一代的Smalltalk系统,这次修改的目标着重于在标准硬件上的移植性等方面,被称为Smalltalk-80,它包括: Smalltalk-80是在PARC之外能获得到的第一个语言变体,最初作为Smalltalk-80版本1,给与了少数公司(惠普、苹果公司、泰克和DEC)和大学(UC Berkeley),用于同行评审和在它们自己的平台上实现。后来在1983年普遍可获得的实现,叫做Smalltalk-80版本2,发行为虚拟机规定和映像(具有对象定义的独立于平台的文件)。 1988年Xerox PARC为了将Smalltalk推向市场而成立了分拆公司ParcPlace Systems。ANSI Smalltalk自从1998年来是标准的语言参考。 两个当前流行的Smalltalk实现变体,是这些最初Smalltalk-80映像的后代。Squeak是开源实现,它经由Apple Smalltalk,派生自Smalltalk-80版本1.03。经由Smalltalk-80 2.5和ObjectWorks(二者都是ParcPlace Systems的产品),派生自Smalltalk-80版本2。 面向对象编程. 如同其他面向对象语言,Smalltalk-80(而非Smalltalk-72)的中心概念是“对象” 。一个对象总是一个“类”的一个“实例”。类是描述它们的实例的属性和行为的“蓝图”。例如,一个GUI窗口类,可以声明窗口拥有的属性,比如标签、位置和窗口是否可见。这个类还可以声明其实例支持的操作,比如打开、关闭、移动和隐藏。每个特定窗口对象,对这些属性都有自己的值,它们每个都能进行它的类定义的操作。 Smalltalk对象确切的可以做三件事: 一个对象持有的状态总是私有于这个对象。其他对象只能通过发动请求(消息)至这个对象,来让它做出查询或变更这个状态。任何消息可以发送给任何对象:当接收到一个消息的时候,接收者确定这个消息是否合适。Alan Kay评论说,尽管关注于对象,消息才是Smalltalk中最重要的概念:“最大的想法是消息传递,它是Smalltalk/Squeak核心的全部意义所在(它是我们在Xerox PARC阶段从未真正完成的某种东西)。” 不同于多数其他语言,Smalltalk对象可以在系统运行的同时进行修改。现场编码和飞速应用补丁,是Smalltalk的主导编程方法论,并且是它高效的主要原因。 Smalltalk是“纯”面向对象编程语言,这意味着,不像C++和Java,在作为对象的值和作为原始类型的值之间没有区别。在Smalltalk中,原始值比如整数、布尔值和字符,也是对象,这么说的意义在于它们也是相应类的实例,而且要发送消息来调用在它们上的运算。编程者可以通过子类,改变或扩展实现原始值的类,使得可以向它们的实例定义新行为,例如实现一个新的控制结构,甚至使得它们现有行为得以改变。这个事实被总结成常听到的一句短语:“在Smalltalk中,所有东西都是对象”,它可以更精确的表达为:“所有的值都是对象”,因为变量不是。 因为所有的值都是对象,类也是对象。每个类都是这个类的元类的一个实例。元类都是codice_1(元类类)的实例,它也是对象,并且是codice_2(元类元类)的实例。代码块是Smalltalk表达匿名函数的方式,它也是对象。 Hello, World!例子. Hello, World!程序,实质上被所有计算机语言的课本用作要学习的第一个程序,它展示了这个语言的最基本语法和环境。对于Smalltalk,这个程序可极其简单的书写。下列代码中,消息codice_3被发送给对象codice_4,具有字符串文字codice_5作为它的实际参数。调用codice_3方法,导致它的实际参数,即字符串文字codice_5,显示在叫做“副本”(Transcript)的终端窗口: Transcript show: 'Hello, World!'. 注意需要打开Transcript窗口,来看到这个例子的结果。 语法. Smalltalk-80语法是相当极简主义的,只基于了一小把的声明和保留字。事实上,Smalltalk中只保留了六个“关键字”:codice_8、codice_9、codice_10、codice_11、codice_12和codice_13。它们的准确术语是“伪变量”,是服从变量标识符规则的标识符,但指示了编程者所不能变更的绑定。codice_8、codice_9和codice_10伪变量是单例实例。codice_11和codice_12,在响应一个消息而激活的方法中,指称这个消息的接收者;但是发送给codice_12的消息,在这个方法的定义类的超类中查找方法,而非这个接收者的类中,这允许子类中的方法调用在超类中的同名方法。codice_13指称当前的活动记录。 内建的语言构造只有消息发送、赋值、方法返回和某些对象的文字语法。从它最初作为给所有年龄儿童的语言开始,标准的Smalltalk语法以更像英语,而非主流编码语言的方式使用标点符号。语言余下部份,包括用于条件求值和迭代的控制结构,都由标准Smalltalk类库实现在内建构造之上。出于性能上的原因,实现可以识别并特殊处理某些这种消息,但这只是优化而并未硬性规定入语言语法。 谚语“Smalltalk语法适合一张明信片”,所提及的是的一个代码片段,展示了一个方法的所有基本标准语法元素: exampleWithNumber: x | y | true & false not & (nil isNil) ifFalse: [self halt]. y := self size + super size. #($a #a 'a' 1 1.0) do: [ :each | Transcript show: (each class name); show: (each printString); show: ' ' ]. ^x 4 将消息codice_57发送给codice_54具有实际参数codice_47(回答将是codice_9)。 注意,Smalltalk-80语言自身,不包含着这些算符的含义。上述的结果,都只是这些消息的接收者(这里是数值实例),为了响应消息codice_53和codice_57而定义并返回的。这个机制的副作用是运算符重载,消息codice_57可以被其他对象所理解,允许使用形如codice_64的表达式来比较它们。 表达式. 一元消息可以一个接一个的写成方法链式调用: 3 factorial factorial log 它发送codice_40到codice_54,接着发送codice_40到前面的结果codice_50,接着发送codice_69到前面的结果codice_70,产生最终的结果codice_71。 一个表达式可以包括多次消息发送。在这个情况下,表达式依据一个简单的优先级次序来分析。一元消息有最高的优先级,随后是二元消息,最后是关键字消息。例如: 3 factorial + 4 factorial between: 10 and: 100 被求值如下: 最后的消息发送的回答,是整个表达式的结果。 组合. 在需要的时候使用圆括号可以改变求值的次序。例如: (3 factorial + 4) factorial between: 10 and: 100 将改变表达式含义,首先计算codice_87产生codice_84。接着codice_84接收第二个codice_40消息,产生codice_91。codice_91接着接收codice_83,回答codice_9。 注意由于二元消息的含义,不是硬性规定入Smalltalk-80语法的,它们全部都被认为有相等的优先级,并简单的从左至右来求值。因此,使用二元消息的Smalltalk表达式的含义,可能不同于传统释义: 3 + 4 * 5 被求值为codice_95,产生codice_96。要得到预期回答codice_97,必须使用圆括号来显式的定义运算次序: 3 + (4 * 5) 复合. 以点号分隔的表示式按顺序执行。注意在变量定义和随后的表达式之间没有点号。一个表达式序列的值,是最后的表达式的值。除了最后的表达式之外,所有的表达式的值都被忽略。注意点号是分隔符而并非终结符,因此最终的点号是可选的。 下列(假想)例子中,书写了一序列的表达式,每个都用点号分隔。这个例子首先建立类codice_98的一个新实例,存储它在一个变量中,接着向它发送两个消息: window := Window new. window label: 'Hello'. window open 级联. 如果像上述例子这样,将一序列消息都发送给相同的接收者,它们也可以写为方法级联调用,具有用分号分隔的单独消息: Window new label: 'Hello'; open 这种将前面例子的重新为一个单一表达式,避免了对将新窗口存储在临时变量的需要。依据平常的优先级规则,首先向codice_98类发送一元消息codice_100,接着向codice_100回答的那个对象,发送codice_102和codice_103。 可以使用codice_104消息来返回一个级联消息的接收者。 代码块. 块是头等对象。代码块即匿名函数,可以被表达一个文字值(它是一个对象,因为所有值都是对象)。这是通过方括号达成的: [ :params | ] 这里的codice_105是代码可以接受的形式参数的列表。结果的块对象可以形成一个闭包:它可以在任何时间访问它外围的词法作用域内的变量。这意味着下列Smalltalk代码: [:x | x + 1] 可以理解为:formula_1,或用λ演算表达为:formula_2 : formula_3。 块可以通过发送给它们codice_106消息来执行。块有一个参数用codice_107,有2个参数使用codice_108,以此类推直到4个参数,对多于4个参数使用codice_109并将参数作为数组传递。例如下面的表达式: [:x | x + 1] value: 3 可以被求值为:formula_4,或用λ演算表达为:formula_5。 块返回(常称为回答)其主体的最后一个表达式的值,除非有一个由显式的codice_110指示的返回,这时返回这个返回表达式的值。在块内部的返回,充当了一种逃出(escape)机制。在一个嵌套的块表达中的返回表达式,将终止在字面上包围的方法。 块的文字表示是一种创新,它一方面允许特定代码有更重大的可读性;它允许涉及迭代的算法一更清晰和简洁的方式编码。典型的在某些语言中使用循环写成的代码,可以在Smalltalk中使用块简洁的书写,有时在单一一行之内。更加重要的,块允许使用消息和多态来表达控制结构,因为块推延了计算,而多态可以用来选择交替者(alternative)。所以在Smalltalk 80中,codice_111被书写和实现为: expr ifTrue: [ expr为真时求值的语句 ] ifFalse: [ expr为假时求值的语句 ] 再举一例,向一个搜集发送消息codice_112: positiveAmounts := allAmounts select: [:anAmount | anAmount isPositive] 注意这与函数式编程有关,这里的计算模式被抽象成了高阶函数。codice_112等价于在一个适当的函子上的高阶函数filter。 控制结构. 在Smalltalk中控制结构没有特殊的语法。它们转而实现为发送到对象上的消息。以条件执行为例,布尔类codice_114定义了codice_115、codice_116、codice_117和codice_118方法。比如向一个布尔对象,发送codice_115消息,并传递一个代码块作为实际参数,这个块被执行当且仅当布尔接收者为真。下面用一个例子来展示: result := a > b ifTrue: [ 'greater' ] ifFalse: [ 'less or equal' ] 块也被用来实现,用户定义控制结构、枚举器、访问者、异常处理、可插拔的行为和很多其他模式。 迭代. 下面例子,从一个字符串中过滤出其中所含有的元音字符: aString := 'This is a string'. vowels := aString select: [:aCharacter | aCharacter isVowel]. 在最后一行,向字符串对象codice_120发送一个codice_112消息,它具有一个代码块codice_122作为实际参数。这个代码块,表示一个测试,代码块文字将被用作一个谓词函数,它回答codice_8,当且仅当这个字符串的一个元素codice_124,应当被包括在满足这个测试的字符搜集之中。 字符串类codice_125响应codice_112消息,要调用的codice_112方法,定义并实现在搜集类codice_128中;它将给codice_112的实际参数选择块,传送给形式参数codice_130;然后将绑定了选择块的codice_130嵌入到迭代块的代码之中,再把这个迭代块作为向字符串自身发送的codice_132消息的实际参数,从而将这个字符串所包含的每个字符,都作为实际参数传送给这个迭代块,而各做一次求值。在求值迭代块的时候,通过codice_107消息,将迭代元素传送给codice_130所绑定的选择块,它回答一个布尔值;接着向它发送codice_115消息,如果这个布尔值是对象codice_8,则将这个字符增加到要返回的字符串中。 字符串类codice_125响应codice_132消息,要调用的codice_132方法,定义在可迭代类codice_140中,而实现在可序列化搜集类codice_141中,这个类是codice_140类的子类和codice_125类的超类。 异常处理. Smalltalk的异常处理机制,codice_144类及其子类比如codice_145类,类似于CLOS的异常处理样式,使用块作为处理器: [ 一些运算. Error signal: 'an error occurred'. 另一些运算 ] on: Error do: [ :ex | 处理器代码. ex return ] 异常处理器的codice_146实际参数,提供对挂起运算的状态的访问,比如它的栈帧、行号、接收者和实际参数等,并且通过发送codice_147、codice_148、codice_149或codice_150之一,还可用来控制计算怎样继续。 类. 类通过实例变量定义它的实例的结构,通过方法定义它的实例的行为。每个方法都有叫做选择子的一个名字,它在这个类之内是唯一性的。 定义. 下面是个平凡的类定义: Object subclass: #MessagePublisher instanceVariableNames: " classVariableNames: " poolDictionaries: " category: 'Smalltalk Examples' 多数这种定义经常由编程环境来填充。这里的类定义是给codice_151类的一个消息,用来建立它叫做codice_152的一个子类。 在Smalltalk中类是头等对象,它可以就像任何其他对象一样接收消息,并可以在执行时间动态的创建。codice_151类在收到这个codice_154消息之时,原则上首先在其元类codice_155中查找对应方法,未果而上溯继承链在其超类codice_156类中找到对应方法实现。 在Smalltalk中,是这个实例的私有变量,它可以在定义它们的类的任何实例方法中,还有在它的子类中定义的方法中,通过名字来访问。在需要于一个类的所有实例、这个类本身和它的子类之间,共享某个数据的时候,要使用,它是由这个类和它的所有实例共享的私有变量。池变量是在可以没有继承关联的多个类之间共享的变量。池变量最初存储在池字典中,现在它们应当被定义为专门的类(codice_157的子类)的类变量。 codice_158指示有关的类的“群组”,在现代Smalltalk版本如Pharo中被替代为codice_159,包系统是利用简单的命名约定,组织Squeak和Pharo源代码的简单而轻量级的方式。 在一个类所对应的元类中定义的实例变量叫做类实例变量,每个类都有自己私有的类实例变量,子类将继承这些类实例变量,但是子类会拥有这些变量的它们自己的私有复本,类和它们的子类不分享类实例变量。例如,可以定义一个类实例变量叫做codice_160来跟踪一个给定的类有多少实例。 类不能直接访问它的实例的实例变量,而实例不能访问它们的类的类实例变量。如果需要的话必须定义变异子与访问子。 方法. 当一个对象接收到一个消息的时候,调用匹配这个消息名字的一个方法。所有方法都是公开的和虚拟的(也就是动态查找的)。方法被分组入指示它们意图的协议(protocol)之中。 为一个类增加方法涉及到codice_161类,其中的codice_162方法,编译一个方法源代码并返回一个codice_163类的实例;codice_164方法,将给定的一个编译过的方法增加到方法字典中。在codice_161类的子类codice_166类中,codice_167方法,编译一个方法的源代码,并为这个方法指派上给定的归类(对于方法称为协议)。 对象负责在运行时间动态的确定,执行哪个方法来响应一个消息,尽管在很多语言中,这可能是(有时或总是)在编译时间静态确定的。下列代码定义一个方法codice_168,并且这个定义将在这个对象收到codice_168消息的时候发生。 publish Transcript show: 'Hello World!' 下列名字为codice_170的方法,演示了接收多个实际参数并返回一个值: quadMultiply: i1 and: i2 "这个方法将给定的两个数相乘并对结果乘以4." | mul | mul := i1 * i2. ^mul * 4 执行任何前导了codice_110(脱字符或codice_172)的表达式,都导致这个方法于这一点退出,并返回这个表达式的值。终止而没有显式返回某个表达式的一个方法,将隐含的返回自身。 实例化. 为一个类新建一个实例,要用到codice_100方法,它定义在codice_161类中。下列例子代码: MessagePublisher new 建立并返回codice_152类的一个新实例。它典型的会被赋值到一个变量: publisher := MessagePublisher new 但是也可以向一个临时的匿名对象发送一个消息: MessagePublisher new publish 类方法. 类方法就是在一个类的元类中定义的方法。 比如搜集类codice_128的类方法中,实例创建方法有codice_177、codice_178、一直到codice_179方法。在下面的例子中,将codice_178用于有序搜集类codice_181,它是codice_141类的子类,故而能最终上溯至超类搜集类: rectangles := OrderedCollection with: (Rectangle left: 0 right: 10 top: 100 bottom: 200) with: (Rectangle left: 10 right: 10 top: 110 bottom: 210). aPoint := Point x: 20 y: 20. collisions := rectangles select: [:aRect | aRect containsPoint: aPoint]. 反射. 反射是一个计算机科学术语,适用于有能力检查它们自己的结构的软件程序,例如检查它们的分析树或输入和输出参数的数据类型。反射是动态、交互式语言比如Smalltalk和Lisp的一个特征。具有反射的交互式程序(要么解释的要么编译的)维护所有内存内对象的状态,包括代码对象自身,这是在解析/编译期间生成的,并且是在编程上可访问和修改的。 反射也是Smalltalk这种有元模型的语言的一个特征。元模型是描述这个语言的模型,开发者可以使用元模型来做事,比如游历、检查和修改一个对象的分析树,或找到特定种类的结构的所有实例(例如在元模型中codice_183类的所有实例)。 Smalltalk-80是完全的反射式系统,用Smalltalk-80语言实现。Smalltalk-80提供了结构性和计算性反射二者。Smalltalk是结构性反射式系统,其结构是由Smalltalk-80对象定义的。定义这个系统的类和方法也是对象,并且完全是它们所有助力定义的系统的一部份。Smalltalk编译器将文本源代码编译成方法对象,典型是的codice_163的实例。通过把它们存储入一个类的方法字典,而增加到这个类。类层级的定义类的那部份,可以向系统增加新类。这个系统是通过运行建立或定义类和方法的Smalltalk-80代码来扩展的。Smalltalk-80系统是个现场(living)系统,承载着在运行时间扩展自身的能力。 因为类是对象,可以向它们提问比如:“你实现了哪些方法?”或“你定义了什么字段/槽/实例变量?”。所以通过能应用于系统中的任何对象的普通的代码,对象可以轻易的检查、复制、(去)序列化,诸如此类。 Smalltalk-80还提供计算性反射,有能力观察系统的计算状态。在派生自最初Smalltalk-80的语言中,一个方法的当前活动(activation),可以作为通过伪变量命名的一个对象来访问,这个伪变量是作为六个保留字之一的codice_13。通过发送消息至codice_13,一个方法活动可以提问比如:“谁给你发送了这个消息?”。这些设施使得有可能实现协程,或类似Prolog的回溯,而不需要修改虚拟机。异常系统也是使用这个设施实现的。这个设施更有趣的用法之一,是在 web框架之中,它通过为每个编辑的页面存储续体,并在它们之间切换来导航一个web站点,缓解了编程者处理Web浏览器的返回按钮的复杂性。使用Seaside编程web服务器,可以使用更常规的编程风格来完成。 Smalltalk如何使用反射的一个例子,是处理错误的机制。当一个对象被发送了一个它没有实现的消息的时候,虚拟机发送给这个对象codice_187消息,具有这个消息的实化作为实际参数。这个消息(它是另一个对象,是codice_188的实例),包含这个消息的选择子和它的实际参数的一个codice_189。在交互式Smalltalk系统中,codice_187的缺省实现,是打开一个错误窗口(一个codice_191)向用户报告错误。通过它和反射设施,用户可以检查错误在其中发生的上下文,重新定义犯错的代码,并继续,这一切都在这个系统之中,使用Smalltalk-80的反射设施。 通过建立只理解(实现)codice_187的一个类,可以建立一个实例,经由它的codice_187方法能拦截发送给它的任何消息。这种实例可以叫做透明代理(proxy)。可以使用这种代理来实现很多设施,比如分布式Smalltalk,这里的消息在多个Smalltalk系统之间交换,和数据库接口,这里的对象透明的从数据库中排除错误,还有promise等。分布式Smalltalk的设计影响了如CORBA这样的系统。 基于映像的持久存储. 多数流行的编程系统,将静态的程序代码(以类定义、函数或过程的形式),分离于动态的或运行时间的程序状态(比如对象或其他形式的程序数据)。它们在程序启动的时候装载程序代码,而任何先前的程序状态必须从配置文件或其他数据源显式的重新建立。程序(和编程者)未显式保存的设置,在每次重启时都必须再次设立。传统的程序在每次程序保存一个文件、退出和重载的时候,还失去很多有用的文档信息。这会失去细节比如回退历史或光标位置。基于映像的系统不会因为计算机关闭或OS更新,而强制失去所有这些东西。 但是很多Smalltalk系统,不区分程序数据(对象)和代码(类)。事实上,类也是对象。因此,多数Smalltalk系统,存储整个程序状态(包括类和非类对象二者)在一个文件之中。这个映像可以接着由Smalltalk虚拟机装载,将类Smalltalk系统恢复成先前的状态。这是受到了FLEX的启发,它是Alan Kay创建的语言并描述于他的科学硕士毕业论文中。 Smalltalk映像类似于(可重启的)核心转储,并可以提供与核心转储相同的功能,比如延迟或远程调试,具有对出错时刻的程序状态的完全访问。将应用代码建模为某种形式的数据的其他语言比如Lisp,也经常使用基于映像的持久存储。这种持久存储的方法,对于快速开发是强力的,因为所有开发信息(比如程序的解析树),都保存而利用于调试。但是它作为一个真实的持久存储机制,也有一个严重的缺点。首先,开发者可能经常想要隐藏实现细节,并使它们在运行时间不可获得。出于法律和维护的原因,允许任何人在运行时间修改程序,对于在运行时间环境不暴露源代码的编译后的系统,不可避免的介入复杂性和潜在的错误。其次,尽管持久存储机制易于使用,它缺乏多数多用户系统需要的真正持久存储能力。最明显的是进行同多个用户并行访问相同的数据库的事务。 实现列表. OpenSmaltalk. OpenSmaltalk VM(OS VM)是Smalltalk运行时环境的著名实现,很多现代Smalltalk VM基于或派生自它。OS VM自身是从一组Smalltalk源代码文件(它们叫做VMMaker),转译成原生C语言源代码(通过使用叫做Slang的转译器),它依次再针对特定平台和硬件架构来编译,实际上确使Smalltalk映像的跨平台执行。源代码可以在GitHub上获得并在MIT许可证下发布。OS VM的知名派生者有: JavaScript VM. 其他. 外部链接. -{H|zh-hans:重定向;zh-hant:重新导向;}--{H|zh-cn:字符;zh-tw:字元;}--{H|zh-hans:文件; zh-hant:档案;}--{H|zh-hans:快捷方式; zh-hant:捷径;}--{H|zh-hans:项目;zh-hant:专案;zh-tw:计划;zh-hk:计划;zh-mo:计划;}--{H|zh-cn:计算机; zh-sg:电脑; zh-tw:电脑;}- -{H|zh-hans:汇编语言;zh-hant:组合语言}- -{H|zh-hans:标记语言;zh-hant:置标语言}-
Smalltalk
本站由爱斯园团队开发维护,感谢
那些提出宝贵意见和打赏的网友,没有你们的支持,
网站不可能发展到今天,
继往开来,善终如始,我们将继续砥砺前行。
Copyright ©2014 iissy.com, All Rights Reserved.