Go一小时入门

Table of Contents

语法

hello world

第一个Go程序,仍然从hello world开始!

package main

import "fmt"

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

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

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

标识符

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</p>

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等方法。

参数获取

获取路径中的参数: #+END_SRC router.GET("user:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) }) #+END_SRC

获取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语言的字符串连接可以通过+实现;
  • 空标识符“_”是一个占位符,用于在赋值操作的时候将某个值赋值给空标识符,从而达到丢弃该值的目的。空标识符不是一个新的变量,因此将它用于:=操作符的时候,必须同时为至少另一个值赋值;
  • 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明:=,编译器会提示错误 no new variables on left side of :=,但是=赋值是可以的,因为这是给相同的变量赋予一个新的值;
  • 如果你在定义变量a之前使用它,则会得到编译错误 undefined: a;
  • 如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误:xx declared and not used;
  • 单纯地给变量(如:a)赋值是不够的,这个值必须被使用,但是全局变量是允许声明但不使用的;
  • Go没有三目运算符,所以不支持 ?: 形式的条件判断;
  • 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.

资源

Date: 2019-10-28 Mon 09:27