Go语言3小时光速入门02——常用结构与依赖管理

Go语言数组

数组是什么东西应该不用做过多说明了吧。

数组的定义:
和大部分编程语言不同go是在类型前面加上[长度]标识数组,go的数组也是从0开始的

var 数组变量名 [长度]类型

如:
//定义一个变量名为arr长度为10的int数组
var arr [10]int
//将数组的第一个下标
arr[0] = 123

//定义一个数组并初始化
var arr2= [5]int{1,2,3,4,5}

数组也可以自动推导
//通过自动推导定义一个长度为5,内容为1,2,3,4,5的int数组
arr3 := [...]int{1,2,3,4,5}

数组的赋值和其他语言一样,除了初始赋值外,通过下标获取修改如:arr[1]=321

数组的遍历

go语言的数组遍历一般有两种办法:

  1. 和大部分语言一样,通过数组长度和下标遍历
  2. 通过range 数组遍历(相当于Java中的迭代器和foreach)range返回两个结果,第一个是下标,第二个是内容

arr_test.go

/**
数组遍历
 */
func TestArrayTravel(t *testing.T){
	arr := [...]int{1,2,3,4,5}
	//通过长度上限的下标正常递增遍历 len(arr)获取数组的长度
	for i := 0; i < len(arr); i ++{
		t.Log(arr[i])
	}
	//通过range遍历数组,index:下标,value:值
	for index,value := range arr{
		t.Log(index,":",value )
	}
}

数组的截取

go语言中数组的截取格式

数组[开始索引(包含), 结束索引(不包含)]

数组截取后返回的是一个切片,切片见下一节

arr_test.go

/*
数组截取
arr[开始索引(包含):结束索引(不包含)]
从头开始开始索引可以省略,
到尾结束结束索引可省略
 */
func TestArrSub(t *testing.T){
	arr := [...]int{1,2,3,4,5,6,7,8,9}
	arr2 := arr[:5]
	arr3 := arr[1:]
	t.Log(arr2)
	t.Log(arr3)
}

Go语言切片(Slice)

Go语言切片是对数组的抽象

因为数组的长度不可改变,Go提供了一种灵活,功能强悍的内置类型切片(动态数组),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。相当于python的列表。Java可以假想为List(但是在扩容前会修改切片内容会修改原始数组的值)

切片的定义

除了上面的数组截取可以返回切片外

切片定义和数组相同只是不用指定大小初始

var 切片变量名 []类型

如:
var slice_1 []int

另外也可以通过make创建一个切片

make([]类型,长度,容量)

如:
slice_2 := make([]int,10,20)
其中切片中的长度和容量的关系是:
	
	创建时会初始化长度内等值,不会初始化容量的值

切片添加数据

在go中通过append(切片,值)向切片中添加数据

slice_test.go:

func TestSliceInit(t *testing.T){
	var s []int
	t.Log(len(s),cap(s))
	//向切片中添加数据
	s = append(s,1)
	t.Log(len(s),cap(s))

	s1 := []int{1,2,3,4,5}
	t.Log(len(s1),cap(s1))

	s2 := make([]int,2,3)
	t.Log(len(s2),cap(s2))
	s2 = append(s2,123)
	t.Log(s2[0],s2[1],s2[2])
}



另外与数组不同,切片不能通过==直接比较

这也是切片与数组的主要两个区别:一个长度不可变不能扩容,一个不能直接比较


* 注意:切片的坑。数组截取时返回的切片本质上是一个指向原数组开始下标的指针和长度,容量大小。所以在没有扩容前可以通过切片直接修改原数组的值,但是当扩容后指向新数组,再修改就不会修改原数组的值了,所以一定要注意。当切片的cap满了后,乘2扩展(源码是:len <=1204,cap * 2, len>1024 , cap = cap * ( 1+1/4 ),且基于类型如int,string会有不同处理)
go源码中切片结构

slice_test.go:


/**
切片共享内存存储空间
 */
func TestSliceShareMemory(t *testing.T){
	year := []int{1,2,3,4,5,6,7,8,9,10,11}

	Q1 := year[:4]
	summer := year[3:8]
	t.Log(Q1,len(Q1),cap(Q1))
	t.Log(summer,len(summer),cap(summer))
	summer[0] = -1
	t.Log(Q1,len(Q1),cap(Q1))
	t.Log(summer,len(summer),cap(summer))
}
/**
切片自动扩容
 */
func TestSliceGrowing(t *testing.T){
	s := []int{}
	for i:=0;i < 10; i++{
		s = append(s,i)
		t.Log(len(s),cap(s))
	}
}

切片删除数据

这里我们可以直接参考Java中ArrayList的删除(基于arrayCopy,比如我们需要删除第123个元素,只需要截取123之前的元素和123之后的元素组成新切片)

/**
切片删除元素
 */
func main(){
	arr := [...]int{1,2,3,4,5,6,7}
	slice := arr[:]
	//删掉第3个元素
	slice = apend(slice[0:3],slice[3:])
}


Go语言Map

相当于Java、C++中的map和Python中的字典。go中基于Key-Value的一种基本数据结构.

map的创建

  1. 直接创建
变量名 := map[key类型]值类型{key:value,...}

map_1 := map[string]int{"a": 1, "b": 2, "c": 3}

map_2 := map[string]int{}
  1. 使用make创建
变量名 := make(map[key类型]值类型,初始容量)

map_3 := make(map[string]int, 3)

map的取值与修改

取值:	map变量名[key]
修改:	map变量名[key] = value值

删除:	delete(map变量名, key)

map_3["d"] = 4
//通过内置函数delete删除map_3中key为d的元素
delete(map_3,"d")

注意:在go中,如果map对应的key不存在,不会返回nil,而是返回类型默认的零值。

判断key是否在map中,map[key]接收返回两个值第一个是value,第二个是bool是否存在

if _, ok := map[key]; ok {
	//存在
}else{
	//不存在
}

map_test.go:

/*
如果map中key不存在不会返回nil,而是返回默认值
如果要确定是否存在
返回两个结果。第一个时值,第二个是是否存在
 */
func TestAccesNotExistingKey(t *testing.T){
	m := map[int]int{}
	m[3] = 0
	t.Log(m[1],m[3])
	if v,ok := m[1];ok{
		t.Log(v)
	}else{
		t.Log("key不存在")
	}
}

map的遍历

和数组一样可以通过range对map进行遍历

for map的key,map的value := range map{
}

map_test.go:

/*
map的遍历
*/
func TestMapForeach(t *testing.T){
	m := map[int]int{1:1,2:2,3:3,4:4}
	for key,value:=range m{
		t.Log(key,":",value)
	}
}

map的拓展

*map与工厂

前面说过go语言中函数也可以作为参数,这里我们可以把函数当做value放到map中,与鸭子类型接口结合。

map_test.go:

/*
可以在map中传入函数,通过key取出执行
相当于Java的Map<key,Function<R.V>>
 */
func TestMapFactory(t *testing.T){
	m := map[int]func(param int)int{}
	m[1] = func(param int)int{return param}
	m[2] = func(param int)int{return -param}
	m[3] = func(param int) int {return param * 2}

	t.Log(m[1](2),m[2](3),m[3](4))
}

Set的实现

在go中没有自带的set这种类型结构,但是我们可以基于map自己实现(实际上其它语言的set也大都基于map实现的)

其实就是构建一个map[类型]bool,value的bool用于判断是数据否存在。

map_test.go:


/*
golang中实现Set
map[type]bool
 */
func TestSet(t *testing.T){
	set := map[int]bool{}
	set[1] = true
	set[2] = true
	n := 1
	if set[n]{
		t.Logf("元素%d存在",n)
	}else{
		t.Logf("元素%d不存在",n)
	}

	delete(set,1)
	n = 1
	if set[n]{
		t.Logf("元素%d存在",n)
	}else{
		t.Logf("元素%d不存在",n)
	}

}


make和new

make和new都是用来申请内存的,但是二者主要区别在于

new 一般用于类型内存(如基本类型,结构体等)申请内存,返回的是对应类型的指针

make 只用于给slice、map、chan等结构申请内存,返回的是对应的这三个类型本身

事实上因为有自动推导的存在,new一般用的比较少。

Go语言字符串常用函数

在go语言中string是数据类型,而不是如Java和C++一样是引用或指针类型。

string 是只读的 byte slice,len 函数可以它所包含的 byte 数,即:
len(string) = 这个string包含的byte数

string 的 byte 数组可以存放任何数据

/*
golang中
string是数据类型,不是指针或引用
	(所以初始化类型是空字符串不是nil)
string 是只读等byte slice。len()可以获取包含等byte数(不是字符数)
string等byte数组可以放任何数据
string byte[]不可修改,不能再赋值 s[x] = ""(编译错误)
[]rune(string):可以取出string的unicode编码
 */
func TestString(t *testing.T){
	var s string
	t.Log(s)
	s = "hello"
	t.Log(s)
	s = "\xE4\xB8\xA5"
	t.Log(s,len(s))
	s = "\xE42\xB38\xA15"
	t.Log(s,len(s))
	s = "hhha"
	//s[1] = 's'
	s = "寻&q