GO语言的进阶之路-Golang高级数据结构定义
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
我们之前学习过Golang的基本数据类型,字符串和byte,以及rune也有所了解,但是说起高级点的数据类型,可能我们还是不太清楚,那么今天就跟着我脚步一起学习一下这些高级数据类型数据吧。相信有部分人可能学习过Python,那么我这篇博客基本上不用看了,因为对你来说会觉得so easy。因为太多的相似之处了,只是写法不同。本章主要介绍数组(array),切片(scice),字典(map),结构体(struct)等等。
一.数组
有可能你学习过shell或是python,其实从输出的角度上来说,两者区别不大,但是Golang的数组那是别有一番风味啊,首先在学习数组之前,你要了解数组的两个参数重要参数,一个是数组的长度,一个是数组的容量。只要你明白了golang语言中数组这两个性质,那么在定义的数组时你就会跳过一些坑。比如说你想把容量为3的数组赋值给容量为10的数组,是不可行的,因为容量为三的数组,其长度是3,容量为10的数组,其长度是10,(如果你想把一个数组赋值给另一个数组,首先要让数组的长度相等,其次两边的类型要一致)数组的长度是类型的一部分,所以容量为3的数组是无法赋值给容量为10的数组,就是因为其长度不同,当然你也可以说是类型不同导致。
最后我要强调的是在Golang定义一个数组后,这个数组的容量是没法改变的。
1.定义一个数组并循环看其初值;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 var num [3]int //表示定义一个容量为3的数组,如果没有赋初值的话默认就是"0". 14 fmt.Printf("该数组的第一个数字是:%d ",num[0]) 15 fmt.Printf("该数组的最后一个数字是:%d ",num[len(num)-1]) 16 for i,v := range num { 17 fmt.Printf("数组的下标是:%d,数组的下标对应的初值是: %d ",i,v) 18 } 19 for _,v := range num { 20 fmt.Printf("数组的初值是:%d ",v) 21 } 22 } 23 24 25 26 #以上代码执行结果如下: 27 该数组的第一个数字是:0 28 该数组的最后一个数字是:0 29 数组的下标是:0,数组的下标对应的初值是: 0 30 数组的下标是:1,数组的下标对应的初值是: 0 31 数组的下标是:2,数组的下标对应的初值是: 0 32 数组的初值是:0 33 数组的初值是:0 34 数组的初值是:0
2.数组的花式定义和赋值
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 var num [5]int //先定义一个容量为5的数组num。 14 num = [5]int{1,3,5,7,9} //给这num数组赋值 15 fmt.Printf("num数组的元素是:%d ",num) 16 17 var a [3]int = [3]int{1,2,3} //将一个容量为三长度也为3的数组赋值给另一个容量为三的数组. 18 fmt.Printf("a数组的元素是:%d ",a) 19 fmt.Printf("a[1]所对应的值是:%d ",a[1]) //表示取a数组下标对应是1的value. 20 fmt.Printf("a数组的容量是:%d,该容量的长度是:%d,还可以存取%d个成员 ",cap(a),len(a),(cap(a)-len(a))) //cap函数用于计算数组的容量,len函数用于计算数组的长度. 21 22 23 b := [...]int{1,2,3,4} //这种定义方式其实就是不写具体的容量参数,那么容量的值就和长度是相等的。 24 fmt.Printf("b数组的元素是:%d ",b) 25 fmt.Printf("该数组的容量是:%d,该容量的长度是:%d,还可以存取%d个成员 ",cap(b),len(b),(cap(b)-len(b))) 26 27 c := [...]int{4:20,7:-1} //定义下标为4的值为20,下标为7的值为-1。给指定数组下标赋初值,数组的长度为最大下标的加1,如果一个数组没有写明容量的话,会根据其下标最大的元素来定义其容量和长度。 28 fmt.Printf("c数组的元素是:%d ",c) 29 fmt.Printf("该数组的容量是:%d,该容量的长度是:%d,还可以存取%d个成员 ",cap(c),len(c),(cap(c)-len(c))) 30 } 31 32 33 #以上代码执行结果如下: 34 num数组的元素是:[1 3 5 7 9] 35 36 a数组的元素是:[1 2 3] 37 a[1]所对应的值是:2 38 a数组的容量是:3,该容量的长度是:3,还可以存取0个成员 39 40 b数组的元素是:[1 2 3 4] 41 该数组的容量是:4,该容量的长度是:4,还可以存取0个成员 42 43 c数组的元素是:[0 0 0 0 20 0 0 -1] 44 该数组的容量是:8,该容量的长度是:8,还可以存取0个成员
3.数组的内存大小以及内存地址的查看;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 "unsafe" 13 ) 14 15 func main() { 16 array1 := [4]int{1,2,3} //定义array2这个数组,开辟了一款内存。 17 fmt.Printf("array1的元素是:%d ",array1) 18 fmt.Printf("array1数组所占内存是:%d bytes ",unsafe.Sizeof(array1)) //一个数组占有8个字节,容量为4的数组其内存是就是32字节 19 var array2 [4]int //定义一个 20 array2 = array1 21 fmt.Printf("array1的地址是:%d array2的地址是:%d ",&array1[0],&array2[0]) 22 23 var n1,n2 int 24 n1 = 100 25 n2 = n1 //定义的n1和n2都是单独的容器,他们的内存地址是不一样的哟! 26 fmt.Printf("n1的内存地址是:%d n2的内存地址是:%d ",&n1,&n2) //打印n1和n2的内存地址 27 28 fmt.Println(n2) 29 fmt.Println(n1 == n2) //这是判断两个变量对应的值是否相同!如果是就为真(true),是否不是九尾假(false) 30 } 31 32 33 #以上代码输出结果如下: 34 array1的元素是:[1 2 3 0] 35 array1数组所占内存是:32 bytes 36 array1的地址是:825741296640 37 array2的地址是:825741296768 38 n1的内存地址是:825741271528 39 n2的内存地址是:825741271536 40 100 41 false
4.字节数组
其实我们在之前就用过关于数组的东西,比如字节数组“[]byte”,其实它就是一个数组
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "crypto/md5" 12 "fmt" 13 ) 14 15 func main() { 16 data := []byte("yinzhengjie") //定义一个byte数组. 17 md5sum := md5.Sum(data) //调用Golang的md5算法将字节数组换算成一个唯一的md5值用于文件校验。 18 fmt.Printf("%x ",md5sum) //打印其的md5值 19 fmt.Printf("%x ",255) //一个十六进制的数字的取之范围是"00-FF",所以2个16进制表示一个字符。md5就是由十六进制的数字组成的。 20 } 21 22 23 24 #以上代码执行结果如下: 25 a1424987f80af77e96f540ccda1e68e5 26 ff
5.数组的应用
1 [root@yinzhengjie ~]# more md5.go 2 /* 3 #!/usr/bin/env gorun 4 @author :yinzhengjie 5 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 6 EMAIL:y1053419035@qq.com 7 */ 8 9 10 11 package main 12 13 import ( 14 "io/ioutil" 15 "fmt" 16 "os" 17 "crypto/md5" 18 ) 19 20 func main() { 21 var s string 22 for i := 1; i < len(os.Args); i++ { 23 s = os.Args[i] 24 printFile(s) 25 } 26 } 27 28 func printFile(name string) { 29 buf, err := ioutil.ReadFile(name) //读取文件的内容传给buf,当然它接受到的数据时仍然是字节,即[]uint8. 30 if err != nil { 31 fmt.Println(err) 32 return 33 } 34 md5sum := md5.Sum(buf) //把字节buf的值用md5算法算出其md5值。 35 fmt.Printf("经计算,文件'%v'的MD5值是:%x ",os.Args[1],md5sum) 36 } 37 [root@yinzhengjie ~]# 38 [root@yinzhengjie ~]# go run md5.go startup.cfg 39 经计算,文件'startup.cfg'的MD5值是:c577d25cb647991e2b44e12c67649fcc 40 [root@yinzhengjie ~]#
二.切片
Golang的切片长得和数组很像,我们可以对一个数组做切片。要注意的是:当我们对一个数组做切片的时候,如果我们修改了切片下标所对应的值,那么被切片的数组的值也会跟着改变,因为他们都指向了同一块内存地址。
1.对数组做切片操作;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 ) 13 14 func main() { 15 primes := [8]int{2,3,5,7,9,11,13,15,} //定义一个数组 16 fmt.Printf("`primes`数组的值:%d ",primes) 17 var sum []int = primes[1:4] //定义一个切片 18 fmt.Printf("`sum`切片的值:%d ",sum) 19 fmt.Printf("`sum[0]`所对应的内存地址是:%x ",&sum[0]) 20 fmt.Printf("`primes[1]`所对应的内存地址是:%x ",&primes[1]) 21 var s1 []int 22 s1 = sum 23 fmt.Printf("`s1`切片对应的值为:%d ",s1) 24 fmt.Printf("s1[0] == sum[0]为:%v ",&s1[0] == &sum[0]) 25 } 26 27 28 29 #以上代码输出结果如下: 30 `primes`数组的值:[2 3 5 7 9 11 13 15] 31 `sum`切片的值:[3 5 7] 32 `sum[0]`所对应的内存地址是:c042046088 33 `primes[1]`所对应的内存地址是:c042046088 34 `s1`切片对应的值为:[3 5 7] 35 s1[0] == sum[0]为:true
2.切片的原理;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 names := [4]string{ //定义了一个字符串数组 14 "尹正杰", 15 "百度", 16 "谷歌", 17 "翻墙", 18 } 19 fmt.Println(names) 20 21 a := names[0:2] 22 b := names[1:3] 23 fmt.Println(a,b) 24 25 b[0] = "xxx" //修改b的元素,会将names的对应的地址做相应的修改。 26 fmt.Println(a,b) 27 fmt.Println(names) 28 } 29 30 31 #以上代码输出结果如下: 32 [尹正杰 百度 谷歌 翻墙] 33 [尹正杰 百度] [百度 谷歌] 34 [尹正杰 xxx] [xxx 谷歌] 35 [尹正杰 xxx 谷歌 翻墙]
3.切片的字面量
嗨,可能你有可能听不懂“字面量”,好吧,其实它就是对切片做初始化赋值,仅此而已!
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 num := []int{100,200,300,400,500} //切片的初始化方法,专业术语叫做切片字面量。 14 fmt.Println(num) 15 16 r := []bool{true,false,true,true} 17 fmt.Println(r) 18 } 19 20 21 22 #以上代码输出结果如下: 23 [100 200 300 400 500] 24 [true false true true]
4.切片的花式玩法;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 num := []int{2,3,5,7,9,11,13} //定义一个切片 14 fmt.Println(num) 15 num = num[1:4] //第一次对切片做切片操作,取值结果为:[3 5 7] 16 fmt.Println(num) 17 num = num[:2] //第二次切了又切,取值结果为[3 5] 18 fmt.Println(num) 19 num = num[1:] //第三次是在第二次切片操作后又一次切片操作,取值结果为[5] 20 fmt.Println(num) 21 } 22 23 24 25 #以上操作结果如下: 26 [2 3 5 7 9 11 13] 27 [3 5 7] 28 [3 5] 29 [5]
5.空切片;
切片包括2个属性,即长度和容量.因此我们不能看两个切片的长度为0就说这2个变量是相等的。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 num := []int{1,2,3} //定义一个切片 14 var s []int //定义一个空切片 15 fmt.Printf("`s`的值为:%v;长度为:%d;容量为%d ",s,len(s),cap(s)) 16 if s == nil { 17 fmt.Printf("s为空 ") 18 } 19 s1 := num[:0] //将切片num的值赋值给s1. 20 fmt.Printf("`s1`的值为:%v;长度为:%d;容量为%d ",s1,len(s1),cap(s1)) 21 fmt.Println(s1 == nil) //虽然s1的值为[],长度为0,但是容量为3,因此该切片不是空切片!切片包括2个属性,即长度和容量。 22 } 23 24 25 26 #以上代码输出结果如下: 27 `s`的值为:[];长度为:0;容量为0 28 s为空 29 `s1`的值为:[];长度为:0;容量为3 30 false
6.切片的追加操作
切片和数组不同,数组没有网容器里添加元素的方法,但是切片可以的。也就是说,只要切片的容量固定,我们可以根据容量大小往里添加数据相应的元素。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 var s []int //定义一个空切片s. 14 s = []int{1,2,3} //给这个空切片s赋值. 15 slice_attribute(s) 16 s = append(s,0) //往切片s追加一个"0"元素。 17 slice_attribute(s) 18 s = append(s,2,3,4) //继续往切片s追加“2,3,4”等元素。 19 slice_attribute(s) 20 21 } 22 23 func slice_attribute(s []int) { 24 fmt.Printf("len=%d cap=%d %v ",len(s),cap(s),s) //打印切片的长度,容量以及对应的value. 25 } 26 27 28 29 30 #以上代码输出结果如下: 31 len=3 cap=3 [1 2 3] 32 len=4 cap=6 [1 2 3 0] 33 len=7 cap=12 [1 2 3 0 2 3 4]
7.用make函数定义一个切片;
make函数不仅仅可以定义一个切片,还可以定义一个map(你可以理解成字典)。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 slice1 := make([]int,5) //表示定义一个长度为5的切片 14 my_slice("slice1",slice1) 15 slice2 := make([]int,0,5) //表示定义一个长度为0,容量为5的切片 16 my_slice("slice2",slice2) 17 slice3 := slice2[:2] 18 my_slice("slice3",slice3) 19 slice4 := slice3[2:5] 20 my_slice("slice4",slice4) 21 } 22 23 func my_slice(s string ,x []int) { 24 fmt.Printf("`%s`切片长度为:%d 切片容量为:%d 切片中的元素是:%v ",s,len(x),cap(x),x) 25 } 26 27 28 #以上代码执行结果如下: 29 `slice1`切片长度为:5 切片容量为:5 切片中的元素是:[0 0 0 0 0] 30 `slice2`切片长度为:0 切片容量为:5 切片中的元素是:[] 31 `slice3`切片长度为:2 切片容量为:5 切片中的元素是:[0 0] 32 `slice4`切片长度为:3 切片容量为:3 切片中的元素是:[0 0 0]
8.小试牛刀;
好了,关于切片的基本上这些就够用了,我们可以来小试牛刀一下啦~看看你掌握了多少;
A.反转切片的值;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func main() { 13 var num []int 14 num = []int{1,3,5,7} 15 fmt.Printf("切片反转之前的顺序是:%d ",num) 16 my_rerversal(num) 17 fmt.Printf("切片反转之后的顺序是:%d ",num) 18 } 19 20 func my_rerversal(s []int) { //该函数用于反转 21 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 22 s[i], s[j] = s[j], s[i] 23 } 24 } 25 26 27 #以上代码执行结果如下: 28 切片反转之前的顺序是:[1 3 5 7] 29 切片反转之后的顺序是:[7 5 3 1]
B.随机反转切片的值;
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 9 package main 10 11 import ( 12 "bufio" 13 "os" 14 "fmt" 15 16 "strconv" 17 ) 18 19 var ( 20 s string 21 line string 22 ) 23 func main() { 24 f := bufio.NewReader(os.Stdin) 25 num := []int{100,200,300,400,500,600,700,800} 26 fmt.Printf("现有一些数字:·