GO语言的进阶之路-面向对象编程

GO语言的进阶之路-面向对象编程

                                                    https://www.cnblogs.com/yinzhengjie/p/7138769.html

                                                作者:尹正杰

 

 

  当你看完这篇文章之时,我可以说你的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:[email protected]
 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:[email protected]
 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:[email protected]
 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 

复制代码

 

四.给对象定义一个别名;

  玩过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:[email protected]
 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:[email protected]
 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:[email protected]
  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 }

复制代码

 以上代码用法展示 

 

五.指针接受者;

  我们知道如何定义一个对象,以及如何定义对象的方法,那么问题来了,如果我想要修改一个对象的公有属性那该如何处理呢?这个时候我们就得用到指针类型了,学习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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
 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:[email protected]
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:[email protected]
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:[email protected]
 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:[email protected]
 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:[email protected]
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:[email protected]
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:[email protected]
 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:[email protected]
 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}

复制代码

 

当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。 欢迎加入高级自动化运维之路:598432640

你可能感兴趣的:(go)