美文网首页转载部分Go
实战系列:(四)一天搞定Go语言

实战系列:(四)一天搞定Go语言

作者: foundwei | 来源:发表于2019-08-14 15:50 被阅读0次

    写在前面

           本文是Go语言的快速入门教程,适合于具有一定C语言或者Java语言基础的开发人员,如果您是一位Go语言的熟练使用者,请绕行!像作者这个年纪的老码农通常都是从Pascal语言或者c语言开始学习计算机技术的,所以学起Go语言生来就有一种似曾相识的感觉,还是蛮轻松的!
           当然,目前笔者也只能算是了解Go语言而已,还不能算熟悉,离精通那就差的更远了!说到这个计算机相关技术的掌握程度,又得发点牢骚,了解,熟悉,精通,这是不同的水平层次的表现。作为一个保守的,略带谦虚气质的老码农,我从来不敢说自己精通某某技术,某某语言,顶多敢在简历里面写上熟练掌握和使用某某语言、技术而已。如若让我看到谁的简历里面敢写精通某某语言、某某技术,碰巧又遇到我来面试,那他肯定死的很惨。我会面的他无地自容、信心丧失、立马寻死!当然大神除外,大神级的人物也轮不到我来面试,呵呵!

    一、Go语言简介

           咱们书归正传,来一起学习一下Go语言。Go的全称是GoLang,“够浪”你就来学Go语言,呵呵!笔者之前没有Go语言的任何经验,也是临危受命,被逼无奈,开始了“够浪”的学习和开发历程。


    地鼠logo

           这个小地鼠不一般,它是Go语言的吉祥物,蛮可爱的!就和Go一样,简洁明了。
           Go是一个开源的编程语言,它能够构造简单、可靠且高效的软件。Go是从2007年末由Google公司的Robert Griesemer, Rob Pike, Ken Thompson主持开发的,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

    Go语言有如下特点:

    • 简洁、快速、安全
    • 并行、有趣、开源
    • 内存管理、数组安全、编译迅速

           下面我们就一起来开启“够浪”的快速学习之旅。

    二、Go语言基础

           计算机软件经历了数十年的发展,形成了多种学术流派,有面向过程编程、面向对象编程、函数式编程、面向消息编程等,这些思想究竟孰优孰劣,众说纷纭,作者不予评价。
           Go语言对这些思想均有所吸收。例如,Go语言接受了函数式编程的一些想法,支持匿名函数与闭包。再如,Go语言接受了以Erlang语言为代表的面向消息编程思想,支持goroutine和通道,并推荐使用消息而不是共享内存来进行并发编程。总体来说,Go语言是一个非常现代化的语言,精小但非常强大。
           Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
    Go 语言的主要特性如下:

    • 自动垃圾回收
    • 更丰富的内置类型
    • 函数多返回值
    • 错误处理
    • 匿名函数和闭包
    • 类型和接口
    • 并发编程
    • 反射
    • 语言交互性

           说了这么多,赶紧让大家来亲身体会一下吧!因为Go语言在语法上很大程度的类似C语言,故此本文不会面面俱到的讲解Go的语法,只会着重指出与C不同的地方以及Go自身有特色的内容。
           第一个Go程序,仍然从hello world开始!

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Hello, World!")
    }
    

           也像Java也像C,有package来做包管理,import来导入类库(包),main是入口函数。

    1. 第一行代码 package main 定义了包名。package main表示一个可独立执行的程序,每个Go应用程序都包含一个名为main的包;
    2. import "fmt" 告诉Go编译器这个程序需要使用fmt包;
    3. func main() 是程序开始执行的函数,即入口函数;
    4. 当标识符(包括常量、变量、类型、函数名、结构字段等)以一个大写字母开头,那么使用这种形式的标识符对象就可以被外部包的代码所使用,这被称为导出(类似OOP中的public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(类似于OOP中的protected )。

           在Go程序中,一行代表一个语句结束。每个语句不需要以分号;结尾,因为这些工作都将由Go编译器自动完成。如果你打算将多个语句写在同一行,它们则必须使用;人为区分,但在实际开发中并不鼓励这种做法。
           下面就从Go的语法方面介绍Go的特点,主要集中在区别于C语言的部分。

    标识符

           Go语言标识符、关键字、保留字、注释与C语言类似,不再赘述。

    变量声明

           Go使用var来声明变量,变量类型放在后面,例如:

    var age int
    

    一次可以声明多个变量,例如:

    var a, b int = 1, 2
    

           声明变量可以省略var,使用:=声明符(初始化声明)。但要注意省略 var, := 左侧如果没有声明新的变量,就产生编译错误。例如:

    var intValue int 
    intValue := 1 //这时候会产生编译错误
    intValue, intValue1 := 1, 2 //此时不会产生编译错误,因为有新的变量声明, := 是一个声明语句
    

    可以将

    var f string = "test" 
    

    简写为

     f := "test"
    

           因为Go可以根据值自行判定变量类型,可以在变量初始化时省略变量的类型而由系统自动推断。

    指针

           与c语言类似,Go也使用指针,&符号返回变量的地址,*符号声明指针变量。

    条件判断语句

           Go的条件判断语句同样有if else和switch,if语句没有什么特别之处,switch语句倒是有很大不同。
           Go 编程语言中 if 语句的语法如下:

    if 布尔表达式 {
    }
    
    if 布尔表达式 {
    } else {
    }
    

           Go 编程语言中 switch 语句的语法如下:

    switch var1 {
        case val1:
            ...
        case val2:
            ...
        default:
            ...
    }
    

           变量var1可以是任何类型,而val1和val2则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。也可以同时测试多个可能符合条件的值,使用逗号分割。不同的case之间不需要使用break分隔。
           switch语句还有一个fallthrough特性。使用fallthrough会强制执行后面的一条case语句,fallthrough不会判断下一条 case的表达式结果是否为true,直接执行。
           Go语言还有一个特殊的控制结构,select语句,与switch语句有些类似。每个case 必须是一个通信操作,要么是发送要么是接收。select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
           Go 编程语言中 select 语句的语法如下:

    select {
        case 通信操作 :
           执行语句;      
        case 通信操作  :
           执行语句; 
        ...
        default : 
           执行语句;
    }
    
    Range

           在讲循环语句之前先说一下Range关键字。Go语言中range关键字用于for循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回key-value对的key值。

    循环语句

           Go 语言没有while和do...while语法,可以通过for 循环来实现其使用效果。Go语言的For循环有3种形式:

    • 和 C 语言的 for 一样:for init; condition; post { }
    • 和 C 语言的 while 一样:for condition { }
    • 和 C 语言的 for(;;) 一样:for { }

           循环控制语句有break, continue, goto。goto语句通常与条件语句配合使用。可用来实现条件转移,构成循环,跳出循环体等功能。但是,在结构化程序设计中一般不主张使用goto语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。
           for循环的range格式可以对slice、map、数组、字符串等进行迭代循环。

    for key, value := range aMap {
        newMap[key] = value
    }
    
    Go的函数

           Go语言函数定义格式如下:

    func function_name( [parameter list] ) [return_types] {
        函数体
    }
    

           Go函数可以有多个返回值,这是Go的一大特点。如下例所示:

    package main
    
    import "fmt"
    
    func swap(x, y string) (string, string) {
       return y, x
    }
    
    func main() {
       a, b := swap("Google", "Runoob")
       fmt.Println(a, b)
    }
    

           Go与C一样,参数传递方式有值传递和引用传递。默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到实际参数。
           Go的函数定义后可作为另外一个函数的实参数传入,Go语言可以很灵活的创建函数,并作为另外一个函数的实参。函数作为参数传递,实现回调,如下例所示:

    package main
    import "fmt"
    
    // 声明一个函数类型
    type cb func(int) int
    
    func main() {
        testCallBack(1, callBack)
        testCallBack(2, func(x int) int {
            fmt.Printf("call back,x:%d\n", x)
            return x
        })
    }
    
    func testCallBack(x int, f cb) {
        f(x)
    }
    
    func callBack(x int) int {
        fmt.Printf("call back,x:%d\n", x)
        return x
    }
    

           Go语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必声明。
           Go语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:

    func (variable_name variable_data_type) function_name() [return_type] {
       /* 方法体 */
    }
    
    变量作用域

           Go 语言中变量可以在三个地方声明:

    • 函数内定义的变量称为局部变量;
    • 在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用;
    • 函数定义中的变量称为形式参数;

           Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。

    数组

           Go语言数组声明需要指定元素类型及元素个数,语法格式如下:

    var variable_name [SIZE] variable_type
    

    初始化数组:

    var balance = [6]float32{100.0, 2.1, 3.4, 7.6, 30.2, 8.9}
    
    切片

           Go语言切片实际上就是动态数组。与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
           你可以声明一个未指定大小的数组来定义切片:

    var sliceA []type
    

    切片不需要说明长度。或使用make()函数来创建切片。也可以指定容量,其中capacity为可选参数。

    make([]T, length, capacity)
    

    切片提供了计算容量的方法cap()可以测量切片最长可以达到多少。一个切片在未初始化之前默认为nil,长度为0。

    空值

           Go语言的空值为nil,在概念上和其它语言的null、None、NULL一样。

    结构体

           Go语言的结构体与C语言类似,结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体有中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

    type struct_variable_type struct {
       member definition;
       member definition;
       ...
       member definition;
    }
    

           如果要访问结构体成员,需要使用点号"."操作符,使用结构体指针访问结构体成员,同样使用 "." 操作符。

    Map

           Key/value的键值对,可以使用内建函数 make ,也可以使用map关键字来定义Map:

    /* 声明变量,默认 map 是 nil */
    var map_variable map[key_data_type]value_data_type
    
    /* 使用 make 函数 */
    map_variable := make(map[key_data_type]value_data_type)
    
    类型转换

           类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go语言类型转换基本格式如下:

    type_name(expression)
    

    type_name为类型,expression为表达式。

    Interface

           Go语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

    type interface_name interface {
       method1 [return_type]
       method2 [return_type]
       method3 [return_type]
       ...
       methodN [return_type]
    }
    
    错误处理

           Go语言通过内置的错误接口提供了非常简单的错误处理机制。error类型是一个接口类型,这是它的定义:

    type error interface {
        Error() string
    }
    

           我们可以在编码中通过实现error接口类型来生成错误信息。函数通常在最后的返回值中返回错误信息,使用errors.New 可返回一个错误信息。
           此外,panic与recover是Go的两个内置函数,这两个内置函数用于处理Go运行时的错误,panic用于主动抛出错误,recover用来捕获panic抛出的错误。还有一个defer语句,其目的类似于Java的finally,在当前函数的末尾执行一些清理代码,而不管此函数如何退出。defer的有趣之处在于它跟代码块没有联系,可以随时出现。

    并发

           Go语言支持并发,我们只需要通过go关键字来开启goroutine即可。goroutine是轻量级线程,goroutine的调度是由Golang运行时进行管理的。goroutine语法格式:

    go 函数名( 参数列表 )
    

           同一个程序中的所有goroutine共享同一个地址空间。goroutine是golang中在语言级别实现的轻量级线程,仅仅利用go就能立刻起一个新线程。

    通道

           通道(channel)是用来传递数据的一个数据结构。通道可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯。操作符<- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

    ch <- v    // 把v发送到通道ch
    v := <-ch  // 从ch接收数据, 并把值赋给v
    

           声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建。

    ch := make(chan int)
    

    三、Web框架

           互联网时代就得学习一下Web框架。Gin是一个go写的web框架,具有高性能的优点。官方地址:https://github.com/gin-gonic/gin
    1、下载并安装
    $ go get -u github.com/gin-gonic/gin

    2、在代码中导入它
    import "github.com/gin-gonic/gin"

    使用gin需要Go的版本号为1.6或更高。

           下面我们就通过一个简单的例子来快速入门Gin。

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
        router := gin.Default()
        router.GET("/ping", func(c *gin.Context) {
            c.JSON(200, gin.H{
                "message": "pong",
            })
        })
        router.Run() // listening on 0.0.0.0:8080
    }
    

    运行这段代码并在浏览器中访问 http://localhost:8080

    HTTP方法

           可以使用 GET, POST, PUT, PATCH, DELETE, OPTIONS等方法。

    参数获取

    获取路径中的参数:

    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
    

    获取Get参数:

    firstname := c.DefaultQuery("firstname", "Guest")
    lastname := c.Query("lastname")
    

    获取Post参数:

    message := c.PostForm("message")
    nick := c.DefaultPostForm("nick", "anonymous")
    
    路由分组

    使用Group方法进行路由分组:

    v1 := router.Group("/v1")
    v2 := router.Group("/v2")
    
    使用中间件

    可以使用在全局上,也可以使用在分组上。

    模型绑定和验证

           若要将请求主体绑定到结构体中,请使用模型绑定,目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定。需要在绑定的字段上设置tag,比如,绑定格式为json,需要这样设置 json:"fieldname" 。当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith或者BindingWith。下面以绑定JSON类型为例:

    // 绑定为json
    type Login struct {
        User     string `json:"user" binding:"required"`
        Password string `json:"password" binding:"required"`
    }
    
    func main() {
        router := gin.Default()
    
        // Example for binding JSON ({"user": "test", "password": "123"})
        router.POST("/login", func(c *gin.Context) {
            var json Login
            if err := c.ShouldBindJSON(&json); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
                return
            }
            
            if json.User != "test" || json.Password != "123" {
                c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
                return
            } 
            
            c.JSON(http.StatusOK, gin.H{"status": "Login successfully!"})
        })
    }
    

           关于Gin就讲这么多,够用了,想了解更加详细的内容,请访问Gin的官方网站。

    四、ORM工具

           有了Web框架,还得有ORM才算配齐。xorm是一个Go语言ORM库. 通过它可以使数据库操作非常简便。xorm的特性如下:

    • 支持Struct和数据库表之间的灵活映射,并支持自动同步表结构
    • 事务支持
    • 支持原始SQL语句和ORM操作的混合执行
    • 使用连写来简化调用
    • 支持使用Id, In, Where, Limit, Join, Having, Table, Sql, Cols等函数和结构体等方式作为条件
    • 支持级联加载Struct
    • 支持LRU缓存(支持memory, memcache, leveldb, redis缓存Store) 和 Redis缓存
    • 支持反转,即根据数据库自动生成xorm的结构体
    • 支持事件
    • 支持created, updated, deleted和version记录版本(即乐观锁)
             下面还是通过一个简单的例子来入门xorm,使用Mysql数据库。
    //匿名导入包:只导入包,而不使用任何包内的结构和类型,也不调用包内的任何函数
    import _ "github.com/go-sql-driver/mysql" 
    
    type Account struct {
        Id      int64
        Name    string `xorm:"unique"`
        Balance float64
        Version int `xorm:"version"`
    }
    
    var x *xorm.Engine
    x, err := xorm.NewEngine("mysql", "root:123456@/admin?charset=utf8")
    
    获取数据

           查询单条数据使用Get方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。
    根据Id来获得单条数据:

    a:=&Account{}
    has, err := x.Id(id).Get(a)
    

    根据where获取单条数据:

    a := new(Account)
    has, err := x.Where("name=?", "admin").Get(a)
    

    返回的结果为两个参数,一个has(bool类型)为该条记录是否存在,第二个参数err为是否有错误。不管err是否为nil,has都有可能为true或者false。

    批量获取数据

    err = x.Desc("balance").Find(&as)
    

    Find方法的第一个参数为slice的指针或Map指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。

    增删改操作

    增加操作:

    _, err := x.Insert(&Account{Name: name, Balance: balance})
    

    删除操作:

    _, err := x.Delete(&Account{Id: id})
    

           方法 Delete 接受参数后,会自动根据传进去的值进行查找,然后删除。比如此处,我们指定了 Account 的 ID 字段,那么就会删除 ID 字段值与我们所赋值相同的记录;如果您只对 Name 字段赋值,那么 xorm 就会去查找 Name 字段值匹配的记录。如果多个字段同时赋值,
    则是多个条件同时满足的记录才会被删除。

    更新操作:

    a := &Account{Id:1}
    has, err := x.Get(a)
    
    a.Balance += 1
    _, err := x.Update(a)
    
    事务
    // 创建 Session 对象
    sess := x.NewSession()
    defer sess.Close()// 开启事务
    if err = sess.Begin(); err != nil {
        return err
    }
    
    if _, err = sess.Update(a1); err != nil {    // 发生错误时进行回滚
        sess.Rollback()    
        return err
    } 
    
    // 完成事务
    return sess.Commit()
    

    五、总结

           实际上笔者也是一名Go的新手,该部分记录一下笔者在使用Go的过程中的一些经验总结以及踩过的坑。

    1. {不能单独放在一行;
    2. Go语言的字符串连接可以通过+实现;
    3. 空标识符“_”是一个占位符,用于在赋值操作的时候将某个值赋值给空标识符,从而达到丢弃该值的目的。空标识符不是一个新的变量,因此将它用于:=操作符的时候,必须同时为至少另一个值赋值;
    4. 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明:=,编译器会提示错误 no new variables on left side of :=,但是=赋值是可以的,因为这是给相同的变量赋予一个新的值;
    5. 如果你在定义变量a之前使用它,则会得到编译错误 undefined: a;
    6. 如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误:xx declared and not used;
    7. 单纯地给变量(如:a)赋值是不够的,这个值必须被使用,但是全局变量是允许声明但不使用的;
    8. Go没有三目运算符,所以不支持 ?: 形式的条件判断;
    9. int转换为String类型时,不能用String()进行类型转换,而应该使用 strconv.Itoa();
    Package strconv implements conversions to and from string representations of basic data types.
    Itoa is equivalent to FormatInt(int64(i), 10)
    

    如果使用string()的话,将返回UTF-8编码值。

    string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. 
    A string may be empty, but not nil. Values of string type are immutable.
    

    六、学习资源

    1. 菜鸟教程 - https://www.runoob.com/go/go-tutorial.html
    2. Gin框架中文文档 - https://www.jianshu.com/p/98965b3ff638/
    3. golang-xorm库快速学习 - https://www.imooc.com/article/46419

                                                                                 2019年8月13日 星期二 于青岛

    相关文章

      网友评论

        本文标题:实战系列:(四)一天搞定Go语言

        本文链接:https://www.haomeiwen.com/subject/vhofjctx.html