go基础学习——切片的一些性质 |青训营笔记

79 阅读4分钟

# 关于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同时发生了改变

image.png

如果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的值没有改变

image.png

删除

对于切片的删除来说

 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的时候,函数的执行完毕以后会按倒序的方式去执行

对于结构体而言,当结构体做参数和做返回值的时候,在执行时候都会被重新拷贝一份,如果不想被拷贝,则可以通过指针的形式进行处理