【Go】C++ 转 Go 第(三)天:defer、slice(动态数组) 与 map
本专栏文章持续更新,新增内容使用蓝色表示。
食用指南
本文适合有 C++ 基础的朋友,想要快速上手 Go 语言。可直接复制代码在 IDE 中查看,代码中包含详细注释和注意事项。
Go 的环境搭建请参考以下文章:
【Go】C++ 转 Go 第(一)天:环境搭建 Windows + VSCode 远程连接 Linux -CSDN博客
defer 关键字
defer 是 Go 语言中用于延迟执行的关键字,类似于 C++ 中的析构函数和 Java 中的 finally 块,主要用于资源清理和异常处理。
-
执行顺序:LIFO(后进先出)
-
参数求值:defer语句执行时立即求值
-
执行时机:在return语句之后、函数真正返回之前执行
-
返回值影响:可以修改命名返回值
defer.go
package mainimport ("fmt"
)// 1. 调用顺序-先进后出
func func1() {fmt.Println("------func1------")
}func func2() {fmt.Println("------func2------")
}func func3() {fmt.Println("------func3------")
}// 2. defer&return 调用顺序
func deferFunc() {fmt.Println("-------defer called-------")
}
func returnFunc() int {fmt.Println("-------return called-------")return 6
}
func returnAndDefer() int {defer deferFunc() // defer是当前函数声明周期全部结束之后才会调用fmt.Println("-------return & Defer-------")return returnFunc()
}func main() {// defer关键字,类似于C++中的析构函数,类似java中的final// 支持多个,按照顺序依次入栈,先进后出// 比如:最后关闭文件// 1. 调用顺序defer func1()defer func2()defer func3()fmt.Println("main::hello go 1")// 2. return & deferreturnAndDefer()
}
执行结果
slice——Go语言的动态数组
数组与Slice的基础对比
arrayAndSlice.go
package mainimport ("fmt"
)// 值拷贝
func printArray(nums [2]int) {fmt.Println("------printArray------")nums[0] = 99for _, value := range nums {fmt.Println(value)}
}// 依旧是值传递,只不过传递的是指针
func printSlice(slice []string) {fmt.Println("------printSlice------")slice[0] = "hi"slice[1] = "Jade"for _, value := range slice {fmt.Println(value)}
}func main() {// 一、固定长度的数组var myArray1 [3]intmyArray2 := [5]int{10, 11, 12}myArray3 := [2]int{20, 21}// 1. 遍历方式// 1)硬编码长度(不推荐)// for i := 0; i < 5; i++// 2)使用 lenfmt.Println("--------len---------")fmt.Printf("myArray1 type : %T \n", myArray1)for i := 0; i < len(myArray1); i++ { // 这个 i 只在循环内有效myArray1[i] = ifmt.Println(myArray1[i])}// 3)使用 range,返回的是索引和值// for index,value:=range myArray2 不需要的使用匿名(_)fmt.Println("--------range---------")fmt.Printf("myArray2 type : %T \n", myArray2)for _, value := range myArray2 {fmt.Println(value)}// 2. 函数传参,严格匹配类型fmt.Println("------BeforePrintArray3------")for _, value := range myArray3 {fmt.Println(value)}printArray([2]int(myArray3))fmt.Println("------AfterPrintArray3------")for _, value := range myArray3 {fmt.Println(value)}// 二、动态数组 切片 slice// 实际上是指向一块内存地址的指针mySlice1 := []string{"hello", "world"}fmt.Println("--------BeforePrintSlice---------")fmt.Printf("mySlice1 type : %T \n", mySlice1)for _, value := range mySlice1 {fmt.Println(value)}printSlice(mySlice1)fmt.Println("--------AfterPrintSlice---------")fmt.Printf("mySlice1 type : %T \n", mySlice1)for _, value := range mySlice1 {fmt.Println(value)}
}
执行结果
Slice的声明与内存管理
sliceDeclaration.go
package mainimport ("fmt""unsafe"
)func main() {// slice 声明方式// 1. 有初始值mySlice1 := []int{1, 2, 3}fmt.Printf("mySlice1 Type is %T , len=%d, value=%v \n", mySlice1, len(mySlice1), mySlice1)// 2. 无初始值// 1)nil切片,默认没有分配空间,底层数组指针为 nilvar mySlice2 []intfmt.Printf("mySlice2 Type is %T , len=%d, value=%v \n", mySlice2, len(mySlice2), mySlice2)// mySlice2[0] = 1 // 此时赋值会越界// 需要为其开辟空间mySlice2 = make([]int, 5)mySlice2[0] = 99fmt.Printf("mySlice2 Type is %T , len=%d, value=%v \n", mySlice2, len(mySlice2), mySlice2)// 2)无初始值,分配空间(两种方法均可)var mySlice3 []int = make([]int, 2)mySlice4 := make([]int, 4) // 较为常见fmt.Printf("mySlice3 Type is %T , len=%d, value=%v \n", mySlice3, len(mySlice3), mySlice3)fmt.Printf("mySlice4 Type is %T , len=%d, value=%v \n", mySlice4, len(mySlice4), mySlice4)// 判断slice是否为空(没有空间)var mySlice5 []int // nil切片if mySlice5 == nil {fmt.Println("mySlice5 is nil,没有空间")} else {fmt.Println("mySlice5 is nil,有空间")}mySlice6 := make([]int, 0) // 空切片,底层数组指针指向一个空数组(但不是nil)if mySlice6 == nil {fmt.Println("mySlice6 is nil,没有空间")} else { // 语法规定,else必须和}{在同一行fmt.Println("mySlice6 is nil,有空间")}// 以下仅为补充,暂时不用看懂// 查看底层指针fmt.Printf("mySlice5 指针: %p\n", (*[0]int)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&mySlice5)))))fmt.Printf("mySlice6 指针: %p\n", (*[0]int)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&mySlice6)))))// nil 切片和空切片的行为几乎相同,但在序列化、反射和一些库函数的处理上会有差异
}
执行结果
Slice操作与内存共享机制
sliceUsage.go
package mainimport "fmt"func main() {// 1. 切片容量增加// 第一个参数是类型,第二个是长度,第三个是容量// 和C++的 reserve() 函数有点类似var nums = make([]int, 2, 3)fmt.Printf("nums type is %T, len=%d,cap=%d,nums=%v \n", nums, len(nums), cap(nums), nums)// 追加元素// 容量不够,会开辟2倍cap的空间nums = append(nums, 2, 3)fmt.Printf("nums type is %T, len=%d,cap=%d,nums=%v \n", nums, len(nums), cap(nums), nums)// 2. 切片截取s := []int{1, 2, 3, 4, 5}// 左闭右开区间s1 := s[0:2]s2 := s[2:4]fmt.Printf("s1 type is %T, len=%d,cap=%d,nums=%v \n", s1, len(s1), cap(s1), s1)fmt.Printf("s2 type is %T, len=%d,cap=%d,nums=%v \n", s2, len(s2), cap(s2), s2)// 从结果可以发现尽管 s1 和 s2 的len一样,但是容量不一样// 从索引0开始 → 容量=5-0=5// 从索引2开始 → 容量=5-2=3// 共享底层数组// 相当于是浅拷贝,只拷贝了地址fmt.Println("\n=== 浅拷贝修改验证 ===")s1[0] = 100s2[0] = 300fmt.Printf("修改后 s: %v\n", s)fmt.Printf("修改后 s1: %v\n", s1)fmt.Printf("修改后 s2: %v\n", s2)// 深拷贝fmt.Println("\n=== 深拷贝修改验证 ===")s3 := make([]int, 3)copy(s3, s)// copy(s3, s[3:4]) // 也可以指定范围s3[0] = 99fmt.Printf("修改后 s: %v\n", s)fmt.Printf("修改后 s3: %v\n", s3)
}
执行结果
map——Go语言的哈希表
map.go
package mainimport ("fmt"
)// 值传递,浅拷贝,传递的是指针
func PrintMap(myMap map[int]string) {// 此处如果修改 myMap 会影响到原 mapfor key, value := range myMap {fmt.Printf(" key=%d, value=%s \n", key, value)}
}// 深拷贝
func CopyMap(myApp1, myApp2 map[int]string) {for key, value := range myApp1 {myApp2[key] = value}
}
func main() {// map 声明,map[key]valuefmt.Println("=======声明=======")// 1. 方式一var myMap1 map[string]stringif myMap1 == nil {fmt.Println("myMap1 是一个空map")} else {fmt.Println("myMap1 是一个非空map")}// 分配空间myMap1 = make(map[string]string, 2)myMap1["c"] = "C++"myMap1["p"] = "python"myMap1["g"] = "Go"fmt.Println(myMap1)// 2. 方式二myMap2 := make(map[int]string)myMap2[1] = "Go"myMap2[2] = "C++"fmt.Println(myMap2)// 3. 方式三myMap3 := map[int]string{1: "C++",2: "Go",3: "Python", // 注意此处也要有逗号}fmt.Println(myMap3)// 遍历// go为了避免开发者依赖顺序,所以是乱序的,每次的结果可能不同fmt.Println("\n=======遍历=======")PrintMap(myMap3)// 修改fmt.Println("\n=======修改=======")myMap3[3] = "Java"PrintMap(myMap3)// 删除fmt.Println("\n=======删除=======")delete(myMap3, 3)PrintMap(myMap3)// 深拷贝fmt.Println("\n=======深拷贝=======")myMap4 := make(map[int]string)CopyMap(myMap3, myMap4)PrintMap(myMap4)}
执行结果
如有问题或建议,欢迎在评论区中留言~