load help; @[name] dao.tutorial @[name] @[title] 道语言编程教程 @[title] @[name] dao.tutorial.basics @[name] @[title] 基础 @[title] @[text] 此教程将从经典的@[green]世界你好@[green]开始介绍, 并涵盖道语言的基础部分。 @[subsection] 世界你好 @[subsection] 在屏幕简单地打印“世界你好”,可使用, @[code] io.write( 'Hello world!' ) @[code] 这个例子使用了内置的模块@[green]io@[green]来访问输出输入(IO)功能。 @[green]write()@[green]是@[green]io@[green]模块提供的一个方法, 可用来在标准输出打印。在道里,字符串通常由单引号或双引号来引用表示。 @[subsection] 运行程序 @[subsection] 道支持三种方式运行。 最通常的方式是将代码写入一个文件(hello_world.dao), 然后在命令行使用道解释器执行: @[code] $$ dao hello_world.dao @[code] 另一种方式是将代码直接从命令行传给道解释器: But if you just want to run the codes once, you can simple run them from command line using, @[code] $$ dao -e "io.write( 'Hello world!' )" @[code] 最方便学习的执行方式是交互式执行。 从命令行,不加任何参数执行道解释器,将进入交互式。 @[code] $$ dao Dao Virtual Machine 2.0 Built date: Jun 16 2013 Changeset ID: FOS.a019d384dd7c Copyright(C) 2006-2013, Fu Limin Dao is released under the terms of the Simplified BSD License Dao Language website: http://daoscript.org (dao) @[code] 如果你道解释器安装正常,使用交互模式运行道将自动 载入帮助文件。并显示如下信息: @[code] 帮助模块已载入。 现在您可以使用"help()"来列出所有可用的帮助条目; 或者运行"help('help')"来获得此帮助系统的详细信息。 @[code] 如果你未看到此信息,尝试如下方式执行: @[code] $$ DAO_HELP_LANG=ZH dao @[code] 如果你查看的是此帮助的网页版,你可以使用一下命令在交互模式下查看 次帮助文件: @[code] (dao) help( 'dao.tutorial.basics' ) @[code] @[subsection] 注释代码 @[subsection] 给自己的代码加注释通常是个好习惯。 道语言支持单行和多行注释。 单行注释以@[green]#@[green]开始,直到行末; 多行注释以@[green]#{@[green]开始,@[green]#}@[green]结束。 例如: For example, @[code] # 这个是单行注释例子: io.write( "Hello World!" #{ 代码中注释 #} ); #{ 这里是多行注释例子。 这里是多行注释例子。 #} @[code] @[text] ################################################################################ ################################################################################ ######## Types ################################################################################ ################################################################################ @[name] dao.tutorial.types @[name] @[title] 基本类型 @[title] @[text] @[subsection] 数字 @[subsection] 道语言对以下数字类型有内在支持: @[green]int@[green], @[green]float@[green], @[green]double@[green], @[green]complex@[green] 和 @[green]long@[green]。 道语言里整数和浮点数的表达方式跟其他语言一样。 不过浮点数被默认为双精度@[green]double@[green](从发布dao-2.0-devel-2013-06-16开始)。 要显示表示单(双)精度浮点数@[green]float@[green](@[green]double@[green]), 有必要在整数和浮点数后附加@[green]F@[green] (@[green]D@[green])后缀。 The imaginary part of a @[green]complex@[green] number can be expressed as an @[green]int@[green] or @[green]float@[green] number with a @[green]C@[green] suffix. A general @[green]complex@[green] number can be expressed by combining an integer or a floating point number as the real part and an imginary part, which are both stored as double precision floating point numbers. A @[green]complex@[green] number can be expressed as an @[green]int@[green] or @[green]float@[green] number with a @[green]$@[green] suffix. @[green]long@[green] type represents arbitrary precision integers. They can be expressed as an @[green]int@[green] number with a @[green]L@[green] or a @[green]Lx@[green] suffix, where @[green]x@[green] is an integer between 2 and 16 (inclusive), representing the base of the number (used for parsing and printing only). Examples, @[code] I1 = 123 I2 = 0xabc F1 = 456.7 F2 = 123e4 D1 = 123D D2 = 456.7D D3 = 123E4 C1 = 123$ C2 = 456.6$ L1 = 123456789L L2 = 10001001110L2 L3 = 0x123abc456L16 @[code] These types support most of the common operators that are meaningful for them. For example, all the basic arithmetic operators such as: @[green]+@[green] (addition, unary plus), @[green]-@[green] (subtraction, unary minus), @[green]*@[green] (multiplication), @[green]/@[green] (division), @[green]%@[green] (modulo) and @[green]**@[green] (power) are supported. @[code] I1 = 123 + 456 I2 = 789 % 123 F1 = 123.5 ** 3 D1 = 789.5D / 123 L1 = 123456789L ** 123L @[code] Please see @[node]dao.type.int@[node], @[node]dao.type.float@[node], @[node]dao.type.double@[node], @[node]dao.type.complex@[node] and @[node]dao.type.long@[node] for more information. @[subsection] Strings @[subsection] In Dao, a string can be expressed as a sequence of characters enclosed by a pair of single qutotation marks or a pair of double quotation marks. The characters placed between the quotation marks must be formated according some rules (such as escaping quotation marks and other special characters and using numeric encodings of characters etc.). Please see @[node]dao.type.string@[node] for more information. @[code] mbs = 'str' wcs = "道语言" mbs2 = 'It\'s green' wcs2 = "\u9053\u8bed\u8a00" # the same as wcs; @[code] To use strings as it is written without special treatment of the characters, they can be expressed as @[green]verbatim@[green] string, which are quoted with a pair of identical compound marks in the forms of @[green]@[]@[green] and @[green]@@[]@[green]. Any number of letters, digits, underscores, blank spaces, dots, colons, dashes and assignment marks can be placed in between the squared brackets to make sure the marks will not appear inside the string. The difference between using @[green]@[]@[green] and @[green]@@[]@[green] is the same as the difference between using single quotation marks and double quotation marks. Please see @[node]dao.type.string@[node] for more information. @[code] # C++ codes in MBS: cpp = @[cpp x] class AA { int index; }; struct BB{}; @[cpp x] # Lua codes in WCS: lua = @@[lua] local a = 1; function Test() io.write( 'Hello' ) end @@[lua] @[code] The content of a string can be accessed or altered using sub-indexing or slicing: @[code] str = 'ABCDEFGHIJK'; io.writeln( str[1] ) # the second character; io.writeln( str[:4] ) # the substring from the start to the 4th character; io.writeln( str[6:] ) # the substring from the 6th character to the end; io.writeln( str[3:8] ) # the substring from the 3rd to the 8th character; # Set single character: str[1] = 'X'; str[1] = 'X'[0]; # Set a substring: str[2:5] = '1234' # str = 'AB1234GHIJK' str[2:5] = '123456' # str = 'AB123456GHIJK' str[2:5] = '12' # str = 'AB12GHIJK' # Using negative index: io.writeln( str[-1] ) # the last character; io.writeln( str[-2] ) # the second last character; io.writeln( str[-2:] ) # the last two characters; @[code] String can be concaternated using @[green]+@[green] or @[green]+=@[green], @[code] str = 'ABCDEFGHIJK'; str2 = str + '123' # str2 = ABCDEFGHIJK123 # Append a string: str += '123' # str = ABCDEFGHIJK123 # Append a character: str += 88 # str = ABCDEFGHIJK123X @[code] @[subsection] Arrays @[subsection] Dao has built-in support for multi-dimensional numeric arrays. Such arrays can be defined by using the squared brackets @[green][]@[green] or @[green]array{}@[green]. Using such constructs, one can either enumerate all the elements as a vector/matrix, or specify an arithmetic progression with a start value, a step value and the number of steps. If the step value is omitted, it will be assumed to be one. @[code] vec1 = [1, 2, 3] # array vector, or 1x3 matrix; vec2 = [1.0; 2; 3] # array 3x1 matrix, or transposed vector; mat1 = [1D, 2; 3, 4] # array 2x2 matrix; mat2 = [ [1, 2], [3, 4] ] # 2x2 matrix mat3 = [ 5 ~ [1, 2, 3] ] # 5x3 matrix; mat4 = array{ 1, 2; 3, 4 } # 2x2 matrix @[code] Like string, array support sub-indexing, slicing and negative indices: @[code] mat = [ 1, 2, 3; 4, 5, 6; 7, 8, 9 ]; # 3x3 matrix; rowvec = mat[1,:] # the second row; colvec = mat[:,1] # the second column; submat1 = mat[:1,:] # the first two rows; submat2 = mat[:,1:] # the last two columns; submat3 = mat[:1,1:] # intersection between the first two rows and the last two columns; mat[0,:] = [11, 22, 33] # set the first row to [11, 22, 33]; mat[:,1] += [11, 22, 33] # add [11, 22, 33] to the second column; mat[:,1] += 100 # add 100 to the second column; mat[:1,1:] += [10, 20; 30, 40] # add [10, 20; 30, 40] to sub-matrix of mat; @[code] Please see @[node]dao.type.array@[node] for more information. @[subsection] Lists @[subsection] List can be created in similar ways as array, by enumerating elements or specifying an arithmetic progression, but using @[green]{}@[green] or @[green]list{}@[green] instead of @[green][]@[green] or @[green]array{}@[green]. To use arithmetic progression, @[green]list{}@[green] must be used to avoid ambiguity with hash maps. @[code] list1 = { 1, 2, 3 } # list list2 = { 1.0, 2, 3 } # list list3 = { 5 ~ 1 ~ 2 } # list list4 = { 'abc', 'def' } # list list5 = { 123, 'abc' } # list list6 = list{ 3 ~ 'a' } # { 'a', 'a', 'a' } list7 = { 3 ~ 'a' ~ 'b' } # { 'a', 'ab', 'abb' } @[code] List also supports sub-indexing, slicing and negative indices: @[code] alist = { 0, 1, 2, 3, 4, 5 } item = alist[1] item = alist[-2] sublist = alist[2:4] alist[3] = 10 alist[4] += 10 @[code] @[subsection] Maps and hash maps @[subsection] A map or hash map organize a set of key/value pairs into a structure for efficient lookup. The keys in a map are ordered, while the keys in a hash map is unordered. A map can be created using @[green]{key=>value...}@[green] or @[green]map{key=>value...}@[green]. Replacing the @[green]=>@[green] will create hash maps. Map and hash map can be used in identical ways. @[code] amap = { 'abc' => 123, 'def' => 456 } ahash = { 'abc' : 123, 'def' : 456 } amap = map{ 'abc' => 123, 'def' => 456 } ahash = map{ 'abc' : 123, 'def' : 456 } @[code] Sub-scripting and slicing are also supported for map to access value(s) through key(s). @[code] amap = { 'abc' => 123, 'def' => 456, 'ghi' => 789 } value = amap[ 'abc' ]; submap = amap[ 'abc' : 'def' ]; @[code] @[subsection] Tuples @[subsection] Tuple is a very handy type, which can be used to hold a fixed number of items, with type information recorded for each of them. In a tuple, each item can have a name, which can be used to access the item as field. They can be created in similar ways as creating lists and maps, but use @[green]()@[green] instead. @[code] tup1 = ( 123, 'abc' ) # tuple with unnamed items; tup2 = ( index => 123, 'abc' ) # the first item is named as "index"; tup3 = tuple{ 123, name => 'abc' } # the second item is named as "name"; @[code] Each item of a tuple can be accessed using its index or field (item name). New tuples can be created from other tuples by slicing. @[code] tup = ( index => 123, 'abc', [1,2,3] ) id = tup[0] id = tup.index tup.index = 456 tup2 = tup[:1] # ( index => 123, 'abc' ) @[code] @[text] ################################################################################ ################################################################################ ######## Controls ################################################################################ ################################################################################ @[name] dao.tutorial.controls @[name] @[title] Dao Constrol Structures @[title] @[text] @[subsection] If-Else @[subsection] 当某条件表达式值为真时,执行一块代码: @[code] if( expr1 ){ block1; }elif( expr2 ){ block2; }else{ block3; } @[code] 如果条件表达式@[cyan]expr1@[cyan]的值为真,@[cyan]block1@[cyan]将被执行; 否则如条件@[cyan]expr2@[cyan]为真,@[cyan]block2@[cyan]被执行; 否则执行@[cyan]block3@[cyan]。可使用零个或多个@[green]elseif / elif@[green], 和零个或一个@[green]else@[green]。 @[code] if( 2 > 1 ) io.writeln("2 is larger than 1.") @[code] @[subsection] For @[subsection] 道语言支持多种形式的for循环,最方便使用的是下面这种, @[code] for( 变量 = 初值 : 步长 : 终值 ){ 代码; } @[code] 循环将从@[cyan]变量=初值@[cyan]开始执行,然后每次循环后,@[cyan]变量@[cyan]的值按@[cyan]步长@[cyan] 增加,直到@[cyan]变量@[cyan]的值变得大于@[cyan]终值@[cyan]时终止循环。 这里@[cyan]步长@[cyan]可以省略,其缺省值是1。 @[cyan]初值@[cyan],@[cyan]步长@[cyan]和@[cyan]终值@[cyan]总是在循环开始前被计算。 C/C++类型@[green]for@[green]循环: @[code] for( init; condition; step ){ block; } @[code] @[green]for@[green]语句块的执行顺序是这样的: @[list] ==执行循环初始表达式@[cyan]init@[cyan];转到3; ==执行@[cyan]step@[cyan]; ==执行条件表达式@[cyan]condition@[cyan]; ==检查@[cyan]condition@[cyan]的值:如为真,转到5,否则转到6; ==执行@[cyan]block@[cyan];转到2; ==终止循环,开始执行循环体后面的语句。 @[list] 一般情况下,C/C++类型@[green]for@[green]循环等价于下面的@[green]while@[green]循环: @[code] init; while( condition ){ block; step; } @[code] 但如果@[cyan]block@[cyan]包含一个不被其他循环语句包含的@[green]skip@[green]语句,那么上面这个@[green]while@[green]循环 与C/C++类型@[green]for@[green]循环并不完全等价。因在@[green]for@[green]循环体中,@[green]skip@[green]只跳过@[cyan]block@[cyan] 块余下的部分,而在上面的@[green]while@[green]循环中,@[green]skip@[green]将同时跳过@[cyan]step@[cyan]语句。 道语言也支持Python风格的@[green]for-in@[green]循环: @[code] for( item in list ){ block; } @[code] 关联表也可用在for-in循环中,这里@[cyan]item@[cyan]将会是一对键与值, @[code] for( item in a_map ){ block; } @[code] 在一个for-in循环中可以使用多个in语句,但那些列表或关联表必须含有相同个数的元素, 否则,程序将抛出例外, @[code] for( item1 in list1; item2 in list2; ... ){ block; } @[code] 例子, @[code] for( i=0; i<3; i++ ){ io.writeln( i ); } hash = { "b" => 11, "a" => 22, "e" => 33, "c" => 44 }; for( a in hash.key(); b in hash.value(); c in {1 : 1 : hash.size()-1 } ){ #if a == "a" break io.writeln( a, "\t", b, "\t", c ); } @[code] 这个例子抛出了一个例外,因为最后那个列表的元素数目比其他第一个列表少一。 @[subsection] While @[subsection] 当某条件表达式值为真时,@[green]反复@[green]执行一块代码: @[code] while( expr ){ block; } @[code] 如果条件表达式@[cyan]expr@[cyan]值为真,反复执行@[cyan]block@[cyan]直到表达式@[cyan]expr@[cyan]值为假。 @[code] i = 0; while( i < 5 ){ io.writeln( i ); i ++; } @[code] @[subsection] Do-While @[subsection] @[code] do{ block; } while ( condition ) @[code] 执行代码块@[cyan]block@[cyan], 然后在条件表达式式@[cyan]condition@[cyan]为真 的情况下重复执行@[cyan]block@[cyan]。 @[subsection] Switch-Case @[subsection] @[green]Switch-case@[green]开关控制可基于一个表达式的值选择相应的代码块执行。它可以用来方便地实现的分支控制。 @[code] switch( exprs ){ case C1 : block1 case C2 : block2 case C3 : block3 ... default: block0 } @[code] 如果表达式@[cyan]exprs@[cyan]的值等于某个@[green]case@[green]语句的@[cyan]Ci@[cyan],代码块@[cyan]blocki@[cyan]将被执行。 这里@[cyan]Ci@[cyan]必须是常量,可以是不同类型的值。与C/C++不同的是,代码块@[cyan]blocki@[cyan]被执行后, 程序执行将跳到@[green]switch@[green]块外,因此这里不需要象C/C++里那样使用@[green]break@[green]语句来显示跳出 @[green]switch@[green]块。 如果你需要对不同的@[green]case@[green]值都执行同一块代码,你仅需将那些@[green]case@[green]值放在一起: @[code] switch( value ){ case C1, C2, C3 : block3 ... default: block0 } @[code] 在这种情况下,@[green]case@[green]值@[cyan]C1,C2,C3@[cyan]将对应于同一代码块@[cyan]block3@[cyan]。 @[code] a = "a"; switch( a ){ case 1, "a" : io.write("case 1 or a"); default : io.write("case default"); } @[code] 道语言的switch-case结构还允许使用值区间作为case, 这样当switch值属于这个区间时,此case块将被执行。 需要注意的是,如果switch值属于多个相互重叠的区间, 那么对应下界最小的区间的case块将被执行。 @[code] switch( 5 ){ case 1 ... 5 : io.writeln( 'case 1-5' ); case 5 ... 10 : io.writeln( 'case 5-10' ); case 10 ... 11 : a = 1; } @[code] @[subsection] Break and Skip @[subsection] @[text] ################################################################################ ################################################################################ ######## Routine ################################################################################ ################################################################################ @[name] dao.tutorial.routine @[name] @[title] Dao Routine (Function) @[title] @[text] 函数是一可反复使用的代码块,它由关键字@[green]routine@[green]定义。 它可在被使用时接受参数,参数的名称,类型和缺省值需在定义时给定。 它也可在使用结束时返回一个或多个结果。 考虑到一些用户习惯,关键词@[green]function, sub@[green]也可用来定义函数, 其作用跟@[green]routine@[green]完全等同。 @[subsection]定义@[subsection] @[code] routine name( [ param1 [ : type | [:]= const_expr ] [, param2 [...], ... ] ] ) { [...] } @[code] @[code] routine func( a, b ) { io.writeln( a, b ); a = 10; b = "test"; return a, b; # return more than one results. } r1, r2; ( r1, r2 ) = func( 111, "AAA" ); r3 = func( r1, r2 ); io.writeln( "r1 = ", r1 ); io.writeln( "r2 = ", r2 ); io.writeln( "r3 = ", r3 ); @[code] @[subsection]命名参数传递@[subsection] 道语言函数的参数可按参数名字传递: @[code] func( b => 123, a => "ABC" ); @[code] @[subsection]参数类型与缺省值@[subsection] 正如道语言变量可声明为有类型的,道函数的参数也可声明为有类型,并还可声明为有缺省值。 @[code] routine MyRout( name : string, index = 0 ) { io.writeln( "NAME = ", name ) io.writeln( "INDEX = ", index ) } @[code] 这里参数@[cyan]name@[cyan]被声明为字符串,参数@[cyan]index@[cyan]被声明为整数,并以零为缺省值。 如果一个函数调用使用错误的参数,或没有数据传递给没有缺省值的参数, 将导致报错,并终止运行。 参数缺省值可指定给参数表中的任意参数,而不管指定缺省值的参数后面的参数是否有缺省值。如, @[code] routine MyRout2( i=0, j ){ io.writeln( i, " ", j ) } MyRout2( j => 10 ) @[code] 在这种情况下,如果你想用@[cyan]i@[cyan]的缺省值,那么你需要将数据按参数名字传给@[cyan]j@[cyan]。 @[subsection]常量参数@[subsection] 要申明不为函数所修改的常量参数,只需在参数类型前添加 const 即可: @[code] routine Test( a : const list ) { a[1] = 100; # 错误!!! io.writeln( a ); } a = { 1, 2, 3 } Test( a ); @[code] @[subsection]按引用传递参数@[subsection] 在函数调用时,如果参数名前使用了@[green]&@[green], 那么该参数将作为引用被传递。 只有基本数据类型的局部变量可作为引用被传递给函数调用,‘ 并且也只有在函数参数列表里创建引用。 @[code] routine Test( p : int ) { p += p; } i = 10; Test( & i ); io.writeln( i ); @[code] @[subsection]参数聚组@[subsection] 类似于Python里,道语言也支持参数聚组(grouping), 它由一对括号定义,将括号里的参数聚集为一组。 当一个元组被作为参数传递给有参数聚组的函数时, 如果与参数聚组对应的元组的元素类型与组里的参数类型相符合, 那么该元组将被展开,并将其元素传递给组里相应的参数。 @[code] routine Test( a : int, ( b : string, c = 0 ) ) { io.writeln( a, b, c ); } t = ( 'abc', 123 ) Test( 0, t ) @[code] @[subsection]函数重载@[subsection] 道语言里,函数可按参数类型进行重载。也就是可对于拥有不同参数类型的函数 使用同样的名称,函数调用时,道虚拟机根据调用中的参数选择正确的函数来运行。 @[code] routine MyRout( index : int, name = "ABC" ) { io.writeln( "INDEX = ", index ) io.writeln( "NAME = ", name ) } MyRout( "DAO", 123 ) # 调用上例中的MyRout() MyRout( 456, "script" ) # 调用此例中的MyRout() @[code] @[subsection]匿名函数@[subsection] 在道语言里有匿名函数,是基本类型(也就是first-class function), 他们按如下方式定义: @[code] foo = routine( x, y : TYPE, z = DEFAULT ) { codes; } @[code] 除了以下三点不同外,这种函数的定义跟普通函数的定义完全一样: @[list] ==它不需要函数名,但当它被创建时,需要赋值给一个变量; ==参数缺省值不必是常量,可以是任意表达式,该表达时将在函数被创建时求值; ==函数体里可使用定义在外层函数的局部变量;根据变量的类型,他们的拷贝或 引用将被输入到被创建的函数里。 @[list] 例子: @[code] a = "ABC"; rout = routine( x, y : string, z = a+a ){ a += "_abc"; io.writeln( "lambda ", a ) io.writeln( "lambda ", y ) io.writeln( "lambda ", z ) } rout( 1, "XXX" ); @[code] @[subsection]发生器(Generator)和协程(Coroutine)@[subsection] 当一个函数被调用时,如果它函数名前有@[green]@@[green]符号, 此调用将不执行该函数,而是返回一个发生器或协程。 在这样的函数里,可使用@[green]yield@[green]语句来向调用者返回值。 当@[green]yield@[green]语句被执行时,此函数的运行将被暂停, 并将运行控制权转给其调用者,而当调用者再次重新执行 此函数时,函数将从暂停处继续执行。 @[green]yield@[green]语句执行完后,它将会象函数调用一样返回值, 被返回的值就是其调用者作为参数传入的值。 当函数运行至函数末尾或@[green]return@[green]语句后, 函数将终止运行,并且不可续。 @[code] # int => tuple routine gen1( a = 0 ) { k = 0; while( k ++ < 3 ) a = yield( k, a ); return 0,0; } routine gen2( a = 0 ) { return gen1( a ); } g = @gen2( 1 ); # 第一次调用时,参数可省略, # 创建时用到的参数将被在第一次调用时使用: io.writeln( 'main1: ', g() ); io.writeln( 'main2: ', g( 100 ) ); io.writeln( 'main3: ', g( 200 ) ); @[code] @[code] routine foo( a = 0, b = '' ) { io.writeln( 'foo:', a ); return yield( 2 * a, 'by foo()' ); } routine bar( a = 0, b = '' ) { io.writeln( 'bar:', a, b ); ( r, s ) = foo( a + 1, b ); io.writeln( 'bar:', r, s ); ( r, s ) = yield( a + 100, b ); io.writeln( 'bar:', r, s ); return a, 'ended'; } co = @bar( 1, "a" ); io.writeln( 'main: ', co() ); io.writeln( 'main: ', co( 1, 'x' ) ); io.writeln( 'main: ', co( 2, 'y' ) ); # 协程已运行完,再调用将产生异常: io.writeln( 'main: ', co( 3, 'z' ) ); @[code] 发生器和协程也可由标准方法@[green]stdlib.coroutine()@[green]来创建, 不过这种情况下,函数必须同时使用标准方法@[green]stdlib.yield()@[green] 来返回值。 另外,这两种方法创建的发生器或协程有个很重要的区别。 使用前缀@[green]@@[green]创建的发生器或协程支持类型检查, 而使用@[green]stdlib.coroutine()@[green]创建的不支持。 @[text] ################################################################################ ################################################################################ ######## Class ################################################################################ ################################################################################ @[name] dao.tutorial.class @[name] @[title] Dao Class and Object-Oriented Programming @[title] @[text] 道语言对面向对象编程(object-oriented programming, OOP)有良好的支持。 道语言类使用关键字@[green]class@[green]定义。 一个类就是一组变量与函数的集合的抽象表示,这些变量和函数称为类的成员; 成员变量的值将决定该类的特性,成员函数决定了该类的行为。 类本身是程序员定义的抽象类型,它的值即实例就是一个具体的数据集合,此集合 包含了类定义的成员变量。 类实例可以以函数调用的方式调用类的构造函数创建,也可通过枚举类成员变量来创建。 类成员的访问权限可由修饰词@[green]private@[green](私有),@[green]protected@[green](保护) 和@[green]public@[green](公开)来限定,缺省权限为公开。 这些修饰词后可跟也可不跟冒号。 类与类之间可存在继承和包含关系。一个类(子类)可定义为另一个类(基类)的引申。 子类可从基类继承某些特性,在基类的基础上扩充或专门化某些功能。类也可以包含其他类 的实例为成员。 @[subsection]基类定义@[subsection] 类的定义由关键字class开始申明类的名称, 然后在由花括号所包围的类体里,申明类的成员常量,变量 和方法等。 @[code] class MyNumber { private var value = 0; var name : string; public routine MyNumber( value = 0, s = "NoName" ){ value = value; name = s; } routine setValue( v ){ value = v } routine getValue(){ return value } routine setValue( v : float ); } routine MyNumber::setValue( v : float ) { value = v; } @[code] 类定义体里使用关键字@[green]var@[green]申明类的成员变量。 同普通变量类似,类成员变量也可按下面方式申明为有固定类型, @[code] var variable : type; @[code] 这里@[cyan]type@[cyan]必须是一类型名,这样@[cyan]variable@[cyan]的类型将固定为@[cyan]type@[cyan]的类型; 或者@[cyan]variable@[cyan]必须是@[cyan]type@[cyan]的实例,如果@[cyan]type@[cyan]也是类的话。 类的成员变量还可拥有缺省值。在类创建时,有缺省值的成员变量将被首先以缺省值填充。 类的成员变量的缺省值可按以下方式指定, @[code] var variable = init_value; var variable : type = init_value; @[code] 这里@[cyan]init_value@[cyan]也必须是一常量。 在道语言里,类的定义体也是类的构造函数。和其他某些脚本语言里的类构造函数类似, 道语言里的类构造函数并不是真正用来构造类实例,而是用来初始化类的成员数据。 道语言里,类可拥有多个重载的构造函数,用来根据不同的构造函数调用参数以不同的方式初始化类实例。 例子, @[code] class MyNumber( value = 0, name = "NoName" ) { private my Value = 0; # value类型为整形,缺省值为零 my Name : string; # name必须是字符串 Value = value; self.Name = name; public routine setValue( value ){ Value = value } routine getValue(){ return Value } routine setValue( value : int ); } routine MyNumber::setValue( value : int ) { Value = value; } @[code] 在类的构造函数或成员函数里,可使用一特殊变量@[green]self@[green],它表示类的当前实例。 与C++里的类成员函数定义类似,类的成员函数可在类定义体内申明,然后在类体外定义。 不过在道语言里,类体外定义的成员函数的参数表必须和申明时的完全一致。 Dao里,虚拟函数也可用关键词@[green]virtual@[green]来申明。 如果一个类成员函数里没使用类成员变量,且没调用其他使用了类成员变量的成员函数, 那么此成员函数可按如下方式调用, @[code] classname.method(...) @[code] @[subsection]类实例@[subsection] 新的类实例可以由调用该类的构造函数得到,构造函数的调用方式跟普通函数调用完全一样。 @[code] obj1 = MyNumber(1); @[code] 类实例也可通过枚举类的成员变量创建,这种实例创建方式主要适合于比较简单的类,因为这种不需要 复杂操作进行初始化。 @[code] obj2 = MyNumber{ 2, "enumerated" }; @[code] 在枚举成员变量时,可指定成员的名称, @[code] obj3 = MyNumber{ Name => "enumerated"; Value => 3; }; @[code] 当类实例由枚举创建时,类实例先被成员变量的缺省值填充,然后使用枚举中的数据初始化相应的 成员变量。通过枚举创建类实例比通过调用类构造函数创建类实例要快得多,调用类构造函数有一系列 额外开销,如参数传递,函数运行时数据的分配等。 枚举创建实例很适合于创建大量简单类的实例。 @[subsection]类成员数据@[subsection] 正如上面提到,类的成员变量由关键字@[green]var@[green]在类体内申明。 类的常量和静态成员可分别由@[green]const@[green]和@[green]static@[green]来申明。 @[code] class Klass { const aClassConst = "KlassConst"; static aClassStatic; } @[code] @[subsection]Setters, Getters 和运算符重载@[subsection] 相对于给私有或保护的成员变量定义@[cyan]setXyz()@[cyan]或@[cyan]getXyz()@[cyan]方法, 更好的方式是定义所谓的Setters和Getters,定义这样的方法使得从外部环境可以直接 对私有或保护的成员变量进行访问,就像访问公开成员一样。 对于成员变量@[cyan]Xyz@[cyan]其Setters应被定义为@[green].Xyz=()@[green], 而其Getters应被定义为@[cyan].Xyz()@[cyan]。 当Setters被定义后@[cyan]Xyz@[cyan]可由@[cyan]obj.Xyz=abc@[cyan]设定; 而当Getters被定义后@[cyan]Xyz@[cyan]可由@[cyan]obj.Xyz@[cyan]获取。 @[code] class MyNumber0 { private var value = 0; public routine MyNumber0( v = 0 ){ value = v; } operator .value=( v ){ value = v; io.writeln( "value is set" ) } operator .value(){ return value } } num = MyNumber0( 123 ) num.value = 456 io.writeln( num.value ) @[code] @[cyan] 你也许已经猜到,通过Setters和Getters对类成员变量的访问会有比较大的运算开销。 因此为了效率起见,需要从外部访问的成员变量应被设为公开。 只有在必要时才用Setters和Getters,比如当你需要在那些成员变量被访问时作些额外工作时。 @[cyan] 被支持重载的运算符还包括: @[list] ==[operator =(...)] 用作赋值运算; ==[operator ()(...)] 用作函数调用; ==[operator [](...)] 用作取元素; ==[operator []=(...)] 用作设定元素; @[list] 其运算符的重载将载后续版本中支持。 @[subsection]重载成员方法@[subsection] 类的成员方法可以象普通函数那样被重载。类的构造函数也可被重载,给类定义于类同名的 成员方法即可,不过在重载的类构造函数中不可使用关键词@[green]my@[green]来定义成员变量。 如类@[cyan]MyNumber@[cyan]可作如下修改使得它只可从数字或该类的实例构造和赋值, @[code] class MyNumber { private var value : int; public routine MyNumber( value = 0 ){ # accept builtin number as parameter self.value = value; } # 接受MyNumber实例为参数的构造函数,类似于C++里的复制构造函数: routine MyNumber( value : MyNumber ){ self.value = value.value } operator .value=( v : int ){ value = v } operator .value=( v : MyNumber ){ value = v.value } operator .value(){ return value } } num1 = MyNumber( 123 ) num1.value = 456 io.writeln( num1.value ) num2 = MyNumber( num1 ) io.writeln( num2.value ) num2.value = 789 io.writeln( num2.value ) num2.value = num1 io.writeln( num2.value ) @[code] @[subsection]类继承@[subsection] @[code] class ColorRBG { var Red = 0; var Green = 0; var Blue = 0; routine ColorRBG( r, g, b ){ Red = r; Green = g; Blue = b; } routine setRed( r ){ Red = r; } routine setGreen( g ){ Green = g; } routine setBlue( b ){ Blue = b; } routine getRed(){ return Red; } routine getGreen(){ return Green; } routine getBlue(){ return Blue; } } yellow = ColorRBG( 255, 255, 0 ); # create an instance. @[code] 下面将定义@[cyan]ColorRBG@[cyan]的一个派生类, @[code] class ColorQuad : ColorRBG { var alpha = 0; # alpha component for tranparency. routine ColorQuad( r, g, b, a ) : ColorRBG( r, g, b ){ alpha = a; } } yellow2 = ColorQuad( 255, 255, 0, 0 ); # not tranparent. yellow2.alpha = 127; # change to half tranparency. @[code] 当从已有类派生新类时,基类必须放在派生类的参数列表后,并由@[green]:@[green]隔开。 如果有多个基类,这基类都应被放在@[green]:@[green]后并被@[green],@[green]隔开。 派生类构造函数的参数可按例子所显示的方式传递给基类的构造函数。 定义派生类时,如果只有一个父类,派生类将同时继承父类的构造函数。 @[text] ################################################################################ ################################################################################ ######## Class ################################################################################ ################################################################################