Golang学习-复合数据类型(数组,slice,map)

数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice,它是可以增长和收缩的动态序列,slice功能也更加灵活,但是要先理解slice工作原理的话需要先理解数组。

	var a [3]int64    // 初始化长度为3的int64类型的数组 因为没有为每个元素分配值,所有都为零值,注意此处的零值不一定是0主要是根据数组的元素类型来确定
	fmt.Println(a[0]) // 0
	a[2] = 5
	fmt.Println(a[len(a)-1]) // 5
	
	for i, v := range a {
		fmt.Printf("a[%d] value is %d \n", i, v)
	}
	q := [...]string{"a","b","c"} //使用... 来表示数组的长度等于{}中元素的个数

关于range关键字:
range用于迭代各种数据结构,包括切片,数组,映射,字符串和通道,它有两个返回值,index(下标),value(对应下标的元素值)。
这里需要特别提到的是,当使range遍历字符串的时候,他会按照Unicode码点(rune)进行迭代,

	str := "你好"
	for index, char := range str {
		fmt.Printf("Index: %d, Char: %c\n", index, char)
	}
	/*
	Index: 0, Char: 你
	Index: 3, Char: 好
	 */

此处会发现 index并不是 0 1 2 3 递增的,而是直接从0跳到了3,我们之前提到过,string的本质就是字节数组,但是在Unicode编码中,一个字符可能需要一个或者多个字节才能表示,当我们迭代每一个字符时,则会读取到多个字节,导致index的跳跃。

Slice(切片)

Slice代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很相似,只是没有固定长度而已。

	var month []string = []string{"January", "February", "March", "April", "May", "June"}
	fmt.Println(month[0]) // January
	//make创建slice(类型,长度,容量) 长度表示这个slice中有多少个元素(即这些元素会被赋予零值),容量表示的是slice可容纳多少个元素,
	//循环只会打印其长度的值,而非其容量
	//关于容量参数可以忽略,在使用append等函数对slice进行追加操作时,如果其长度超过了容量Go 语言会创建一个新的底层数组,并将原始数据复制到新数组中,同时切片会引用新的底层数组。
	//所以,是否显式指定容量取决于你对切片的操作需求。如果你事先知道切片的容量需求,指定容量可以避免频繁地进行数组扩容操作,提高性能。如果容量不确定或不重要,可以省略容量参数。
	var numbers []int = make([]int, 5, 12)
	for i, v := range numbers {
		fmt.Printf("numbers[%d] is %d \n", i, v)
		/*
			numbers[0] is 0
			numbers[1] is 0
			numbers[2] is 0
			numbers[3] is 0
			numbers[4] is 0
		*/
	}

数组和slice之间有着紧密的联系。一个slice是一个轻量的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用了一个数组对象。一个silce由三个部分构成:指针,长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,需要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一边是从slice的开始位置到底层数据的结尾位置,内置的lencap函数分别返回slice的长度和容量。

多个slice之间可以共享底层数据,并且引用的数组部分区间可能重叠。如下代码所示

	var sort1 []int8 = []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	var sort2 []int8 = sort1[0:5]
	var sort3 []int8 = make([]int8, 5, 5)
	copy(sort3, sort1[0:5])
	sort2[0] = 11
	fmt.Println(sort1[0]) //11
	fmt.Println(sort2[0]) //11
	fmt.Println(sort3[0]) //1

sort2和sort1的部分数组引用区间相同(因为sort2是从sort1中截取得到的),所以当我们修改他们相同引用区间的部分时,两个数组都会受到影响,而使用copy函数拷贝出来的数组是重新拷贝出一个新的数组,则不会被影响到。

golang中的参数传递都是值传递,但是需要注意一点,因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。如下示例:

package main

import "fmt"

func main() {
	num := 55
	rpNum(num)
	fmt.Println(num) //55
	fmt.Println("===============================")
	nums := make([]int, 5)
	fmt.Println(nums) // [0 0 0 0 0]
	fmt.Println("===============================")
	rpSlice(nums)
	fmt.Println(nums) //[1 0 0 0 0]
	fmt.Println("===============================")
	bob := obj{name: "Bob", age: 15}
	rpStruct(bob)
	fmt.Println(bob.name) // Bob
	fmt.Println("===============================")
	array := [5]int{0, 1}
	rpArray(array)
	fmt.Println(array) // [0 1 0 0 0]

	hMap := map[string]int{
		"id": 1,
	}
	rpMap(hMap)
	fmt.Println(hMap["id"]) //5

}

type obj struct {
	name string
	age  int
}

func rpNum(num int) {
	num = 53
}

func rpSlice(slice []int) {
	slice[0] = 1
}

func rpStruct(object obj) {
	object.name = "spider"
}

func rpArray(array [5]int) {
	array[0] = 5
}
func rpMap(hMap map[string]int) {
	hMap["id"] = 5
}


可以看到,当传递 int,struct,数组对象作为参数时,我们的修改并没有影响原值,而当我们使用slicemap时,会对原值产生影响。对于引用类型(如 slicesmap),虽然它们是通过值传递的,但传递的是指向底层数据的指针。因此,对这些数据的修改会影响原始数据

append函数

内置的append函数用于向slice追加元素:

	appendList := make([]string, 0)
	//关于此处为什么要重新给appendList赋值,上面我们讲过,当append时slice的长度超过了slice的容量,则会创建一个新的底层数组
	//如果没有创建新的底层数组,那么此时的slice指向的地址和append函数返回的对象指向的底层数组的地址是一致的,如果创建了则不一样
	//很多情况下我们无法得知slice是否进行了扩容(创建了新的数组) 所以直接重新赋值,避免slice并没有被更新内存地址
	appendList = append(appendList, "first")
	fmt.Println(appendList) //[first]

copy函数

copy函数用于将一个切片的元素复制到另一个切片。在上面的示例中我们已经提到过了但是我们需要注意以下两点

  1. dst 切片必须足够大以容纳要复制的元素;如果不够大,则只会复制到 dst 的长度。
  2. 如果 dst 和 src 指向同一个切片,copy 操作是安全的,Go 会处理好内部的复制逻辑。

删除元素

// 移除某个元素
func remove(slice []int, i int) []int {
	//第一种方法,截取i位前的数据,然后再后面附加上i+1位以后的内容
	//slice = append(slice[:i], slice[i+1:]...)
	//return slice
	//第二种方法,通过将第i+1位的元素附加到i位开始的后面,返回时需要删除最后一位
	copy(slice[i:], slice[i+1:])
	return slice[:len(slice)-1]
}

Map

哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的key都是不用的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。
在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有相同的类型,但是key和value可以是不同的数据类型。其中key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断已经存在。虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用作key类型则是一个坏的想法,因为浮点数的精度问题是不适合使用==比较的。对于value数据类型则没有任何的限制。

	//map的创建方式 make(map[keyType]valueType)
	ages := make(map[string]int16)
	//或者使用map[keyType]valueType{key:value}初始化并赋值
	//但是key必须支持==运算符的数据类型 比如以下类型就不支持 float slice map本身 function等
	ages = map[string]int16{} //也可以是空的 不赋值
	ages = map[string]int16{
		"Bob":     15,
		"Charlie": 20,
	}
	fmt.Println(ages)

Map中的元素通过key访问,也可以使用range迭代Map,需要注意的是,Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。

	bob := ages["Bob"]
	alice := ages["Alice"]
	fmt.Printf("Bob:%d \n", bob)     //15
	fmt.Printf("Alice:%d \n", alice) //0 虽然map中没有名为 alice 的 key 但是会返回零值

	//	//_ = &ages["Bob"] //map中的元素不是变量,无法对map的元素进行取址操作。 这样是无法通过编译的

	//也可以通过range迭代 range返回两个值 分别是key和value
	for name, age := range ages {
		fmt.Printf("%s:%d \n", name, age)
	}

使用内置的delete函数可以删除元素

	personMap := map[string]string{
		"name":    "Bob",
		"gender":  "male",
		"address": "USA",
	}
	delete(personMap, "address")
	fmt.Println(personMap) //map[gender:male name:Bob]
	delete(personMap, "age") //移除不存在的key也不会报错

map上的大部分操作,包括查找、删除、lengrange循环都可以安全工作在nil值的map上,他们的行为和一个空的map类似但是向一个nil值的map存入元素是不被允许的。在向map存数据前必须先创建map

	var nilMap map[string]int64
	fmt.Println(nilMap == nil)    //true
	fmt.Println(len(nilMap) == 0) //true
	nilMap["age"] = 18            //会报错,因为nilMap并没有指向一个内存地址

通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在则返回零值。这个规则很实用,但是有时候可能需要知道对应的元素是否真的是在map之中。例如,如果一个元素类型是一个数字,你可能需要区分一个已经存在的0,和不存在而返回的零值的0。我们可以通过使用多个返回值的方式,在这种场景下,map的下标语法将产生两个值;第二个是布尔值,用于报告元素是否真正的存在。 如下所示

	zeroMapTest := map[string]int16{
		"Michele": 19,
	}
	age, ok := zeroMapTest["Michele"]
	fmt.Printf("Michele's age is %d,the value is %t \n", age, ok) //Michele's age is 19,the value is true
	age, ok = zeroMapTest["Bob"]
	fmt.Printf("Bob's age is %d,the value is %t \n", age, ok) //Bob's age is 0,the value is false

和slice一样,map之间也不能进行相等比较;唯一能比较的就是nil。要判断两个map是否包含相同的key和value,我们必须通过一个循环实现。

golang中没有set类型,可以用map实现类似set的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值