zoukankan      html  css  js  c++  java
  • 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("学员的姓名是:33[31;1m%s33[0m,学员编号是:33[31;1m%d33[0m
    ",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('
    ')   //将读取的内容按照"
    "换行符来切分,注意里面是单引号哟!
    155         line = strings.Trim(line, "
    ") //表示只脱去换行符:"
    ",你可以自定义脱去字符,等效于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("               欢迎来到33[31;1m%s33[0m教室
    ",Classroom)
    177             for  {
    178                 fmt.Print("请输入:>>>")
    179                 line, _ := f.ReadString('
    ')   //将读取的内容按照"
    "换行符来切分,注意里面是单引号哟!
    180                 line = strings.Trim(line, "
    ") //表示只脱去换行符:"
    ",你可以自定义脱去字符,等效于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类型对象的地址为:33[31;1m%v33[0m
     ",&Rec_area{100,200})
    23     var   s *Rec_area = &Rec_area{10,20}
    24     fmt.Printf("*可以表示一个变量是指针类型(*Rec_area是一个指针变量):33[31;1m%v33[0m
    ",s )
    25     fmt.Printf("*也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值:33[31;1m%v33[0m
    ",*s)
    26     var  id *Rec_area = &Rec_area{10,20}
    27     fmt.Printf("查看这个指针变量的地址 , 基本数据类型直接打印地址:33[31;1m%v33[0m
    ",&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("正方形的面积是:33[31;1m%v33[0m,正方形的周长是:33[31;1m%v33[0m
    ",s.area(),s.perimeter())
    43     fmt.Printf("圆形的面积是:33[31;1m%v33[0m,圆形的周长是:33[31;1m%v33[0m
    ",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("文件的字节大小为:33[31;1m%v33[0m
    ",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("文件的字节大小为:33[31;1m%v33[0m
    ",*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 == '
    ' {  //循环读取每一行,遇到换行符就自加“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("该文本的行号是:33[31;1m%d33[0m 行
    ",lines.Sum)
    51         fmt.Printf("该文本的字节大小是:33[31;1m%d33[0m 字节
    ",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 该文本的行号是:1172 该文本的字节大小是: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 == '
    ' {
    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("该文本的行号是:33[31;1m%d33[0m行;
    ",l.Sum)
    50         fmt.Printf("该文本的字节大小是:33[31;1m%d33[0m字节.
    ",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("已解压:33[31;1m%s33[0m
    ",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("已解压:33[31;1m%s33[0m
    ",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}
  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    win10右键新建markdown文件
    force down pool_member
    自控力差,你可能忽略了一种更底层的能力
    多线程的通信问题
    多线程的安全问题
    Java实现多线程的两种方式
    为什么你成不了数据分析高手?可能是缺少这个思维
    jstack && jmap
    对ElasticSearch主副分片的理解
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/7138769.html
Copyright © 2011-2022 走看看