第二章 基础知识
2.1 编译和运行Icon程序
作者假设你的系统上安装了Icon 9版本。
假设你把Icon程序命名为“test”。你把你的Icon程序放在一个文件“test.icn”中。然后用icont命令编译它
icont test
如果编译没有出现错误,则使用命令运行它
test
如果有错误,Icon编译器会告诉你它在哪里遇到了错误以及错误是什么。我们将在稍后的第14章中进行更全面的讨论。
2.2 Hello, world
习惯上,写一个“hello, world”程序作为开始,正常的话它会输出“hello, world”来。
例1 Hello world
procedure main()
write("hello, world")
end
您可以看到编写过程的方法:以procedure开始,以end结束。过程后面是过程名称和参数列表,可以为空。
write函数将其参数写入输出,然后终止该行(再用一条write会从下一行开始写)。
字符串是用双引号括起来。
当程序开始运行时,它执行名为main的过程,就像在C中一样。
在下面的例2中,我们展示了在Icon中,用新行或分号(如果您愿意,也可以两者都用)分隔序列中的表达式。Icon按顺序执行表达式。该writes函数(注意相比上例中函数多个s)将其参数写入输出,但不终止该行。接下来写的内容将在同一行。
例2 表达式序列
procedure main()
writes(”hello,”)
write(” world”)
end
或
procedure main()
writes(”hello,”);
write(” world”)
end
或
procedure main()
writes(”hello,”); write(” world”)
end
Icon是一种弱类型语言。这意味着变量没有被声明为具有特定的数据类型。只有值才有数据类型,任何类型的值都可以赋值给任何变量。就此而言,变量根本不需要声明。下面例3说明了这一点。
赋值操作符是“:=”。
变量x被分配了两个不同类型的值,第一个是字符串,然后是整数。
过程write可以像输出字符串一样输出整数。事实上,它会输出任何它知道如何转换为字符串的东西。
例3 弱类型
procedure main()
x := "Example "
writes(x)
x := 1
write(x)
end
例4显示了局部声明和交换操作符。
过程中的局部变量在进入过程时分配内存,在过程返回时消失。如果没有对变量的声明,如例3中的x,则编译器将其变为局部变量。
“:=:”是交换操作符;它将交换两个变量的值。
例4 局部声明
procedure main()
local x,y
x := " Example "
y := 2
write(x,y)
x :=: y
write(x,y)
end
当创建变量时,它们被赋予初始值null,这导致大多数操作在运行时报告错误。您将经常遇到这种运行时错误。
例5 未初始化变量
procedure main()
local x
write(x+1)#this will cause an error at run time
# (and notice: comments begin with
# and run to the end of the line)
end
2.3 声明
我们已经看到了局部声明。Icon实际上提供了以下所有种类的声明:
local x,y,z #定义在过程内。每当进入过程时,创建变量的新副本。在过程返回时删除它们。这些名称仅在过程中是已知的。对于未声明的变量,假设是局部的,但不要使用此特性:稍后引入全局声明可能会导致过程停止工作。
static x,y,z #定义在过程内。在程序开始执行时创建变量的副本。这些名称仅在过程中是已知的。静态变量只有一个副本。它在过程调用之间保持其值。
global x,y,z #定义在过程外。在程序开始执行时创建变量的副本。只有在没有为局部变量或静态变量声明相同名称的所有过程中才知道这些名称。全局变量只有一个副本。
procedure #定义在过程外,不能嵌套。参见第5章“过程”
name(x,y,z)
...
end
record name(x,y,z) #定义在过程外。参加第2.10节和第12章
link name #定义在过程外。告诉链接器该程序使用文件名中声明的过程、记录或全局变量。名称可以是Icon标识符,但如果包含Icon标识符中不允许的字符,则必须是带引号的字符串。
2.4 退出程序
有几种方法可以退出Icon程序。您已经看到的方法是从过程main返回。还有两个函数也被使用,exit和stop。
exit() #以正常退出状态退出程序(即,告诉操作系统一切正常)。
exit(n) #退出程序并返回整数n作为退出状态。这是告诉操作系统事情不正常的方法,但是您必须知道操作系统如何解释这些退出状态值才能使用它。
stop(s1,s2,...,sn) #输出字符串s1 s2…Sn,退出时显示错误状态。进一步请看第8章I/O部分。
2.5 数字
2.5.1 整数字面值
您可以将整数字面值(常量)写成十进制数,例如25。
2.5.2 整数运算符
+ 加号 优先级8
- 减号 优先级8
* 乘号 优先级9
/ 除号 优先级9
% 余号 优先级9
^ 乘方 优先级10
除幂运算符首先在最右边执行外,其他运算符从左到右执行。运算符*、/和%在+和-之前完成。^运算符在其他运算符之前执行。也就是说,优先级数大的操作符在优先级小的运算符之前执行。
2.6 字符串
2.6.1 字符串字面值
你写一个用引号括起来的字符串字面值(常量):
"Like this"
如果您需要在字符串中包含引号,请在其前面加上反斜杠,例如"\""。如果你需要包含一个反斜杠,在它前面放一个反斜杠,"\\"。有一些特殊的方法可以包含其他字符,见后面的章节。
2.6.2 字符串操作符
您可以使用“||”运算符连接两个字符串,例如:
s:="ab"
s:=s||"cd"
write(s)
会输出“abcd”。
您可以使用一元“*”运算符找出字符串的长度,例如:
s:="abc"
write(*s)
s:="a"
write(*s)
s:=""
write(*s)
会输出
3
1
0
2.6.3 字符串下标
字符串中的字符按照字符串长度从1开始编号。你可以给字符串下标,就像在大多数其他语言中给数组下标一样,把索引放在字符串后面的括号里:
s:="find"
write(s[3])
s[4] := "e"
write(s)
会输出
n
fine
与C语言不同,它没有单独的字符类型。只有长度为1的字符串。上面的表达式[3]返回一个长度为1的字符串“n”。
当用字符串下标赋值,可以赋值多个字符的字符串或空字符串。例如:
s:="fund"
s[4] := ""
write(s)
s[3] := "nny"
write(s)
会输出
fun
funny
Icon还允许您使用位置范围下标字符串,选择多于或少于一个字符。
s[i:j]
(其中i≤j),它从字符i到字符j之间选择子字符串,但不包括字符j。
如果分配给子字符串,则替换所选字符。如果在赋值时i=j,则插入到字符i之前。如果i=j=*s+1,则追加到字符串s之后
s:="12345"
write(s)
s[3:3]:="x" #当i=j时,会把字符串插到原字串位置i字符之前
write(s)
会输出
12345
12x345
s:="12345"
write(s)
s[3:4]:="x" #当i:i+1时,会把字符串插到原字串位置i字符那
write(s)
会输出
12345
12x45
s:="12345"
write(s)
s[3:5]:="x" #当i:i+n(2或更大)时,会把原字串位置i到i+(n-1)的字符串变"",然后插入此位置
write(s)
会输出
12345
12x5
s:="12345"
write(s)
s[3:5]:="xyz" #当i:i+n(2或更大)时,会把原字串位置i到i+(n-1)的字符串变"",然后插入此位置
write(s)
会输出
12345
12xyz5
s:="12345"
write(s)
s[*s+1:*s+1]:="yz" #i=j=*s+1,则追加到字符串s之后
write(s)
会输出
12345
12345yz
s := "12345"
write(s[6]) #超出长度不会报错,只会输出空串
会输出
s := "12345"
write(s[0]) #取下标0会输出空串
会输出
s := "12345"
write(s[-1]) #当负数下标会从右侧数取位置
会输出
5
2.6.4 比较运算符
下面是数字和字符串的基本比较运算符:
i=j s1==s2 相等 优先级6
i~=j s1~==s2 不相等 优先级6
i<j s1<<s2 小于 优先级6
i<=j s1<<=s2 小于等于 优先级6
i>j s1>>s2 大于 优先级6
i>=j s1>>=s2 大于等于 优先级6
2.7 基本控制结构
以下是Icon中使用的三种最常见的控件结构。我们省略了大部分细节,详见第4章:
2.7.1 If表达式
你可以使用if表达式选择要执行的代码:
if expr1 then expr2 else expr3
目前,我们只在expr1中使用一个比较运算符。如果expr1成功(在其他语言中我们会说,如果expr1为真,但在Icon中我们不会这么说,我们在这儿说“成功”),那么Icon执行expr2,否则,Icon执行expr3。
第3章和第4章将更详细地讨论expr1的其他选项。
因为if表达式是一个表达式,所以它返回一个值,expr2或expr3的值。
2.7.2 While表达式
你可以使用while表达式来重复执行一些代码:
while expr1 do expr2
同样,目前我们将把自己限制在expr1中的单个关系操作符。只要expr1成功,Icon就执行expr2。尽管while表达式是一个表达式,但它不返回值。
2.7.3 表达式序列
你可以使用大括号{expr1;expr2;…;exprn},对表达式序列进行分组,以便将它们包含在if表达式或while表达式中。大括号内的表达式用分号分隔,或者用新行分隔,或者两者同时分隔,就像过程体中的表达式序列一样。
表达式序列是一个表达式。它返回序列中最后一个表达式的值。
2.8 基本生成器
生成器是传递一系列值的表达式,是Icon的核心。它们将在第3章中深入讨论。这里我们只展示其中一个用途。
这循环
every i := 1 to 10 do expr
是Icon中的for循环。表达式“1 to 10”是一个生成器,它生成整数1,2,…10。每个值被赋给变量i,表达式expr被求值。
几个生成器可以与&操作符组合使用,以实现嵌套循环的效果:
every i := 1 to 10 & j := 1 to 10 do expr
行为类似于两个嵌套的for循环。当i = 1时,j将从1迭代到10,然后当i = 2时,j将从1迭代到10,以此类推。
您还可以添加测试以消除一些迭代:
every i := 1 to 10 & j := 1 to 10 & i ~= j do expr
如果I和j的值相同就会忽略expr的取值。
2.9 基本列表
2.9.1 List创建:list (n)
列表类似于其他语言中的数组。您可以使用list函数创建一个元素编号为1、2、…n的列表
例如:
L:=list(3)
将创建一个长度为3的列表,并将其赋值给变量L。列表中的所有元素将初始化为null。
2.9.2 列表下标
长度为n的列表是一个包含n个元素的数组,元素的编号从1到n,就像数组一样。列表下标的方式与字符串相同:将下标表达式放在列表后面的括号中,例如:
L:=list(2)
L[1]:=5
L[2]:=10
write(L[1])
会输出
5
您可以创建长度为0的列表。只需list(0)
2.9.3 List创建:[...]
如果要创建一个包含特定值的短列表,你可以在括号中列出你想要的值:
L:=[5,10]
write(L[1])
会输出
5
您可以通过写[]来创建一个长度为0的列表。
2.9.4 List创建:list(n,x)
如果你想创建一个所有元素都相同的列表,但不是null,使用list(n,x),它将创建一个长度为n的列表,所有元素都是x。
2.9.5 列表操作符
你可以用操作符“|||”连接两个列表,例如:
s:=[5,6]
s:=s|||[7,8]
write(s[3])
会输出
7
x|||y的结果是一个新的列表,其中包含x的元素和y的元素。列表x和y不会改变。
你可以使用一元*运算符找出列表的长度,例如:
s:=[1,2,3]
write(*s)
s:=[]
write(*s)
会输出
3
0
可以使用===或~===操作符比较两个列表是否相同。(连续使用三个等号。)怎样才能成为相同的列表呢?请看
L:=[1,2]
M:=L
在此代码之后,L === M将成功,L ~=== M将失败。赋值M:=L使L和M指向同一个列表。再看
L:=[1,2]
M:=[1,2]
在此代码之后,L ~=== M将成功,L === M将失败。赋值M:=[1,2]使得M指向一个不可能与L相同的新列表。即使L和M指向具有相同长度和相同内容的列表,它们也不相同。
例子:
x:=1
y:=1
write(if x === y then "equal" else "not equal")
会输出
equal
例子:
x:=1
y:="1"
write(if x === y then "equal" else "not equal")
会输出
not equal
例子:
L:=[[1],[1]]
write(if L[1] === L[2] then "equal" else "not equal")
会输出
not equal
因为两次单独出现的[1]创建了两个不同的列表。
例子:
L:=list(2,[1])
write(if L[1] === L[2] then "equal" else "not equal")
会输出
equal
因为在list函数中出现[1]只计算一次,创建一个列表,它被分配给L的两个元素。
2.9.6 列表与字符串的区别
下面是列表和字符串之间的一些区别:
字符串有字面值,列表没有字面值。
列表是可变值;字符串是不可变的。这意味着您可以更改列表中的一个元素,并在与该元素相等(===)的所有列表中看到该更改。如果您更改字符串变量中的一个字符,Icon实际上会用所做的更改创建一个新字符串,并将该新字符串赋值给变量。
当您将值赋给列表的元素时,列表的长度不会改变。将字符串赋值给下标字符串时,字符串的长度可以更改。您分配的字符串在替换该位置的字符时被拼接。
过程write将输出一个字符串。它不会写出一个列表。
可以将一个列表赋值给另一个列表的元素。你可以将一个列表赋值给它自身的元素,得到一个循环结构。
虽然Icon里皆是表达式,但不能对一个引号的字符串用下标方式改变内容,如:
"abcd"[2]:="x" #这是错的
但神奇的是,Icon列表这么写却是合法的,如:
[1,2,3][2]:=5
2.9.7 main过程参数
过程main接受一个参数,是个列表变量,所有命令行传来的参数都放入这个字符串列表。如上所述,实际上不必用参数声明过程main。下面是一个使用参数的例子,一个程序回显命令行参数:
procedure main(args)
i := 1
while i <= *args do {
write(args[i])
i:=i+1
}
end
执行test -a 123 -b xyz
会显示
-a
123
-b
xyz
2.10 记录
您可以在Icon中创建新的记录数据类型,就像在Pascal(记录)、C(结构)和c++(类)中一样。声明一个记录类型:
record rname(f1,f2,...,fn)
• “rname”是给记录类型的名称。
• f1, f2, …, fn是给定给记录的字段(成员)的名称。
• 记录声明只允许在过程声明之外使用。
例子
record Point(x,y)
可用于在二维坐标系中定义一个点。
一个点可以通过表达式创建:
r := Point(1,2)
它将创建一个Point类型的新记录,将其x字段初始化为1,y字段初始化为2,并将Point记录赋值给变量r。
记录的字段可以使用二进制“。”,字段引用,操作符,例如。
记录的字段可以使用“.”操作符来访问,例如:
r.x := r.y
与 Pascal、C 和 C++ 不同,Icon没有显式指针。
p1 := Point(1,2)
p2 := p1 #p2 points to p1
p2.x := 2 #also changes p1.x
write(if p1 === p2 then "equal" else "not equal")
write(if p1.x === p2.x then "equal"
else "not equal")
会输出
equal
equal