大部分程序处理输入然后从产生输出,这就是关于计算的大致定义。但是程序怎样获取数据的输入呢?一些程序自己生成数据,更多的时候,输入来自一个外部源:文件、网络连接、其他程序的输出、键盘、命令行参数等。随后的一些样例将从命令行参数开始讨论这些输入。
os
包提供一些函数和变量,以与平台无关的方式和操作系统打交道。命令行参数以os
包中的Args
名字的变量供程序访问,在os
包外面,使用os.Args
这个名字。
变量os.Args
是一个字符串slice
。slice
是Go
中的基础概念,很快会在接下来的教程中讨论到它。现在只需要理解它是一个动态容量的顺序数组s
即可,可以通过s[i]
来访问的单个元素,通过s[m:n]
来访问一段连续子区间,数组用len(s)
表示。与大部分编程语言一样,在Go
中,所有的索引使用半开区间,即包含第一个索引,不包含最后一个索引,因为这样逻辑比较简单。例如Slice s[m:n]
,其中,$0\leq m\leq n\leq len(s)$,包含n-m
个元素。
os.Args
的第一个元素是os.Args[0]
,它是命令本身的名字;另外的元素是程序开始执行时的参数。表达式s[m:n]
表示一个从第m
个到第n-1
个元素的slice
,所以下一个示例中slice
需要的元素是os.Args[1:len(os.Args)]
。如果m
或n
缺失,默认分别是0
或len(s)
,所以我们可以将期望的slice
简写为os.Args[1:]
。
这里有一个UNIX echo
命令的实现,它将命令行参数输出到一行。该实现需要导入两个包,使用由圆括号括起来的列表,而不是独立的import
声明。两者都是合法的,但为了方便起见,我们使用列表的方式。导入的顺序是没有关系的,gofmt
工具会将其按照字母顺序表进行排序。
|
|
在代码第一行中使用了注释,注释以//
开头。所有以//
开头的文本是给程序员看的注释,编译器将会忽略它们。在一个包声明前,我们通常会使用注释对其进行描述;
var
关键字声明了两个string
类型的变量s
和sep
。变量可以在声明的时候初始化。如果变量没有明确地初始化,它将隐式地初始化为这个类型地空值。例如,对于数字初始化的结果是0
,对于字符串是空字符串""
。在这个示例中,s
和sep
隐式初始化为空字符串。
对于数字,Go
提供常规的算术和逻辑操作符。当应用于字符串时,+
操作符对字符串的值进行追加操作,所以表达式
|
|
表示将sep
和os.Args[i]
追加到一起。程序中使用的语句
|
|
是一个赋值语句,将sep
和os.Args[i]
追加到旧的s
上面,并且重新赋值给s
,它等价于下面的语句:
|
|
操作符+=
是一个赋值运算符。每一个算数和逻辑操作符(例如+
或者*
)都有一个对齐的赋值操作符。
echo
程序会循环每次输出,这个程序通过反复追加来构建一个字符串。字符串s
一开始为空字符串""
,每一次循环追加一些文本。在第一次迭代后,一个空格被插入,这样当循环结束时,每个参数之间都有一个空格。这是一个二次过程,如果参数数量很大成本会比较高,不过对于这个程序还好,之后的教程中会介绍几个改进版本。
循环的索引变量i
在for
循环开始处声明。:=
符号用于短变量声明,这种语句声明一个或多个变量,并且根据初始化的值给予合适的类型,会在之后的教程中讨论到它。
递增语句i++
对i
进行加1,它等价于i += 1
,又等价于i = i +1
。对应的递减语句i--
对i
进行减1.这些是语句,而不像其他C族语言中一样是表达式,所以j = i++
在Go
语言中是不合法的,并且在Go
中仅支持后缀,所以--i
也不合法。
for
是Go
里面的唯一循环语句。它有几种形式,这里展示其中一种,其他几种会在之后的教程中详细介绍。
|
|
for
循环的三个组成部分两边不用小括号(与C族语言,java进行区分)。大括号是必须的,但左大括号必须和post
(后置)语句在同一行。
可选的initialization
(初始化)语句在循环开始之前执行。如果存在,它必须是一个简单的语句,比如一个简短的变量声明,一个递增或赋值语句,或者一个函数调用。condition
(条件)是一个布尔表达式,在循环的每一次迭代开始前推演,如果推演结果是真,循环则继续执行。post
语句在循环体之后被执行,然后条件被再次推演,直到条件变成假之后循环才介绍。
其实三部分都是可以省略的,如果没有initialization
和post
语句,分号可以省略:
|
|
如果条件部分都不存在,例子如下:
|
|
循环时无限的,但是这种形式的循环可以在循环体里面加上break
或者return
等语句进行终止。
另一种形式的for
循环在字符串
或者slice
数据上迭代,例如上面的echo
程序可以改为:
|
|
每一次迭代,range
都会产生一对值:所以和这个索引处的元素值。这个例子里,因为不需要索引,但是在语法上range
循环需要处理,因此页必须处理所有。一个方法时我们将所有赋予一个临时变量(如temp
)然后忽略它,但是,但是!在Go
中不允许存在无用的临时变量,不然会出现错误。
在Go
中的解决方案时使用空标识符,它的名字是_
(即下划线)。空标识符可以用在任何语法需要变量名但是程序逻辑不需要的地方,例如丢弃每次迭代产生的无用的所以。大多数Go
程序员喜欢搭配使用range
和_
来写上面的echo
程序,因为所有在os.Args
上面是隐式的,所以更不容易犯错。
同时,在这个改进的echo
程序中使用了短的变量声明来声明和初始化s
和sep
(代码第11行),但是我们可以等价地分开声明变量。以下几种声明字符串变量地方式是等价的:
|
|
为什么优秀的程序员喜欢用第一种?因为第一种形式的短变量声明更加简洁,但是这种形式的声明通常在一个函数内部使用,不适合包级别的变量声明(即func语句的外面)。
第二种形式依赖默认初始化为空字符串的""。
第三种形式很少用,除非我们声明多个变量。
第四种形式是显式的变量类型,在类型一致的情况下是冗余的信息,在类型不一致的情况是必需的。
在实践种,我们应当使用前两种形式,使用显式的初始化来说明初始化变量的重要性,使用隐式的初始化来表明初始化变量不重要。
如上所述,每次循环,字符串s
都有了新的内容。+=
语句通过追加旧的字符串、空格字符和下一个参数,生成一个新的字符串,然后把新字符串赋给s
。旧的内容不再需要使用,会被例行垃圾回收。
如果有大量的数据需要处理,这样的代价会比较大。一个简单且高效的方式是使用strings
包中的Join
函数:
|
|