Go 语言常用数据结构
1. 数组和切片
-
数组
-
数组的声明
//声明一个长度是3的数组,初始话为0值 var a [3]int a[0] = 1 //声明数组的同时初始化 b := [3]int{1,2,3} c := [2][2] int {{1,2},{3,4}} //声明一个不定长数组 d := [...]int {1,2,3,4,5,6}
-
数组的遍历
//使用for进行遍历(这里也可以进行普通的下标进行遍历) for _ /*索引值,下划线表示占位,不使用*/,e /*元素值*/:= range a{ t.Log("占位循环:",e) }
-
数组截取
语句的表达式如下
a[开始索引(包含):结束索引(不包含)]
不包含结束索引是为了防止使用数组长度作为结束索引时发生下标越界
-
-
切片
-
切片结构体的基本元素
- ptr:指向一个数组的指针
- len:我们可以访问的元素个数
- cap:后端空间的长度(和元素个数很像,类似于java里ArrayList的容量和数组长度,当元素个数超入容量了之后会直接发生两倍的扩容)
切片的声明
-
//声明一个切片 var s0 []int; t.Log(cap(s0),len(s0))//0 0 //往切片里添加一个元素 s0 = append(s0, 1); t.Log(cap(s0),len(s0))//1 1 //创建一个数组长度是2,容量是3的切片 s1 := make([]int,2,3) t.Log(cap(s1),len(s1))//3 2
要注意的是,使用append函数往切片里添加元素的时候原来的切片并不会改变(切片是不可变对象?),需要把这个的返回值重新赋值给原来的切片。也就是说如果频繁的使用这种方式网切片里添加元素就会导致大量的内存垃圾触发垃圾回收
-
切片共享的存储结构
使用多个切片指向同一个存储空间
month := []int {1,2,3,4,5,6,7,8,9,10,11,12} q2 := month[3:6] t.Log(len(q2),cap(q2))// 3 9 summer := month[4:7] t.Log(len(summer), cap(summer))// 3 8
需要注意的是,当我们改变任意一个切片,由于底层修改了内存里的数据,其他切片仍然会被一起修改
-
-
数组和切片的比较
比较项 数组 切片 容量是否可伸缩 否 是 是否可以进行比较 是 只能判断是否为nil
2. Map
-
Map的声明和使用
m := map[键类型] 值类型 {键:值 ,...}
// 声明一个Map m := map[string] int {"one":1,"two":2,"three":3} t.Log(m["one"])//1 //使用make声明一个map m2 := make(map[string] int,10) //往map里添加键值对 m2["add"] = 1//1
需要注意的是,当我们在map里查找一个不存在的key的时候,返回的是一个0值。为了区分到底是0值还是该键值对不存在,在获取map的值时会返回一个bool值来表示是否是键值对不存在,true表示存在,false表示不存在
value,ok := m2["one"] t.Log(value,ok)// 0,false value,ok = m2["add"] t.Log(value,ok)// 1,true
-
map的遍历
使用遍历数组时一样可以使用的range来遍历map
for k,v := range m{ t.Log(k,":",v) }
-
map与工厂模式
map的value可以时函数类型,如下
m := map[int]func(op int) int{} m[1] = func(op int) int {return op} m[2] = func(op int) int {return op*op} m[3] = func(op int) int {return op*op*op} t.Log(m[1](2),m[2](2),m[3](2))//2 4 8
-
map构建set集合
我们可以利用
map [type]bppl
来实现set集合(go没有内置set的数据结构),使用name来保存你想要的元素,放入元素以后把value置为 true。这样当我们尝试在这个map里取不存在的name的时候,会默认返回0值(false),重复放入元素的时候由于name的唯一性也不会造成重复内容
-
3. 字符串
-
与其他语言的差异
- string时一个基本数据类型,会初始化为0值,不是指针或引用类型
- string时只读的不可变的字节切片,len时他包含的字节数(不是字符数)
- string的byte数组可以存放任何数据(包括二进制数据)
-
Unicode和UTF-8
- Unicode时一种字符集
- UTF-8时Unicode的存储实现
s := "中" c := []rune(s) t.Log(len(c),len(s)) // 1 3 t.Logf("中 unicode %x",c[0]) // 中 unicode 4e2d t.Logf("中 UTF-8 %x",s) // 中 UTF-8 e4b8ad
key value 字符 "中" Unicode 0x4E2D UTF-8 0xE4B8AD 字节数组 [0x4E, 0xB8, 0xAD] -
string的高级功能
-
字符串分割
string[] strings,Split(待分割的字符串, 分割的依据)
-
字符串拼接
string strings.jion(字符串数组, 中间间隔的字符串)
-
string和数值的转换
string strove.Itia(数值) 数转字符串
number,bool strove.Atoi(字符串) 字符串转数,返回的bool是是否成功
-
4.函数
-
Go语言中函数的特点
可以有多个返回值
func funcDemo()(int,int){ //返回两个随机数 return rand.Intn(10),rand.Intn(20) }
所有的参数传递都是值传递(切片和map都是值传递,而然他们中的数据是指针,所以修改他们实际上修改的都是后端地址保存的数据)
函数可以作为变量的值
-
函数可以作为另一个函数的参数和返回值(下面是一个函数作为参数的例子:函数运行时间计时器)
//入口函数 func TestFunc(t *testing.T) { a,b := funcDemo() t.Log(a,b) a,b = timeSpent(funcDemo) t.Log(a,b) } //需要计时的函数 func funcDemo()(int,int){ //返回两个随机数 return rand.Intn(10),rand.Intn(20) } //计时函数的函数,需要传入一个函数(但是现在这个函数的定义必须要符合规则) func timeSpent(inner func()(int, int)) (int,int) { start := time.Now() ret1,ret2 := inner() fmt.Println("用时:",time.Since(start).Nanoseconds()) return ret1,ret2 }
-
函数的可变参数
不需要指定参数的个数,传入多少接受多少。底层通过数组来实现,相当于传入数组
func add (param ...int) int { result := 0; for v,_ := range param { result += v } return result }
-
defer函数
被defer关键字修饰的函数调用不会立即执行函数题,而是先执行下面语句,在程序段返回的时候才会执行(相当于Java中的finally语句块)
func TestDefer(t *testing.T) { defer finallyFunc() fmt.Println("开始执行") //执行一次异常, 发现defer语句块仍然执行了 panic("err") } func finallyFunc() { fmt.Println("一定会执行的语句块") }