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

时间:2021-09-12 17:10:24

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

                                                作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

  当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看懂60%的语法结构,因为涉及一些unix的代码可能没有Linux运维基础的同学在学习的时候会很吃力,看起来也会带来一定的难度,如果有时间的话我会给大家解析Docker部门精辟的源码。好了,回归正题吧,我们今天要学习的内容是什么呢?即面向对象编程。当然,不要用屌丝的心态来说:“那要是没对象的还咋编程呢?”,哈哈~正杰要告诉你的是:“此对象非彼对象”,没有“对象”照样编程啊!那么问题来了,到底什么事面向对象编程呢?

一.什么是面向对象编程;

  在Golang的对象可以用一句话总结:“面向对象就是将要处理的数据跟函数进行绑定的方法”。

  如果你从来没有学过Python的话就是可以理解是class,如果在学习Golang之前从来没有接触过其他语言(比如说我)的话,那么你可以这样理解:“它是一种编程风格,就是把一切东西看成一个个对象,比如人,车,面包,等等,然后把这些对象拥有的属性变量,比如年龄,民族,工作地点,变质期,寿命,还有操作这些属性变量的函数打包成一个类来表示,这个类的一个抽象就是一个对象,比如人这个类包含一些属性,比如年龄,名字,住址等,他还有一些对别人告诉这些属性的功能,比如:说,看,走等!!”。这就是的面向对象的特点!!!

二.为什么要有面向对象编程;

  说到面向对象编程,就不得不说一下面向过程编程,我上次跟大家分享过面向过程编程的方法,也就是定义一些函数,减少了代码的重复性,增加了代码的扩展性和易读性等等。而且当大家“啪啪啪”代码敲的起劲的时候突然冒出个面向对象,对它的出现不免会有所疑惑,面向过程已经如此好了,干嘛还要面向对象呢?其实,我们举个例子来说明一下你就知道了。比如让你写一个人的模型,用函数写你要如果实现呢?比如现在要让你写关于:“刘德华,范冰冰,蔡依林”他们三个人的特点,没错,你可以面向过程式编程用函数将他们的特点定义 出来,那么问题来了,如果我想在外部调用“刘德华”或是“范冰冰”的特点该如果还实现呢?用函数可能非常难以实现。因为面向过程式编程虽然不印象实现的功能,但是其复杂度很低,所以,在实现一些功能上可能欠缺点火候。
  这个时候,面向对象就思想就出来啦,面向对象就可能很轻松的实现在外部调用“刘德华”或是“范冰冰”的特点。想要了解更多关于面向对象的发展是可以问我的大师兄“百度”,当然也可以问下我的二师兄“谷歌”,在这里不扯太多历史了,大家多去敲一些代码就会体现面向对象的好处,因为Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。其实接触面向对象编程起初大家都是拒绝的,都有抵触心理。但是时间一长你就知道哪个才是你想要的!就好像你整天和你的男性朋友玩的很愉快,突然有一天一个长得非常哇塞的小姐姐出现在你面前,你在你的男性朋友面向说着:“这女孩也就那样,长得还行吧”,然后备注兄弟私下找机会和这个妹子约会,吃饭,看电影是一个道理。

三.如果定义一个对象;

  当你看到这里的时候,恭喜你成功被*了,如果你现在还有抵触心理学习面向对象编程的话,建议关闭该网页,因为内心的抵触你在学习这篇博客的内容会很吃力,可能看着看着你就不懂了,忘记之前的面向过程编程,让我们重新学习一种新的编程风格吧。当然,我们也可以对比一下两者的不同,这样学习起来也方便记忆。

  下面的案例是:在二维空间中,求两点之间的距离。

1.用函数实现求两点的距离;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"math"
"fmt"
) type Point struct {
X,Y float64
} func Distence(p, q Point) float64 {
return math.Hypot(q.X-p.X,q.Y-p.Y) //"Hypot"是计算两点之间点距离
} func main() {
p := Point{1,2}
q := Point{4,6}
fmt.Println(Distence(p,q)) //函数的调用方式,即传值的方式调用。
} #以上代码输出结果如下:
5

2.用对象实现求两点的距离;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"math"
"fmt"
) type Point struct { //定义一个结构题体,你可以理解是是Python中的class
X,Y float64
} func (p Point)Distence(q Point) float64 { //给p对象定义一个Distence的方法,你可以理解绑定了一个Distence的方法。
return math.Hypot(q.X-p.X,q.Y-p.Y)
} func main() {
p := Point{1,2}
q := Point{4,6}
fmt.Println((p.Distence(q))) //类的调用方式,注意,如果定义就要如何调用!(这里是调用p的Distence方法。)
} #以上代码输出结果如下:
5

  当你看了这两段代码你现在可能会反问,实现的效果都是一样的啊,即他们的运行结果都相同,只是在定义和调用的方式不同而已。并不能明显体现出他们的差异性。也比较不出来他们的差别。我只是想说好戏还在后头,我们慢慢来体会它的奥妙之处,现在,我们就知道如何定义了一个对象了吧。那求多个点的长度又该如果定义呢?

3.小试牛刀;

 计算多个点的连线的总长度,实现代码如下:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"math"
"fmt"
) type Point struct {
X,Y float64
} func (p Point)Distence(q Point) float64 {
return math.Hypot(q.X-p.X,q.Y-p.Y)
} func Distence(path []Point) float64 { //定义一个path变了,path其是包含Point类型的数组切片, Slice可以理解为动态增长的数组.
var s float64
for i := 0;i <len(path) - 1 ; i++ {
s += path[i].Distence(path[i+1])
}
return s
} func main() {
path := []Point{{10,20},{30,40},{50,60}}
fmt.Println(Distence(path))
} #以上代码输出结果如下:
56.568542494923804

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

  玩过linux的朋友可能知道:“alias”这个命令,没错,就是起别名的意思,目的是为了方便用户操作,在Golang里也有可以用type设置别名,这种方法在Go里面随处可见,因为Golang就是一门面向对象编程的语言。

1.Time模块的使用;

  也许,您在看官网的时候,会发现time模块,起本质的实现就是基于type的起别名的方法来实现新的功能,将原本的“float64”起别名为“Duration”类型,最后给“Duration”类型绑定特有的方法,这样time也就有了很多中方法,比如Now,String,Second等方法,下面我们来看看time模块常用的方法。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"time"
"fmt"
) func main() {
var n time.Duration //其实"Duration"就是利用别名来实现的。
n = 3 * time.Hour + 30 * time.Second //表示3小时又30分钟的时间。
fmt.Println(int64(n))
fmt.Println(n.String()) //可读性最高,这是"Duration"特有的方法。
fmt.Println(n.Seconds())
fmt.Println(n.Minutes()) t := time.Now() t1 := t.Add(time.Hour)
t2 := t.Add(-time.Hour) fmt.Println(t1)
fmt.Println(t2)
fmt.Println(t1.Sub(t2)) //计算时间长度
} #以上代码输出结果如下:
10830000000000
3h0m30s
10830
180.5
2017-07-09 10:49:45.8808815 +0800 CST
2017-07-09 08:49:45.8808815 +0800 CST
2h0m0s

2.定义一个新类型;

  由于Golang的默认编码是中文编码,所以我们才存变了的时候可以用中文来当做变量名,在python3.x版本默认的编码也是utf-8,这一点大家很喜欢。但是我要在这里说的是,可以这么干,但是最好不要这么干,因为在涉及到对象的属性的时候你会遇到一些坑,先不要着急,我在后面的博客中也会分享到关于Golang的公有属性和私有属性。大家可以一起看看我用中文定义的变量名称,大家不要这么干,我这里就是为了方便说明如何定义对象,如果给定义的对象起别名,以及如何调用对象等等。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" type 车的属性 struct {
名称 string
描述 string
是否需要备用 bool
} type 车的特性 []车的属性 //“车的特性”类型是包含结构体“车的属性”类型的数组切片,你可以理解是起了一个别名。 func (零件 车的特性) 备件方法()(车的信息 车的特性) { //给“车的特性”绑定一个叫“备件”的方法起名为“零件”。
for _,part := range 零件{
if part.是否需要备用 { //只将有备胎的车追加到“车的信息”这个空切片中。
车的信息 = append(车的信息,part)
}
}
return 车的信息
} type 汽车 struct { //“汽车”由“车身大小”组成
车身大小 string
车的特性 //没有给“车的特性”指定一个名称,我们是要保证实现“内嵌”。这样可以提供自动的委托,不需特殊的声明,
// 例如“汽车.备件方法()”和“汽车.车的特性.备件方法()”是等同的。
} var (
特斯拉 = 车的特性{
{"Tesla_90D(加速时间)", "100km/2.9s", true},
{"车身大小", "109.47万元", false},
{"颜色", "red", false},
} 宝马 = 车的特性{
{"BMW M4敞篷轿跑车(加速时间)", "100km/4.4s", true},
{"价格", "1,098,000美元", true},
{"倍耐力轮胎", "兰博基尼Huracan LP580-2前轮原配", true},
{"夏季冰丝汽车坐垫", "1088.00", true},
} 兰博基尼 = 车的特性{
{"Avetador(加速时间)", "100km/2.8s", true},
{"价格", "648.80-801.15万", true},
{"颜色", "黑色", false},
{"夏季冰丝汽车坐垫", "1088.00", true},
}
) func main() {
roadBike := 汽车{车身大小: "5037×2070×mm", 车的特性: 特斯拉}
mountainBike := 汽车{车身大小: "1678*1870*1398", 车的特性: 宝马}
recumbentBike := 汽车{车身大小: "4780*2030*1136", 车的特性: 兰博基尼}
fmt.Println(roadBike.备件方法())
fmt.Println(mountainBike.备件方法())
fmt.Println(recumbentBike.备件方法())
comboParts := 车的特性{}
comboParts = append(comboParts, mountainBike.车的特性...)
comboParts = append(comboParts, roadBike.车的特性...)
comboParts = append(comboParts, recumbentBike.车的特性...) fmt.Println(len(comboParts), comboParts[9:])
fmt.Println(comboParts.备件方法())
} #以上代码执行结果如下:
[{Tesla_90D(加速时间) 100km/2.9s true}]
[{BMW M4敞篷轿跑车(加速时间) 100km/4.4s true} {价格 1,098,000美元 true} {倍耐力轮胎 兰博基尼Huracan LP580-2前轮原配 true} {夏季冰丝汽车坐垫 1088.00 true}]
[{Avetador(加速时间) 100km/2.8s true} {价格 648.80-801.15万 true} {夏季冰丝汽车坐垫 1088.00 true}]
11 [{颜色 黑色 false} {夏季冰丝汽车坐垫 1088.00 true}]
[{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.小试牛刀;

  还记得我们上次用面向过程编程写的一个学员管理系统吗?其实我们也可以稍微用结构体来装饰一下,实现一下功能。代码如下:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"fmt"
"encoding/json"
"bufio"
"os"
"strings"
"strconv"
"io/ioutil"
) type Student struct { //定义一个名称为“Student”的结构统;
ID int //定义学员编号
NAME string //定义姓名
} type ClassRoom struct { //定义一个名为“ClassRoom”的结构体;
teacher string //定义一个名称为“teacher”字符串类型的变量,
students map[string]*Student //定义一个变量名为“students”其类型为map的变量。
} var classrooms map[string]*ClassRoom //声明一个名为“classrooms”的变量,指定其类型是map,要注意的是map中的value是指针类型的结构统哟。 var student_num = make(map[int]Student) //定义存取学生成员的函数,注意这里是要初始化的,字典在使用前必须初始化,不然会报错! func (s *ClassRoom)Delete() { } func (c *ClassRoom) Add(args []string) error { //自定义添加学生信息的函数
if len(args) != 2 {
fmt.Println("您输入的字符串有问题,案例:add 01 bingan")
return nil
}
id := args[0]
student_name := args[1]
student_id, _ := strconv.Atoi(id) for _, s := range student_num {
if s.ID == student_id {
fmt.Println("您输入的ID已经存在,请重新输入")
return nil
}
}
student_num[len(student_num)+1] = Student{ student_id,student_name}
fmt.Println("Add successfully!!!")
return nil
} func (c *ClassRoom)Drop(args []string)error {
if len(args) != 1 {
fmt.Println("你愁啥?改学生ID压根就不存在!")
return nil
}
id := args[0]
student_id, _ := strconv.Atoi(id)
for i, j := range student_num {
if j.ID == student_id {
delete(student_num, i) //删除map中该id所对应的key值。但是该功能需要完善!
fmt.Println("delete successfully!")
return nil
}
}
fmt.Println("你愁啥?学生ID压根就不存在!")
return nil
} func (c *ClassRoom)Update(args []string)error { //定义修改的函数
if len(args) != 2 {
fmt.Println("您输入的字符串有问题,案例:add 01 bingan")
return nil
}
id := args[0] //取出ID
student_name := args[1] //取出姓名
student_id, _ := strconv.Atoi(id) //将字符串ID变成数字类型。
for i, j := range student_num {
if j.ID == student_id {
student_num[i] = Student{ student_id,student_name} //这其实就是一个赋值的过程。
fmt.Println("update successfully!")
return nil
}
}
fmt.Println("你愁啥?学生ID压根就不存在!")
return nil
} func (c *ClassRoom)List(args []string) error{ //给“ClassRoom”绑定一个“List”方法。
if len(student_num) == 0 {
fmt.Println("数据库为空,请自行添加相关信息!")
return nil
}
for _,value := range student_num{
fmt.Printf("学员的姓名是:\033[31;1m%s\033[0m,学员编号是:\033[31;1m%d\033[0m\n",value.NAME,value.ID)
}
return nil
} func (c *ClassRoom)Save(args []string)error { //定义存取的函数
if len(args) == 0 {
fmt.Println("请输入您想要保存的文件名,例如:save student.txt")
return nil
}
file_name := args[0]
f, err := json.Marshal(student_num) //把变量持久化,也就是将内存的变量存到硬盘的时进行的序列化的过程 if err != nil {
fmt.Println("序列化出错啦!")
}
ioutil.WriteFile(file_name, f, 0644) //将数据写入硬盘,并制定文件的权限。
fmt.Println("写入成功")
return nil
} func (c *ClassRoom)Load(args []string) error { //定义加载的函数。
if len(args) != 1 {
fmt.Println("输入错误,请重新输入.")
return nil
}
file_name := args[0]
s, _ := ioutil.ReadFile(file_name)
json.Unmarshal(s, &student_num)
fmt.Println("读取成功!")
return nil
} func (c *ClassRoom)Exit(args []string) error { //定义对出的脚本
os.Exit(0) //里面的数字表示用户结束程序的返回值,返回0意味着程序是正常结束的。
return nil
} func main() {
classrooms = make(map[string]*ClassRoom) /*初始化字典,因为上面只定义没有初始化。初始化赋值这里不能加":=",
因为作用域不同(会将全局作用域的值给覆盖掉),加了得到的结果返回:"null".*/
fmt.Println("学生管理系统迷你版!")
f := bufio.NewReader(os.Stdin) //用它读取用户输入的内容
for {
fmt.Print("请选择您要去的教室")
fmt.Print("请输入:>>>")
line, _ := f.ReadString('\n') //将读取的内容按照"\n"换行符来切分,注意里面是单引号哟!
line = strings.Trim(line, "\n") //表示只脱去换行符:"\n",你可以自定义脱去字符,等效于line = strings.TrimSpace(line)
content := strings.Fields(line) //按照空格将得来的字符串做成一个切片。 if len(content) == 0 { //脱去空格
continue
}
if len(content) == 1 {
fmt.Println("您输入的字符串有问题,案例:select yinzhengjie!")
continue
}
ClassRoom_Chose := content[0] //定义执行命令的参数,如add,upadte,list,delete....等等
Classroom_len := content[1:]
Classroom := Classroom_len[0] //这个就是讲字符串切片转换成字符串。
if len(Classroom_len) == 1 {
classrooms[Classroom] = &ClassRoom{
students: make(map[string]*Student),
}
} else {
fmt.Println("您输入的参数有问题!")
}
if ClassRoom_Chose == "select" || ClassRoom_Chose == "SELECT" {
fmt.Printf(" 欢迎来到\033[31;1m%s\033[0m教室\n",Classroom)
for {
fmt.Print("请输入:>>>")
line, _ := f.ReadString('\n') //将读取的内容按照"\n"换行符来切分,注意里面是单引号哟!
line = strings.Trim(line, "\n") //表示只脱去换行符:"\n",你可以自定义脱去字符,等效于line = strings.TrimSpace(line)
content := strings.Fields(line) //按照空格将得来的字符串做成一个切片。
if len(content) == 0 { //脱去空格
continue
}
cmd := content[0] //定义执行命令的参数,如add,upadte,list,delete....等等
args := content[1:] //定义要执行的具体内容
actiondict := map[string]func([]string) error{ //定义用户的输入内容
"add": classrooms[Classroom].Add, //表示用户输入的是字符串"add"时,其要执行的结构体的方法是classrooms[Classroom].Add,也就是“Add”方法,以下定义同理。
"list": classrooms[Classroom].List,
"update": classrooms[Classroom].Update,
"delete": classrooms[Classroom].Drop,
"save": classrooms[Classroom].Save,
"load": classrooms[Classroom].Load,
"exit": classrooms[Classroom].Exit,
}
action_func := actiondict[cmd] //定义用户执行的函数
if action_func == nil { //如果输入有问题,告知用户用法
fmt.Println("Usage: {add|list|where|load|upadte|delete|}[int][string]")
continue
}
err := action_func(args)
if err != nil {
fmt.Println("您输入的字符串有问题,案例:add 01 yinzhengjie")
continue
}
continue
}
}
}
}
 [root@yinzhengjie tmp]# go run student_mange.go
学生管理系统迷你版!
请选择您要去的教室请输入:>>>list
您输入的字符串有问题,案例:select yinzhengjie!
请选择您要去的教室请输入:>>>select 中国检科院
欢迎来到中国检科院教室
请输入:>>>list
数据库为空,请自行添加相关信息!
请输入:>>>add 1 yinzhengjie
Add successfully!!!
请输入:>>>add 2 liu
Add successfully!!!
请输入:>>>add 3 wu
Add successfully!!!
请输入:>>>add 4 han
Add successfully!!!
请输入:>>>
请输入:>>>list
学员的姓名是:wu,学员编号是:3
学员的姓名是:han,学员编号是:4
学员的姓名是:yinzhengjie,学员编号是:1
学员的姓名是:liu,学员编号是:2
请输入:>>>update 1 尹正杰
update successfully!
请输入:>>>list
学员的姓名是:liu,学员编号是:2
学员的姓名是:wu,学员编号是:3
学员的姓名是:han,学员编号是:4
学员的姓名是:尹正杰,学员编号是:1
请输入:>>>delete 1
delete successfully!
请输入:>>>list
学员的姓名是:liu,学员编号是:2
学员的姓名是:wu,学员编号是:3
学员的姓名是:han,学员编号是:4
请输入:>>>save test
写入成功
请输入:>>>exit
You have new mail in /var/spool/mail/root
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run student_mange.go
学生管理系统迷你版!
请选择您要去的教室请输入:>>>select yinzhengjie
欢迎来到yinzhengjie教室
请输入:>>>list
数据库为空,请自行添加相关信息!
请输入:>>>load test
读取成功!
请输入:>>>list
学员的姓名是:liu,学员编号是:2
学员的姓名是:wu,学员编号是:3
学员的姓名是:han,学员编号是:4
请输入:>>>EXIT
Usage: {add|list|where|load|upadte|delete|}[int][string]
请输入:>>>exit
[root@yinzhengjie tmp]#

以上代码用法展示

五.指针接受者;

  我们知道如何定义一个对象,以及如何定义对象的方法,那么问题来了,如果我想要修改一个对象的公有属性那该如何处理呢?这个时候我们就得用到指针类型了,学习Golan的时候我们不得不对指针要了解啊,不过Golang语言中的类和其他语言(C++,Java,Python)还真不一样,因为他没有继承的概念,这是很多程序员较好的一点,但是事实证明它的缺点还是不少的,后续会跟大家分享。先看看我们的例子吧。

1.案列一,对象的类型要定义指针类型;

  如果类型传的不是指针类型,也就是将Point类型去掉的话,那么其方法是不能修改该类型的源数据的,而是单独开辟了一块内存。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main /*
1>.我们希望改变对象的成员;
2>.等价于函数的第一个参数是指针;
*/ import (
"fmt"
) type Point struct {
X,Y float64
} func (p *Point) ScaleBy(factor float64) { //想要修改p的值就得传指针类型"*Point"
p.X *= factor // 等价于:X = X * factor,对对象P进行操作,修改其共有属性。
p.Y *= factor
} func main() {
//两种调用方式:
p := Point{100,200}
p.ScaleBy(2) //姿势一:直接调用
fmt.Println(p) p1 := Point{100,200} //姿势二:声明结构体后再用指针指向
p2 :=&p1 //使用结构体调用,再取其内存地址
p2.ScaleBy(2)
fmt.Println(p2)
} #以上代码执行效果如下:
{200 400}
&{200 400}

2.原地修改字典的Value;

  上面的那个案例就是我们想要修改结构体重的公有属性,需要用到指针,还记得我之前跟大家分享如何修改字典的值吗?http://www.cnblogs.com/yinzhengjie/p/7079626.html(搜索:“结构体的指针”)在这里,我继续给大家一个案例,此时我把value的值修改成一个结构体,方法还是一样的。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" type Student struct {
ID int
NAME string
} func main() {
dict := make(map[int]*Student)
dict[1] = &Student{
ID:100,
NAME:"yinzhengjie",
} dict[2] = &Student{
ID:200,
NAME:"尹正杰",
} fmt.Println(dict[1])
s := dict[1]
s.ID = 100000 //原地修改字典的value.
fmt.Println(dict)
fmt.Println(dict[1])
} #以上代码执行结果如下:
&{100 yinzhengjie}
map[1:0xc042044400 2:0xc042044420]
&{100000 yinzhengjie}

3.巧说Golang指针类型(*)地址运算符(&)

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" type Rec_area struct {
height float64
width float64
} func (Area *Rec_area)Size()float64 {
return Area.width * Area.height
} func main() {
fmt.Printf("&是取地址符号, 取到Rec_area类型对象的地址为:\033[31;1m%v\033[0m\n ",&Rec_area{100,200})
var s *Rec_area = &Rec_area{10,20}
fmt.Printf("*可以表示一个变量是指针类型(*Rec_area是一个指针变量):\033[31;1m%v\033[0m\n",s )
fmt.Printf("*也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值:\033[31;1m%v\033[0m\n",*s)
var id *Rec_area = &Rec_area{10,20}
fmt.Printf("查看这个指针变量的地址 , 基本数据类型直接打印地址:\033[31;1m%v\033[0m\n",&id)
} #以上代码输出结果如下:
&是取地址符号, 取到Rec_area类型对象的地址为:&{100 200}
*可以表示一个变量是指针类型(*Rec_area是一个指针变量):&{10 20}
*也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值:{10 20}
查看这个指针变量的地址 , 基本数据类型直接打印地址: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在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数。接下来,我们一起看下例子就知道了。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" var null interface{} // 定义"null"为空接口 func main() {
var i int = 30000
s := "Yinzhengjie"
null = i // "null"可以存储任意类型的数值,可以给他赋值一个int,也可以给他赋值一个string.
fmt.Println(null)
null = s
fmt.Println(null)
} #以上代码执行结果如下:
30000
Yinzhengjie

3.声明接口;

  我们上面学习过了结构体,在其他语言中我们称之为“类”。接口就是程序员根据自己的需求,把自己定义好的类按照相应的需求绑定起来的过程。就好比一根柴夫上山去砍柴,它把干的树木困在用绳子捆绑在一起,把心坎的树木又困在一起,然后把他们扛回家,干的树木就用于生火做饭。湿的木头就用来盖一个草屋。我们写代码的程序员,没错,就是你,就好比那个柴夫,而那些干的,湿的树木就好比你自己定义的类,而柴夫用于困树木的绳子就好比你现在正要学习的接口。绳子可以捆绑各种物体,不仅仅限于树木哟,比如说绳子还可以捆人(千万不要污哟,我就是打个比方)等等。

  所以说,一个接口有多大能耐不在于它本身,而在于用它的那个人。任何一门语言也是如此,当你说一门语言low的时候,那说明你可能还没有正真的了解他。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"fmt"
)
type Myinstence interface { //里面绑定一个“Instence”
Instence()
}
type Student struct { //定义一个名称为“Student”的结构体,其只存储字符串。
Name string
}
type Techer struct { //定义一个名称为“Techer”的结构体,其只存储数字。
ID float64
}
type STU Student //给“Student”起个别名为“STU”,用户在调用其类型的时候不能调用“Student”,只能调用“STU”, // 您可以理解成这已经是一个二次开发的 func (s STU) Instence() { //给“STU”类型的结构体定义一个“Instence”方法,注意,我这里故意没有给对象“s”传递的类型
// 是“STU”,而非“*STU”类型,所以这个接口对该对象的“Name”属性的修改是不生效的!
s.Name += " Golang"
} func (t *Techer) Instence(factor float64) { //发现没有,我这里是故意给对象“t”也绑定一个“Instence”方法(和“s”的方法同名),但是我传的指针类型即“*Techer”,所以这个接口对该对象的“ID”属性的修改是生效的!
t.ID *= factor
} func main() {
var Instantiation Myinstence //声明一个变量Instantiation,指定其接口为Myinstence,注意:该接口有Instence方法,
// 而我们有2个结构体都用到了该方法,那么到底要执行哪个呢?还得看调用者如何去调用接口了,如果调用符合相应的接口就会去执行相应的方法。 Instantiation = &STU{Name:"yinzhengjie"}
Instantiation.Instence()
fmt.Println(Instantiation) //修改并未生效,如果给“s”对象传递的数据类型是“*STU”,结果应该是:&{yinzhengjie Golang} p := Techer{100}
p.Instence(5)
fmt.Println(p) //我们会发现之前给“Techer”传入的参数“100”被修改。
} #以上代码执行结果如下:
&{yinzhengjie}
{500}

4.小试牛刀;

A.定义计算周长和面积的接口;

  我们可以通过一个接口,来计算各个形状的面积或者是周长,比如下面我就举了一个很简单的例子,你可以通过一个接口,只要生命一次变量。指定这个变量的类型为你自定义的接口,只要你调用的方式符合你的定义的类型就会触发相应的代码,我们可以一起看下这个案例:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import "fmt"
var PI float64 = 3.1415926
type square struct { //定义正方向边长X
X float64
}
type circle struct { //定义圆形的半径
r float64
}
type Area_Perimeter interface { //定义求面积和边长的接口
area() float64
perimeter() float64
}
func (s *square) area() float64 { //给正方向绑定求面积方法
return s.X*s.X
}
func (c *circle) area() float64 { //给圆形绑定求面积的方法
return PI * c.r* c.r
} func (s square) perimeter() float64 {
square_perimeter := s.X * 4
return square_perimeter
}
func (c circle) perimeter() float64 {
circle_perimeter := 2*PI*c.r
return circle_perimeter
} func main() {
var s ,c Area_Perimeter
s = &square{10} //通过接口给“square”结构体传值。
c = &circle{20}
fmt.Printf("正方形的面积是:\033[31;1m%v\033[0m,正方形的周长是:\033[31;1m%v\033[0m\n",s.area(),s.perimeter())
fmt.Printf("圆形的面积是:\033[31;1m%v\033[0m,圆形的周长是:\033[31;1m%v\033[0m\n",c.area(),c.perimeter())
} #以上代码执行结果如下:
正方形的面积是:100,正方形的周长是:40
圆形的面积是:1256.63704,圆形的周长是:125.663704

B.定义统计文件字节大小的接口;

  还记得我上次跟大家分享的“io.Copy”吗?没错,就是用来读取的文件的,我们利用它具有读取文件的特性可以获取文件的字节大小。

 [root@yinzhengjie tmp]# more  num.txt
1
2
3
4
5
6
尹正杰 [root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# more test.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"io"
"os"
"fmt"
) type Byte_Counter struct { //定义一个统计字节变量的类。
Sum int
} func (b *Byte_Counter) Write(p []byte)(int, error) { //读取文件字节大小。
b.Sum += len(p)
return len(p),nil
} func main() {
T := new(Byte_Counter) //此时的T其实是指针,要注意其余make的用法区别,new返回指针,make返回初始化后的(非零)值。
// make是引用类型初始化的方法。 io.Copy(T,os.Stdin) //将格式化输入的东西传给指针T fmt.Printf("文件的字节大小为:\033[31;1m%v\033[0m\n",T.Sum)
} [root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run test.go < num.txt
文件的字节大小为:23
[root@yinzhengjie tmp]#

  当然,上面的那种写法您如果觉得low的话,可以用下面的方法,两种效果是等效的。

 [root@yinzhengjie tmp]# more num.txt
1
2
3
4
5
6
尹正杰 You have new mail in /var/spool/mail/root
[root@yinzhengjie tmp]# more test.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"io"
"os"
"fmt"
) type Byte_Counter int func (b *Byte_Counter) Write(p []byte)(int, error) {
*b += Byte_Counter(len(p))
return len(p),nil
} func main() {
T := new(Byte_Counter) //此时的b其实是指针
io.Copy(T,os.Stdin)
fmt.Printf("文件的字节大小为:\033[31;1m%v\033[0m\n",*T)
}
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run test.go < num.txt
文件的字节大小为:23
[root@yinzhengjie tmp]#

C.统计文本的行数和大小;

  通过上面的案例,其实我们也可以给他加一个功能,即统计行数的功能。

 [root@yinzhengjie tmp]# more line_byte_cont.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"io"
"os"
"fmt"
) type Byte_Counter struct {
Sum int
} func (b *Byte_Counter) Write(p []byte)(int, error) { //读取文件字节大小。
b.Sum += len(p)
return len(p),nil
} type Line_Counter struct {
Sum int
} func (L *Line_Counter) Write(p []byte)(int, error) { //用于读取文件行号 for _,j := range p{ //循环p的内容
if j == '\n' { //循环读取每一行,遇到换行符就自加“1”。
L.Sum += 1 //由于对象“L”传的的是指针类型(*Line_Counter),换句话说,“L”是指针接受者,最终“L
”的参数会变动。
}
}
return len(p),nil
} func main() {
lines := new(Line_Counter)
bytes := new(Byte_Counter)
w := io.MultiWriter(lines,bytes) //io模块的“MultiWriter”方法可以接受2个指针类型。将两个Writer(lines,bytes
),合并成单个的Writer。类似于管道,但是他们是有区别的。 io.Copy(w,os.Stdin) //将用户输入的数据传给w,最终交给lines和lines指针去处理。
fmt.Printf("该文本的行号是:\033[31;1m%d\033[0m 行\n",lines.Sum)
fmt.Printf("该文本的字节大小是:\033[31;1m%d\033[0m 字节\n",bytes.Sum)
} [root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# more a.txt
1
2
3
4
5
6
尹正杰
7
8
9
10
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run line_byte_cont.go < a.txt
该文本的行号是:11 行
该文本的字节大小是:31 字节
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#

D.扩展,缓存器的应用;

  bytes.buffer是一个缓冲byte类型的缓冲器存放着都是byte 。Buffer 是 bytes 包中的一个 type Buffer struct{…}。

 [root@yinzhengjie tmp]# more buffer.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"io"
"fmt"
"bytes"
) type Byte_Counter struct {
Sum int
} func (b *Byte_Counter) Write(p []byte)(int, error) { //读取文件字节大小。
b.Sum += len(p)
return len(p),nil
} type Line_Center struct {
Sum int
} func (b *Line_Center) Write(p []byte)(int, error) { //读取文件行号.
b.Sum = 1
for _,j := range p{
if j == '\n' {
b.Sum ++
}
}
return len(p),nil
} func main() {
l := new(Line_Center)
b := new(Byte_Counter)
buf := new(bytes.Buffer) //bytes.buffer是一个缓冲byte类型的“Buffer”缓冲器。里面存放着都是byte 类型的数据。
buf.WriteString(`yinzhengjie`) //往缓冲器中写入字节类型,注意写入是用的符号哟!
w := io.MultiWriter(l,b) //可以理解将l和b方法传给w,也就是说w具有这两种方法去处理数据。
io.Copy(w,buf) //将缓存的数据传给w,这样w就可以调用它的方法去执行相应的代码啦。
fmt.Printf("该文本的行号是:\033[31;1m%d\033[0m行;\n",l.Sum)
fmt.Printf("该文本的字节大小是:\033[31;1m%d\033[0m字节.\n",b.Sum)
}
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# more a.txt
1
2
3
4
5
6
尹正杰
7
8
9
10
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run buffer.go < a.txt
该文本的行号是:1行;
该文本的字节大小是:11字节.
[root@yinzhengjie tmp]#

八.实现tar包的归档与压缩(面向接口编程)

  tar 包实现了文件的打包功能,可以将多个文件或目录存储到单一的 .tar 文件中。下面让我们一起来实现一个解压tar包的功能吧,具体代码是事例如下:

 [root@yinzhengjie tmp]# tar cf yinzhengjie.tar test/
[root@yinzhengjie tmp]# rm -rf test/
[root@yinzhengjie tmp]# ll
total 20
-rw-r--r-- 1 root root 1 Jul 13 11:35 tar.go
-rw-r--r-- 1 root root 1769 Jul 13 12:12 untar.go
-rw-r--r-- 1 root root 10240 Jul 13 12:21 yinzhengjie.tar
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# more untar.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"archive/tar"
"os"
"io"
"fmt"
) func main() {
tr := tar.NewReader(os.Stdin) /*从 “*.tar”文件中读出数据是通过“tar.Reader”完成的,所以首先要创建“tar.Reader”。也就是说这个文件是可读类型的。"tar.NewReader"只接受一个io.reader类型。

可以通过“tar.NewReader”方法来创建它,该方法要求提供一个“os.Reader”对象,以便从该对象中读出数据。*/
for {
hdr,err := tr.Next() //此时,我们就拥有了一个“tar.Reader”对象 tr,可以用“tr.Next()”来遍历包中的文件

if err != nil {
return
}
fmt.Printf("已解压:\033[31;1m%s\033[0m\n",hdr.Name)
//io.Copy(ioutil.Discard,tr) //表示将读取到到内容丢弃,"ioutil.Discard"可以看作是Linux中的:/dev/nul
l!
info := hdr.FileInfo() // 获取文件信息
if info.IsDir() { //判断文件是否为目录
os.Mkdir(hdr.Name,0755) //创建目录并赋予权限。
continue //创建目录后就要跳过当前循环,继续下一次循环了。
}
f,_ := os.Create(hdr.Name) //如果不是目录就直接创建该文件
io.Copy(f,tr) //最终将读到的内容写入已经创建的文件中去。
f.Close() /*不建议写成“defer f.Close()”因为“f.Close()”会将缓存中的数据写入到文件中,同时“f.Close()”
还会向“*.tar”文件的最后写入结束信息,如果不关闭“f”而直接退出程序,那么将导致“.tar”文件不完整。而
“defer f.Close()”是在函数结束后再执行关闭文件,那么在这个过程中,内存始终会被占用着,浪费这不必要的资
源。*/
}
} [root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run untar.go < yinzhengjie.tar
已解压:test/
已解压:test/yinzhengjie/
已解压:test/yinzhengjie/test4.txt
已解压:test/yinzhengjie/test1.txt
已解压:test/yinzhengjie/test2.txt
已解压:test/yinzhengjie/test5.txt
已解压:test/yinzhengjie/test3.txt
[root@yinzhengjie tmp]# ll
total 24
-rw-r--r-- 1 root root 1 Jul 13 11:35 tar.go
drwxr-xr-x 3 root root 4096 Jul 13 12:21 test
-rw-r--r-- 1 root root 1769 Jul 13 12:12 untar.go
-rw-r--r-- 1 root root 10240 Jul 13 12:21 yinzhengjie.tar
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# ls -R test/
test/:
yinzhengjie test/yinzhengjie:
test1.txt test2.txt test3.txt test4.txt test5.txt
[root@yinzhengjie tmp]#

  tar 本身不具有压缩功能,只能打包文件或目录,那么如果你硬是想要你的代码支持压缩功能其实很简单,只需要添加一行代码,就有如此的功效。同理,如果您想要您的代码支持解密的功能,你也可以先对数据进行解密。然后在解压缩,最宠在交给tar去处理解即可。以上代码优化有如下:

 [root@yinzhengjie tmp]# ll
total 12
-rw-r--r-- 1 root root 328 Jul 13 17:12 tar.go
drwxr-xr-x 3 root root 4096 Jul 13 22:24 test
-rw-r--r-- 1 root root 2071 Jul 13 22:15 untar.go
[root@yinzhengjie tmp]# tar -zcf yinzhengjie.tar.gz test/
You have new mail in /var/spool/mail/root
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# mv test/ 111
[root@yinzhengjie tmp]# ll
total 16
drwxr-xr-x 3 root root 4096 Jul 13 22:24 111
-rw-r--r-- 1 root root 328 Jul 13 17:12 tar.go
-rw-r--r-- 1 root root 2071 Jul 13 22:15 untar.go
-rw-r--r-- 1 root root 241 Jul 13 22:31 yinzhengjie.tar.gz
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# more untar.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"archive/tar"
"os"
"io"
"fmt"
"compress/gzip"
"log"
) func main() {
uncompress,err := gzip.NewReader(os.Stdin) //讲传入的文件解压传给“uncompress”
if err != nil {
log.Fatal(err) //意思是当程序解压失败时,就立即终止程序,“log.Fatal”一般用于程序初始化。
}
tr := tar.NewReader(uncompress) /*从 “*.tar”文件中读出数据是通过“tar.Reader”完成的,所以首先要创建“tar.Reader”

可以通过“tar.NewReader”方法来创建它,该方法要求提供一个“os.Reader”对象,以便从该对象中读出数据。*/
for {
hdr,err := tr.Next() //此时,我们就拥有了一个“tar.Reader”对象 tr,可以用“tr.Next()”来遍历包中的文件. if err != nil {
return
}
fmt.Printf("已解压:\033[31;1m%s\033[0m\n",hdr.Name)
//io.Copy(ioutil.Discard,tr) //表示将读取到到内容丢弃,"ioutil.Discard"可以看作是Linux中的:/dev/null.
info := hdr.FileInfo() // 获取文件信息
if info.IsDir() { //判断文件是否为目录
os.Mkdir(hdr.Name,0755) //创建目录并赋予权限。
continue //创建目录后就要跳过当前循环,继续下一次循环了。
}
f,_ := os.Create(hdr.Name) //如果不是目录就直接创建该文件
io.Copy(f,tr) //最终将读到的内容写入已经创建的文件中去。
f.Close() /*不建议写成“defer f.Close()”因为“f.Close()”会将缓存中的数据写入到文件中,同时“f.Close()”
还会向“*.tar”文件的最后写入结束信息,如果不关闭“f”而直接退出程序,那么将导致“.tar”文件不完整。而
“defer f.Close()”是在函数结束后再执行关闭文件,那么在这个过程中,内存始终会被占用着,浪费这不必要的资
源。*/
}
}
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run untar.go < yinzhengjie.tar.gz
已解压:test/
已解压:test/yinzhengjie/
已解压:test/yinzhengjie/test4.txt
已解压:test/yinzhengjie/test1.txt
已解压:test/yinzhengjie/test2.txt
已解压:test/yinzhengjie/test5.txt
已解压:test/yinzhengjie/test3.txt
[root@yinzhengjie tmp]# ll
total 20
drwxr-xr-x 3 root root 4096 Jul 13 22:24 111
-rw-r--r-- 1 root root 328 Jul 13 17:12 tar.go
drwxr-xr-x 3 root root 4096 Jul 13 22:31 test
-rw-r--r-- 1 root root 2071 Jul 13 22:15 untar.go
-rw-r--r-- 1 root root 241 Jul 13 22:31 yinzhengjie.tar.gz
[root@yinzhengjie tmp]# ll -R test/
test/:
total 4
drwxr-xr-x 2 root root 4096 Jul 13 22:31 yinzhengjie test/yinzhengjie:
total 20
-rw-r--r-- 1 root root 10 Jul 13 22:31 test1.txt
-rw-r--r-- 1 root root 10 Jul 13 22:31 test2.txt
-rw-r--r-- 1 root root 10 Jul 13 22:31 test3.txt
-rw-r--r-- 1 root root 10 Jul 13 22:31 test4.txt
-rw-r--r-- 1 root root 10 Jul 13 22:31 test5.txt
[root@yinzhengjie tmp]#

  我们既然知道了如果将一个"*.tar"文件解包,那么如果将一个目录制作成一个tar包呢?我试着写了一下,但是又个小bug,希望大神帮忙指正。

 [root@yinzhengjie tmp]# ll
total 32
-rw-r--r-- 1 root root 17850 Jul 14 09:21 startup.cfg
-rw-r--r-- 1 root root 3179 Jul 14 14:03 tar.go
drwxr-xr-x 3 root root 4096 Jul 14 13:59 test
-rw-r--r-- 1 root root 2080 Jul 14 09:21 untar.go
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# more tar.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import (
"fmt"
"os"
"path/filepath"
"archive/tar"
"io"
) var dir_list,file_list []string //创建两个个动态字符串数组,即切片。用来存取文件和目录。 func walkFunc(path string, info os.FileInfo, err error) error { /*“walkFunc”可以获取3个参数信息,即:文件的绝对路径,
通过“os.FileInfo”获取文件信息,用“err”返回错误信息,最后需要返回一个“error”类型的数据。*/
if info.IsDir() { //判断文件类型如果是目录就把他放在目录的动态数组中,
dir_list = append(dir_list,path)
}else { //如果不是目录那就按照文件处理,将它放在文件的目录中去。
file_list = append(file_list,path)
}
return nil //返回空值。
} func main() {
filepath.Walk(os.Args[2], walkFunc) /*将命令行参数的第三个参数传递给“walkFunc”函数。即用“filepath.Walk”遍历“os.Args[2]”目录下的所有的文件名*/ f,err := os.Create(os.Args[1]) //创建一个“*.tar”的文件。
if err != nil {
fmt.Println(err)
return
}
defer f.Close() //有的小伙伴总是忘记关文件,我们可以用defer关键字帮我们忘记关闭文件的坏习惯。 tw := tar.NewWriter(f) //向“*.tar”文件中写入数据是通过“tar.Writer”完成的,所以首先要创建“tar.Writer”。我们通过“tar.NewWriter”创建他需要提供一个可写的对象,我们上面创建的文件就得到用处。
defer tw.Close() for _,d_list := range dir_list{
fileinfo,err := os.Stat(d_list) //获取目录的信息
if err != nil{
fmt.Println(err)
}
hdr,err := tar.FileInfoHeader(fileinfo,"")/*“tar.FileInfoHeader”其实是调用“os.FileInfo ”方法获取文件的信息的,你要知道文件有两个属性,
一个是文件信息,比如大小啊,编码格式,修改时间等等,还有一个就是文件内容,就是我们所看到的具体内容。 */
if err != nil {
fmt.Println(err)
}
err = tw.WriteHeader(hdr) //由于是目录,里面的内容我们就不用管理,只记录目录的文件信息。
if err != nil {
fmt.Println(err)
}
}
for _,f_list := range file_list {
fileinfo,err := os.Stat(f_list) //同理,我们将文件也做相应的梳理,获取文件的头部信息,将其传给“tar.Writer”处理。
if err != nil {
fmt.Println(err)
}
hdr,err := tar.FileInfoHeader(fileinfo,"")
if err != nil {
fmt.Println(err)
}
err = tw.WriteHeader(hdr)
if err != nil{
fmt.Println(err)
}
f1,err := os.Open(f_list) //由于是文件,我们就可以看其内容,将头部信息写入后还是不够的,还需要将具体的内容写进去,这样我们得到的才是一个完整的文件。
if err != nil{
fmt.Println(err)
}
io.Copy(tw,f1) //用io.Copy方法将读到的内容传给“tar.Writer”,让其进行写入到他的对象f中去(也就是“tw := tar.NewWriter(f)”中的“f”)
}
}
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run tar.go yinzhengjie.tar test/
[root@yinzhengjie tmp]# ll
total 40
-rw-r--r-- 1 root root 17850 Jul 14 09:21 startup.cfg
-rw-r--r-- 1 root root 3179 Jul 14 14:03 tar.go
drwxr-xr-x 3 root root 4096 Jul 14 13:59 test
-rw-r--r-- 1 root root 2080 Jul 14 09:21 untar.go
-rw-r--r-- 1 root root 7168 Jul 14 14:04 yinzhengjie.tar
[root@yinzhengjie tmp]# mkdir test_tar && mv yinzhengjie.tar test_tar && cd test_tar
[root@yinzhengjie test_tar]# ll
total 8
-rw-r--r-- 1 root root 7168 Jul 14 14:04 yinzhengjie.tar
[root@yinzhengjie test_tar]# go run /tmp/untar.go < yinzhengjie.tar
已解压:test/
已解压:yinzhengjie/
已解压:test1.txt
已解压:test2.txt
已解压:test3.txt
已解压:test4.txt
已解压:test5.txt
[root@yinzhengjie test_tar]# ll
total 36
drwxr-xr-x 2 root root 4096 Jul 14 14:06 test
-rw-r--r-- 1 root root 10 Jul 14 14:06 test1.txt
-rw-r--r-- 1 root root 10 Jul 14 14:06 test2.txt
-rw-r--r-- 1 root root 10 Jul 14 14:06 test3.txt
-rw-r--r-- 1 root root 10 Jul 14 14:06 test4.txt
-rw-r--r-- 1 root root 10 Jul 14 14:06 test5.txt
drwxr-xr-x 2 root root 4096 Jul 14 14:06 yinzhengjie
-rw-r--r-- 1 root root 7168 Jul 14 14:04 yinzhengjie.tar
[root@yinzhengjie test_tar]# cat test1.txt
123你好
[root@yinzhengjie test_tar]# cat test2.txt
111你好
[root@yinzhengjie test_tar]#

扩展:

  关于Golang结构体的调用姿势还有很多种,这里就给大家举例出来集中调用方式,看你自己习惯用哪一种,总有一种姿势适合你~哈哈~

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
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/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" type Student struct {
Name string
Id int
} func (s *Student) Update(id int) {
s.Id = id
} func main() {
var f func(int)
s := Student{Name:"yinzhengjie"}
f = s.Update
f(200)
fmt.Println(s) //静态绑定,只能修改s这个学生 var f1 func(s *Student,id int)
f1 = (*Student).Update
f1(&s,300)
fmt.Println(s) //动态绑定,我们可以修改s这个学生。 s1 := Student{Name:"尹正杰"}
f1(&s1,400) //同时也可以修改 s1这个学生。
fmt.Println(s1) //动态绑定,我们可以说是延迟绑定。 } #以上代码直接结果如下:
{yinzhengjie 200}
{yinzhengjie 300}
{尹正杰 400}