GO 数组与切片详解

GO 数组与切片

一:数组

数组是存放元素的容器,必须指定存放的元素的类型和容量(长度)

数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。

注:数组的长度是数组类型的一部分,即如果两个数组的长度不一样是无法进行比较的

(一):数组定义

var 数组变量名 [元素数量]类型

数组的长度必须是常量,并且长度是数组的一部分。一旦定义,长度不能变。[3]int 和 [4]int是不同的类型

数组可以通过下标进行访问,下标是从0开始的,最后一个元素下标是len-1。

var a [3]int
(二):数组初始化

如果不初始化,默认元素都是零值(布尔值:false,整型和浮点型都为0,字符串为 “”)

方式一:

指定长度

package main

import "fmt"

func main()  {
	var a1 [3]bool
	
	// 初始化方式一
	a1 =  [3]bool{true, true, true}
	fmt.Println(a1)
}

方式二:

根据初始值自动推断数组的长度是多少

package main

import "fmt"

func main()  {
	// 初始化方式二
	a2 :=  [...]int{0,1,2,3,43,44}
	fmt.Println(a2)
}

方式三

根据索引初始化

package main

import "fmt"

func main()  {
	// 初始化方式三
	a3 := [5]int{0:1,4:5}
	fmt.Println(a3)
}
(三):数组遍历

方式一

for循环遍历

package main

import "fmt"

func main()  {
	// 方式一,for循环
	name := [...]string{"小红","小明","李华"}
	for i := 0; i < len(name); i++ {
		fmt.Println(name[i])
	}
	// 方式二,for range
	for k,v := range name {
		fmt.Println(k,v)
	}
}

方式二

for range

package main

import "fmt"

func main()  {
	// 方式二,for range
	for k,v := range name {
		fmt.Println(k,v)
	}
}
(四):多维数组

多维数组创建

package main

import "fmt"

func main()  {
	// 多维数组
	// [[1,2],[3,4],[5,6]]
	var a5 [3][2]int
	a5 = [3][2]int{
		[2]int{1,2},
		[2]int{3,4},
		[2]int{5,6},
	}
	fmt.Println(a5)
}

多维数组的遍历

package main

import "fmt"

func main()  {
	// 多维数组遍历
	for _,v1 := range a5{
		fmt.Println(v1)
		for _, v2 := range v1{
			fmt.Println(v2)
		}
	}
}

二:切片

​ 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。比如求和时只能对相同个数的数组求和,而且也无法继续向数组中添加新元素。

​ 切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。

​ 切片不保存数据,所以针对切片的修改都是针对底层数据的修改。

(一):切片的定义及初始化
package main

import "fmt"

func main()  {
	// 切片定义
	var s1 []int  // 定义一个int类型的切片
	var s2 []string  // 定义一个string类型的切片
	fmt.Println(s1 == nil)  // 判断s1是否为空
	var s3 = []bool{true,false,true}  // 初始化
	fmt.Println(s1,s2,s3)
	fmt.Println(s3 == nil)  // 判断s3是否为空
}
(二):切片长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量

切片指向一个底层的数组,是一个引用,并没有具体的值

切片的长度就是它元素的个数

切片的容量为底层数组从切片的第一个元素到最后的元素的数量

package main

import "fmt"

func main()  {
	// 长度和容量
	s1 = []int{1,2,3}
	s2 = []string{"小红","小明","李华"}
	fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1),cap(s1))
	fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2),cap(s2))

	// 由数组得到切片的长度和容量
	s4 := [...]int{1,3,5,7,9,11,13}
	s5 := s4[0:4]  // 对数组按索引切割,左包含右不包含
	s6 := s4[3:]
	// 容量为底层数组从切片的第一个元素到最后的元素的数量
	fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5),cap(s5))
	fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6),cap(s6))
	// 对切片进行切片
	s7 := s6[1:2]
	fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7),cap(s7))
}

// 结果
// len(s1):3 cap(s1):3
// len(s2):3 cap(s2):3
// len(s5):4 cap(s5):7
// len(s6):4 cap(s6):4
// len(s7):1 cap(s7):3
(三):make函数创建切片

make([]type, 数量,容量)

如果不传入容量的值则容量的值默认等于数量

package main

import "fmt"

func main()  {
	// make函数创造切片
	s1 := make([]int, 5, 10)
	fmt.Printf("s1: %d, len(s1): %d, cap(s1): %d", s1, len(s1), cap(s1))
}
(四):切片详解

​ 切片就是一个框,框住了一块连续的内存。属于引用类型,真正的数据都是保存在底层数组中的。

​ 切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等的元素。切片唯一合法的比较操作时和nil比较。一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0.但是我们不能说一个长度和容量都是0的切片一定是nil。

var s1 []int  // s1 == nil
// 切片为空,但s2 != nil,因为s2已开辟了一块内存空间
s2 := []int{}  
s3 := make([]int,0)  // s3 与 s2 同理

​ 所以要判断一个切片是否是空的,要用 len() 方法来确定,不能用 s==nil 来判定

(五):切片的赋值拷贝
package main

import "fmt"

func main()  {
	// 切片赋值
	s2 := []int{1,2,3,4}
	s3 := s2  // s2,s3都指向了同一个底层数组
	fmt.Printf("s2为:%d, s3为: %d\n", s2, s3)
	s3[3] = 1000
	fmt.Printf("s2为:%d, s3为: %d\n", s2, s3)
}

// 结果
// s2为:[1 2 3 4], s3为: [1 2 3 4]
// s2为:[1 2 3 1000], s3为: [1 2 3 1000]

s2,s3都指向了同一个底层数组[1 2 3 4],相当于在底层数组上框了一个框,当底层的数组发生改变时,s2,s3都会发生改变。

(六):切片的遍历
package main

import "fmt"

func main()  {
	// 切片的遍历
	// for索引遍历
	for i := 0; i < len(s3); i++{
		fmt.Println(s3[i])
	}
	// for range遍历
	for _,v := range s3 {
		fmt.Println(v)
	}
}
(七):切片追加元素

append()

​ 当使用索引添加元素时需注意索引越界问题,当切片的索引超过切片的容量时就会提示索引越界。可使用append()方法进行添加。

​ Go语言的内建函数append()可以为切片动态添加元素。每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时切片指向的底层数组就会更换。扩容操作往往发生在append()函数调用时

注:必须用变量接受append的返回值

package main

import "fmt"

func main()  {
	s1 := []string{"北京","上海","南京"}
	fmt.Printf("s1:%v, len(s1):%d, cap(s1):%d\n", s1, len(s1), cap(s1))

	// 调用append函数必须使用原来的切片变量接受返回值
	// append追加元素,原来的底层数组放不下的时候,Go底层就会把底层数组换一个
	s1 = append(s1, "山西")  
	fmt.Printf("s1:%v, len(s1):%d, cap(s1):%d\n", s1, len(s1), cap(s1))
	
	// append 只能添加字符串,当传入变量时需将变量拆开
	s2 := []string{"成都","杭州","西安"}
	s1 = append(s1,s2...)
}

扩容策略:

  • 首先判断,如果新申请容量大于旧容量的2倍,最终容量就是新申请的容量
  • 否则判断,如果旧切片的长度小于1024,则最终容量就是旧容量的两倍
  • 否则判断,如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的1/4,直到最终容量大于等于新申请的容量
  • 如果最终容量计算值溢出,则最终容量就是新申请容量

需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样

(八):copy复制

copy复制相当于将底层数组拷贝了出来放入另外一块内存空间

package main

import "fmt"

func main()  {
	a1 := []int{1,3,5}
	a2 := a1
	a3 := make([]int,3,3)
	copy(a3,a1)
	fmt.Println(a1,a2,a3)
	a1[0] = 100
	fmt.Println(a1,a2,a3) 
}

// 结果
[1 3 5] [1 3 5] [1 3 5]
[100 3 5] [100 3 5] [1 3 5]
(九):从切片中删除元素

Go语言中没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。代码如下:

package main

import "fmt"

func main()  {
	 a1 := []int{1,3,5,7,9}

	 // 将3和5删掉,...为解包的意思
	 a1 = append(a1[:1], a1[3:]...)
	 fmt.Println(a1)
}
(十):指针

Go语言中不存在指针操作,只需要记住两个符号:

  1. & 取地址
  2. * 根据地址取值
package main

import "fmt"

func main()  {
	// 取地址
	s := 123
	p := &s
	fmt.Println(p)
	fmt.Printf("%T\n", p)

	// 根据内存地址取值
	m := *p
	fmt.Println(m)
	fmt.Printf("%T\n", m)
}

// 结果
0xc0000a2058
*int
123
int
(十一):new

内存分配

make和new的区别:

  • make和new都是用来申请内存的
  • new很少用,一般用来给基本数据类型申请内存,string/int返回的是对应类型的指针
  • make是用来给slice、map、chan申请内存的,make函数返回的是对应的这三个类型本身
(十二):map

Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

形式:map[keyType]valueType

package main

import "fmt"

func main()  {
	var m1 map[string]int
	fmt.Println(m1 == nil)  // 还没有初始化(还没有开辟内存空间)
	// 初始化
	m1 = make(map[string]int, 10)  // 要估算好该map的容量,避免在程序运行过程中动态扩容
	m1["xiaoming"] = 18
	m1["kid"] = 25
	fmt.Println(m1)
	fmt.Println(m1)
}

# 结果
true
map[kid:25 xiaoming:18]
25

判断key是否存在

package main

import "fmt"

func main()  {
	var m1 map[string]int
	m1 = make(map[string]int, 10)  
	m1["xiaoming"] = 18
	m1["kid"] = 25

	// 判断map中是否有某个值
	// 方式一:直接打印,如果不存在则返回0值
	fmt.Println(m1["hack"])
	// 方式二:判断(推荐使用),使用value和ok两个变量来接受map的值,value为存在key时的值,ok是一个布尔值,如果存在则为true,如果不存在则为false
	value,ok := m1["hack"]
	if !ok {
		fmt.Println("不存在这个值")
	} else {
		fmt.Println(value)
	}
}

// 结果
0
不存在这个值

map的遍历

package main

import "fmt"

func main()  {
	var m1 map[string]int
	m1 = make(map[string]int, 10)  
	m1["xiaoming"] = 18
	m1["kid"] = 25
	
	// map的遍历
	for k,v := range m1 {
		fmt.Println(k,v)
	}
}

// 结果
xiaoming 18
kid 25

删除键值对

delete(map,key)

map:表示要删除键值对的map

key:表示要删除的额键值对的键

package main

import "fmt"

func main()  {
	var m1 map[string]int
	m1 = make(map[string]int, 10)  
	m1["xiaoming"] = 18
	m1["kid"] = 25

	// 删除键值对
	delete(m1,"kid")
	fmt.Println(m1)
	delete(m1,"hack")  // 如果删除的key不存在,则不进行任何操作
}

# 结果
map[xiaoming:18]

元素为map类型的切片

注:对于map和切片都必须进行初始化

package main

import "fmt"

func main()  {
	// 元素类型为map的切片
	var s1 = make([]map[int]string, 1, 10)

	// 必须对内部的map做初始化
	s1[0] = make(map[int]string, 1)
	s1[0][1] = "张三"
	fmt.Println(s1)
}

值为切片类型的map

注:对于map和切片都必须进行初始化

package main

import "fmt"

func main()  {
	// 值为切片类型的map
	m1 := make(map[string][]int, 5)
	m1["体重"] = []int{1,2,3,4}
	fmt.Println(m1)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值