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.