4.1 crowbar ver.0.2

crowbar book_ver.0.1不能使用数组,会让用户感觉不太实用,因此在book_ver.0.2中我们将引入数组的概念。

4.1.1 crowbar的数组

在crowbar ver.0.2中,可以像代码清单4-1那样使用数组。

代码清单4-1 数组

创建数组

a={1,2,3,4,5,6,7,8};

显示数组

for(i = 0; i < a.size(); i++){

print("(" + a[i] + ")");

}

print("\n");

创建九九乘法表

a99 = new_array(9,9);

for(i = 0; i < 9; i++){

for(j = 0; j < 9; j++){

a99[i][j] = (i+1) * (j+1);

}

}

显示九九乘法表

for(i = 0; i < a99.size(); i = i + 1){

for(j = 0; j < a99[i]. size(); j = j + 1){

print("[" + a99[i][j] + "]");

}

print("\n");

}

附带:显示字符串长度

print("len.." + "abc".length() + "\n");

与C语言不同,在大多数脚本语言中,数组(或者列表)都可以用常量表示。例如Perl中,像这样:

(1, 2, 3)

就可以创建一个由1、2、3组成的数组(列表)。

Ruby和Python是这样的:

[1, 2, 3]

而Tcl是这样的:

{1, 2, 3}

语言不同,数组的定义方式也不相同。我总觉得crowbar与C语言比较相似,既然在C中数组用 {}初始化,那么在crowbar中也使用 {}吧。

{1, 2, 3}

用这种方式来创建数组。同理,

a = {1, 2, 3};

就可以把数组赋值给变量。(如果这么写的话)可以像下面这样直接用下标指定元素。

{1, 2, 3}[1]

这个表达式的值是整数型的2。

另外,数组的元素中可以包含数组或其他所有数据类型,并且每个元素都可以包含不同类型的数据。

a = {true, 1, "abc", {5,10.0}};

这样一来,在a中包含了布尔型、整数型、字符串和数组四个类型的元素,其中第4个元素包含了数组。 a[3] 返回一个数组, a[3][1] 返回10.0。如上所述,crowbar中虽然不存在多维数组,但实际上可以用“数组的数组”方式来实现。

4.1.2 访问数组元素

从上一节给出的例子中我们不难看出,用 []的方式可以访问数组元素。当然,下标是从0开始的。

b = a[i];

这个语句把数组 a的第i个元素赋值给变量 b。

a[i] = 5;

上面这个的语句将整数5赋值给 a的第i个元素,但如果a不是数组类型的话,运行时就会出现错误。还有, []中必须是整数类型。

crowbar的数组不支持自动扩展。在Perl等有些语言里,只要使用像 a[100] = 10;这样的语句就可以让数组自动扩展,与数组当前的大小无关。在把所有数组都视为关联数组(字符串等也可以作为下标)的语言(如JavaScript)中,也可以随时随地给 a[100] 赋值。但是,这种语言的设计方式在我看来迟早会出现bug。在crowbar里,如果 a的元素数是5的话,无论是给 a[10]赋值还是引用 a[10]元素,都会引发运行时的错误。

4.1.3 数组是一种引用类型

crowbar的数组是一种引用类型。什么是引用类型呢?说白了,就是指向原始值的变量类型,其实就是指针。与C语言不同的是,crowbar的数组类型不会发生访问错误内存地址的情况。

一个数组赋值给变量 a时,实际上 a保存的是这个数组的“指向”的值,将这个值赋给其他变量的话,两个变量就指向了同一个数组。因此,下面这段程序会输出 a[1]..5。

a = {1, 2, 3};

b = a;

b[1] = 5;

print("a[1].." + a[1] + "\n");

把 a赋值给 b,这样一来变量 a和变量 b就和图4-1一样,同时指向同一个数组。

figure_0134_0032

图4-1 两个变量引用同一个数组

补充知识 “数组的数组”和多维数组

前面提到过,crowbar语言中虽然没有多维数组,但有了“数组的数组”基本上就可以实现用多维数组做的事情。

如果要在crowbar中引入多维数组的话,元素就要用下面这种形式引用了。

a[3, 4]

这种多维数组有一个不方便的地方,就是不能单独取出数组中的一部分。

举个例子,销售额以月为单位存在数组中,如果想指定月份和日期取出某一日的销售额,要写成下面这样:

取11月15日的销售额(month, day都从0开始)

uriage[10][14]

如果想要定义一个函数来计算某个月的总营业额,要写成下面这样,只把某个月的数组作为参数传递给这个函数。

接受一个数组并计算合计值的函数(以11月的销售额为例)

calc_sum(uriage[10]);

上面的例子,数组的数组可以做到,多维数组就没办法了(特别是当 calc_sum() 是通用函数的时候)。

难道说多维数组就一点优点都没有吗?也不能这么说。由于在crowbar语言中数组是引用类型的,因此数组的数组会像图4-2中那样分配内存,如果是多维数组的话也许会像图4-3那样。根据 malloc()函数的机制,申请内存时多少都需要一些管理空间* [1]申请多个不连续的空间会加重GC的负担。总而言之,多维数组在运行效率上,可能比数组的数组要高一些 [2]

figure_0135_0033

图4-2 “数组的数组”的内存构造

figure_0135_0034

图4-3 “多维数组”的内存构造

另外,使用数组的数组可以改变某个特定子数组的长度或者将其设置为null。当然,要说它的方便性还能举出很多例子来,但是也有不需要它的时候,比如一个五子棋的棋盘,已经定好了是8×8,因此用多维数组来表示会更明确。

C#、D、Ada等语言能够同时支持数组的数组和多维数组。

4.1.4 为数组添加元素

crowbar的数组不能简单地使用赋值语句进行自动扩展,而是需要显式地添加元素来扩展数组。下面的代码在数组的末尾追加了一个元素。

a = {1, 2, 3};

a.add(4);

这样一来, a所指向的数组就变成了 {1, 2, 3, 4}。

根据实际情况,有些数组希望一次就生成指定的大小。比如,想要管理40名学生的身高,用索引值来表示学号,但是生成数据的顺序是随机的。像这种情况最好从一开始就预先生成一个40个元素的数组。

我觉得还是不要过多摆弄语法,使用原生函数比较好。原生函数 new_array()可以生成一个指定大小的数组。

a = new_array(40);

初始化状态下,所有的元素都是 null。

给这个函数传入多个参数,也可以生成多维数组(数组的数组)。

a = new_array(5, 10);

上例创建了一个元素最多到 a[4][9]的数组。

4.1.5 增加(模拟)函数调用功能

前面的章节中,使用了一种类似函数调用式的语法结构为数组添加元素。

a.add(3);

为了支持这种函数调用(类似的表示方法),我们修改了语法结构,也为字符串类型增加了 length()函数。

4.1.6 其他细节

变更了左值的处理方式,接着引入了自增和自减运算符。 i++ 时 i 会增加1, —时同理。自增和自减运算符只能放在后面,不支持前置的 ++、 —。

C语言中自增和自减运算符前置和后置的含义不同,人们很难读懂在一行里面写满了表达式的代码,所以我觉得自增和自减最好还是独占一行,这样一来前置和后置的含义就相同了,不论写在哪边都一样了。就我个人来说一般习惯写在后面。

也许有人会问了,在一行里只能写一个自增和自减的话,为什么它们非要是表达式呢?变成语句不是更好吗?如果是这样的话, for语句的第三个表达式就没法使用 i++了,所以它们还是用作表达式吧。