复合数据类型小菜主要讨论,记录五种类型一一数组、slice、map、结构体、JSON。数组和结构体是聚合类型,他们的值由许多元素或成员字段的值组成。数组是由同构的元素组成-每个数组元素都是完全相同的类型。结构体则是由异构的元素组成的。数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。

1.数组

数组拷贝是值拷贝,不是引用类型拷贝

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。

数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数

01.数组声明

//声明长度为3,数据类型为int,变量名arr1数组
var arr1 [3]int  

//声明长度为5,数据类型为int并初始化值,变量名为arr2数组
arr2 := [5]int{1,2,3,4,5}

//声明并赋值
var arr3 [4]int = [4]int{1,2,3,4}

//使用...来声明数组
arr4 := [...]int{1,3,5,7}

//初始化部分值
arr5 := [...]int{50:5,99:-1}

//声明二维数组
var arr6 = [4][5]int

02.数组遍历

//第一种遍历
arr := [5]int{1,2,3,4,5}
for i := 0; i < len(arr); i++{
 fmt.Println(arr[i])
}
//第二种遍历
arr1 := [5]int{10,9,8,7,6}
for i,v := range arr1{
 fmt.Println(i,v)
}

2.Slice

slice不是值拷贝

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

一个slice由三个部分构成:指针长度容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s)

arr := [...]int{0,1,2,3,4,5,6,7}
s := arr[2:6]  //遵循左闭右开原则,即 x<= val <y
fmt.Println(s)  //2,3,4,5 

fmt.Println(arr[:6])  //0,1,2,3,4,5
fmt.Println(arr[2:])  //2,3,4,5,6,7
fmt.Println(arr[:])   //0,1,2,3,4,5,6,7

01.slice扩展

arr := [...]int{0,1,2,3,4,5,6,7}

s1 := arr[2:6]  //[2,3,4,5](6,7) len长度4,cap容量6
                // 0,1,2,3, 4,5
s2 := s1[3:5]   //[5,6] len长度2,cap容量3

fmt.Println(s1[4]) //报错,取不到下标为4,切片是对数组view,数组能取到的值就是小于len下标

slice可以向后扩展,但不能向前扩展。扩展条件 len < cap

s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)

02.make函数

内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以省略,在这种情况下,容量将等于长度。

语法:make([]T,len,cap)

sli := make([]int, 3,10)

fmt.Printf("sli=%v,len = %d,cap=%d",sli,len(sli),cap(sli))  //sli=[0,0,0], len = 3,cap = 10

03.slice比较

slice唯一合法的比较操作是和nil比较

if summer == nil { /* ... */}

一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0,但是也有非nil值的slice的长度和容量也是0的,例如[]int{}或make([]int, 3)[3:]。与任意类型的nil值一样,我们可以用[]int(nil)类型转换表达式来生成一个对应类型slice的nil值

var s []int    // len(s) == 0, s == nil
s = nil        // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{}    // len(s) == 0, s != nil

如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。除了和nil相等比较外,一个nil值的slice的行为和其它任意0长度的slice一样;例如reverse(nil)也是安全的。除了文档已经明确说明的地方,所有的Go语言函数应该以相同的方式对待nil值的slice和0长度的slice。

04.append函数向slice追加元素

var sli []int

fmt.Printf("sli = %v, len = %d, cap = %d",sli,len(sli),cap(sli))  //[], len = 0,cap = 0
sli = append(sli,1,2,3,4)
fmt.Printf("sli = %v, len = %d, cap = %d",sli,len(sli),cap(sli))  //[1,2,3,4], len = 4,cap = 4

//append追加多个值,甚至可以是slice
sli = append(sli,9,8,7,6)

05.append追加原理

func appendInt(x []int, y int) [] int{
    var z []int
	zlen := len(x) + 1
	if zlen <= cap(x){ 
	   z = x[:zlen]
	}else{
	   zcap := zlen
	   if zcap < 2*len(x){
	     zcap = 2 * len(x)
	   }
	   z = make([]int, zlen, zcap)	   
	   copy(z,x)
	}
	z[len(x)] = y
	
	return z
}

func main(){
   var x,y []int
   for i := 0; i < 10; i++{
     y = appendInt(x,i)
	 fmt.Printf("%d cap=%d\t%v\n",i,cap(y),y)
	 x = y
   }
}
//输出:
//0  cap=1    [0]
//1  cap=2    [0 1]
//2  cap=4    [0 1 2]
//3  cap=4    [0 1 2 3]
//4  cap=8    [0 1 2 3 4]
//5  cap=8    [0 1 2 3 4 5]
//6  cap=8    [0 1 2 3 4 5 6]
//7  cap=8    [0 1 2 3 4 5 6 7]
//8  cap=16   [0 1 2 3 4 5 6 7 8]
//9  cap=16   [0 1 2 3 4 5 6 7 8 9]

优化后,向appendInt中传入可变参数

func appendInt(x []int, y ...int) [] int{
    var z []int
    zlen := len(x) + len(y)
    if zlen <= cap(x){ 
       z = x[:zlen]
    }else{
       zcap := zlen
       if zcap < 2*len(x){
    	 zcap = 2 * len(x)
       }
       z = make([]int, zlen, zcap)	   
       copy(z,x)
    }
    copy(z[len(x):], y)
    
    return z
}

func main() {
   var y []int

   y = appendInt(y,1,2,3,4,5,6,7)	
   fmt.Printf("cap=%d\t%v\n",cap(y),y)
}
//输出:cap=7   [1 2 3 4 5 6 7]

06.slice对数组

arr := [6]int{0,1,2,3,4,5}

s := arr[2:]
s1 := append(s,6)
s2 := append(s1,7)
s3 := append(s2,8)
s4 := append(s3,9)

fmt.Println(s)   //[2,3,4,5]
fmt.Println(s1)  //[2,3,4,5,6]
fmt.Println(s2)  //[2,3,4,5,6,7]
fmt.Println(s3)  //[2,3,4,5,6,7,8]
fmt.Println(s4)  //[2,3,4,5,6,7,8,9]
fmt.Println(arr) //[0,1,2,3,4,5]

slice虽然是对数组操作,但是当超出数组边界时,此刻s1,s2,s3,s4都不在对arr进行view,而是重新在底层new Array,这步是GO操作的。添加元素时,如果超越cap,系统会重新分配更大的底层数组。现在只有s是对arr的一种view

由于值传递的关系,必须接收append的返回值。

07.slice push,pop,copy

//向切片push一个值
stack = append(stack,v)  //push v

//弹出栈顶元素
top := stack[len(stack) - 1]

//返回弹出后元素slice
stack = stack[:len(stack) - 1]

//将s2拷贝到s1
copy(s1,s2)

08.slice移除

func remove(slice []int, i int) []int {
   copy(slice[i:], slice[i+1:])
   return slice[:len(slice)-1]
}

func main() {
   s := []int{5, 6, 7, 8, 9}
   fmt.Println(remove(s, 2)) // "[5 6 8 9]"
}

3.Map

Map中的key值是无序的。map[K]V,复合结构map[k1]map[k2]v

哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。

在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中KV分别对应keyvalue。map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。

01.创建map

//第一种
var ages map[string]int

//第二种
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34

//第三种
ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}

fmt.Println(ages)  //map[alice:31 charlie:34]

02.删除map

delete(ages, "alice")

fmt.Println(ages) //map[charlie:34]

对map操作中,即使某个元素不存在也没有关系;如果一个查找失败将返回value类型对应的零值

ages["kkk"] = ages["kkk"] + 1
fmt.Println(ages) //map[charlie:34 kkk:1]

delete(ages, "test")
fmt.Println(ages) //map[charlie:34 kkk:1]

当map不存在时,会返回value类型对应的零值,如果value的类型为int,对应的零值0,就和真正的零值分不开。可以这样操作

ages,ok := ages["kkk"]
if !ok {
//...
}

03.简短赋值

ages["kkk"] += 1
//或
ages["kkk"]++

//但是绝对不能这样
ages["test"] = ages["kkk"]++   //写法错误,go中赋值右侧是表达式,自增是语句不是表达式

ages["kkk"]++
ages["test"] = ages["kkk"]     //写法正确

map中的元素并不是一个变量,不能对map的元素进行取地址操作: _ = &ages[“kkk”]

04.map遍历

name := map[int]string{
    3: "张三",
	1: "李四",
	5: "王五",
	8: "李二麻子",
}

for k,v := range name{
  fmt.Printf("%d\t%s\n",k,v)
}
//输出:
//3       张三
//1       李四
//5       王五
//8       李二麻子

由于map的key是无序的,所以想要map输出有顺序,就必须手动排序

import (
   "fmt"
   "sort"
)

name := map[int]string{
    3: "张三",
    1: "李四",
    5: "王五",
    8: "李二麻子",
}

var names = make([]int,0, len(name))
for k := range name{
   names = append(names,k)
}
sort.Ints(names)
fmt.Println(names)

for k,v := range name{
  fmt.Printf("%d\t%s\n",k,v)
}
//输出:
//[1 3 6 9]
//3       张三
//1       李四
//6       王五
//9       李二麻子

4.struct

01.定义


type TreeNode Struct{
	left, Right *TreeNode
	Value int
}