Golang

  本文也并非 Go 语言基础语法的全部罗列,只是摘录一些独特特性。

变量

指针

1
var p *int	// 指针默认值nil

数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var arr [10]int

a := [3]int{1, 2}           // 未初始化元素值为 0
b := [...]int{1, 2, 3}      // 通过初始化值确定数组长度
c := [5]int{2: 100, 4: 200} // 通过索引号初始化元素,未初始化元素值为 0
fmt.Println(a, b, c)        // print "[1 2 0] [1 2 3] [0 0 100 0 200]"

// 支持多维数组,第二维不能写"..."
d := [...][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}

// 传指针参数,若传值则会拷贝整个数组
func modify(array *[5]int) {
    *array)[0] = 10	// 传值则 array[0] = 10
}
func main() {
    array := [5]int{1, 2, 3, 4, 5}
    modify(&array)
}

切片

1
2
3
4
5
6
7
8
9
// 声明slice时,方括号内没有任何字符
var s1 []int	// 空切片
s2 := []int{}

// make([]T, length, capacity)
var s3 []int = make([]int, 0)	// capacity可省略,则和length相同
s4 := make([]int, 0, 0)

s5 := []int{1, 2, 3}

流程控制

if

  条件表达式没有括号。支持一个初始化表达式, 初始化字句和条件表达式直接需要用分号分隔。

1
2
3
4
5
if a := 3; a == 4 {
    fmt.Println("a == 4")
} else { //左大括号必须和条件语句或else在同一行
    fmt.Println("a != 4")
}

for + range

1
2
3
4
5
6
7
str := "abcde"
for i := range str {
    fmt.Printf("%c ", str[i])	// print "a b c d e "
}
for _, c := range str {	// 匿名变量忽略index
    fmt.Printf("%c ", c)		// print "a b c d e "
}

函数

  Go 语言函数定义格式如下:

1
2
3
4
5
6
// 若没有返回值,可直接省去返回值列表
func FuncName( /* 参数列表 */ ) (o1 type1, o2 type2 /* <-返回值列表 */ ) {
    // 函数体
    ...
    return v1, v2	// 可返回多个值
}

函数类型

1
2
3
4
5
6
7
type FuncType func(int, int) int //声明一个函数类型

func Add(a, b int) int {
    return a + b
}

var f FuncType = Add

不定参数

  与 C++ 语法类似。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//形如 ...type 格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
func Fun01(args ...int) {
    for _, n := range args { // 遍历参数列表
        fmt.Printf("%d ", n)
    }
}

func Fun02(args ...int) {
    Fun01(args[1:]...) // Fun02()参数列表中,第1个参数及以后的参数传递给 Fun01
}

func main() {
    Fun02(1, 2, 3, 4, 5) // print "2 3 4 5 "
}

闭包

  闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。当闭包被调用的时候,不管在程序什么地方调用,闭包都能够使用这些常量或者变量,而不关心这些捕获了的变量和常量是否已经超出了作用域。在Go语言里,所有的匿名函数(Go语言规范中称之为函数字面量)都是闭包。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
i := 666
str := "yyf"

func() {
    i := 12345	// 若此处使用 i = 12345,则会捕获外部变量 i 并修改
    str = "go"
    fmt.Printf("内部: i = %d, str = %s\n", i, str)
    // print "内部: i = 12345, str = go"
}() // 可直接调用匿名函数

fmt.Printf("外部: i = %d, str = %s\n", i, str)
// print "外部: i = 666, str = go"

  下面是一个函数返回值为闭包的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// squares 返回一个匿名函数 func() int
func squares() func() int {
    var x int
    return func() int {
        x++ // 捕获外部变量
        return x * x
    }
}

func main() {
    f := squares() // 可先将匿名函数赋值给一个变量f,再进行调用
    fmt.Println(f())	// print "1"
    fmt.Println(f())	// print "4"
    fmt.Println(f())	// print "9"
    fmt.Println(f())	// print "16"
}

  对 squares 的一次调用会生成一个局部变量 x 并返回一个匿名函数,可以看到变量的生命周期不由它的作用域决定:squares 返回后,变量 x 仍然隐式的存在于 f 中。

复合类型

map

  map 是一种无序的键值对的集合。 - 一个 map 里所有的 key 都是唯一的,必须是支持 ==!= 操作符的类型。 - 切片、函数、包含切片的结构类型由于具有引用语义,也不能作为 key。 - 在函数间传递映射并不会制造出该映射的一个副本,不是值传递,而是引用传递。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var m1 map[int]string	// 空(nil)map

m2 := map[int]string{}
m3 := make(map[int]string)

m4 := make(map[int]string, 10)	// 第2个参数指定容量

// 初始化的右括号不能换行
var m5 map[int]string = map[int]string{123: "qop", 233: "lina"}
m6 := map[int]string{123: "qop", 233: "lina"}
m6[345] = "siren"  // 追加, go底层会自动为map分配空间。无法对 nil map 追加元素,必须先初始化

// 判断某个 key 所对应的value是否存在
// 若存在,第一个返回值是 value,第二个返回值是 true
// 若不存在,第一个返回值是 空,第二个返回值是 false
value, ok := m6[999]

delete(m6, 123) // 删除 key 值为 123  map

结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type Student struct {
    id   int
    name string
    sex  byte
    age  int
}

var s1 Student = Student{1, "yyf", 'm', 18}
s2 := Student{2, "lilith", 'f', 20}
s3 := Student{id: 3, name: "longdd"}	// 指定初始化某个成员,没有初始化的成员为零值

// 指针变量
var s4 *Student = &Student{4, "zhou", 'm', 16}
s5 := &Student{5, "820", 'm', 3}
// Go没有 -> 操作符,对于指针变量 p,p.成员 和 (*p).成员 是等价的
s5.id = 6
(*s5).id = 7
  • 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的(只可以使用 == 或 != 运算符比较)。
  • 向函数传递结构体参数,可以传值,也可以传引用。
  • 要使某个符号(包括结构体变量或者结构体的成员变量)对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头。
  • 定义结构体字段时,Go 支持只提供类型而不写字段名的方式,即匿名字段,也称为嵌入字段。当匿名字段也是一个结构体的时候,那么这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体。所有的内置类型和自定义类型都可以作为匿名字段,包括结构体指针。