main
包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main
里的main
函数 也很特殊,它是整个程序执行时的入口。
Go语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响Go代码的正确解析。
os
包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。
os.Args的第一个元素:os.Args[0],是命令本身的名字;其它的元素则是程序启动时传给它的参数。
变量会在声明时直接初始化。如果变量没有显式初始化,则被隐式地赋予其类型的零值(zero value)。
Go语言只有for循环这一种循环语句。for循环有多种形式,其中一种如下所示:
for initialization; condition; post {
// zero or more statements
}
for循环三个部分不需括号包围。大括号强制要求,左大括号必须和post语句在同一行。
initialization语句是可选的,在循环开始前执行。initalization如果存在,必须是一条简单语句(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。condition
是一个布尔表达式(boolean expression),其值在每次循环迭代开始时计算。如果为true
则执行循环体语句。post
语句在循环体执行结束后执行,之后再次对condition
求值。condition
值为false
时,循环结束。
for循环的这三个部分每个都可以省略,如果省略initialization
和post
,分号也可以省略:
// a traditional "while" loop
for condition {
// ...
}
如果连condition
也省略了,像下面这样:
// a traditional infinite loop
for {
// ...
}
这就变成一个无限循环,尽管如此,还可以用其他方式终止循环,如一条break
或return
语句。
for
循环的另一种形式,在某种数据类型的区间(range)上遍历,如字符串或切片。
如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用strings
包的Join
函数。
map存储了键/值(key/value)的集合,对集合元素,提供常数时间的存、取或测试操作。键可以是任意类型,只要其值能用==
运算符比较,值则可以是任意类型。
map
中不含某个键时不用担心,首次读到新行时,等号右边的表达式counts[line]
的值将被计算为其类型的零值,对于int
即0。
map
的迭代顺序并不确定,从实践来看,该顺序随机,每次运行都会变化。这种设计是有意为之的,因为能防止程序依赖特定遍历顺序,而这是无法保证的。
bufio
包,它使处理输入和输出方便又高效。Scanner
类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。
程序使用短变量声明创建bufio.Scanner
类型的变量input
。
input := bufio.NewScanner(os.Stdin)
该变量从程序的标准输入中读取内容。每次调用input.Scan()
,即读入下一行,并移除行末的换行符;读取的内容可以调用input.Text()
得到。Scan
函数在读到一行时返回true
,不再有输入时返回false
。
fmt.Printf
函数对一些表达式产生格式化输出。该函数的首个参数是个格式字符串,指定后续参数被如何格式化。各个参数的格式取决于“转换字符”(conversion character),形式为百分号后跟一个字母。举个例子,%d
表示以十进制形式打印一个整型操作数,而%s
则表示把字符串型操作数的值展开。
Printf
有一大堆这种转换,Go程序员称之为动词(verb)。下面的表格虽然远不是完整的规范,但展示了可用的很多特性:
%d 十进制整数
%x, %o, %b 十六进制,八进制,二进制整数。
%f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00
%t 布尔:true或false
%c 字符(rune) (Unicode码点)
%s 字符串
%q 带双引号的字符串"abc"或带单引号的字符'c'
%v 变量的自然形式(natural format)
%T 变量的类型
%% 字面上的百分号标志(无操作数)
默认情况下,Printf
不会换行。按照惯例,以字母f
结尾的格式化函数,如log.Printf
和fmt.Errorf
,都采用fmt.Printf
的格式化准则。而以ln
结尾的格式化函数,则遵循Println
的方式,以跟%v
差不多的方式格式化参数,并在最后添加一个换行符。(译注:后缀f
指format
,ln
指line
。)
os.Open
函数返回两个值。第一个值是被打开的文件(*os.File
),其后被Scanner
读取。os.Open
返回的第二个值是内置error
类型的值。如果err
等于内置值nil
,那么文件被成功打开。如果err
的值不是nil
,说明打开文件时出错了。
函数和包级别的变量(package-level entities)可以任意顺序声明,并不影响其被调用。
map
是一个由make
函数创建的数据结构的引用。map
作为参数传递给某函数时,该函数接收这个引用的一份拷贝(copy,或译为副本),被调用函数对map
底层数据结构的任何修改,调用者函数都可以通过持有的map
引用看到。
ReadFile
函数返回一个字节切片(byte slice),必须把它转换为string
,才能用strings.Split
分割。
实现上,bufio.Scanner
、ioutil.ReadFile
和ioutil.WriteFile
都使用*os.File
的Read
和Write
方法,但是,大多数程序员很少需要直接调用那些低级(lower-level)函数。
常量声明和变量声明一般都会出现在包级别,所以这些常量在整个包中都是可以共享的,或者你也可以把常量声明定义在函数体内部,那么这种常量就只能在函数体内用。目前常量声明的值必须是一个数字值、字符串或者一个固定的boolean值。
Go语言在net这个强大package的帮助下提供了一系列的package,使用这些包可以更简单地用网络收发信息,还可以建立更底层的网络连接,编写服务器程序。
goroutine是一种函数的并发执行方式,而channel是用来在goroutine之间进行参数传递。main函数本身也运行在一个goroutine中,而go function则表示创建一个新的goroutine,并在这个新的goroutine中执行这个函数。
当一个goroutine尝试在一个channel上做send或者receive操作时,这个goroutine会阻塞在调用处,直到另一个goroutine从这个channel里接收或者写入值,这样两个goroutine才会继续执行channel操作之后的逻辑。
switch, Go语言并不需要显式地在每一个case后写break,语言默认执行完case后的逻辑语句会自动退出。当然了,如果你想要相邻的几个case都执行同一逻辑的话,需要自己显式地写上一个fallthrough语句来覆盖这种默认行为。
Go语言里的switch还可以不带操作对象(译注:switch不带操作对象时默认用true值代替,然后将每个case的表达式和true值进行比较);可以直接罗列多种条件,像其它语言里面的多个if else一样,下面是一个例子:
func Signum(x int) int {
switch {
case x > 0:
return +1
default:
return 0
case x < 0:
return -1
}
}
这种形式叫做无tag switch(tagless switch);这和switch true是等价的。
像for和if控制语句一样,switch也可以紧跟一个简短的变量声明,一个自增表达式、赋值语句,或者一个函数调用。
break和continue语句会改变控制流。和其它语言中的break和continue一样,break会中断当前的循环,并开始执行循环之后的内容,而continue会跳过当前循环,并开始执行下一次循环。这两个语句除了可以控制for循环,还可以用来控制switch和select语句。
continue会跳过内层的循环,如果我们想跳过的是更外层的循环的话,我们可以在相应的位置加上label,这样break和continue就可以根据我们的想法来continue和break任意循环。这看起来甚至有点像goto语句的作用了。
指针:指针是可见的内存地址,&操作符可以返回一个变量的内存地址,并且*操作符可以获取指针指向的变量内容,但是在Go语言里没有指针运算,也就是不能像c语言里可以对指针进行加或减操作。
方法和接口: 方法是和命名类型关联的一类函数。Go语言里比较特殊的是方法可以被关联到任意一种命名类型。接口是一种抽象类型,这种类型可以让我们以同样的方式来处理不同的固有类型,不用关心它们的具体实现,而只需要关注它们提供的方法。