Go是静态语言。变量是有明确类型的。

Packages

Go程序是由package组成。

包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。

Go的标准库提供了100多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如fmt包,就含有格式化输出、接收输入的函数。Println是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。

  1. fmt.Println("Hello, 世界", "xxx")

程序开始于main package。但是不一定需要放到main目录下。
main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main里的main 函数也很特殊,它是整个程序执行时的入口。

必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。

Import

通过import导入其他package。可以逐个导入,或者通过小括号()组合起来导入。注意,没有逗号。

  1. // 逐个导入
  2. import "fmt"
  3. import "math"
  4. //组合导入
  5. import (
  6. "fmt"
  7. "math"
  8. )

Exported names

Exported names相当于public变量或函数。以大写字母开头即为exported names。

  1. //此处math.Pi, Pi为exported name
  2. func main() {
  3. fmt.Println(math.Pi)
  4. }

Variables

标准格式

Go语言的变量声明的标准格式为:关键字 var 开头,后置变量类型,行尾无须分号。

  1. var i int

批量格式

  1. var (
  2. a int
  3. b string
  4. c []float32
  5. d func() bool
  6. e struct {
  7. x int
  8. }
  9. )

或者

  1. var c, python, java bool

或者声明时赋值

  1. var i, j int = 1, 2

如果声明时赋值,可以不写类型。会根据值自动判断类型。

  1. var c, python, java = true, false, "no!"

简短格式

  1. 名字 := 表达式
  • 定义变量,同时显式初始化。
    1. i := 1
    2. //等价于
    3. var i = 1
    “:=”不能重新声明一个已经声明过的对象。但是如果使用“:=”同时为多个变量赋值,只要有一个变量是之前没有声明过的,即可通过编译。 ```go //以下是非法的 var i = 1 i := 2

//以下的合法的,此处并没有重新声明i,只是对i进行了赋值 var i = 1 i, flag := 2, false

  1. - 不能提供数据类型。
  2. - 只能用在函数内部。
  3. “:=”只能用在函数体中,一个通用的用法是,用在ifforswitch语句的初始化,使变量成为一个临时变量。
  4. ```go
  5. func main() {
  6. for j := 3; j <= 5; j++ {
  7. fmt.Println(j) //这里可以正常输出
  8. }
  9. fmt.Println(j) //这里会报错
  10. }

默认赋值

如果变量在定义的时候没有初始值,会赋默认值。

  1. var i int //0
  2. var f float64 //0
  3. var b bool //false
  4. var s string //""

获取变量类型

利用反射机制。reflect.TypeOf()

  1. mySlice := []string{"aaa", "bbb"}
  2. fmt.Println(reflect.TypeOf(mySlice))

类型转换

通过T(v)的方式把变量v强制转换为类型T

  1. var i int = 42 //等价于 i:=42
  2. var f float64 = float64(i) //等价于 f:=float64(i)
  3. var u uint = uint(f)

必须通过这种方式显示进行类型转换,下面这种是非法的,会报错。

  1. // 非法
  2. var f float64 = math.Sqrt(25)
  3. // 合法。因为Math.Sqrt()的参数是float类型,必须进行显示类型转换
  4. var f float64 = math.Sqrt(float(25))

常量

常量的定义以const开头。可以指定类型,也可以不指定。
常量的类型可以是character,string,boolean,或者numeric values。
常量不能用“:=”定义。

  1. const Pi = 3.14
  2. const World = "世界"
  3. const Truth = true
  4. const b string = "abc"

Functions

go函数的返回值可以是多个。函数名后两个括号,一个是参数,一个是返回值。

  1. func swap(x, y string) (string, string) {
  2. return y, x
  3. }
  4. func main() {
  5. a, b := swap("hello", "world")
  6. fmt.Println(a, b)
  7. }

函数返回时,如果在返回值定义类型时定义了返回的变量,可以不指定返回的值。如下例,返回的变量就是x和y。
运行结果是:“7 10”。

  1. func split(sum int) (x, y int) {
  2. x = sum * 4 / 9
  3. y = sum - x
  4. return
  5. }
  6. func main() {
  7. fmt.Println(split(17))
  8. }

For循环

Go语言只有一个循环结构,就是for循环。初始化语句,条件表达式和后置语句部分不加“()”,循环体必须加“{}”。
循环变量是局部变量,scope仅在for循环中。使用“:=”赋值。
即使循环体只有一行语句,也要加“{ }”。

  1. func main() {
  2. sum := 0
  3. for i := 0; i < 10; i++ {
  4. sum += i
  5. }
  6. }

可以省略初始化语句和后置语句。

  1. func main() {
  2. sum := 1
  3. for ; sum < 1000; {
  4. sum += sum
  5. }
  6. }

去掉分号也可以使用for。相当于其他语言中的while语句。

  1. func main() {
  2. sum := 1
  3. for sum < 1000 {
  4. sum += sum
  5. }
  6. }

无限循环

  1. for{
  2. }

用于遍历。
每次循环迭代,range产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但range的语法要求,要处理元素,必须处理索引。可使用用空标识符(blank identifier),即_(也就是下划线),丢弃不需要的循环索引,并保留元素值。

  1. for _, arg := range os.Args[1:] {
  2. s += sep + arg
  3. sep = " "
  4. }

If

if语句的判断条件不加“()”,执行部分必须加“{}”。
可以在条件表达式前以简短格式定义变量,scope是if和else之内。

  1. if v := math.Pow(x, n); v < lim {
  2. return v
  3. } else {
  4. fmt.Printf("%g >= %g\n", v, lim)
  5. }

switch

switch语句与其他语言相同,但是case语句不需要break,匹配后会自动break。如果需要让其匹配后继续执行default分支,加fallthrough。

  1. switch os := runtime.GOOS; os {
  2. case "darwin":
  3. fmt.Println("OS X.")
  4. case "linux":
  5. fmt.Println("Linux.")
  6. fallthrough //加了fallthrough,所以default也会执行
  7. default:
  8. // freebsd, openbsd,
  9. // plan9, windows...
  10. fmt.Printf("%s.\n", os)
  11. }

switch可以不加条件使用,相当于switch true。

  1. switch {
  2. case t.Hour() < 12:
  3. fmt.Println("Good morning!")
  4. case t.Hour() < 17:
  5. fmt.Println("Good afternoon.")
  6. default:
  7. fmt.Println("Good evening.")
  8. }

defer语句

defer语句会将函数推迟到外层函数返回后执行。
它的参数会立即求值,然后压栈。等外层函数返回后,再按照先进后出的顺序执行。
下例的执行结果是,“done 9 8 7 6 5 4 3 2 1 0”。(此处用空格代替换行)

  1. func main() {
  2. fmt.Println("counting")
  3. for i := 0; i < 10; i++ {
  4. defer fmt.Println(i)
  5. }
  6. fmt.Println("done")
  7. }

指针

指针中存储了变量的内存地址。
与C不同的是,Go没有指针运算。
*T指向T类型的变量,其零值为nil。

  1. var p *int

&操作符会生成一个指向其操作数的指针。

  1. i := 42
  2. p = &i

*操作符表示指针实际指向的内存地址的值。这就是常说的“间接引用”或“重定向”。

  1. fmt.Println(*p) //通过指针p读取i
  2. *p = 21 //通过指针p设置i

结构体

一个结构体就是一组字段。
结构体可以隐式给全部字段赋值,或者显式只给部分字段赋值。
结构体的字段用“.”来访问。
结构体可以通过指针进行间接访问。

  1. type Vertex struct {
  2. X int
  3. Y int
  4. }
  5. func main() {
  6. v := Vertex{1, 2} //创建一个Vertex类型的结构体
  7. v2 := Vertex{X: 1} //给指定结构体字段赋值
  8. p := &Vertex{3, 4} //创建一个*Vertext类型的结构体(指针)
  9. v.X = 4 //直接访问结构体字段
  10. (*p).X = 5 //指针间接访问结构体字段
  11. p.x = 5 //指针隐式间接访问结构体字段
  12. }

数组

类型n[T]表示拥有n个T类型值的数组。数组不能改变大小。

  1. var a [10]int

slice

数组的长度是不能改变的,而slice则为数组元素提供了动态大小的视角。可以当作是在数组上的滑动窗口。
类型[]T表示元素类型为T的slice。
slice通过两个下标来界定。一个上界,一个下界,冒号分隔。

  1. a[low: high] //左闭右开,low包含,high不包含

切片实际不存储数据,更改切片的元素,实际就是更改底层数组的元素。与之共享底层数组的其他切片会感知到变化。

  1. names := [4]string{"John", "Paul", "George", "Ringo"}
  2. a := names[0:2]
  3. b := names[1:3]
  4. b[0] = "XXX"
  5. fmt.Println(a, b) //[John XXX] [XXX George]
  6. fmt.Println(names) //[John XXX George Ringo]

创建切片并初始化时,会同时创建一个底层数组。

  1. q := []int{2, 3, 5, 7, 11, 13}
  2. r := []bool{true, false, true, true, false, true}
  3. s := []struct {
  4. i int
  5. b bool
  6. }{
  7. {2, true},
  8. {3, false},
  9. {5, true},
  10. {7, true},
  11. {11, false},
  12. {13, true},
  13. }

map

  1. counts := make(map[string]int)
  2. input := bufio.NewScanner(os.Stdin)
  3. for input.Scan() {
  4. counts[input.Text()]++
  5. }
  6. // NOTE: ignoring potential errors from input.Err()
  7. for line, n := range counts {
  8. if n > 1 {
  9. fmt.Printf("%d\t%s\n", n, line)
  10. }
  11. }

当两次输入相同时,打印结果却不同。
map的迭代顺序并不确定,从实践来看,该顺序随机,每次运行都会变化。这种设计是有意为之的,因为能防止程序依赖特定遍历顺序。(可参考链接