Smalltalk-超简明教程.docx_第1页
Smalltalk-超简明教程.docx_第2页
Smalltalk-超简明教程.docx_第3页
Smalltalk-超简明教程.docx_第4页
Smalltalk-超简明教程.docx_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Smalltalk 超简明教程Simberson 公司的David Buck 撰写的此文档是为C/C+/C#/Java 程序员介绍Smalltalk 的,而且已经得到他善意的许可而得以发布。如果你对正式的培训有兴趣请联系Simberson,咨询Smalltalk 培训。我听说很多Java 和C#程序员说他们看不懂Smalltalk 代码。Smalltalk 因为只有很少的语法,所以实际上它非常的简单。Smalltalk 的强大之处是它的环境和类库。本文意图通过代码对比,使Java 和C#程序员迅速理解Smalltalk。Copyright David Buck 2005 临时变量Smalltalk 不需要变量的类型声明。临时变量通过两个竖线来定义JavaSmalltalkint a;char b;float c;| a b c | 赋值Smalltalk 使用 := 赋值JavaSmalltalka = 5;a := 5 消息Smalltalk 有三种消息类型形式参数例子一元运算符以小写字母开始的字符和数字0squared二元运算符连接符1+关键字使用多个冒号终结文字和数字字符1或者多个do:,between: and:传输一个或多个参数时,需要使用关键字消息。每一个参数前面需要添加关键字。Smalltalk不使用和;分割参数。例子:JavaSmalltalkmyAccount.getBalance();myAccount.setBalance (10);myAccount.transfer(20,anotherAccount)myAccount.equals (anotherAccount);myAccount getBalancemyAccount setBalance: 10myAccount transfer: 20 to:anotherAccountmyAccount = anotherAccount 运算优先级:一元运算符(首先运算)二元运算符(其次运算)关键字(最后运算)在同等优先级下,运算是从左到右JavaSmalltalk3 + 5 * 6 /答案是333 + 5 * 6 “答案是:48”注意Smalltalk 版本,这个表达式实际上是两个消息:消息1接受者:3消息: +参数:5结果: 8消息2接受者: 8消息: *参数: 6结果:48 语句Smalltalk 使用句号分割语句。最后一条语句不需句号。JavaSmalltalkmyAccount.deposit(20);myAccount.transfer(20,anotherAccount);myAccount deposit: 20.myAccount transfer: 20 to:anotherAccount 常量Smalltalk 中整数类型,字符类型,字符串类型,布尔类型,浮点数类型和双精度浮点数类型都是头等类对象。整数类型是无限精度的,会按需自动增长,而不会溢出(译者注:跟内存大小有关)。实际上,Smalltalk 中没有char 类型,byte 类型,short 类型,int 类型或者long 类型的相对应的类型。它们都是整数类型。JavaSmalltalk55012308r12300x7f16r7f3r21012 (你可以选择你想用的基数)200L2e-52e-52e-5d2d-5h$hu03A9Character value: 16r3A9hellohellocantcant 特定词Smalltalk 中,nil 引用一个真实的对象,它是UndefinedObject 类的实例。true 是True类的实例,flase 是Flase 类的实例JavaSmalltalkthisselfnullniltruetruefalsefalsesuperbase (C#)super 方法返回JavaSmalltalkreturn value;value 级联JavaSmalltalkmyAccountdeposit: 20;transfer: 20 to: anotherAccount 注释JavaSmalltalk/* comment */ another commentcomment 实例创建Smalltalk 中,类是真实的对象。创建实例,只需向class 发送new。针对类的方法称为类方法(类似于Java 的static methods)。JavaSmalltalknew Reservation();Reservation new 构造函数Smalltalk 没有构造函数。如果你想执行实例初始化,你可以重定义“new”类方法来初始化实例。JavaSmalltalkReservation()startTime = new GregorianCalendar().getTime();endTime = new GregorianCalendar().getTime();Reservation class methods:newsuper new initializeReservation instance method:initializestartTime := Timestamp now.endTime := Timestamp now 块Smalltalk 称对象为块,它是包含可执行代码的对象。Java 中最相似的东西是匿名内部类,C# 2.0 中,跟匿名托管相类似。执行“块”无需参数,可以向它发送一个value 消息Smalltalk| block |block := 3 + 4.block value answer is 7块可以有参数,每一个块参数的声明必须以冒号(:)开头,以竖线(|)表示参数列表的结束和块代码的开始。Smalltalk| block |block := :x :y | x * 2 + y.block value: 5 value: 3answer is 13 语法结束到此为止,我们已经介绍了Smalltalk 所有的语法。其余的事情属于类库部分。你是否注意到我们是否忘记了某些事情?if-then-else 或者while 循环?Smalltalk 只使用块和普通的消息发送。 控制结构Smalltalk 没有在语言中内置控制结构。Smalltalk 使用向true 或者flase 对象发生消息作为替代。JavaSmalltalkif (tries 5)return Too many tries;elsereturn Trying again;tries 5ifTrue: Too many triesifFalse: Trying again注意表示从方法返回,不只是适用于块。 循环Smalltalk 使用块做循环。由于块是对象,所有我们可以先他们发生消息JavaSmalltalkint tries = 0;while (tries = 5) tryAgain();tries+;| tries |tries := 0.tries rotate(a,v); /For C+t.rotate(a,v); /For Java 上面的代码向对象t发送消息(注:就是调用类方法)rotate,并指定了参数a和v。为了理解这段代码,读者通常需要继续察看作为参数的变量的申明及其类型。让我们假设其有如下的申明: Transformation t;float a;Vector v; 在Smalltalk中,变量可以引用任何对象,因此在申明变量的时候没有必要指定变量的类型。例如: | t a v| 由于没有变量类型申明,好的Smalltalk程序员都会使用能暗示变量类型的变量名。因此,上面的申明我们通常如下表示: | aTransformation angle aVector| 但是后面请允许我继续使用先前的变量名,因为杂志专栏给我可利用的版面很小。让我们进一步消除那些不必要的东西来继续“优化”C+和Java的语法。例如,下面的代码应该仍然很好理解: t.rotate(a,v); /原文t rotate(a,v); /有谁需要句点吗?(t和rotate中间的圆点)t rotate a,v; /有谁需要括号吗? 为了进一步改进上面的语法,我们需要知道参数a 和 v 究竟表示什么。让我们假设整个示例的意思是“围绕端点v进行角度为a的旋转”。那么下一步可能如下: t.rotate by a around v; /有谁需要逗号吗? 可是如何才能知道上面这个语句中每个单词的意思呢?我们知道,在这个例子当中,t 是一个变量,rotate 是一个类方法的名称,by 是一个分隔符,a 是一个变量,around 又是一个分隔符,最后的 v 是一个变量。为了消除歧义,我们可以假设如下的变换:在所有的分隔符后面添加一个冒号。那么我们就得到下面的句子: t.rotate by: a around: v; /有谁需要歧义吗? 最终,我们要求所有的分隔符都是方法名称的一部分。也就是说,我们需要的方法的名称是“rotate by: around:”,最后让我们去掉空格,就成了“rotateby:around:”。我们最好将中间的单词开头大写,于是“rotateBy:around:”,因此我们的例子就变成了: t.rotateBy: a around: v; /这就是Smalltalk 也就是说方法名被分成了好几个部分。幸运的是将这些分开的部分想象成一个整体的名字并不困难。当一个类方法被定义的时候,我们可能会写成下面这样: self rotateBy: angle around: vector |result| result := COMPUTE ANWSER. result 在执行的时候,t和self,a和angle,v和vector有一对一的映射关系。需要注意的是表示返回,相当于return关键字。变量self则相当于this。如果方法的最后没有显式的返回表达式,则却省为self。也就是说,不写return语句也不会有什么问题。这同时也意味着无论调用者是否需要,类方法都将返回一个对象的引用。事实上,Smalltalk的语法要求self不能出现在函数名的最前面,如下所示: rotateBy: angle around: vector |result| result:= COMPUTE ANSWER. result Smalltalk的这种关键字语法的优点就是,针对不同的方法可以使用不同的关键字。例如,我们可以像下面这样定义第二个函数: t rotateAround: vector by: angle 没有必要刻意的去记住参数的顺序,因为关键字暗示了他们的顺序。当然,作为编程者也有可能滥用关键字。例如,如果我们像下面这样定义关键字: t rotate: angle and: vector 很明显,使用这个函数的人无法通过关键字确定参数的顺序。这是很不好的编程风格。如果只有一个参数的话,则无所谓。但是我们仍然需要只有一个关键字的方法名: t rotateAroundXBy: anglet rotateAroundYBy: angle 我更倾向于将关键字理解为一种说明性的参数。但是如果没有参数的时候该怎么办呢? t makeIdentity: /Can a colon at the end make sense? 如果关键字是说明性参数,而实际的参数却没有的话,那么我们就不是用关键字。因此一个没有关键字的消息(类方法)将写成下面这样: t makeIdentity /This is Smalltalk 当然,二元操作符(也是类方法,类似C+语言的operator)的定义中也可以使用关键字,但是一元操作符不用(上面的makeIdentity是一元消息,不是一元操作符)。当很多消息同时使用的时候,我们可能得到下面这样一个表达式: a negative | (b between: c and: d) ifTrue: a := c negated 作为读者,我相信你现在知道消息negative被发送到对象a(无参数),然后true或者false将被返回;between: c and: d被发送到对象b,并返回true或者false。然后对这两个返回结果进行或运算,得到的结果对象被作为ifTrue:a:= c negated消息的接受者。这是一个if-then条件表达式,但是并不像C+或者Java那样需要使用特别的语法。在Smalltalk中,这跟其他的类方法调用的语法没有任何差别,只不过消息的接收者是一个布尔对象,而消息的关键字是ifTrue,参数是a:= c negated(这实际上是一个block对象)。在Smalltalk中你将不会看到a:= -c这样的表达式,因为没有-这样的一元操作符。但是可以写成 5 这样的表达式,但是这里的负号被作为常量定义的组成部分。因此,当你看到诸如“-3.5 negated truncated factorial”这样的表达式时,你应该立即认识到这里没有关键字。因此-3.5必须作为negated消息的接收者;而得到的结果3.5则被作为truncated消息的接收者;进一步得到的结果3将作为factorial消息的接收者,最终得到结果6。当然,这里也存在优先级的问题,通常是从左到右,一次元消息优先级最高,多参数消息的优先级最低。对于编程的人来说这很重要,但是对于读者来说这并不影响其阅读和理解。哦,对了从左到右的优先级实际上意味着: 1+2*3 等于 9 操作符之间没有优先级之分。当然你也可能会看到下面这样的表达式,那是因为编写者知道不这样写可能会给不太熟悉Smalltalk的阅读者造成困扰: (1+2)*3 尽管这里的括号并不必要。 分号与句号的不同 大多数的非Smalltalk读者可能会认为分号是表达式的终结符,但在Smalltalk中使用的是句号。因此我们不会写下面这样的表达式: account deposit: 100 dollars;collection add: transformation; 正确的写法应该是下面这样: account deposit: 100 dollars.collection add: transformation. 哦,你可能会很奇怪这里怎么可能有一个dollars消息,其实这没什么特别的。为了使这个表达式合法,Integer类中必须有一个dollars方法的定义(100将被当作Integer对象处理)。虽然在标准的Smalltalk环境中并没有这样的定义,但是我们可以自己添加。Smalltalk的基类可以简单的通过定义新的继承类来扩展。 因此,句号是表达式的终结符,但是在最后一行表达式后面也可以省略(因此你也可以把句号当成表达式之间的分隔符)。但是分号也是合法的,他用在层跌消息语法中(往一个对象中一次发送多个对象的语法)。例如: | p |p := Client new.p name: Jack.p age: 32.p address: Earth. 对于上面这样一个表达式我们应该如下编写: | p |p := Client new.pname: Jack;age: 32;address: Earth. 或者干脆写成: | p |p := Client newname: Jack;age: 32;address: Earth. 上面的例子中格式并没太大关系,只要愿意我们甚至可以把所有的句子都写成一行。很关键的分号指出了前一条消息发送到接受对象并使其状态发生变化之后,下一条消息将被继续发到这个接受对象。(而不是发到上一条消息的返回值对象,上条消息的返回值将被抛弃或者忽视)。在最后一个例子当中,new被发送到类来生成一个实例(作为返回值)。然后“name:Jack”被发送到这个实例。第一个分号表示“name:Jack”消息返回的结果将被忽略,紧接着“age:32”被发送到先前的实例。第二个分号表示“age:32”返回的结果被忽略,紧接着“address:Earth”被发送到先前的实例。最后,“address:Earth”返回的结果被保存到变量p。修改接收者属性的类方法通常都返回接收者自身。因此变量p就最终被绑定到了被修改了好几次的新生成的类实例对象上。我们可以将上面的分号替换成“AND ALSO”就会感觉很容易理解这段代码的意思了。在Smalltalk中类似这样向同一个对象连续发送消息的语法称作层跌消息。分号也可以在子表达式中使用,例如“p := (Client new name: Jack; age: 32; address: Earth)”注意这里的括号。 Get/Set方法使用与实例变量相同的名称 类似name,age,address这样的类实例变量在Smalltalk中全都属于私有变量。但是类方法可以提供对这些变量的访问。在C+中(Java也类似),例如,我们可以定义下面这样的访问类实例变量的方法: long getAge () return age;void setAge (long newAge) age = newAge; 如果你有好几打的类,并且你使用上面这样的编码约定,你将会发现最终你的代码中有一大堆的以get和set打头的类方法。当然,如果你碰巧决定通过去掉这些重复的前缀以便让类方法名短些,你会发现C+的编译器将会无法编译,因为他无法从名称区分变量和函数。但是Java的编译器对于下面这样的代码则没有什么问题。 long age () return age;void age (long newAge) age = newAge; 你能区分变量age和消息age吗?你应该可以。当你使用这条消息的时候,你需要加上括号,就像“age()或者age(22)”;当你引用这些变量的时候,则不需要使用括号。同样的类方法在Smalltalk中可以写成下面这样: age ageage: newAge age := newAge 不过,我们通常通过写成两行来让代码更可读: ageageage: newAgeage := newAge 在Smalltalk中没有括号可以帮助你区分变量和消息,但是要区分也不是很难。如果你行的话,应该可以看出下面这句话中哪些是变量,哪些是方法: age age age: age age + age age 好吧,答案是3;第一个age必然是一个变量,第四个也是(每个关键字后面都必须是一个子表达式;所有的表达式都必须以变量开头),还有第七个(在一个二元操作符后面,也必须是一个子表达式)。用更简单典型的例子应该更容易理解,如下: name size /name 必然是一个变量self name /name必然是一个类方法 Collection的广泛使用 The two most heavily used types of collections in Smalltalk are ordered collection and dictionaries.Arrays are conceptually ordered collections that cant change size.| a b |a := OrderedCollection newadd: #red;add: #green;yourself.b := Dictionary newat: #red put: #rouge;at: #green put: #vert;yourself.In each of the above assignments, the variable is bound to the result of the last message executed; i.e., the result of “yourself” which is, hopefully, the newly created collection. Message “yourself” is designed to return the receiver (almost like a no-op) but “add:” and “at:put:” do not (they return their last parameter). So without “yourself”, we would bind “a” to #green” and “b” to #vert.I deliberately used cascading above in order to explain why “yourself” is used at all since it appears in isolated methods in the built-in classes.The big advantage to Smalltalk collections is that you can put any objects whatsoever in the collections.Even the keys in dictionaries can be any type of object. Also, the objects in a specific collection dont have to be the same type; they can all be different types. We dont need to invent new kinds of collections just because we want to accumulate instances of a brand new class.Ordered collections can be accessed like arrays; e.g., “a at: 1” indexed starting at 1. Dictionaries are accessed the same way too; e.g., “b at: #red”. But there are many applications where we dont need to deal with the keys. For these, there are very simply looping facilities that iterate through the items; e.g.,a do: :item |USE item FOR SOMETHING.b do: :item |USE item FOR SOMETHING.Loop variable “item” gets the elements of the collection one-by-one even if they are all different types.If necessary, we can easily find out what something is at execution time; e.g., “item isKindOf: Boat” returns true or false. There are also many special-case query messages like “item isCollection” or “item isNumber”. There are also many looping constructs that create and return new collections such asc := clients select: :client | client netWorth 500000.d := clients collect: :client | client name.In the first case, we get a subcollection of those clients that are pretty rich. In the second, we get a collection of names (the original collection was a collection of clients).序列化抽象无须创建新的classEvery now and then, a reader will find code that looks likestuff value: x value: y value: zwhere the keywords are all “value:”. This clearly looks useless and bewildering to a non-Smalltalk reader. What is happening is that the programmer has (typically) created a new abstraction facility.Let me explain this fundamental capability that only Smalltalk supports with a challenge. Given that we have already introduced the Client class earlier, suppose that we want a simple facility for looping through an individual clients parts; i.e., we want the loop to first give us the name, then the next time around, we want the age, and finally we want the address.The conventional solution in C+ and Java is to create a special stream class or enumerator, say called ClientIterator, with facilities to initialize it, to ask it if it is at the end, and, if not, to ask for the next object in the iterator. Then we can have a loop that sets up the iterator and, while not at the end, gets the next object and processes it. The advantage of the iterator is that it can provide its own variables for keeping track of where its at in the sequencing process; i.e., its not necessary to extend the Client class with “temporary” instance variables needed for iterating.An example piece of code that might make use of the intended abstraction is shown below:aClient := CODE TO GET ONE.aClient partsDo: :object |object printOn: TranscriptNotice that partsDo: looks like a looping construct where object is the loop variable. The first iteration, we get the name and print it on the transcript (a special workspace in the Smalltalk programming environment). Then we get the age in the second iteration and finally the address in the third. Also note that “partsDo:” is a keyword message where “aClient” is the receiver and “:object | object printOn:Transcript” (a block) is the parameter.Before we go too far, let me just show you the Smalltalk solution. Then Ill explain how it works and provide more sophisticated examples. All we do is add one method to class Client as follows:partsDo: aBlockaBlock value: self name.aBlock value: self age.aBlock value: self address.The clue to understanding this is the realization that blocks are nameless functions. To better understand what I mean, suppose that we want to assign a function to a variable BUT WE DONT WANT TO CALL IT. Let me make up the syntax for a C-like language as follows (I do know the correct syntax for doing this in C but it doesnt help to elucidate the important idea; so Im not using it):a = f (x) return x + 1; /C-Like syntaxa := :x | x + 1 /Smalltalk syntaxNow variable “a” is the function object itself. Since f is a function, we might expect to be able to call it with the notation “f (10)” to get back 11. But we should also be able to call it by executing “a (10)” because as value is the function. Executing the function through variable “a” doesnt require any knowledge of its original name.So in Smalltalk, we dont even bother with a name for the function. We simply assign it to any variable and use it through that variable. To “call” the function, we unfortunately dont have the nice notation used above. We have to settle for “a value: 10” which returns 11. During execution, parameter x is bound to 10, x + 1 is computed, and the end of the block is reached. When that happens, the last value computed is returned.In general, we rarely execute blocks directly. Instead, we write abstractions like “partsDo:” above and hide the cumbersome “call” to the block in the method providing the abstraction.Consider some more examples. Suppose we have an Airplane class that maintains a list of passengers.Lets just suppose that we want to sequence through those passengers that are kids (suppose we define kids to be 12 years old or younger). Then a piece of code for doing that might look likeanAirplane passengers do: :person |person age = 12ifTrue: .DO SOMETHING with person.If we need to sequence through kids in other contexts, it becomes useful to create an abstraction that simplifies our code. All we need to do is implement a “kidsDo:” abstraction as follows in class Airplane (Ive added line numbers for ease of reference):1. kidsDo: aBlock2. Here, self is an Airplane. 3. self passengers do: :person |4. person age = 125. ifTrue: aBlock value: personThen we would modify the example to use the new abstraction as follows:6. anAirplane kidsDo: :kid |7. .DO SOMETHING with kid.8. All done. Can you see how the code beginning with line 6 works? When the “kidsDo: ” message at 6 executes, the “kidsDo:” method in line 1 is invoked. So variable aBlock in line 1 is bound to “:kid | .DO SOMETHING with kid.” (let me call this the kid block). Inside the kidsDo: method, “do:” in line 3 iterates through all passengers. In line 5, aBlock is sent a “value:” message only when a person is found that is 12 years old or younger. When this “value:” message with “person” as parameter executes, a function call is made to the kid block which causes “kid” to be bound to “person” and “.DO SOMETHING with kid.” in line 7 to execute. When the end of the block is reached, execution goesback to the “do:” loop in “kidsDo:” (the end of line 5) as the loop continues to look for other kids.When the loop finally finished, execution returns from the “kidDo:” method invocation in line 6 and we reach line 8.To recap, code in 6 causes code in lines 1 through 5 to execute which repeatedly causes code in the kid block (line 7) to execute.In general, blocks provide Smalltalk with one of the simplest and

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论