# 关于go语言基础学习的记录
这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
基本感受
本人之前学过c++,python和javascript,比较直观的感受就是这门语言结合了三种语言的强大之处,像声明统一用var,
这是js的写法,还有常量的const,以及函数闭包,后面的协程的go routine.
在学习go语言基础的时候,我认为抓住几个关键的点——函数,结构体,切片(类似于cpp的链表或者vector)
本文重点来说一说在go语言的切片的一些性质
首先明晰一个概念——指针,go语言的指针用来表示内存地址的类型。 go语言的切片,有点类似于c++的STL库中的vector
切片,是一种动态数组。 每个切片对象内部都维护者数组指针,切片长度,以及切片容量三个数据。
type slice struct {
array unsafe.Pointer
len int
cap int
}
由于cap相同,导致内存地址相同, 当向切片中追加的数据个数大于容量(cap)时,内部会自动扩容并且每次扩容当前容量的2倍
字典是表示键值对的结合 结构体用于自定义一些数据集合 接口用于约束和泛指数据类型
type Person struct {
name string
age int
}
p1 := Person{name: "武沛齐", age: 18}
p2 := p1 // 内部将p1重新拷贝一份
fmt.Println(p1) // {武沛齐 18}
fmt.Println(p2) // {武沛齐 18}
p1.name = "alex"
fmt.Println(p1) // {alex 19}
fmt.Println(p2) // {武沛齐 19}
这是深拷贝
type Person struct {
name string
age int
}
p1 := &Person{"武沛齐", 18}
p2 := p1
fmt.Println(p1) // &{武沛齐 18}
fmt.Println(p2) // &{武沛齐 18}
p1.name = "alex"
fmt.Println(p1) // &{alex 18}
fmt.Println(p2) // &{alex 18}
结构体指针赋值,是浅拷贝 基于结合结构体和结构体指针的特性来说,基于指针实现数据变化后同步是遍布的. type Person struct { name string age int }
p1 := Person{name: "提休", age: 19} p2 := &p1
fmt.Println(p1) // {提休 19} fmt.Println(p2) // &{提休 19}
p1.name = "alex"
fmt.Println(p1) // {alex 19} fmt.Println(p2) // &{alex 19}
在对结构体进行相等赋值的时候,本质上都拷贝了,只不过由于数据存储方式的不同,拷贝的有些是数据,有些是内存地址(指针)
package main
import "fmt"
func main() {
type Person struct {
name string
age int
hobby [2]string
num []int
parent map[string]string
}
p1 := Person{
name: "二狗子",
age: 19,
hobby: [2]string{"裸奔", "大保健"}, // 拷贝
num: []int{69, 19, 99, 38}, // 未拷贝 (内部维护指针指向数据存储的地方)
parent: map[string]string{"father": "Alex", "mother": "Monika"}, // 未拷贝 (内部维护指针指向数据存储的地方)
}
p2 := p1
fmt.Println(p1)
fmt.Println(p2)
p1.parent["father"] = "武沛齐"
fmt.Println(p1)这里p1改变了,p2同时也改变了
fmt.Println(p2)
} 对于那些默认拷贝的情况,可以改变指针类型,让数据实现同步修改。
对于函数的变长参数,加上...三个点即可
切片的容量会进行自动扩容 当len小于cap的时候发生的是深拷贝
v1 := make([]int,1,3)
v2 := append(v1,66)
fmt.Println(v1) // [0 ]
fmt.Println(v2) // [0,66]
v1[0] = 999
fmt.Println(v1) // [999 ]
fmt.Println(v2) // [999,66]
可以看到这里改变v1,v2同时发生了改变
如果len=cap,然后扩容,进行的就是拷贝,浅拷贝
v1 := []int{11,22,33}
v2 := append(v1,44)
v1[0] = 999
fmt.Println(v1) // [999 22 33]
fmt.Println(v2) // [11 22 33 44]
可以看到这里v2的值没有改变
删除
对于切片的删除来说
package main
import "fmt"
func main() {
v1 := []int{11, 22, 33, 44, 55, 66}
deleteIndex := 2
// 切片获取到 {11,22,44、55、66}
// 又获取到 {44, 55, 66},将44、55、66要追加到
result := append(v1[:deleteIndex], v1[deleteIndex+1:]...)
fmt.Println(result) // [11 22 44 55 66]
fmt.Println(v1) //[11 22 44 55 66 66]
}
这个例子需要记住,这里的result相当于引用,里面v1的33左边和右边的数字被拷贝,然后66没有改变。所以v1
是11 22 44 55 66 66
关于函数的一些用法,多参数时候
package main
import "fmt"
func do(num ...int) int {
sum := 0
for _, value := range num {
sum += value
}
return sum
}
func main() {
r1 := do(1, 2, 3, 3)
r2 := do(0, 1, 1)
fmt.Println(result)
}
注意事项:边长参数只能放在最后且只能有一个
defer:用于在一个函数执行完成以后自动触发的语句,一般用于结束操作以后释放资源 当存在多个defer的时候,函数的执行完毕以后会按倒序的方式去执行
对于结构体而言,当结构体做参数和做返回值的时候,在执行时候都会被重新拷贝一份,如果不想被拷贝,则可以通过指针的形式进行处理