GO语言的进阶之路-面向对象编程
GO语言的进阶之路-面向对象编程
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看懂60%的语法结构,因为涉及一些unix的代码可能没有Linux运维基础的同学在学习的时候会很吃力,看起来也会带来一定的难度,如果有时间的话我会给大家解析Docker部门精辟的源码。好了,回归正题吧,我们今天要学习的内容是什么呢?即面向对象编程。当然,不要用屌丝的心态来说:“那要是没对象的还咋编程呢?”,哈哈~正杰要告诉你的是:“此对象非彼对象”,没有“对象”照样编程啊!那么问题来了,到底什么事面向对象编程呢?
一.什么是面向对象编程;
在Golang的对象可以用一句话总结:“面向对象就是将要处理的数据跟函数进行绑定的方法”。
如果你从来没有学过Python的话就是可以理解是class,如果在学习Golang之前从来没有接触过其他语言(比如说我)的话,那么你可以这样理解:“它是一种编程风格,就是把一切东西看成一个个对象,比如人,车,面包,等等,然后把这些对象拥有的属性变量,比如年龄,民族,工作地点,变质期,寿命,还有操作这些属性变量的函数打包成一个类来表示,这个类的一个抽象就是一个对象,比如人这个类包含一些属性,比如年龄,名字,住址等,他还有一些对别人告诉这些属性的功能,比如:说,看,走等!!”。这就是的面向对象的特点!!!
二.为什么要有面向对象编程;
说到面向对象编程,就不得不说一下面向过程编程,我上次跟大家分享过面向过程编程的方法,也就是定义一些函数,减少了代码的重复性,增加了代码的扩展性和易读性等等。而且当大家“啪啪啪”代码敲的起劲的时候突然冒出个面向对象,对它的出现不免会有所疑惑,面向过程已经如此好了,干嘛还要面向对象呢?其实,我们举个例子来说明一下你就知道了。比如让你写一个人的模型,用函数写你要如果实现呢?比如现在要让你写关于:“刘德华,范冰冰,蔡依林”他们三个人的特点,没错,你可以面向过程式编程用函数将他们的特点定义 出来,那么问题来了,如果我想在外部调用“刘德华”或是“范冰冰”的特点该如果还实现呢?用函数可能非常难以实现。因为面向过程式编程虽然不印象实现的功能,但是其复杂度很低,所以,在实现一些功能上可能欠缺点火候。
这个时候,面向对象就思想就出来啦,面向对象就可能很轻松的实现在外部调用“刘德华”或是“范冰冰”的特点。想要了解更多关于面向对象的发展是可以问我的大师兄“百度”,当然也可以问下我的二师兄“谷歌”,在这里不扯太多历史了,大家多去敲一些代码就会体现面向对象的好处,因为Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。其实接触面向对象编程起初大家都是拒绝的,都有抵触心理。但是时间一长你就知道哪个才是你想要的!就好像你整天和你的男性朋友玩的很愉快,突然有一天一个长得非常哇塞的小姐姐出现在你面前,你在你的男性朋友面向说着:“这女孩也就那样,长得还行吧”,然后备注兄弟私下找机会和这个妹子约会,吃饭,看电影是一个道理。
三.如果定义一个对象;
当你看到这里的时候,恭喜你成功被洗脑了,如果你现在还有抵触心理学习面向对象编程的话,建议关闭该网页,因为内心的抵触你在学习这篇博客的内容会很吃力,可能看着看着你就不懂了,忘记之前的面向过程编程,让我们重新学习一种新的编程风格吧。当然,我们也可以对比一下两者的不同,这样学习起来也方便记忆。
下面的案例是:在二维空间中,求两点之间的距离。
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 "math" 12 "fmt" 13 ) 14 15 type Point struct { 16 X,Y float64 17 } 18 19 func Distence(p, q Point) float64 { 20 return math.Hypot(q.X-p.X,q.Y-p.Y) //"Hypot"是计算两点之间点距离 21 } 22 23 func main() { 24 p := Point{1,2} 25 q := Point{4,6} 26 fmt.Println(Distence(p,q)) //函数的调用方式,即传值的方式调用。 27 } 28 29 30 31 #以上代码输出结果如下: 32 5
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 ( 11 "math" 12 "fmt" 13 ) 14 15 type Point struct { //定义一个结构题体,你可以理解是是Python中的class 16 X,Y float64 17 } 18 19 func (p Point)Distence(q Point) float64 { //给p对象定义一个Distence的方法,你可以理解绑定了一个Distence的方法。 20 return math.Hypot(q.X-p.X,q.Y-p.Y) 21 } 22 23 func main() { 24 p := Point{1,2} 25 q := Point{4,6} 26 fmt.Println((p.Distence(q))) //类的调用方式,注意,如果定义就要如何调用!(这里是调用p的Distence方法。) 27 } 28 29 30 31 #以上代码输出结果如下: 32 5
当你看了这两段代码你现在可能会反问,实现的效果都是一样的啊,即他们的运行结果都相同,只是在定义和调用的方式不同而已。并不能明显体现出他们的差异性。也比较不出来他们的差别。我只是想说好戏还在后头,我们慢慢来体会它的奥妙之处,现在,我们就知道如何定义了一个对象了吧。那求多个点的长度又该如果定义呢?
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 "math" 12 "fmt" 13 ) 14 15 type Point struct { 16 X,Y float64 17 } 18 19 20 func (p Point)Distence(q Point) float64 { 21 return math.Hypot(q.X-p.X,q.Y-p.Y) 22 } 23 24 func Distence(path []Point) float64 { //定义一个path变了,path其是包含Point类型的数组切片, Slice可以理解为动态增长的数组. 25 var s float64 26 for i := 0;i <len(path) - 1 ; i++ { 27 s += path[i].Distence(path[i+1]) 28 } 29 return s 30 } 31 32 func main() { 33 path := []Point{{10,20},{30,40},{50,60}} 34 fmt.Println(Distence(path)) 35 } 36 37 38 39 #以上代码输出结果如下: 40 56.568542494923804
四.给对象定义一个别名;
玩过linux的朋友可能知道:“alias”这个命令,没错,就是起别名的意思,目的是为了方便用户操作,在Golang里也有可以用type设置别名,这种方法在Go里面随处可见,因为Golang就是一门面向对象编程的语言。
1.Time模块的使用;
也许,您在看官网的时候,会发现time模块,起本质的实现就是基于type的起别名的方法来实现新的功能,将原本的“float64”起别名为“Duration”类型,最后给“Duration”类型绑定特有的方法,这样time也就有了很多中方法,比如Now,String,Second等方法,下面我们来看看time模块常用的方法。
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 "time" 12 "fmt" 13 ) 14 15 func main() { 16 var n time.Duration //其实"Duration"就是利用别名来实现的。 17 n = 3 * time.Hour + 30 * time.Second //表示3小时又30分钟的时间。 18 fmt.Println(int64(n)) 19 fmt.Println(n.String()) //可读性最高,这是"Duration"特有的方法。 20 fmt.Println(n.Seconds()) 21 fmt.Println(n.Minutes()) 22 23 24 t := time.Now() 25 26 t1 := t.Add(time.Hour) 27 t2 := t.Add(-time.Hour) 28 29 fmt.Println(t1) 30 fmt.Println(t2) 31 fmt.Println(t1.Sub(t2)) //计算时间长度 32 } 33 34 35 36 #以上代码输出结果如下: 37 10830000000000 38 3h0m30s 39 10830 40 180.5 41 2017-07-09 10:49:45.8808815 +0800 CST 42 2017-07-09 08:49:45.8808815 +0800 CST 43 2h0m0s
2.定义一个新类型;
由于Golang的默认编码是中文编码,所以我们才存变了的时候可以用中文来当做变量名,在python3.x版本默认的编码也是utf-8,这一点大家很喜欢。但是我要在这里说的是,可以这么干,但是最好不要这么干,因为在涉及到对象的属性的时候你会遇到一些坑,先不要着急,我在后面的博客中也会分享到关于Golang的公有属性和私有属性。大家可以一起看看我用中文定义的变量名称,大家不要这么干,我这里就是为了方便说明如何定义对象,如果给定义的对象起别名,以及如何调用对象等等。
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 type 车的属性 struct { 13 名称 string 14 描述 string 15 是否需要备用 bool 16 } 17 18 type 车的特性 []车的属性 //“车的特性”类型是包含结构体“车的属性”类型的数组切片,你可以理解是起了一个别名。 19 20 func (零件 车的特性) 备件方法()(车的信息 车的特性) { //给“车的特性”绑定一个叫“备件”的方法起名为“零件”。 21 for _,part := range 零件{ 22 if part.是否需要备用 { //只将有备胎的车追加到“车的信息”这个空切片中。 23 车的信息 = append(车的信息,part) 24 } 25 } 26 return 车的信息 27 } 28 29 type 汽车 struct { //“汽车”由“车身大小”组成 30 车身大小 string 31 车的特性 //没有给“车的特性”指定一个名称,我们是要保证实现“内嵌”。这样可以提供自动的委托,不需特殊的声明, 32 // 例如“汽车.备件方法()”和“汽车.车的特性.备件方法()”是等同的。 33 } 34 35 36 var ( 37 特斯拉 = 车的特性{ 38 {"Tesla_90D(加速时间)", "100km/2.9s", true}, 39 {"车身大小", "109.47万元", false}, 40 {"颜色", "red", false}, 41 } 42 43 宝马 = 车的特性{ 44 {"BMW M4敞篷轿跑车(加速时间)", "100km/4.4s", true}, 45 {"价格", "1,098,000美元", true}, 46 {"倍耐力轮胎", "兰博基尼Huracan LP580-2前轮原配", true}, 47 {"夏季冰丝汽车坐垫", "1088.00", true}, 48 } 49 50 兰博基尼 = 车的特性{ 51 {"Avetador(加速时间)", "100km/2.8s", true}, 52 {"价格", "648.80-801.15万", true}, 53 {"颜色", "黑色", false}, 54 {"夏季冰丝汽车坐垫", "1088.00", true}, 55 } 56 ) 57 58 59 60 func main() { 61 roadBike := 汽车{车身大小: "5037×2070×mm", 车的特性: 特斯拉} 62 mountainBike := 汽车{车身大小: "1678*1870*1398", 车的特性: 宝马} 63 recumbentBike := 汽车{车身大小: "4780*2030*1136", 车的特性: 兰博基尼} 64 fmt.Println(roadBike.备件方法()) 65 fmt.Println(mountainBike.备件方法()) 66 fmt.Println(recumbentBike.备件方法()) 67 comboParts := 车的特性{} 68 comboParts = append(comboParts, mountainBike.车的特性...) 69 comboParts = append(comboParts, roadBike.车的特性...) 70 comboParts = append(comboParts, recumbentBike.车的特性...) 71 72 fmt.Println(len(comboParts), comboParts[9:]) 73 fmt.Println(comboParts.备件方法()) 74 } 75 76 77 #以上代码执行结果如下: 78 [{Tesla_90D(加速时间) 100km/2.9s true}] 79 [{BMW M4敞篷轿跑车(加速时间) 100km/4.4s true} {价格 1,098,000美元 true} {倍耐力轮胎 兰博基尼Huracan LP580-2前轮原配 true} {夏季冰丝汽车坐垫 1088.00 true}] 80 [{Avetador(加速时间) 100km/2.8s true} {价格 648.80-801.15万 true} {夏季冰丝汽车坐垫 1088.00 true}] 81 11 [{颜色 黑色 false} {夏季冰丝汽车坐垫 1088.00 true}] 82 [{BMW M4敞篷轿跑车(加速时间) 100km/4.4s true} {价格 1,098,000美元 true} {倍耐力轮胎 兰博基尼Huracan LP580-2前轮原配 true} {夏季冰丝汽车坐垫 1088.00 true} {Tesla_90D(加速时间) 100km/2.9s true} {Avetador(加速时间) 100km/2.8s true} {价格 648.80-801.15万 true} {夏季冰丝汽车坐垫 1088.00 true}]
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 "encoding/json" 13 "bufio" 14 "os" 15 "strings" 16 "strconv" 17 "io/ioutil" 18 ) 19 20 type Student struct { //定义一个名称为“Student”的结构统; 21 ID int //定义学员编号 22 NAME string //定义姓名 23 } 24 25 type ClassRoom struct { //定义一个名为“ClassRoom”的结构体; 26 teacher string //定义一个名称为“teacher”字符串类型的变量, 27 students map[string]*Student //定义一个变量名为“students”其类型为map的变量。 28 } 29 30 var classrooms map[string]*ClassRoom //声明一个名为“classrooms”的变量,指定其类型是map,要注意的是map中的value是指针类型的结构统哟。 31 32 var student_num = make(map[int]Student) //定义存取学生成员的函数,注意这里是要初始化的,字典在使用前必须初始化,不然会报错! 33 34 func (s *ClassRoom)Delete() { 35 36 } 37 38 39 func (c *ClassRoom) Add(args []string) error { //自定义添加学生信息的函数 40 if len(args) != 2 { 41 fmt.Println("您输入的字符串有问题,案例:add 01 bingan") 42 return nil 43 } 44 id := args[0] 45 student_name := args[1] 46 student_id, _ := strconv.Atoi(id) 47 48 for _, s := range student_num { 49 if s.ID == student_id { 50 fmt.Println("您输入的ID已经存在,请重新输入") 51 return nil 52 } 53 } 54 student_num[len(student_num)+1] = Student{ student_id,student_name} 55 fmt.Println("Add successfully!!!") 56 return nil 57 } 58 59 func (c *ClassRoom)Drop(args []string)error { 60 if len(args) != 1 { 61 fmt.Println("你愁啥?改学生ID压根就不存在!") 62 return nil 63 } 64 id := args[0] 65 student_id, _ := strconv.Atoi(id) 66 for i, j := range student_num { 67 if j.ID == student_id { 68 delete(student_num, i) //删除map中该id所对应的key值。但是该功能需要完善! 69 fmt.Println("delete successfully!") 70 return nil 71 } 72 } 73 fmt.Println("你愁啥?学生ID压根就不存在!") 74 return nil 75 } 76 77 78 func (c *ClassRoom)Update(args []string)error { //定义修改的函数 79 if len(args) != 2 { 80 fmt.Println("您输入的字符串有问题,案例:add 01 bingan") 81 return nil 82 } 83 id := args[0] //取出ID 84 student_name := args[1] //取出姓名 85 student_id, _ := strconv.Atoi(id) //将字符串ID变成数字类型。 86 for i, j := range student_num { 87 if j.ID == student_id { 88 student_num[i] = Student{ student_id,student_name} //这其实就是一个赋值的过程。 89 fmt.Println("update successfully!") 90 return nil 91 } 92 } 93 fmt.Println("你愁啥?学生ID压根就不存在!") 94 return nil 95 } 96 97 98 func (c *ClassRoom)List(args []string) error{ //给“ClassRoom”绑定一个“List”方法。 99 if len(student_num) == 0 { 100 fmt.Println("数据库为空,请自行添加相关信息!") 101 return nil 102 } 103 for _,value := range student_num{ 104 fmt.Printf("学员的姓名是:\033[31;1m%s\033[0m,学员编号是:\033[31;1m%d\033[0m\n",value.NAME,value.ID) 105 } 106 return nil 107 } 108 109 func (c *ClassRoom)Save(args []string)error { //定义存取的函数 110 if len(args) == 0 { 111 fmt.Println("请输入您想要保存的文件名,例如:save student.txt") 112 return nil 113 } 114 file_name := args[0] 115 f, err := json.Marshal(student_num) //把变量持久化,也就是将内存的变量存到硬盘的时进行的序列化的过程 116 117 if err != nil { 118 fmt.Println("序列化出错啦!") 119 } 120 ioutil.WriteFile(file_name, f, 0644) //将数据写入硬盘,并制定文件的权限。 121 fmt.Println("写入成功") 122 return nil 123 } 124 125 126 func (c *ClassRoom)Load(args []string) error { //定义加载的函数。 127 if len(args) != 1 { 128 fmt.Println("输入错误,请重新输入.") 129 return nil 130 } 131 file_name := args[0] 132 s, _ := ioutil.ReadFile(file_name) 133 json.Unmarshal(s, &student_num) 134 fmt.Println("读取成功!") 135 return nil 136 } 137 138 139 func (c *ClassRoom)Exit(args []string) error { //定义对出的脚本 140 os.Exit(0) //里面的数字表示用户结束程序的返回值,返回0意味着程序是正常结束的。 141 return nil 142 } 143 144 145 146 func main() { 147 classrooms = make(map[string]*ClassRoom) /*初始化字典,因为上面只定义没有初始化。初始化赋值这里不能加":=", 148 因为作用域不同(会将全局作用域的值给覆盖掉),加了得到的结果返回:"null".*/ 149 fmt.Println("学生管理系统迷你版!") 150 f := bufio.NewReader(os.Stdin) //用它读取用户输入的内容 151 for { 152 fmt.Print("请选择您要去的教室") 153 fmt.Print("请输入:>>>") 154 line, _ := f.ReadString('\n') //将读取的内容按照"\n"换行符来切分,注意里面是单引号哟! 155 line = strings.Trim(line, "\n") //表示只脱去换行符:"\n",你可以自定义脱去字符,等效于line = strings.TrimSpace(line) 156 content := strings.Fields(line) //按照空格将得来的字符串做成一个切片。 157 158 if len(content) == 0 { //脱去空格 159 continue 160 } 161 if len(content) == 1 { 162 fmt.Println("您输入的字符串有问题,案例:select yinzhengjie!") 163 continue 164 } 165 ClassRoom_Chose := content[0] //定义执行命令的参数,如add,upadte,list,delete....等等 166 Classroom_len := content[1:] 167 Classroom := Classroom_len[0] //这个就是讲字符串切片转换成字符串。 168 if len(Classroom_len) == 1 { 169 classrooms[Classroom] = &ClassRoom{ 170 students: make(map[string]*Student), 171 } 172 } else { 173 fmt.Println("您输入的参数有问题!") 174 } 175 if ClassRoom_Chose == "select" || ClassRoom_Chose == "SELECT" { 176 fmt.Printf(" 欢迎来到\033[31;1m%s\033[0m教室\n",Classroom) 177 for { 178 fmt.Print("请输入:>>>") 179 line, _ := f.ReadString('\n') //将读取的内容按照"\n"换行符来切分,注意里面是单引号哟! 180 line = strings.Trim(line, "\n") //表示只脱去换行符:"\n",你可以自定义脱去字符,等效于line = strings.TrimSpace(line) 181 content := strings.Fields(line) //按照空格将得来的字符串做成一个切片。 182 if len(content) == 0 { //脱去空格 183 continue 184 } 185 cmd := content[0] //定义执行命令的参数,如add,upadte,list,delete....等等 186 args := content[1:] //定义要执行的具体内容 187 actiondict := map[string]func([]string) error{ //定义用户的输入内容 188 "add": classrooms[Classroom].Add, //表示用户输入的是字符串"add"时,其要执行的结构体的方法是classrooms[Classroom].Add,也就是“Add”方法,以下定义同理。 189 "list": classrooms[Classroom].List, 190 "update": classrooms[Classroom].Update, 191 "delete": classrooms[Classroom].Drop, 192 "save": classrooms[Classroom].Save, 193 "load": classrooms[Classroom].Load, 194 "exit": classrooms[Classroom].Exit, 195 } 196 action_func := actiondict[cmd] //定义用户执行的函数 197 if action_func == nil { //如果输入有问题,告知用户用法 198 fmt.Println("Usage: {add|list|where|load|upadte|delete|}[int][string]") 199 continue 200 } 201 err := action_func(args) 202 if err != nil { 203 fmt.Println("您输入的字符串有问题,案例:add 01 yinzhengjie") 204 continue 205 } 206 continue 207 } 208 } 209 } 210 }
1 [root@yinzhengjie tmp]# go run student_mange.go 2 学生管理系统迷你版! 3 请选择您要去的教室请输入:>>>list 4 您输入的字符串有问题,案例:select yinzhengjie! 5 请选择您要去的教室请输入:>>>select 中国检科院 6 欢迎来到中国检科院教室 7 请输入:>>>list 8 数据库为空,请自行添加相关信息! 9 请输入:>>>add 1 yinzhengjie 10 Add successfully!!! 11 请输入:>>>add 2 liu 12 Add successfully!!! 13 请输入:>>>add 3 wu 14 Add successfully!!! 15 请输入:>>>add 4 han 16 Add successfully!!! 17 请输入:>>> 18 请输入:>>>list 19 学员的姓名是:wu,学员编号是:3 20 学员的姓名是:han,学员编号是:4 21 学员的姓名是:yinzhengjie,学员编号是:1 22 学员的姓名是:liu,学员编号是:2 23 请输入:>>>update 1 尹正杰 24 update successfully! 25 请输入:>>>list 26 学员的姓名是:liu,学员编号是:2 27 学员的姓名是:wu,学员编号是:3 28 学员的姓名是:han,学员编号是:4 29 学员的姓名是:尹正杰,学员编号是:1 30 请输入:>>>delete 1 31 delete successfully! 32 请输入:>>>list 33 学员的姓名是:liu,学员编号是:2 34 学员的姓名是:wu,学员编号是:3 35 学员的姓名是:han,学员编号是:4 36 请输入:>>>save test 37 写入成功 38 请输入:>>>exit 39 You have new mail in /var/spool/mail/root 40 [root@yinzhengjie tmp]# 41 [root@yinzhengjie tmp]# 42 [root@yinzhengjie tmp]# 43 [root@yinzhengjie tmp]# 44 [root@yinzhengjie tmp]# go run student_mange.go 45 学生管理系统迷你版! 46 请选择您要去的教室请输入:>>>select yinzhengjie 47 欢迎来到yinzhengjie教室 48 请输入:>>>list 49 数据库为空,请自行添加相关信息! 50 请输入:>>>load test 51 读取成功! 52 请输入:>>>list 53 学员的姓名是:liu,学员编号是:2 54 学员的姓名是:wu,学员编号是:3 55 学员的姓名是:han,学员编号是:4 56 请输入:>>>EXIT 57 Usage: {add|list|where|load|upadte|delete|}[int][string] 58 请输入:>>>exit 59 [root@yinzhengjie tmp]#
五.指针接受者;
我们知道如何定义一个对象,以及如何定义对象的方法,那么问题来了,如果我想要修改一个对象的公有属性那该如何处理呢?这个时候我们就得用到指针类型了,学习Golan的时候我们不得不对指针要了解啊,不过Golang语言中的类和其他语言(C++,Java,Python)还真不一样,因为他没有继承的概念,这是很多程序员较好的一点,但是事实证明它的缺点还是不少的,后续会跟大家分享。先看看我们的例子吧。
1.案列一,对象的类型要定义指针类型;
如果类型传的不是指针类型,也就是将Point类型去掉的话,那么其方法是不能修改该类型的源数据的,而是单独开辟了一块内存。
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 /* 11 1>.我们希望改变对象的成员; 12 2>.等价于函数的第一个参数是指针; 13 */ 14 15 import ( 16 "fmt" 17 ) 18 19 type Point struct { 20 X,Y float64 21 } 22 23 24 func (p *Point) ScaleBy(factor float64) { //想要修改p的值就得传指针类型"*Point" 25 p.X *= factor // 等价于:X = X * factor,对对象P进行操作,修改其共有属性。 26 p.Y *= factor 27 } 28 29 func main() { 30 //两种调用方式: 31 p := Point{100,200} 32 p.ScaleBy(2) //姿势一:直接调用 33 fmt.Println(p) 34 35 p1 := Point{100,200} //姿势二:声明结构体后再用指针指向 36 p2 :=&p1 //使用结构体调用,再取其内存地址 37 p2.ScaleBy(2) 38 fmt.Println(p2) 39 } 40 41 42 #以上代码执行效果如下: 43 {200 400} 44 &{200 400}
2.原地修改字典的Value;
上面的那个案例就是我们想要修改结构体重的公有属性,需要用到指针,还记得我之前跟大家分享如何修改字典的值吗?http://www.cnblogs.com/yinzhengjie/p/7079626.html(搜索:“结构体的指针”)在这里,我继续给大家一个案例,此时我把value的值修改成一个结构体,方法还是一样的。
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 type Student struct { 13 ID int 14 NAME string 15 } 16 17 func main() { 18 dict := make(map[int]*Student) 19 dict[1] = &Student{ 20 ID:100, 21 NAME:"yinzhengjie", 22 } 23 24 dict[2] = &Student{ 25 ID:200, 26 NAME:"尹正杰", 27 } 28 29 30 fmt.Println(dict[1]) 31 s := dict[1] 32 s.ID = 100000 //原地修改字典的value. 33 fmt.Println(dict) 34 fmt.Println(dict[1]) 35 } 36 37 38 39 #以上代码执行结果如下: 40 &{100 yinzhengjie} 41 map[1:0xc042044400 2:0xc042044420] 42 &{100000 yinzhengjie}
3.巧说Golang指针类型(*)地址运算符(&)
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 type Rec_area struct { 13 height float64 14 width float64 15 } 16 17 func (Area *Rec_area)Size()float64 { 18 return Area.width * Area.height 19 } 20 21 func main() { 22 fmt.Printf("&是取地址符号, 取到Rec_area类型对象的地址为:\033[31;1m%v\033[0m\n ",&Rec_area{100,200}) 23 var s *Rec_area = &Rec_area{10,20} 24 fmt.Printf("*可以表示一个变量是指针类型(*Rec_area是一个指针变量):\033[31;1m%v\033[0m\n",s ) 25 fmt.Printf("*也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值:\033[31;1m%v\033[0m\n",*s) 26 var id *Rec_area = &Rec_area{10,20} 27 fmt.Printf("查看这个指针变量的地址 , 基本数据类型直接打印地址:\033[31;1m%v\033[0m\n",&id) 28 } 29 30 31 32 #以上代码输出结果如下: 33 &是取地址符号, 取到Rec_area类型对象的地址为:&{100 200} 34 *可以表示一个变量是指针类型(*Rec_area是一个指针变量):&{10 20} 35 *也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值:{10 20} 36 查看这个指针变量的地址 , 基本数据类型直接打印地址:0xc042056020
六.结构体的公有属性和私有属性;
我们定义了一个结构体其实可以理解是定义了一个类,那么这个结构体本身都有什么属性呢?这个时候我们就得引入两个概念即:结构体的公有属性和私有属性。从名称上来说,公有属性就是共享的,谁都可以调用它的属性,所谓私有属性就是这个类特有的,在外部是不可以被调用的。然后Golang区分公有属性和私有属性的机制就是类的方法是否首字母大写,如果首字母大写的方法就是公有属性,如果首字母小写的话就是私有属性。
还记得我之前给大家演示一个把你本地代码PUSH到GitHub上吗?
(未更新)
七.结构体的绑定方法接口(interface)
刚刚接触到"interface"的小伙伴可能会有些认生,觉得interface并没有说明卵用。赶紧改变你的观念吧!它的功能可强大了,比如我们常用的调试方法(“fmt.Println”)就是用interface实现的。在Golang官网中由很多模块都是用interface实现的。我称之为“大胃王”!因为他可以接受任意的数据类型(当然,接受的这种数据类型得在这个接口中由相应的方法去处理它哟!)。比如指针类型,字符串类型,地址运算符,整型,布尔值啊等等。所以说interface的应用在Golang语言中用法还是很广泛的,其实我们最常用的就是用它来绑定多个方法(method)。好了,可能光这样说你还感受不到接口的方法,接下来,正杰带你一起来体会一下它的喜怒哀乐。
1.为什么需要接口;
比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类,可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦,如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护方便了。
其实说简单点就是实现归一化处理,实际生产环境中也有不少的案例。比如,不同的机器来自不同的厂商,但是每个厂商都要提供接口和其他实现共享数据。再比如你去电影院买票,想看“摔跤吧,爸爸”这部电影,那么你就得想导购员买票,如果这部电影已经上映了,那么她就会把票卖给你,如果没有上映那就不会卖给你,因为她们的系统里没有影片信息(你可以理解是没有提供相应的接口)。
2.定义一个空接口;
一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数。接下来,我们一起看下例子就知道了。
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 var null interface{} // 定义"null"为空接口 13 14 func main() { 15 var i int = 30000 16 s := "Yinzhengjie" 17 null = i // "null"可以存储任意类型的数值,可以给他赋值一个int,也可以给他赋值一个string. 18 fmt.Println(null) 19 null = s 20 fmt.Println(null) 21 } 22 23 24 25 #以上代码执行结果如下: 26 30000 27 Yinzhengjie
3.声明接口;
我们上面学习过了结构体,在其他语言中我们称之为“类”。接口就是程序员根据自己的需求,把自己定义好的类按照相应的需求绑定起来的过程。就好比一根柴夫上山去砍柴,它把干的树木困在用绳子捆绑在一起,把心坎的树木又困在一起,然后把他们扛回家,干的树木就用于生火做饭。湿的木头就用来盖一个草屋。我们写代码的程序员,没错,就是你,就好比那个柴夫,而那些干的,湿的树木就好比你自己定义的类,而柴夫用于困树木的绳子就好比你现在正要学习的接口。绳子可以捆绑各种物体,不仅仅限于树木哟,比如说绳子还可以捆人(千万不要污哟,我就是打个比方)等等。
所以说,一个接口有多大能耐不在于它本身,而在于用它的那个人。任何一门语言也是如此,当你说一门语言low的时候,那说明你可能还没有正真的了解他。
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 type Myinstence interface { //里面绑定一个“Instence” 14 Instence() 15 } 16 type Student struct { //定义一个名称为“Student”的结构体,其只存储字符串。 17 Name string 18 } 19 type Techer struct { //定义一个名称为“Techer”的结构体,其只存储数字。 20 ID float64 21 } 22 type STU Student //给“Student”起个别名为“STU”,用户在调用其类型的时候不能调用“Student”,只能调用“STU”, // 您可以理解成这已经是一个二次开发的 23 24 func (s STU) Instence() { //给“STU”类型的结构体定义一个“Instence”方法,注意,我这里故意没有给对象“s”传递的类型 25 // 是“STU”,而非“*STU”类型,所以这个接口对该对象的“Name”属性的修改是不生效的! 26 s.Name += " Golang" 27 } 28 29 30 func (t *Techer) Instence(factor float64) { //发现没有,我这里是故意给对象“t”也绑定一个“Instence”方法(和“s”的方法同名),但是我传的指针类型即“*Techer”,所以这个接口对该对象的“ID”属性的修改是生效的! 31 t.ID *= factor 32 } 33 34 35 func main() { 36 var Instantiation Myinstence //声明一个变量Instantiation,指定其接口为Myinstence,注意:该接口有Instence方法, 37 // 而我们有2个结构体都用到了该方法,那么到底要执行哪个呢?还得看调用者如何去调用接口了,如果调用符合相应的接口就会去执行相应的方法。 38 39 Instantiation = &STU{Name:"yinzhengjie"} 40 Instantiation.Instence() 41 fmt.Println(Instantiation) //修改并未生效,如果给“s”对象传递的数据类型是“*STU”,结果应该是:&{yinzhengjie Golang} 42 43 p := Techer{100} 44 p.Instence(5) 45 fmt.Println(p) //我们会发现之前给“Techer”传入的参数“100”被修改。 46 } 47 48 49 50 #以上代码执行结果如下: 51 &{yinzhengjie} 52 {500}
4.小试牛刀;
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 var PI float64 = 3.1415926 12 type square struct { //定义正方向边长X 13 X float64 14 } 15 type circle struct { //定义圆形的半径 16 r float64 17 } 18 type Area_Perimeter interface { //定义求面积和边长的接口 19 area() float64 20 perimeter() float64 21 } 22 func (s *square) area() float64 { //给正方向绑定求面积方法 23 return s.X*s.X 24 } 25 func (c *circle) area() float64 { //给圆形绑定求面积的方法 26 return PI * c.r* c.r 27 } 28 29 func (s square) perimeter() float64 { 30 square_perimeter := s.X * 4 31 return square_perimeter 32 } 33 func (c circle) perimeter() float64 { 34 circle_perimeter := 2*PI*c.r 35 return circle_perimeter 36 } 37 38 func main() { 39 var s ,c Area_Perimeter 40 s = &square{10} //通过接口给“square”结构体传值。 41 c = &circle{20} 42 fmt.Printf("正方形的面积是:\033[31;1m%v\033[0m,正方形的周长是:\033[31;1m%v\033[0m\n",s.area(),s.perimeter()) 43 fmt.Printf("圆形的面积是:\033[31;1m%v\033[0m,圆形的周长是:\033[31;1m%v\033[0m\n",c.area(),c.perimeter()) 44 } 45 46 47 48 #以上代码执行结果如下: 49 正方形的面积是:100,正方形的周长是:40 50 圆形的面积是:1256.63704,圆形的周长是:125.663704
B.定义统计文件字节大小的接口;
还记得我上次跟大家分享的“io.Copy”吗?没错,就是用来读取的文件的,我们利用它具有读取文件的特性可以获取文件的字节大小。
1 [root@yinzhengjie tmp]# more num.txt 2 1 3 2 4 3 5 4 6 5 7 6 8 尹正杰 9 10 [root@yinzhengjie tmp]# 11 [root@yinzhengjie tmp]# more test.go 12 /* 13 #!/usr/bin/env gorun 14 @author :yinzhengjie 15 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/ 16 EMAIL:y1053419035@qq.com 17 */ 18 19 package main 20 21 import ( 22 "io" 23 "os" 24 "fmt" 25 ) 26 27 type Byte_Counter struct { //定义一个统计字节变量的类。 28 Sum int 29 } 30 31 func (b *Byte_Counter) Write(p []byte)(int, error) { //读取文件字节大小。 32 b.Sum += len(p) 33 return len(p),nil 34 } 35 36 func main() { 37 T := new(Byte_Counter) //此时的T其实是指针,要注意其余make的用法区别,new返回指针,make返回初始化后的(非零)值。 38 // make是引用类型初始化的方法。 39 40 io.Copy(T,os.Stdin) //将格式化输入的东西传给指针T 41 42 fmt.Printf("文件的字节大小为:\033[31;1m%v\033[0m\n",T.Sum) 43 } 44 45 [root@yinzhengjie tmp]# 46 [root@yinzhengjie tmp]# 47 [root@yinzhengjie tmp]# go run test.go < num.txt 48 文件的字节大小为:23 49 [root@yinzhengjie tmp]#
当然,上面的那种写法您如果觉得low的话,可以用下面的方法,两种效果是等效的。
1 [root@yinzhengjie tmp]# more num.txt 2 1 3 2 4 3 5 4 6 5 7 6 8 尹正杰 9 10 You have new mail in /var/spool/mail/root 11 [root@yinzhengjie tmp]# more test.go 12 /* 13 #!/usr/bin/env gorun 14 @author :yinzhengjie 15 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/ 16 EMAIL:y1053419035@qq.com 17 */ 18 19 package main 20 21 import ( 22 "io" 23 "os" 24 "fmt" 25 ) 26 27 type Byte_Counter int 28 29 func (b *Byte_Counter) Write(p []byte)(int, error) { 30 *b += Byte_Counter(len(p)) 31 return len(p),nil 32 } 33 34 func main() { 35 T := new(Byte_Counter) //此时的b其实是指针 36 io.Copy(T,os.Stdin) 37 fmt.Printf("文件的字节大小为:\033[31;1m%v\033[0m\n",*T) 38 } 39 [root@yinzhengjie tmp]# 40 [root@yinzhengjie tmp]# 41 [root@yinzhengjie tmp]# go run test.go < num.txt 42 文件的字节大小为:23 43 [root@yinzhengjie tmp]#
C.统计文本的行数和大小;
通过上面的案例,其实我们也可以给他加一个功能,即统计行数的功能。
1 [root@yinzhengjie tmp]# more line_byte_cont.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 package main 10 11 import ( 12 "io" 13 "os" 14 "fmt" 15 ) 16 17 18 type Byte_Counter struct { 19 Sum int 20 } 21 22 func (b *Byte_Counter) Write(p []byte)(int, error) { //读取文件字节大小。 23 b.Sum += len(p) 24 return len(p),nil 25 } 26 27 28 type Line_Counter struct { 29 Sum int 30 } 31 32 func (L *Line_Counter) Write(p []byte)(int, error) { //用于读取文件行号 33 34 for _,j := range p{ //循环p的内容 35 if j == '\n' { //循环读取每一行,遇到换行符就自加“1”。 36 L.Sum += 1 //由于对象“L”传的的是指针类型(*Line_Counter),换句话说,“L”是指针接受者,最终“L 37 ”的参数会变动。 38 } 39 } 40 return len(p),nil 41 } 42 43 func main() { 44 lines := new(Line_Counter) 45 bytes := new(Byte_Counter) 46 w := io.MultiWriter(lines,bytes) //io模块的“MultiWriter”方法可以接受2个指针类型。将两个Writer(lines,bytes 47 ),合并成单个的Writer。类似于管道,但是他们是有区别的。 48 49 io.Copy(w,os.Stdin) //将用户输入的数据传给w,最终交给lines和lines指针去处理。 50 fmt.Printf("该文本的行号是:\033[31;1m%d\033[0m 行\n",lines.Sum) 51 fmt.Printf("该文本的字节大小是:\033[31;1m%d\033[0m 字节\n",bytes.Sum) 52 } 53 54 [root@yinzhengjie tmp]# 55 [root@yinzhengjie tmp]# 56 [root@yinzhengjie tmp]# more a.txt 57 1 58 2 59 3 60 4 61 5 62 6 63 尹正杰 64 7 65 8 66 9 67 10 68 [root@yinzhengjie tmp]# 69 [root@yinzhengjie tmp]# 70 [root@yinzhengjie tmp]# go run line_byte_cont.go < a.txt 71 该文本的行号是:11 行 72 该文本的字节大小是:31 字节 73 [root@yinzhengjie tmp]# 74 [root@yinzhengjie tmp]#
D.扩展,缓存器的应用;
bytes.buffer是一个缓冲byte类型的缓冲器存放着都是byte 。Buffer 是 bytes 包中的一个 type Buffer struct{…}。
1 [root@yinzhengjie tmp]# more buffer.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 package main 10 11 import ( 12 "io" 13 "fmt" 14 "bytes" 15 ) 16 17 18 type Byte_Counter struct { 19 Sum int 20 } 21 22 func (b *Byte_Counter) Write(p []byte)(int, error) { //读取文件字节大小。 23 b.Sum += len(p) 24 return len(p),nil 25 } 26 27 28 type Line_Center struct { 29 Sum int 30 } 31 32 func (b *Line_Center) Write(p []byte)(int, error) { //读取文件行号. 33 b.Sum = 1 34 for _,j := range p{ 35 if j == '\n' { 36 b.Sum ++ 37 } 38 } 39 return len(p),nil 40 } 41 42 func main() { 43 l := new(Line_Center) 44 b := new(Byte_Counter) 45 buf := new(bytes.Buffer) //bytes.buffer是一个缓冲byte类型的“Buffer”缓冲器。里面存放着都是byte 类型的数据。 46 buf.WriteString(`yinzhengjie`) //往缓冲器中写入字节类型,注意写入是用的符号哟! 47 w := io.MultiWriter(l,b) //可以理解将l和b方法传给w,也就是说w具有这两种方法去处理数据。 48 io.Copy(w,buf) //将缓存的数据传给w,这样w就可以调用它的方法去执行相应的代码啦。 49 fmt.Printf("该文本的行号是:\033[31;1m%d\033[0m行;\n",l.Sum) 50 fmt.Printf("该文本的字节大小是:\033[31;1m%d\033[0m字节.\n",b.Sum) 51 } 52 [root@yinzhengjie tmp]# 53 [root@yinzhengjie tmp]# more a.txt 54 1 55 2 56 3 57 4 58 5 59 6 60 尹正杰 61 7 62 8 63 9 64 10 65 [root@yinzhengjie tmp]# 66 [root@yinzhengjie tmp]# go run buffer.go < a.txt 67 该文本的行号是:1行; 68 该文本的字节大小是:11字节. 69 [root@yinzhengjie tmp]#
八.实现tar包的归档与压缩(面向接口编程)
tar 包实现了文件的打包功能,可以将多个文件或目录存储到单一的 .tar 文件中。下面让我们一起来实现一个解压tar包的功能吧,具体代码是事例如下:
1 [root@yinzhengjie tmp]# tar cf yinzhengjie.tar test/ 2 [root@yinzhengjie tmp]# rm -rf test/ 3 [root@yinzhengjie tmp]# ll 4 total 20 5 -rw-r--r-- 1 root root 1 Jul 13 11:35 tar.go 6 -rw-r--r-- 1 root root 1769 Jul 13 12:12 untar.go 7 -rw-r--r-- 1 root root 10240 Jul 13 12:21 yinzhengjie.tar 8 [root@yinzhengjie tmp]# 9 [root@yinzhengjie tmp]# more untar.go 10 /* 11 #!/usr/bin/env gorun 12 @author :yinzhengjie 13 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/ 14 EMAIL:y1053419035@qq.com 15 */ 16 17 package main 18 19 import ( 20 "archive/tar" 21 "os" 22 "io" 23 "fmt" 24 ) 25 26 func main() { 27 tr := tar.NewReader(os.Stdin) /*从 “*.tar”文件中读出数据是通过“tar.Reader”完成的,所以首先要创建“tar.Reader”。也就是说这个文件是可读类型的。"tar.NewReader"只接受一个io.reader类型。 28 , 29 可以通过“tar.NewReader”方法来创建它,该方法要求提供一个“os.Reader”对象,以便从该对象中读出数据。*/ 30 for { 31 hdr,err := tr.Next() //此时,我们就拥有了一个“tar.Reader”对象 tr,可以用“tr.Next()”来遍历包中的文件 32 。 33 if err != nil { 34 return 35 } 36 fmt.Printf("已解压:\033[31;1m%s\033[0m\n",hdr.Name) 37 //io.Copy(ioutil.Discard,tr) //表示将读取到到内容丢弃,"ioutil.Discard"可以看作是Linux中的:/dev/nul 38 l! 39 info := hdr.FileInfo() // 获取文件信息 40 if info.IsDir() { //判断文件是否为目录 41 os.Mkdir(hdr.Name,0755) //创建目录并赋予权限。 42 continue //创建目录后就要跳过当前循环,继续下一次循环了。 43 } 44 f,_ := os.Create(hdr.Name) //如果不是目录就直接创建该文件 45 io.Copy(f,tr) //最终将读到的内容写入已经创建的文件中去。 46 f.Close() /*不建议写成“defer f.Close()”因为“f.Close()”会将缓存中的数据写入到文件中,同时“f.Close()” 47 还会向“*.tar”文件的最后写入结束信息,如果不关闭“f”而直接退出程序,那么将导致“.tar”文件不完整。而 48 “defer f.Close()”是在函数结束后再执行关闭文件,那么在这个过程中,内存始终会被占用着,浪费这不必要的资 49 源。*/ 50 } 51 } 52 53 [root@yinzhengjie tmp]# 54 [root@yinzhengjie tmp]# go run untar.go < yinzhengjie.tar 55 已解压:test/ 56 已解压:test/yinzhengjie/ 57 已解压:test/yinzhengjie/test4.txt 58 已解压:test/yinzhengjie/test1.txt 59 已解压:test/yinzhengjie/test2.txt 60 已解压:test/yinzhengjie/test5.txt 61 已解压:test/yinzhengjie/test3.txt 62 [root@yinzhengjie tmp]# ll 63 total 24 64 -rw-r--r-- 1 root root 1 Jul 13 11:35 tar.go 65 drwxr-xr-x 3 root root 4096 Jul 13 12:21 test 66 -rw-r--r-- 1 root root 1769 Jul 13 12:12 untar.go 67 -rw-r--r-- 1 root root 10240 Jul 13 12:21 yinzhengjie.tar 68 [root@yinzhengjie tmp]# 69 [root@yinzhengjie tmp]# ls -R test/ 70 test/: 71 yinzhengjie 72 73 test/yinzhengjie: 74 test1.txt test2.txt test3.txt test4.txt test5.txt 75 [root@yinzhengjie tmp]#
tar 本身不具有压缩功能,只能打包文件或目录,那么如果你硬是想要你的代码支持压缩功能其实很简单,只需要添加一行代码,就有如此的功效。同理,如果您想要您的代码支持解密的功能,你也可以先对数据进行解密。然后在解压缩,最宠在交给tar去处理解即可。以上代码优化有如下:
1 [root@yinzhengjie tmp]# ll 2 total 12 3 -rw-r--r-- 1 root root 328 Jul 13 17:12 tar.go 4 drwxr-xr-x 3 root root 4096 Jul 13 22:24 test 5 -rw-r--r-- 1 root root 2071 Jul 13 22:15 untar.go 6 [root@yinzhengjie tmp]# tar -zcf yinzhengjie.tar.gz test/ 7 You have new mail in /var/spool/mail/root 8 [root@yinzhengjie tmp]# 9 [root@yinzhengjie tmp]# mv test/ 111 10 [root@yinzhengjie tmp]# ll 11 total 16 12 drwxr-xr-x 3 root root 4096 Jul 13 22:24 111 13 -rw-r--r-- 1 root root 328 Jul 13 17:12 tar.go 14 -rw-r--r-- 1 root root 2071 Jul 13 22:15 untar.go 15 -rw-r--r-- 1 root root 241 Jul 13 22:31 yinzhengjie.tar.gz 16 [root@yinzhengjie tmp]# 17 [root@yinzhengjie tmp]# more untar.go 18 /* 19 #!/usr/bin/env gorun 20 @author :yinzhengjie 21 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/ 22 EMAIL:y1053419035@qq.com 23 */ 24 25 package main 26 27 import ( 28 "archive/tar" 29 "os" 30 "io" 31 "fmt" 32 "compress/gzip" 33 "log" 34 ) 35 36 func main() { 37 uncompress,err := gzip.NewReader(os.Stdin) //讲传入的文件解压传给“uncompress” 38 if err != nil { 39 log.Fatal(err) //意思是当程序解压失败时,就立即终止程序,“log.Fatal”一般用于程序初始化。 40 } 41 tr := tar.NewReader(uncompress) /*从 “*.tar”文件中读出数据是通过“tar.Reader”完成的,所以首先要创建“tar.Reader” 42 , 43 可以通过“tar.NewReader”方法来创建它,该方法要求提供一个“os.Reader”对象,以便从该对象中读出数据。*/ 44 for { 45 hdr,err := tr.Next() //此时,我们就拥有了一个“tar.Reader”对象 tr,可以用“tr.Next()”来遍历包中的文件. 46 47 if err != nil { 48 return 49 } 50 fmt.Printf("已解压:\033[31;1m%s\033[0m\n",hdr.Name) 51 //io.Copy(ioutil.Discard,tr) //表示将读取到到内容丢弃,"ioutil.Discard"可以看作是Linux中的:/dev/null. 52 info := hdr.FileInfo() // 获取文件信息 53 if info.IsDir() { //判断文件是否为目录 54 os.Mkdir(hdr.Name,0755) //创建目录并赋予权限。 55 continue //创建目录后就要跳过当前循环,继续下一次循环了。 56 } 57 f,_ := os.Create(hdr.Name) //如果不是目录就直接创建该文件 58 io.Copy(f,tr) //最终将读到的内容写入已经创建的文件中去。 59 f.Close() /*不建议写成“defer f.Close()”因为“f.Close()”会将缓存中的数据写入到文件中,同时“f.Close()” 60 还会向“*.tar”文件的最后写入结束信息,如果不关闭“f”而直接退出程序,那么将导致“.tar”文件不完整。而 61 “defer f.Close()”是在函数结束后再执行关闭文件,那么在这个过程中,内存始终会被占用着,浪费这不必要的资 62 源。*/ 63 } 64 } 65 [root@yinzhengjie tmp]# 66 [root@yinzhengjie tmp]# go run untar.go < yinzhengjie.tar.gz 67 已解压:test/ 68 已解压:test/yinzhengjie/ 69 已解压:test/yinzhengjie/test4.txt 70 已解压:test/yinzhengjie/test1.txt 71 已解压:test/yinzhengjie/test2.txt 72 已解压:test/yinzhengjie/test5.txt 73 已解压:test/yinzhengjie/test3.txt 74 [root@yinzhengjie tmp]# ll 75 total 20 76 drwxr-xr-x 3 root root 4096 Jul 13 22:24 111 77 -rw-r--r-- 1 root root 328 Jul 13 17:12 tar.go 78 drwxr-xr-x 3 root root 4096 Jul 13 22:31 test 79 -rw-r--r-- 1 root root 2071 Jul 13 22:15 untar.go 80 -rw-r--r-- 1 root root 241 Jul 13 22:31 yinzhengjie.tar.gz 81 [root@yinzhengjie tmp]# ll -R test/ 82 test/: 83 total 4 84 drwxr-xr-x 2 root root 4096 Jul 13 22:31 yinzhengjie 85 86 test/yinzhengjie: 87 total 20 88 -rw-r--r-- 1 root root 10 Jul 13 22:31 test1.txt 89 -rw-r--r-- 1 root root 10 Jul 13 22:31 test2.txt 90 -rw-r--r-- 1 root root 10 Jul 13 22:31 test3.txt 91 -rw-r--r-- 1 root root 10 Jul 13 22:31 test4.txt 92 -rw-r--r-- 1 root root 10 Jul 13 22:31 test5.txt 93 [root@yinzhengjie tmp]#
我们既然知道了如果将一个"*.tar"文件解包,那么如果将一个目录制作成一个tar包呢?我试着写了一下,但是又个小bug,希望大神帮忙指正。
1 [root@yinzhengjie tmp]# ll 2 total 32 3 -rw-r--r-- 1 root root 17850 Jul 14 09:21 startup.cfg 4 -rw-r--r-- 1 root root 3179 Jul 14 14:03 tar.go 5 drwxr-xr-x 3 root root 4096 Jul 14 13:59 test 6 -rw-r--r-- 1 root root 2080 Jul 14 09:21 untar.go 7 [root@yinzhengjie tmp]# 8 [root@yinzhengjie tmp]# more tar.go 9 /* 10 #!/usr/bin/env gorun 11 @author :yinzhengjie 12 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/ 13 EMAIL:y1053419035@qq.com 14 */ 15 16 package main 17 18 import ( 19 "fmt" 20 "os" 21 "path/filepath" 22 "archive/tar" 23 "io" 24 ) 25 26 var dir_list,file_list []string //创建两个个动态字符串数组,即切片。用来存取文件和目录。 27 28 func walkFunc(path string, info os.FileInfo, err error) error { /*“walkFunc”可以获取3个参数信息,即:文件的绝对路径, 29 通过“os.FileInfo”获取文件信息,用“err”返回错误信息,最后需要返回一个“error”类型的数据。*/ 30 if info.IsDir() { //判断文件类型如果是目录就把他放在目录的动态数组中, 31 dir_list = append(dir_list,path) 32 }else { //如果不是目录那就按照文件处理,将它放在文件的目录中去。 33 file_list = append(file_list,path) 34 } 35 return nil //返回空值。 36 } 37 38 39 func main() { 40 filepath.Walk(os.Args[2], walkFunc) /*将命令行参数的第三个参数传递给“walkFunc”函数。即用“filepath.Walk”遍历“os.Args[2]”目录下的所有的文件名*/ 41 42 f,err := os.Create(os.Args[1]) //创建一个“*.tar”的文件。 43 if err != nil { 44 fmt.Println(err) 45 return 46 } 47 defer f.Close() //有的小伙伴总是忘记关文件,我们可以用defer关键字帮我们忘记关闭文件的坏习惯。 48 49 tw := tar.NewWriter(f) //向“*.tar”文件中写入数据是通过“tar.Writer”完成的,所以首先要创建“tar.Writer”。我们通过“tar.NewWriter”创建他需要提供一个可写的对象,我们上面创建的文件就得到用处。 50 defer tw.Close() 51 52 for _,d_list := range dir_list{ 53 fileinfo,err := os.Stat(d_list) //获取目录的信息 54 if err != nil{ 55 fmt.Println(err) 56 } 57 hdr,err := tar.FileInfoHeader(fileinfo,"")/*“tar.FileInfoHeader”其实是调用“os.FileInfo ”方法获取文件的信息的,你要知道文件有两个属性, 58 一个是文件信息,比如大小啊,编码格式,修改时间等等,还有一个就是文件内容,就是我们所看到的具体内容。 */ 59 if err != nil { 60 fmt.Println(err) 61 } 62 err = tw.WriteHeader(hdr) //由于是目录,里面的内容我们就不用管理,只记录目录的文件信息。 63 if err != nil { 64 fmt.Println(err) 65 } 66 } 67 for _,f_list := range file_list { 68 fileinfo,err := os.Stat(f_list) //同理,我们将文件也做相应的梳理,获取文件的头部信息,将其传给“tar.Writer”处理。 69 if err != nil { 70 fmt.Println(err) 71 } 72 hdr,err := tar.FileInfoHeader(fileinfo,"") 73 if err != nil { 74 fmt.Println(err) 75 } 76 err = tw.WriteHeader(hdr) 77 if err != nil{ 78 fmt.Println(err) 79 } 80 f1,err := os.Open(f_list) //由于是文件,我们就可以看其内容,将头部信息写入后还是不够的,还需要将具体的内容写进去,这样我们得到的才是一个完整的文件。 81 if err != nil{ 82 fmt.Println(err) 83 } 84 io.Copy(tw,f1) //用io.Copy方法将读到的内容传给“tar.Writer”,让其进行写入到他的对象f中去(也就是“tw := tar.NewWriter(f)”中的“f”) 85 } 86 } 87 [root@yinzhengjie tmp]# 88 [root@yinzhengjie tmp]# go run tar.go yinzhengjie.tar test/ 89 [root@yinzhengjie tmp]# ll 90 total 40 91 -rw-r--r-- 1 root root 17850 Jul 14 09:21 startup.cfg 92 -rw-r--r-- 1 root root 3179 Jul 14 14:03 tar.go 93 drwxr-xr-x 3 root root 4096 Jul 14 13:59 test 94 -rw-r--r-- 1 root root 2080 Jul 14 09:21 untar.go 95 -rw-r--r-- 1 root root 7168 Jul 14 14:04 yinzhengjie.tar 96 [root@yinzhengjie tmp]# mkdir test_tar && mv yinzhengjie.tar test_tar && cd test_tar 97 [root@yinzhengjie test_tar]# ll 98 total 8 99 -rw-r--r-- 1 root root 7168 Jul 14 14:04 yinzhengjie.tar 100 [root@yinzhengjie test_tar]# go run /tmp/untar.go < yinzhengjie.tar 101 已解压:test/ 102 已解压:yinzhengjie/ 103 已解压:test1.txt 104 已解压:test2.txt 105 已解压:test3.txt 106 已解压:test4.txt 107 已解压:test5.txt 108 [root@yinzhengjie test_tar]# ll 109 total 36 110 drwxr-xr-x 2 root root 4096 Jul 14 14:06 test 111 -rw-r--r-- 1 root root 10 Jul 14 14:06 test1.txt 112 -rw-r--r-- 1 root root 10 Jul 14 14:06 test2.txt 113 -rw-r--r-- 1 root root 10 Jul 14 14:06 test3.txt 114 -rw-r--r-- 1 root root 10 Jul 14 14:06 test4.txt 115 -rw-r--r-- 1 root root 10 Jul 14 14:06 test5.txt 116 drwxr-xr-x 2 root root 4096 Jul 14 14:06 yinzhengjie 117 -rw-r--r-- 1 root root 7168 Jul 14 14:04 yinzhengjie.tar 118 [root@yinzhengjie test_tar]# cat test1.txt 119 123你好 120 [root@yinzhengjie test_tar]# cat test2.txt 121 111你好 122 [root@yinzhengjie test_tar]#
扩展:
关于Golang结构体的调用姿势还有很多种,这里就给大家举例出来集中调用方式,看你自己习惯用哪一种,总有一种姿势适合你~哈哈~
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 type Student struct { 13 Name string 14 Id int 15 } 16 17 func (s *Student) Update(id int) { 18 s.Id = id 19 } 20 21 func main() { 22 var f func(int) 23 s := Student{Name:"yinzhengjie"} 24 f = s.Update 25 f(200) 26 fmt.Println(s) //静态绑定,只能修改s这个学生 27 28 var f1 func(s *Student,id int) 29 f1 = (*Student).Update 30 f1(&s,300) 31 fmt.Println(s) //动态绑定,我们可以修改s这个学生。 32 33 34 s1 := Student{Name:"尹正杰"} 35 f1(&s1,400) //同时也可以修改 s1这个学生。 36 fmt.Println(s1) //动态绑定,我们可以说是延迟绑定。 37 38 } 39 40 41 42 #以上代码直接结果如下: 43 {yinzhengjie 200} 44 {yinzhengjie 300} 45 {尹正杰 400}
本文来自博客园,作者:尹正杰,转载请注明原文链接:https://www.cnblogs.com/yinzhengjie/p/7138769.html,个人微信: "JasonYin2020"(添加时请备注来源及意图备注,有偿付费)
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。