数组与切片


1 数组

  数组是相同数据类型的一组数据的集合。

  数组定义后,长度不能修改,可以通过**下标(索引)**访问元素。

1.1 数组的语法

var variable_name [SIZE] variable_type

  参数说明

  1.variable_name:数组名称

  2.SIZE:数组长度,必须是常量

  3.variable_type:数组保存元素的类型

func Array() {
	// 数组
	var a1 [3]int    // 定义一个int类型的数组a1,长度是3
	var s1 [2]string // 定义一个字符串类型的数组s1,长度是2
	var b [4]bool    // 定义一个布尔类型的数组b,长度是4

	fmt.Printf("a1: %T\n", a1)
	fmt.Printf("s1: %T\n", s1)
	fmt.Printf("b: %T\n", b)

	fmt.Printf("a1: %v\n", a1)
	fmt.Printf("s1: %v\n", s1)
	fmt.Printf("b: %v\n", b)
}

// 运行结果: a1: [3]int   s1: [2]string  b: [4]bool   a1: [0 0 0]  s1: [ ]  b: [false false false false]
// 从上面运行结果可以看出,数组和长度和元素类型共同组成了数组的类型。

1.2 数组初始化

  初始化,就是给数组的元素赋值,没有初始化的数组,默认元素值都是零值,布尔类型是false,字符串是空字符串。

1.2.1 未初始化的数组

func uninitialized_Array() {
	// 未初始化的数组
	var a2 [3]int    // 定义一个int类型的数组a2,长度是3
	var s2 [2]string // 定义一个字符串类型的数组s2,长度是2
	var b1 [2]bool

	fmt.Printf("a2: %v\n", a2)
	fmt.Printf("s2: %v\n", s2)
	fmt.Printf("b: %v\n", b1)
}

// 运行结果: a2: [0 0 0]    s2: [ ]    b1: [false false]

1.2.2 初始化-使用列表

  使用初始化列表,就是将值写在大括号里面。

func initialized_Array() {
	// 使用初始化列表
	var a3 = [5]int{1, 6, 7, 8, 90}
	var s3 = [2]string{"小罗", "https://linuxtech.top/"}
	var b2 = [2]bool{true, false}

	fmt.Printf("a3: %v\n", a3)
	fmt.Printf("s3: %v\n", s3)
	fmt.Printf("b2: %v\n", b2)

	a4 := [3]int{67, 98, 456} // 类型推断
	fmt.Printf("a4: %v\n", a4)
}

// 运行结果: a3: [1 6 7 8 90]   s3: [小罗 https://linuxtech.top/]   b2: [true false]   a4: [67 98 456]

1.2.3 初始化-省略数组长度

  数组长度可以省略,使用...代替,根据初始化值的数量自动推断

func nulen_Array() {
	// 省略数组长度---初始化
	var a4 = [...]int{34, 78, 67, 21}
	var s4 = [...]string{"小罗", "https://linuxtech.top/"}
	var b3 = [...]bool{true, false}

	a5 := [...]int{7, 5, 6, 3, 2, 1, 0}
	fmt.Printf("a4: %v\n", a4)
	fmt.Printf("s4: %v\n", s4)
	fmt.Printf("b3: %v\n", b3)
	fmt.Printf("a5: %v\n", a5)
    fmt.Printf("len(a4): %v\n", len(a4)) // 数组长度
}

// 运行结果: a4: [34 78 67 21] s4: [小罗 https://linuxtech.top/] b3: [true false] a5: [7 5 6 3 2 1 0] len(a4): 4

1.2.4 初始化-指定索引值

  可通过指定所有的方式来初始化,未指定所有的默认未零值。

func indexValue_Array() {
	// 初始化---指定索引值
	var a6 = [...]int{0: 1, 2: 6}
	var s5 = [...]string{1: "小罗", 2: "https://linuxtech.top/"}
	var b4 = [...]bool{2: true, 6: false}

	fmt.Printf("a6: %v\n", a6)
	fmt.Printf("s5: %v\n", s5)
	fmt.Printf("b4: %v\n", b4)
}

// 运行结果:a6: [1 0 6]    s5: [ 小罗 https://linuxtech.top/]    b4: [false false true false false false false]

1.3 修改数组

func edit_array() {
	// 修改数组的元素-直接下标修改
	var a7 = [...]int{78, 79, 80}
	fmt.Printf("a7: %v\n", a7)
	a7[1] = 90
	fmt.Printf("a7: %v\n", a7)
}

1.4 访问数组元素

  可通过下标的方式来访问数组元素。数组的最大下标为数组长度-1,大于这个下标会发生数组越界。

func access_index_array() {
	var a [2]int   // 数组长度是2,最大下标是1
	a[0] = 100
	a[1] = 200

	fmt.Printf("a[0]: %v\n", a[0])
	fmt.Printf("a[1]: %v\n", a[1])

	// 修改a[0]、a[1]
	a[0] = 1
	a[1] = 2
	fmt.Printf("a[0]: %v\n", a[0])
	fmt.Printf("a[1]: %v\n", a[1])
    
    // 数组长度越界  a[3]=1000 
}

1.4.1 数组长度遍历数组

  可根据数组长度,通过for循环的方式遍历数组,数组的长度可以使用len函数获得。

func len_array() {
	// 根据数组的长度和下标遍历数组
	a1 := [...]int{1, 5, 7, 0, 3, 4}

	/*
		fmt.Printf  Go标准库的格式化打印函数,可以自定义输出文字格式。
		"a1[%v]:%v\n" 格式化字符串
		%v:Go里的通用占位符,会自动匹配变量的值格式来输出
		第一个%v:用来填充下标i
		第二个%v:用来填充数组元素a1[i]
		i 循环里的索引(下标),从0一直递增到数组最大长度-1
		a1[i]  通过下标i 访问数组a1里对应的元素(就是你刚才问的「访问索引」用法)
	*/
	for i := 0; i < len(a1); i++ { // i < len(a1) 等同于 i <= len(a1)-1
		fmt.Printf("a1[%v]:%v\n", i, a1[i])
	}
}

1.4.2 for range遍历数组

  可以使用for range循环遍历数组,range返回数组下标和对应的值

func for_range_array() {
	// 使用for range循环遍历数组
	a2 := [...]int{2, 4, 6, 8, 10}
	for i, v := range a2 {
		fmt.Printf("a2[%v]: %v\n", i, v)
	}
}

2 切片

  数组是固定长度,可以容纳相同数据类型的元素的集合。当长度固定时,使用还是带来一些限制,比如:申请的长度太大浪费内存,太小又不够用。

  切片是可变长度的数组,底层就是使用数组实现的,增加了自动扩容功能。

  切片(Slice)是一个拥有相同类型元素的可变长度的序列。

2.1 语法

1.声明切片和声明数组类似,只要不添加长度就可以
  var identifier []type

2.切片是引用类型,可以使用`make`函数创建切片
  var slice1 []type = make([]type, len)
  // 也可以简写为:
  slice1 := make([]type, len)

3.也可以指定容量,其中capacity为可选参数。
  make([]T, length, capacity)

注明:`len`是数组的长度,并且也是切片的初始长度。

func slice() {
	// 切片示例-声明
	// 这种声明的方式还没分配内存
	var names []string
	var numbers []int

	fmt.Printf("names: %v\n", names)
	fmt.Printf("numbers: %v\n", numbers)
}

func make_slice() {
	// 通过make声明切片--make的方式已经分配了内存,只不过没赋值
	var s = make([]int, 2)
	fmt.Printf("s: %v\n", s)
}

2.2 切片的长度和容量

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

func len_cap_slice() {
	// 切片的长度和容量
	var Names = []string{"小罗", "https://linuxtech.top/"}
	var Numbers = []int{4, 7, 9, 0}
	fmt.Printf("len(Names): %d cap(Names):%d \n", len(Names), cap(Names))
	fmt.Printf("len(Numbers): %d cap(Numbers):%d \n", len(Numbers), cap(Numbers))

	var s1 = make([]string, 56, 78)
	fmt.Printf("len(s1): %d cap(s1):%d \n", len(s1), cap(s1))
}

2.3 切片初始化

  切片可以直接初始化,也可以使用数组初始化。

2.3.1 切片的切分

func slice() {
	// 切片的切分
	var s = []int{2, 6, 7, 8, 9, 0} // 索引: 0  1  2  3  4  5
	s1 := s[0:3]                    // [)---前闭后开,不包含3
	fmt.Printf("s1[0:3]: %v\n", s1)

	s2 := s[3:]
	fmt.Printf("s2[3:]: %v\n", s2)

	s3 := s[2:5]
	fmt.Printf("s3[2:5]: %v\n", s3)

	s4 := s[:]
	fmt.Printf("s4[:]: %v\n", s4)
}

2.3.2 直接初始化

func slice_init() {
	// 直接初始化
	s_init := []int{3, 6, 9, 0}
	fmt.Printf("s_init: %v\n", s_init)
}

2.3.3 使用数组初始化

func slice_array_init() {
	// 使用数组初始化
	array_init := [...]int{2, 4, 6, 8, 10, 9}
	slice_array_init1 := array_init[:3]   // [2 4 6]
	slice_array_init2 := array_init[:3]   // [2 4 6]
	slice_array_init_all := array_init[:] // 取数组中的所有元素
	fmt.Printf("slice_array_init1: %v\n", slice_array_init1)
	fmt.Printf("slice_array_init2: %v\n", slice_array_init2)
	fmt.Printf("slice_array_init_all: %v\n", slice_array_init_all)
}

2.3.4 切片表达式

  使用数组的部分元素初始化(切片表达式)

  切片的底层就是一个数组,所以可以基于数组通过切片表达式得到切片

  切片表达式中的low和high表示一个索引范围(左包含,右不包含),得到的切片长度=high-low,容量等于得到切片的底层数组的容量。

func slice_init_array_element() {
	// 使用数组的部分元素初始化(切片表达式)
	Array := [...]int{34, 35, 36, 37, 38, 39}
	s1_Array := Array[2:5]
	fmt.Printf("s1_Array: %v\n", s1_Array)

	s2_Array := Array[2:]
	fmt.Printf("s2_Array: %v\n", s2_Array)

	s3_Array := Array[:3]
	fmt.Printf("s3_Array: %v\n", s3_Array)
}

2.4 空(nil)切片

  一个切片在未初始化之前默认为 nil,长度为 0,容量为0.

func nil_slice() {
	var nilSlice []int
	fmt.Println(nilSlice == nil)
	fmt.Printf("len(nilSlice):%d,cap(nilSlice):%d \n", len(nilSlice), cap(nilSlice))
}

// 运行结果: true   len(nilSlice):0,cap(nilSlice):0

2.5 切片的遍历

  切片的遍历和数组的遍历类似,可使用for循环索引遍历,或者for range循环。

2.5.1 for循环索引

func index_slice() {
	// for循环索引遍历
	indexSlice := []int{2, 4, 6, 8, 0} // var indexSlice = []int{2, 4, 6, 8, 0}
	sliceLength := len(indexSlice)
	/* for i := 0; i < sliceLength; i++ {
		fmt.Printf("indexSlice[%v]:%v\n", i, indexSlice[i])
	} */

	for i := 0; i <= sliceLength-1; i++ { // 快捷键 fori...
		fmt.Printf("indexSlice[%v]:%v\n", i, indexSlice[i]) // 通过下标取值---indexSlice[i]
	}
}

2.5.2 for range循环

func range_slice() {
	// for range循环遍历
	rangeSlice := []int{1, 3, 5, 7, 9}
	for i, v := range rangeSlice { // 快捷键forr...
		fmt.Printf("rangeSlice[%v]:%v\n", i, v)
	}
}

2.6 操作切片元素

  切片是一个动态数组,可使用append()函数添加元素,Go语言中没有删除切片元素的专用方法,可以使用切片本身的特性删除元素。

  切片是引用类型,通过赋值的方式,会修改原有内容,Go提供了copy()函数来拷贝切片。

切片可以模拟数据库的增删改查,即db CRUD

2.6.1 添加元素

func sliceAppend() {
	// 添加元素
	s_append := []int{}
	s_append = append(s_append, 1)
	s_append = append(s_append, 3)
	s_append = append(s_append, 5, 7, 9) // 添加多个元素
	fmt.Printf("s_append: %v\n", s_append)

	s := []int{11, 13, 15}
	s_append = append(s_append, s...)
	fmt.Printf("s_append: %v\n", s_append) // 添加另外一个切片
}

2.6.2 删除元素

  从切片Slice中删除索引为index的元素,操作方法是Slice = append(Slice[:index], Slice[index+1:]...)

  核心原理:新建了一个切片,重新添加元素

func sliceDelete() {
	// 删除元素
	s_delete := []int{90, 91, 92, 93, 94, 95}
	s_delete = append(s_delete[:2], s_delete[3:]...) // 删除索引为2的元素
	fmt.Printf("s_delete: %v\n", s_delete)
}

2.6.3 修改元素

func modify_slice() {
	// 修改元素
	s_modify := []int{3, 5, 7, 0, 11}
	s_modify[2] = 9
	fmt.Printf("s_modify: %v\n", s_modify)
}

2.6.4 查询元素

func query_slice() {
	// 查询元素
	var s_query = []int{5, 8, 9, 12, 13, 45}
	var key = 13
	for i, v := range s_query {
		if v == key {
			fmt.Printf("s_query[%v]:%v\n", i, s_query[i])
			fmt.Printf("i:%d,s_query[i]:%d\n", i, s_query[i])
		}
	}
}

2.6.5 拷贝切片

func copySlice() {
	// 拷贝元素
	s1 := []string{"小罗", "哈尔滨工业大学", "北京大学"}
	s2 := s1 // 赋值时相当于是把内存地址给S2,所以修改s2时,s1也被修改了
	s2[0] = "西安交通大学"
	fmt.Printf("s1: %v\n", s1)
	fmt.Printf("s2: %v\n", s2)

	fmt.Println("---------------------")

	s3 := []string{"小罗", "哈尔滨工业大学", "北京大学"}
	s4 := make([]string, 3) // 类型为字符串,分类内存为3
	copy(s4, s3)            // copy后,再修改时,s3未被修改
	s4[0] = "西北工业大学"
	fmt.Printf("s3: %v\n", s3)
	fmt.Printf("s4: %v\n", s4)
}
// 赋值的情况下,原来的变量被修改了,使用copy函数,原来的变量没有被修改。

文章作者: 罗宇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 罗宇 !
 上一篇
流程控制语句 流程控制语句
Go(Golang)是Google开发的一种静态强类型、编译型语言。语法与C相近,但功能上有:内存安全、GC(垃圾回收),结构形态及CSP-style并发计算。
下一篇 
Map与函数 Map与函数
Go(Golang)是Google开发的一种静态强类型、编译型语言。语法与C相近,但功能上有:内存安全、GC(垃圾回收),结构形态及CSP-style并发计算。
  目录