Golang 学习笔记-基础(二)

Golang 学习笔记-基础(二)

0x0 for循环

  • Go 只有一种循环结构:for 循环。
    基本的 for 循环由三部分组成,它们用分号隔开:
    初始化语句:在第一次迭代前执行
    条件表达式:在每次迭代前求值
    后置语句:在每次迭代的结尾执行
    初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。
    一旦条件表达式的布尔值为 false,循环迭代就会终止。
    注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面没有小括号,大括号 { } 则是必须的。
1
2
3
4
5
6
7
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
  • 另外,表达式中初始化语句和后置语句是可选的,比如可以写成
1
2
3
4
5
6
7
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
  • 也可以把for当做while使用,比如
1
2
3
4
5
6
7
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
  • 如果省略判断表达式,就会进入无限死循环

0x1 if 判断

  • if表达式外无需小括号,但是大括号是必须的
  • if可以在条件表达式之前执行一个简单的短句,该短句声明的变量作用域仅在if之内
1
2
3
4
5
6
7
8
9
10
11
12
13
func testIf(value int) bool {
if temp := value % 2; temp == 0 {
return true
}
return false
}

func main() {
fmt.Println(
testIf(11),
testIf(10),
)
}
  • 在 if 的简短语句中声明的变量同样可以在任何对应的 else 块中使用。

0x2 switch

switch 是编写一连串 if - else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 只运行选定的 case,而非之后所有的 case。 Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。
另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}

case后面也能是执行语句,并且从上到下顺序执行,知道匹配成功为止。
没有条件的 switch 同 switch true 一样。

1
2
3
4
5
6
7
8
9
10
11
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}

这种形式能将一长串 if-then-else 写得更加清晰。

0x3 defer

defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

1
2
3
4
5
6
7
8
9
func main() {
arg := 10
defer fmt.Println("defer method ", arg)
arg++
fmt.Println("main method", arg)
}
//output
//main method 11
//defer method 10

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
fmt.Println("counting")

for i := 0; i < 10; i++ {
defer fmt.Println(i)
}

fmt.Println("done")
}
//output
//counting
//done
//9
//8
//7
//6
//5
//4
//3
//2
//1
//0

0x4 指针

Go 拥有指针。指针保存了值的内存地址。
类型 *T 是指向 T 类型值的指针。其零值为 nil。
var p *int
& 操作符会生成一个指向其操作数的指针。
i := 42
p = &i
& 操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“重定向”。
与 C 不同,Go 没有指针运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
i, j := 7, 8

p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 11 // set i through the pointer
fmt.Println(i) // see the new value of i

p = &j // point to j
*p = *p / 2 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
//output
//7
//11
//4

0x5 结构体

一个结构体(struct)就是一个字段的集合。
结构体字段使用点号来访问。
结构体字段可以通过结构体指针来访问。如果有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。也允许我们使用隐式间接引用,直接写 p.X 就可以。

0x6 数组

类型 [n]T 表示拥有 n 个 T 类型的值的数组。
表达式
var a [10]int
会将变量 a 声明为拥有有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。Go 提供了更加便利的方式来使用数组。

0x7 切片

  • 每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
    类型 []T 表示一个元素类型为 T 的切片。
    切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
    a[low : high]
    它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
    以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
    a[1:4]
1
2
3
4
5
6
7
8
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4]
fmt.Println(s)
}

//output [3 5 7]
  • 切片就像数组的引用
    切片并不存储任何数据,它只是描述了底层数组中的一段。
    更改切片的元素会修改其底层数组中对应的元素。
    与它共享底层数组的切片都会观测到这些修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)

a := names[0:2]
b := names[1:3]
fmt.Println(a, b)

b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}

//output
//[John Paul George Ringo]
//[John Paul] [Paul George]
//[John XXX] [XXX George]
//[John XXX George Ringo]
  • 切片的默认上下标分别是0和切片长度,对于a [10]int数组来说,以下切片等价
    a[0:10]
    a[:10]
    a[0:]
    a[:]

  • 切片的长度和容量
    一个切片底层对应的是一个数组,无论切片如何变化,对应的数组是一个。那么切片的长度就是其包含的元素个数,切片的容量则是切片的第一个元素开始到数组的最后一个元素为止的元素个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)

// Slice the slice to give it zero length.
a := s[:0]
printSlice(a)

// Extend its length.
b := s[:4]
printSlice(b)

// Drop its first two values.
c := s[2:]
printSlice(c)
}

func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

//output
//len=6 cap=6 [2 3 5 7 11 13]
//len=0 cap=6 []
//len=4 cap=6 [2 3 5 7]
//len=4 cap=4 [5 7 11 13]
  • nil 切片
    切片的零值是nil,长度和容量都是0,没有底层数组。

  • make创建切片
    切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。
    make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
    a := make([]int, 5) // len(a)=5
    要指定它的容量,需向 make 传入第三个参数:
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5

  • 切片的切片
    切片可包含任何类型,甚至包括其它的切片。

  • 切片的append真是一个坑啊

0%