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 = "寻"
	t.Log(len(s))

	c := []rune(s)
	t.Log(len(s),len(c))
	t.Logf("寻 unicode %x",c[0])
	t.Logf("寻 utf-8 %x",s)

	s = "寻非!"
	for _,c := range s{
		t.Logf("%[1]c %[1]x",c)
	}
}


常用的字符串操作

  1. strings包
  2. strconv包
func TestStrings(t *testing.T){
	s := "a,b,c"
	//分割
	parts := strings.Split(s,",")
	for _,part:=range parts{
		t.Log(part)
	}
	//重新连接
	t.Log(strings.Join(parts,"→"))
}

func TestConv(t *testing.T){
	//字符串转换
	s := strconv.Itoa(10)
	t.Log(s)
	if num ,err :=strconv.Atoi(s+"w123");err == nil{
		t.Log(num)
	}else{
		t.Log("转换失败",err)
	}
	fs := "1.4564646"
	if v,err := strconv.ParseFloat(fs,32);err != nil{
		t.Log(err)
	}else{
		t.Log(v)
	}
}

Go语言中常用的函数库

对于任何一门较为成熟的非脚本语言,其内置的函数库都是庞大的(虽然Golang的生态圈现在确实还没有像Java,C++这样的老牌语言成熟),限于篇幅不能面面俱到的每一个介绍。下面列出了各个函数的详细说明和demo链接
👇

常用Go函数含详细示例说明

中文函数文档

Go导入外部依赖

go通过go get 来获取远程依赖,使用-u 参数强制从网络更新远程依赖
如:

import (
	// cm 是给远程包取的别名,这样就可以通过 cm.xxx的方式调用了
	cm "github.com/easierway/concurrent_map"
	"testing"
)

执行go get -u强制从网络更新远程依赖,会自动从github中下载需要的包。

需要注意代码在GitHub上的组织形式,以适应 go get(直接以代码路径开始,不要有 src)

init函数

可以在每个.go源文件中定义init函数,在 main 被执行前,所有依赖的package下的init函数都会被执行。对于不同包的init函数按照包导入的依赖关系决定执行顺序,每个包可以有多个 init 函数,并且包里的每个源文件也可以有多个init函数

InitFuc.go:

func init(){
	fmt.Println("pack的基本初始化方法01")
}

func init(){
	fmt.Println("pack的基本初始化方法02")
}

对于导入InitFuc.go文件的在导入执行前都会执行其init()方法

依赖管理

go没有类似于maven的依赖管理工具,只能用vendor路径(1.5发布)

查找依赖包路径等解决方案如下:

  1. 当前包下的vendor目录
  2. 向上级目录查找,直到找到src下等vendor目录
  3. 在GOPATH下面查找依赖包
  4. 在GOROOT目录下查找

常用的主流依赖管理工具有:

godep

glide

dep

目前很多以前的项目包括现在的部分项目都还在继续用以上工具,感兴趣的可以点击链接自行了解。

但是个人并不推荐继续使用上述管理工具,因为在golang 1.11版本后推出了go mod(一方面源于是各路管理工具五花八门太乱了,连官方都看不下去了...)

使用:
go mod 初始化创建命令:
go mod init 模块名
在项目路径的控制台执行后将生成一个go.mod文件

modules是相关Go包的集合,是源代码交换和版本控制的单元。go命令直接支持使用Modules,包括记录和解析对其他模块的依赖性。Modules替换旧的基于GOPATH的方法,来指定使用哪些源文件。

go.mod 提供了module, requirereplaceexclude四个命令

module —— 指定包的名字(路径)
require —— 指定的依赖项模块
replace —— 可以替换依赖项模块
exclude —— 可以忽略依赖项模块

使用go mod 不需要再先执行go get.直接go run 会自动下载到$GOPATH/pkg/mod中

更新时间:2020-03-26 17:56:43

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/go语言3小时光速入门02常用结构与依赖管理
最后更新:2020-03-26 17:56:43

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×