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...)
}