golang数组、切片和map

go语言学习路线:https://www.topgoer.com/

数组

什么是数组

  • 数组(Array)是一段有固定长度的相同类型的数据的集合
  • 数组的长度是固定的,不能进行修改
  • 数组中的值的获取是通过数组的下标来获取,下标从0开始
  • 数组是值类型,赋值和传参实际上是复制了一个新的数组,新数组值的改变不会影响到原数组的变化

数组的定义及初始化

定义格式:var 数组名 [数组长度]数组类型

// 定义一个长度为5,数据类型为int的数组
var arr [5]int

数组的初始化:

package main

import "fmt"

func main() {
	// 从下标0开始赋值,未赋值的元素初始化为该类型的默认值
	var arr1 [4]int = [4]int{1, 2, 3}
	// 没有初始化长度,赋值的元素个数就是数组的长度
	var arr2 = [...]int{1, 2, 3, 4, 5}
	// 如果值是key:value的格式,key为数组的下标,根据下标指定赋值
	var arr3 [4]int = [4]int{1: 3, 2: 8}
	fmt.Println(arr1)
	fmt.Println(arr2)
	fmt.Println(arr3)
}

结果:

[1 2 3 0]
[1 2 3 4 5]
[0 3 8 0]

遍历数组元素

  1. 直接根据数组的下标获取元素,数组下标不能超过数组的长度-1,否则会报panic数组越界
package main

import "fmt"

func main() {
	var arr [4]int = [4]int{1, 2, 3, 4}
	fmt.Println("数组的第二个元素:",arr[1])
	fmt.Println("数组的第三个元素:",arr[2])
}

结果:

数组的第二个元素: 2
数组的第三个元素: 3
  1. 根据for循环进行遍历:
package main

import "fmt"

func main() {
	var arr [4]int = [4]int{1, 2, 3, 4}
	for i := 0; i < len(arr); i++ {
		fmt.Println("数组的第", i+1, "个元素:", arr[i])
	}
}

结果:

数组的第 1 个元素: 1
数组的第 2 个元素: 2
数组的第 3 个元素: 3
数组的第 4 个元素: 4
  1. 根据for range循环遍历数组
package main

import "fmt"

func main() {
	var arr [4]int = [4]int{1, 2, 3, 4}
	for index, value := range arr {
		fmt.Printf("数组的第%d个元素:%d", index+1, value)
		fmt.Println()
	}
}

结果:

数组的第1个元素:1
数组的第2个元素:2
数组的第3个元素:3
数组的第4个元素:4

数组是值类型

  • 数组是值类型:赋值后的对象修改值后不影响原来的对象,作为函数参数传递时,数组修改不会影响原数组的变化
package main

import "fmt"

func main() {
	arr1 := [4]int{1, 2, 3, 4}
	arr2 := arr1
	fmt.Println(arr1)
	fmt.Println(arr2)
	// 修改arr2观察arr1是否会变化
	arr2[0] = 100
	fmt.Println(arr1)
	// 数组是值传递,拷贝一个新的内存空间
	fmt.Println(arr2)
	// 打印两个数组的地址:
	fmt.Printf("数组arr1的地址:%p\n", &arr1)
	fmt.Printf("数组arr2的地址:%p\n", &arr2)
}

结果:

[1 2 3 4]
[1 2 3 4]
[1 2 3 4]
[100 2 3 4]
数组arr1的地址:0xc0000161a0
数组arr2的地址:0xc0000161c0

可以看到:

  • 改变数组arr2的值,arr1数组中的值不会发生任何改变
  • arr1和arr2的地址不同

如果函数中数组作为参数传递:

package main

import "fmt"

func main() {
	arr := [4]int{1, 2, 3, 4}
	fmt.Println("函数执行前arr的值:", arr)
	printArr(arr)
	fmt.Println("函数执行后arr的值:", arr)
	fmt.Printf("数组arr的地址:%p\n", &arr)
}
func printArr(arr [4]int) {
	arr[0] = 100
	fmt.Println("数组的值:", arr)
	fmt.Printf("数组的地址:%p\n", &arr)
}

结果:

函数执行前arr的值: [1 2 3 4]
数组的值: [100 2 3 4]
数组的地址:0xc000016200
函数执行后arr的值: [1 2 3 4]
数组arr的地址:0xc0000161a0

多维数组

以二维数组举例:

package main

import "fmt"

func main() {
	// 二维数组的定义
	var arr [3][4]int = [3][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}
	// 取值
	// 1.从下标获取值
	fmt.Println(arr[0][1])
	fmt.Println(arr[1][2])
	// 2.for循环获取
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Print(arr[i][j], " ")
		}
		fmt.Println()
	}
	// 3.for range循环获取
	for _, v1 := range arr {
		for _, v2 := range v1 {
			fmt.Print(v2, " ")
		}
		fmt.Println()
	}
}

结果:

2
7
1 2 3 4 
5 6 7 8 
9 10 11 12 
1 2 3 4 
5 6 7 8 
9 10 11 12 

切片

切片本质上是对数组的引用

  • 切片的长度不固定,在切片中追加数据可能会引起切片的扩容
  • 切片本身没有任何数据,他们只是对现有数组的引用
  • 切片与数组相比,不需要设定长度,比较灵活
  • 切片的遍历方式与数组相同

切片定义

格式:var 切片名 []数据类型

package main

import "fmt"

func main() {
	var slice []int = []int{1, 2, 3, 4}
	fmt.Println(slice)
	fmt.Printf("数组的长度:%d\n", len(slice))
	fmt.Printf("数组的容量:%d\n", cap(slice))
}

结果:

[1 2 3 4]
数组的长度:4
数组的容量:4

通过数组定义切片

格式:var 切片名 []数据类型 = 数组名[start:end]

  • 意思把数组的第start+1个元素开始,包含start+1个元素,到第end元素为止,不包含end元素赋值给切片

  • start代表数组的起始长度,不写为从0开始

  • end代表数组的其实长度,不写为最后一个元素

package main

import "fmt"

func main() {
	var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	var slice0 []int = arr[2:6]
	var slice1 []int = arr[2:]
	var slice2 []int = arr[:6]
	var slice3 []int = arr[:]
	fmt.Println(slice0)
	fmt.Println(slice1)
	fmt.Println(slice2)
	fmt.Println(slice3)
}

结果:

[2 3 4 5]
[2 3 4 5 6 7 8 9]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6 7 8 9]

通过make创建切片

格式:切片名 := make([]数据类型,长度,容量)

  • 容量可以不加,不加与长度相同
package main

import "fmt"

func main() {

   // 创建一个长度为5,容量为10的切片
   s1 := make([]int, 5, 10)
   fmt.Println(s1)
   fmt.Println(len(s1), cap(s1))
   // 思考:容量为10,长度为5,我能存放6个数据吗?
   s1[0] = 10
   // 不能存放,报错index out of range [7] with length 5
   s1[7] = 200
   // 切片的底层还是数组
   // 直接去赋值是不行的,不用用惯性思维思考
   fmt.Println(s1)
}

切片的扩容

package main

import "fmt"

func main() {
   s1 := make([]int, 0, 5)
   fmt.Println(s1)
   // 切片扩容,append()
   s1 = append(s1, 1, 2)
   fmt.Println(s1)
   // 问题:容量只有5个,那能放超过5个的吗? 可以,切片是会自动扩容的。
   s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7)
   fmt.Println(s1)
   
   // 切片扩容之引入另一个切片。
   // new : 解构   slice.. ,解出这个切片中的所有元素。
   s2 := []int{100, 200, 300, 400}
   // slice = append(slice, anotherSlice...)
   // ... 可变参数 ...xxx
   // [...] 根据长度变化数组的大小定义
   // anotherSlice... , slice...解构,可以直接获取到slice中的所有元素
   // s2... = {100,200,300,400}
   s1 = append(s1, s2...)
}

扩容的内存分析

  1. 每个切片引用了一个底层的数组
  2. 切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
  3. 向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy
  4. 切片一旦扩容,就是重新指向一个新的底层数组。
package main

import "fmt"

func main() {
   // 1、cap 是每次成倍增加的
   // 2、只要容量扩容了,地址就会发生变化
   s1 := []int{1, 2, 3}
   fmt.Println(s1)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:3,cap:3
   fmt.Printf("%p\n", s1)                          // 0xc000016108

   s1 = append(s1, 4, 5)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:5,cap:6
   fmt.Printf("%p\n", s1)                          // 0xc000010390

   s1 = append(s1, 6, 7, 8)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:8,cap:12
   fmt.Printf("%p\n", s1)                          // 0xc00005e060

   s1 = append(s1, 9, 10)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:10,cap:12
   fmt.Printf("%p\n", s1)                          // 0xc00005e060

   s1 = append(s1, 11, 12, 13, 14)
   fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) // len:14,cap:24
   fmt.Printf("%p\n", s1)                          // 0xc00010c000
}

切片是引用类型

  • 与数组不同的是,切片是引用类型,赋值和传递时是把切片的地址拷贝给了另一个切片,切片的改变会影响到原切片的数据
package main

import "fmt"

func main() {
   // 值类型,传递:copy
   arr1 := [4]int{1, 2, 3, 4}
   arr2 := arr1
   fmt.Println(arr1, arr2)
   arr1[0] = 100
   fmt.Println(arr1, arr2)

   // 切片:引用类型
   s1 := []int{1, 2, 3, 4}
   s2 := s1
   fmt.Println(s1, s2)
   s1[0] = 100
   fmt.Println(s1, s2)
   // 通过内存分析发现,s1,s2指向了同一个地址。
   fmt.Printf("%p\n", s1)
   fmt.Printf("%p\n", s2)
}

深拷贝、浅拷贝

深拷贝:拷贝是数据的本身

  • 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct…

浅拷贝:拷贝是数据的地址,会导致多个变量指向同一块内存。

  • 引用类型的数据: slice、map
  • 因为切片是引用类的数据,直接拷贝的是这个地址

切片怎么实现深拷贝 copy

package main

import "fmt"

// 切片实现深拷贝
func main() {
   // 将原来切片中的数据拷贝到新切片中
   s1 := []int{1, 2, 3, 4}
   s2 := make([]int, 0) // len:0 cap:0
   for i := 0; i < len(s1); i++ {
      s2 = append(s2, s1[i])
   }
   fmt.Println(s1)
   fmt.Println(s2)
   s1[0] = 100
   fmt.Println(s1)
   fmt.Println(s2)

   // copy
   s3 := []int{5, 6, 7}
   fmt.Println(s2)
   fmt.Println(s3)

   // 将s3中的元素拷贝到s2中
   //copy(s2, s3)
   // 将s2中的元素拷贝到s3中
   copy(s3, s2)
   fmt.Println(s2)
   fmt.Println(s3)
}

map

Map 是一种无序的键值对的集合。

  • 无序 :map[key]
  • 键值对:key - value /key - value key - value key - value

Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。

不过,Map 是无序的,我们无法决定它的返回顺序。

Map也是引用类型

map的定义

map[Key的数据类型]Value的数据类型

package main

import "fmt"

// map 集合,保存数据的一种结构
func main() {
   // 创建一个map,也是一个变量,数据类型是 map
   // map[key]value
   var map1 map[int]string // 只是声明了但是没有初始化,不能使用 nil
   if map1 == nil {
      fmt.Println("map1==nil")
   }
   // 更多的时候使用的是make方法创建
   var map2 = make(map[string]string) // 创建了map
   fmt.Println(map1)
   fmt.Println(map2)
   // 在创建的时候,添加一些基础数据
   // map[string]int nil
   // map[string]int {key:value,key:value,......}
   var map3 = map[string]int{"Go": 100, "Java": 10, "C": 60}
   fmt.Println(map3) 
   // 关于map的类型,就如定义的一般 map[string]int
   // 类型主要是传参要确定
   fmt.Printf("%T\n", map3)
}

结果:

map1==nil
map[]
map[]
map[C:60 Go:100 Java:10]
map[string]int

map的遍历

  1. map是无序的,每次打印出来的map可能都不一样,它不能通过index获取,只能通过key来获取
  2. map的长度是不固定的,是引用类型的
  3. len可以用于map查看map中数据的数量,但是cap无法使用
  4. map的key 可以是 布尔类型,整数,浮点数,字符串
package main

import "fmt"

func main() {
   var map1 = map[string]int{"Go": 100, "Java": 99, "C": 80, "Python": 60}
   // 循环遍历,只能通过for range
   // 返回 key 和 value
   for k, v := range map1 {
      fmt.Println(k, v)
   }
}

结果:

C 80
Python 60
Go 100
Java 99

每次的结果可能都不一样

map结合slice使用

package main

import "fmt"

func main() {

	user1 := make(map[string]string)
	user1["name"] = "zhangsan"
	user1["age"] = "18"
	user1["sex"] = "男"

	user2 := make(map[string]string)
	user2["name"] = "lisi"
	user2["age"] = "30"
	user2["sex"] = "男"

	user3 := map[string]string{"name": "小蓝", "age": "18", "sex": "男"}
	fmt.Println(user3)

	// 3个数据有了,存放到切片中,供我们使用
	userDatas := make([]map[string]string, 0, 3)
	userDatas = append(userDatas, user1)
	userDatas = append(userDatas, user2)
	userDatas = append(userDatas, user3)

	fmt.Println(userDatas)

	// 0 map[string]string
	for _, user := range userDatas {
		//fmt.Println(i)
		fmt.Println(user["name"], user["age"], user["sex"])
	}

}

结果:

map[age:18 name:小蓝 sex:男]
[map[age:18 name:zhangsan sex:男] map[age:30 name:lisi sex:男] map[age:18 name:小蓝 sex:男]]
zhangsan 18 男
lisi 30 男
小蓝 18 男
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一弓虽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值