Golang语言基础第三部分 | 青训营

49 阅读4分钟

Golang

循环语句

go 语言中的 for 循环有 3 种形式:

for init; condition; post { }
for condition { }
for { }
func main() {
	numbers := [6]int{1, 2, 3, 5}

	for i := 0; i < len(numbers); i++ {
		fmt.Println(numbers[i])
	}

	i := 0
	for i < len(numbers) {
		fmt.Println(numbers[i])
		i++
	}

	for i, x := range numbers {
		fmt.Printf("index: %d, value: %d\n", i, x)
	}

  // 无限循环
	for {
		fmt.Println("endless...")
	}
}

range

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6}

	// 忽略value, 只取index, 支持 string/array/slice/map
	for i := range numbers {
		fmt.Println(numbers[i])
	}

	// 忽略 index
	for _, n := range numbers {
		fmt.Println(n)
	}

	// 忽略全部返回值,仅迭代
	for range numbers {
	}

	m := map[string]int{"a": 1, "b": 2}
	for k, v := range m {
		fmt.Println(k, v)
	}

}

函数

多返回值

单返回值的函数:

func foo1(a string, b int) int {
	return 100
}

多返回值的函数:

// 返回多个返回值,匿名的
func foo2(a string, b int) (int, int) {
	return 666, 777
}

// 返回多个返回值,有形参名称的
func foo3(a string, b int) (r1 int, r2 int) {
	// r1 r2 属于foo3的形参,初始化默认的值是0
	// r1 r2 作用域空间 是foo3 整个函数体的{}空间
	fmt.Println("r1 = ", r1) // 0
	fmt.Println("r2 = ", r2) // 0

	// 给有名称的返回值变量赋值
	r1 = 1000
	r2 = 2000

	return
}

func foo4(a string, b int) (r1, r2 int) {
	// 给有名称的返回值变量赋值
	r1 = 1000
	r2 = 2000

	return
}

init 函数

每个 go 程序都会在一开始执行 init() 函数,可以用来做一些初始化操作:

package main

import "fmt"

func init() {
	fmt.Println("init...")
}

func main() {
	fmt.Println("hello world!")
}
init...
hello world!

如果一个程序依赖了多个包,它的执行流程如下图:

在这里插入图片描述

制作包的时候,项目路径如下:

$GOPATH/GolangStudy/5-init/ 
├── lib1/
│ └── lib1.go
├── lib2/
│ └── lib2.go 
└── main.go

在这里插入图片描述

lib1 .init() ...
lib2 .init() ...
lib1Test()
lib2Test()

闭包

func a() func() int {
	i := 0
	b := func() int {
		i++
		fmt.Println(i)
		return i
	}
	return b
}

func main() {
	c := a()
	c() // 1
	c() // 2
	c() // 3

	a() // 无输出
}

import 导包

  • import _ "fmt"

    给 fmt 包一个匿名, ⽆法使用该包的⽅法,但是会执行该包内部的 init() 方法

  • import aa "fmt"

    给 fmt 包起一个别名 aa,可以用别名直接调用:aa.Println()

  • import . "fmt"

    将 fmt 包中的全部方法,导入到当前包的作用域中,全部方法可以直接调用,无需 fmt.API 的形式

匿名函数

匿名函数的使用:

func main() {
	res := func(n1 int, n2 int) int {
		return n1 * n2
	}(10, 20)

	fmt.Printf("res: %v\n", res)
}

将匿名函数赋值给变量,通过变量调用:

func main() {
	ret := func(n1 int, n2 int) int {
		return n1 + n2
	}
	// 变量调用
	sum := ret(100, 20)
	fmt.Printf("sum: %v\n", sum)
	// 多次调用
	sum2 := ret(1000, 30)
	fmt.Printf("sum2: %v\n", sum2)
}

指针

在这里插入图片描述


在这里插入图片描述


经典:在函数中交换两数的值

func swap(pa *int, pb *int) {
	var temp int
	temp = *pa
	*pa = *pb
	*pb = temp
}

func main() {
	var a, b int = 10, 20

	swap(&a, &b) // 传地址

	fmt.Println("a = ", a, " b = ", b)
} 

defer

defer 声明的语句会在当前函数执行完之后调用:

func main() {
	defer fmt.Println("main end")
	fmt.Println("main::hello go ")
}

/*
main::hello go 
main end
*/

如果有多个 defer,依次入栈,函数返回后依次出栈执行:

在这里插入图片描述

上图执行顺序:func3() -> func2() -> func1()


关于 defer 和 return 谁先谁后:

func deferFunc() int {
	fmt.Println("defer func called...")
	return 0
}

func returnFunc() int {
	fmt.Println("return func called...")
	return 0
}

func returnAndDefer() int {
	defer deferFunc()
	return returnFunc()
}

func main() {
	returnAndDefer()
}
return func called...
defer func called...

结论:return 之后的语句先执⾏,defer 后的语句后执⾏

切片 slice

Golang 默认都是采用值传递,有些值天生就是指针:slice、map、channel。

注意:固定长度数组是值传递,slice 是指针传递

数组

声明数组的方式:(固定长度的数组)

var array1 [10]int
array2 := [10]int{1,2,3,4}
array3 := [4]int{1,2,3,4}

数组的长度是固定的,并且在传参的时候,严格匹配数组类型

// 传入参数的数组长度为4,则只能传递长度为4的数组
func printArray(myArray [4]int) {
	fmt.Println(myArray) // [1 2 3 4]
	myArray[0] = 666     // 数组是值传递
}

func main() {
	myArray := [4]int{1, 2, 3, 4}
	printArray(myArray)
	fmt.Println(myArray) // [1 2 3 4]
}

myArray := [...]int{1, 2, 3, 4} 是自动计算数组长度,但并不是引用传递。

声明动态数组和声明数组一样,只是不用写长度。

// 不指定长度则是动态数组
func printArray(myArray []int) {
	fmt.Println(myArray) // [1 2 3 4]
	myArray[0] = 10      // 动态数组是引用传递
}

func main() {
	myArray := []int{1, 2, 3, 4}
	printArray(myArray)
	fmt.Println(myArray) // [10 2 3 4]
}

slice

slice 的声明方式:通过 make 关键字

// 1 声明一个切片,并且初始化,默认值是1,2,3,长度是3
slice1 := []int{1, 2, 3} // [1 2 3]

// 2 声明一个切片,但是没有给它分配空间
var slice2 []int // slice2 == nil
// 开辟3个空间,默认值是0
slice2 = make([]int, 3) // [0 0 0]

// 3 声明一个切片,同时给slice分配3个空间,默认值是0
var slice3 []int = make([]int, 3) // [0 0 0]

// 4 声明一个切片,同时给slice分配3个空间,默认值是0,通过:=推导出slice是一个切片
slice4 := make([]int, 3) // [0 0 0]

len() 和 cap() 函数:

  • len:长度,表示左指针⾄右指针之间的距离。
  • cap:容量,表示指针至底层数组末尾的距离。

在这里插入图片描述

切⽚的扩容机制,append 的时候,如果长度增加后超过容量,则将容量增加 2 倍。

var numbers = make([]int, 3, 5)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

// 向numbers切片追加一个元素1, len = 4, [0,0,0,1], cap = 5
numbers = append(numbers, 1)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

// 向numbers切片追加一个元素2, len = 5, [0,0,0,1,2], cap = 5
numbers = append(numbers, 2)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

// 向一个容量cap已经满的slice 追加元素, len = 6, cap = 10
numbers = append(numbers, 3)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
len = 3, cap = 5, slice = [0 0 0]
len = 4, cap = 5, slice = [0 0 0 1]
len = 5, cap = 5, slice = [0 0 0 1 2]
len = 6, cap = 10, slice = [0 0 0 1 2 3]

slice 操作

slice 截取是浅拷贝,若想深拷贝需要使用 copy

可以通过设置下限以及上限设置截取切片 [lower-bound: upper-bound],实例:

func main() {
	/* 创建切片 */
	numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	fmt.Println(numbers)

	/* 打印原始切片 */
	fmt.Println("number ==", numbers)

	/* 打印子切片从索引1(包含)到索引4(不包含) */
	fmt.Println("numbers[1:4] ==", numbers[1:4])

	/* 默认下限为 0 */
	fmt.Println("numbers[:3] ==", numbers[:3])

	/* 默认上限为 len(s) */
	fmt.Println("numbers[4:] ==", numbers[4:])

	numbers1 := make([]int, 0, 5)
	fmt.Println(numbers1)

	/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
	numbers2 := numbers[:2]
	fmt.Println(numbers2)

	/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
	numbers3 := numbers[2:5]
	fmt.Println(numbers3)
}
[0 1 2 3 4 5 6 7 8]
number == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
[]
[0 1]
[2 3 4]

利用 copy 函数拷贝切片,是深拷贝。

slice1 := []int{1, 2, 3}
slice2 := make([]int, 3)
copy(slice2, slice1)
slice2[0] = 10
fmt.Println(slice1) // [1 2 3]

直接赋值切片,是浅拷贝。

slice1 := []int{1, 2, 3}
slice2 := slice1
slice2[0] = 10
fmt.Println(slice1) // [10 2 3]

... 是 Go 的一种语法糖。

  • 用法 1:函数可以用来接受多个不确定数量的参数。
  • 用法 2:slice 可以被打散进行传递。
func test(args ...string) {
	for _, v := range args {
		fmt.Println(v)
	}
}

func main() {
	var ss = []string{
		"abc",
		"efg",
		"hij",
		"123",
	}
	test(ss...)
}