4、数组、切片、map、channel

一、数组

  • 数组
    • 数组是块连续的内存空间,在声明的时候必须指定长度,且长度不能改变
    • 所以数组在声明的时候就可以把内存空间分配好,并赋上默认值,即完成了初始化
    • 数组的地址就是首元素的地址
  • 一维数组初始化
func main() {
    
    
	var arr1 [5]int = [5]int{
    
    } //数组必须指定长度和类型,且长度和类型指定后不可改变
	var arr2 = [5]int{
    
    }
	var arr3 = [5]int{
    
    3, 2}            //给前2个元素赋值
	var arr4 = [5]int{
    
    2: 15, 4: 30}    //指定index赋值
	var arr5 = [...]int{
    
    3, 2, 6, 5, 4} //根据{}里元素的个数推断出数组的长度
	var arr6 = [...]struct {
    
    
		name string
		age  int
	}{
    
    {
    
    "Tom", 18}, {
    
    "Jim", 20}} //数组的元素类型由匿名结构体给定
}
  • 二维数组初始化
func main() {
    
    
	//5行3列,只给前2行赋值,且前2行的所有列还没有赋满
	var arr1 = [5][3]int{
    
    {
    
    1}, {
    
    2, 3}}
	//第1维可以用...推测,第2维不能用...
	var arr2 = [...][3]int{
    
    {
    
    1}, {
    
    2, 3}}
}
  • 访问数组里的元素
    • 通过index访问
      • 首元素 arr[0]
      • 末元素 arr[len(arr)-1]
    • 访问二维数组里的元素
      • 位于第三行第四列的元素 arr[2][3]
func main() {
    
    
	//遍历数组里的元素
	//i, ele := range的理解
	//	-> range返回2个,1个是索引i,1个是元素ele
	//	-> 将range的返回用来声明并初始化i和ele
	for i, ele := range arr {
    
    
		fmt.Printf("index=%d, element=%d\n", i, ele)
	}
	//或者这样遍历数组
	for i := 0; i < len(arr); i++ {
    
     //len(arr)获取数组的长度
		fmt.Printf("index=%d, element=%d\n", i, arr[i])
	}
	//遍历二维数组
	for row, array := range arr {
    
     //先取出某一行
		for col, ele := range array {
    
     //再遍历这一行
			fmt.Printf("arr[%d][%d]=%d\n", row, col, ele)
		}
	}
}
  • 通过for range遍历数组时取得的是数组里每一个元素的拷贝
func main() {
    
    
	arr := [...]int{
    
    1, 2, 3}
	for i, ele := range arr {
    
     //ele是arr中元素的拷贝
		arr[i] += 8                              //修改arr里的元素,不影响ele
		fmt.Printf("%d %d %d\n", i, arr[i], ele) //0 9 1
		ele += 1                                 //修改ele不影响arr
		fmt.Printf("%d %d %d\n", i, arr[i], ele) //0 9 2
	}
	for i := 0; i < len(arr); i++ {
    
    
		fmt.Printf("%d %d\n", i, arr[i])
		//0 9
		//1 10
		//2 11
	}
}
  • 数组的cap和len
    • 在数组上调用cap()函数表示capacity容量,即给数组分配的内存空间可以容纳多少个元素;
    • len()函数代表length长度,即目前数组里有几个元素;
    • 由于数组初始化之后长度不会改变,不需要给它预留内存空间,所以len(arr)==cap(arr);
    • 对于多维数组,其cap和len指第一维的长度
  • 数组的参数传递
    • 数组的长度和类型都是数组类型的一部分,函数传递数组类型时这两部分都必须吻合;
    • go语言没有按引用传参,全都是按值传参,即传递数组实际上传的是数组的拷贝,当数组的长度很大时,仅传参开销都很大;
    • 如果想修改函数外部的数组,就把它的指针(数组在内存里的地址)传进来
// 值传递拷贝
func arrPoint(arr [5]int) {
    
    
	fmt.Println(arr[0]) //1
	arr[0] += 10
	fmt.Println(arr[0]) //11
}

// 指针传递地址
func arrPPoint(arr *[5]int) {
    
    
	fmt.Println(arr[0]) //1
	arr[0] += 10
	fmt.Println(arr[0]) //11
}

func main() {
    
    
	var crr [5]int = [5]int{
    
    1, 2, 3, 6, 9}
	arrPoint(crr)
	fmt.Println(crr[0]) //1

	arrPPoint(&crr)
	fmt.Println(crr[0]) //11
}

//

// 参数必须是长度为5的int型数组(注意长度必须是5)
func update_array1(arr [5]int) {
    
    
	fmt.Printf("array in function, address is %p\n", &arr[0])
	arr[0] = 888
}

func update_array2(arr *[5]int) {
    
    
	fmt.Printf("array in function, address is %p\n", &((*arr)[0]))
	arr[0] = 888 //因为传的是数组指针,所以直接在原来的内存空间上进行修改
}

// range遍历是元素拷贝
func forRange() {
    
    
	arr := [...]int{
    
    1, 2, 3, 4, 5}
	for _, ele := range arr {
    
     // ele是arr里元素的拷贝
		ele += 10
	}
	fmt.Println(arr) //[1 2 3 4 5]
}

二、切片

  • 切片概念:切片是一个结构体,包含三个成员变量,array指向一块连续的内存空间,cap表示这块内存的大小,len表示目前该内存里存储了多少元素
    在这里插入图片描述
type slice struct {
    
    
    array unsafe.Pointer
    len int
    cap int
}
  • 切片的初始化
func main() {
    
    
	var s []int              //切片声明,array=nil,len=cap=0
	s = []int{
    
    }              //初始化,len=cap=0
	s = make([]int, 3)       //初始化,len=cap=3
	s = make([]int, 3, 5)    //初始化,len=3,cap=5
	s = []int{
    
    1, 2, 3, 4, 5} //初始化,len=cap=5
	s2d := [][]int{
    
    
		{
    
    1}, {
    
    2, 3}, //二维数组各行的列数相等,但二维切片各行的len可以不等
	}
}
  • 切片的特点
    • 切片相对于数组最大的特点就是可以追加元素,可以自动扩容
    • 追加的元素放到预留的内存空间里,同时len加1
    • 如果预留空间已用完,则会重新申请一块更大的内存空间,capacity大约变成之前的2倍(cap<1024)或1.25倍(cap>1024);把原内存空间的数据拷贝过来,在新内存空间上执行append操作
func sliceAppend() {
    
    
	s := make([]int, 3, 5)
	fmt.Println(len(s), cap(s)) // 3 5
	s = append(s, 100)
	fmt.Println(len(s), cap(s)) // 4 5
	s = append(s, 100)
	fmt.Println(len(s), cap(s)) // 5 5
	s = append(s, 100)
	fmt.Println(len(s), cap(s)) // 6 10
}

// 2倍扩容验证
// cap 5 -> 10
// cap 10 -> 20
// cap 20 -> 40
// cap 40 -> 80
// cap 80 -> 160
func coef_cap() {
    
    
	s := make([]int, 0, 5)
	preCap := cap(s)
	for i := 0; i < 100; i++ {
    
    
		s = append(s, 0)
		curCap := cap(s)
		if curCap > preCap {
    
    
			fmt.Printf("cap %d -> %d\n", preCap, curCap)
			preCap = curCap
		}
	}
}
  • 截取子切片:子切片与目切片的内存共享与内存分离
func sub_slice() {
    
    
	arr := make([]int, 3, 5)
	crr := arr[0:2] //左闭右开
	crr[1] = 8
	fmt.Println(arr) // [0 8 0] 子切片与母切片共享内存空间

	crr = append(crr, 9)
	fmt.Println(arr)                        // [0 8 9]
	fmt.Printf("%p %p\n", &arr[0], &crr[0]) // 0xc0000103f0 0xc0000103f0

	crr = append(crr, 9)
	crr = append(crr, 9)

	// 此时子切片申请一片新的内存,把老数据先拷贝过来,在新内存上执行append操作
	// 此时子切片已经与母切片内存分离
	crr = append(crr, 9)
	fmt.Println(arr)                        // [0 8 9]
	fmt.Println(crr)                        // [0 8 9 9 9]
	fmt.Printf("%p %p\n", &arr[0], &crr[0]) // 0xc0000103f0 0xc000014230
}
  • 子切片传参
    • go语言函数传参,传的都是值,即传切片会把切片的{arrayPointer, len, cap}这3个字段拷贝一份传进来
    • 由于传的是底层数组的指针,所以可以直接修改底层数组里的元素
func update_slice(s []int) {
    
    
	s[0] = 100
}

func main() {
    
    
	s := []int{
    
    1, 2, 3}
	update_slice(s)
	fmt.Println(s) // [100 2 3]
}

三、map

  • map的底层原理:go map的底层实现是hash table,根据key查找value的时间复杂度是O(1)

在这里插入图片描述

  • map的初始化
func main() {
    
    
	var m map[string]int                  //声明map,指定key和value的数据类型
	m = make(map[string]int)              //初始化,容量为0
	m = make(map[string]int, 5)           //初始化,容量为5。强烈建议初始化时给一个合适的容量,减少扩容的概率
	m = map[string]int{
    
    "语文": 0, "数学": 39} //初始化时直接赋值
}
  • 获取map的长度:len(m)获取map的长度(map中的key数量),go不支持对map上执行cap函数
  • 添加和删除key
func main() {
    
    
	m["英语"] = 59    //往map里添加key-value对
	m["英语"] = 70    //会覆盖之前的值
	delete(m, "数学") //从map里删除key-value对
}
  • map读取key对应的value值:读取key对应的value时,如果key不存在,则返回value类型的默认值,所以强烈建议先判断key是否存在
func main() {
    
    
	if value, exists := m["语文"]; exists {
    
    
		fmt.Println(value)
	} else {
    
    
		fmt.Println("map里不存在[语文]这个key")
	}
}
  • map遍历:多次遍历map返回的顺序是不一样的,但相对顺序是一样的,因为每次随机选择一个开始位置,然后顺序遍历
func main() {
    
    

	m := make(map[string]int)
	m["a"] = 10
	m["b"] = 20
	m["c"] = 30

	//遍历map
	for key, value := range m {
    
    
		value += 100 //range的value是值拷贝
		fmt.Printf("%s=%d\n", key, value)
		//a=110
		//b=120
		//c=130
	}
	fmt.Println(m) //map[a:10 b:20 c:30]

	for key := range m {
    
    
		m[key] += 200 //使用key直接对map的原数据进行操作
	}
	fmt.Println(m) //map[a:210 b:220 c:230]

}

四、channel

  • channel底层
    • channel(管道)底层是一个环形队列(先进先出),send(插入)和recv(取走)从同一个位置沿同一个方向顺序执行
    • sendx表示最后一次插入元素的位置,recvx表示最后一次取走元素的位置
      在这里插入图片描述
  • channel初始化
func main() {
    
    
	var ch chan int        //声明
	ch = make(chan int, 8) //初始化,环形队列里可容纳8个int
}
  • channel的len和cap
func main() {
    
    
	var ch chan int
	fmt.Printf("ch is nil %t\n", ch == nil)  //ch is nil true
	fmt.Printf("len of ch is %d\n", len(ch)) //len of ch is 0

	ch = make(chan int, 10)
	fmt.Printf("len of ch is %d\n", len(ch)) //len of ch is 0
	fmt.Printf("cap of ch is %d\n", cap(ch)) //cap of ch is 10
}
  • channel的cap上限:达到cap的上限后,继续send会导致管道阻塞
func main() {
    
    
	ch := make(chan int, 10)
	for i := 0; i < 10; i++ {
    
    
		ch <- 3
	}
	fmt.Printf("len of ch is %d\n", len(ch)) //len of ch is 10
	fmt.Printf("cap of ch is %d\n", cap(ch)) //cap of ch is 10
	ch <- 3                                  //阻塞
	fmt.Printf("len of ch is %d\n", len(ch)) //不会打印
	fmt.Printf("cap of ch is %d\n", cap(ch)) //不会打印
}
  • send和recv
func main() {
    
    
	ch := make(chan int, 8) //初始化,环形队列里可容纳8个int
	ch <- 1                 //往管道里写入(send)数据
	ch <- 2
	ch <- 3
	ch <- 4
	ch <- 5
	v := <-ch      //从管道里取走(recv)数据
	fmt.Println(v) //1
	v = <-ch
	fmt.Println(v) //2
}
  • channel遍历
    • 通过for range的方式遍历管道,遍历前必须先关闭close(ch)管道,禁止再写入元素;
    • 一旦close就不能再往管道里面追加元素
	close(ch) //遍历前必须先关闭管道,禁止再写入元素
	//遍历管道里剩下的元素
	for ele := range ch {
    
    
		fmt.Println(ele) //3 4 5
	}

五、引用类型

  • 引用类型
    • slice、map和channel是go语言里的3种引用类型,都可以通过make函数来进行初始化(申请内存分配)
    • 因为它们都包含一个指向底层数据结构的指针,所以称之为“引用”类型
    • 引用类型未初始化时都是nil,可以对它们执行len()函数,返回0

猜你喜欢

转载自blog.csdn.net/qq23001186/article/details/128986063