2 Go 基础¶
定义变量:
var varname type
var name1, name2, name3 type
var name type = value
var a,b,c = v1, v2, v3
a,b,c := v1, v2 ,v3
_, b = 1, 2
常量:
const name = value
const Pi float32 = 3.14
const i = 100
内置基础类型:
// Boolean
var isActive = bool // default false
var enabled, disabled = true, false
valid := false
// 数值类型, rune(int32), int8, int16, int32, int64, byte(uint8), uint8, uint16, uint32, uint64
// float32, float64(默认)
// complex64
//字符串, go 字符串都是采用UTF8,用 双引号或者反引号括起来定义,类型是 string
var hello string
var emptyString = ""
s := "hello" // 字符串不可变
c := []byte(s) // 把字符串转换为 []byte 类型
c[0] = 'c'
s2 := string(c) // 转为 string
fmt.Printf("%s\n", s2)
m := `hello
world` // ` 括号来的是Raw string
错误类型:
// go有一个error 类型,package.errors
err:=errors.New("emit macho swarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
分组声明:
import (
"fmt"
"os"
)
const (
i = 100
pi = 3.14
)
var(
i int
pi float32
)
iota 枚举:
const (
x = iota //x==0
y = iota //y==1
)
设计规则:
- 大写开头的变量可以导出(共有变量)
- 大写字母开头的函数是共有函数
array, slice, map
// array 数组,数组长度也是类型的一部分,[3]int, [4]int 是不同类型
var arr [n]type
var arr [10]int
arr[0] = 42
arr[1] = 13
fmt.Printf("first %d\n", arr[0])
a := [3]int{1, 2, 3}
b := [10]int{1, 2, 3} //其余元素0
c := [...]int{1,2,3} //自动计算个数
doubleArray := [2][4]int{[4]int{1,2,3,4}, [4]int{5,6,7,8}}
// slice 动态『数组』,引用类型,指向一个底层 array。实际场景中使用 slice 要比 array 多
var fslice []int //声明同数组,只是没有长度
slice := []byte {'a','b'}
a := [10]int{1,2,3,4,5,6,7,8,9,10}
var slice []int
slice = a[:]
/* slice 内置函数
len 获取长度
cap 获取最大容量
append 追加一个或多个元素,返回一个 和 slice 类型一样的slice
copy 从 源 slice src 中复制元素到目标 dst,返回复制的元素个数
*/
// map,字典的概念。 map[keyType]valueType
// map 无序,长度不固定,**引用类型**,len(map)返回 key 数量,map 值可以修改。
// map和其他基本类型不同,不是 thread-safe,多个 go-routine 存取用 mutex lock
var numers map[string]int
numbers := make(map[string]int)
numbers["one"] = 1
numbers["ten"] = 10
fmt.Println("one is ", numbers["three"])
rating := map[string]float32{"c": 5, "Go": 4.5}
goRaking, ok := rating["Go"]
if ok {
fmt.Println("go rating is ", goRaking)
} else {
fmt.Println("no go rating")
}
delete(rating, "C")
make, new 操作:
make 只能用于 内建类型(map/slice/chanell)的内存分配, new 用于各种类型,new 返回指针。 make 返回初始化后的(非零)值。
零值(zero value): 变量未填充之前的默认值,通常是 0
int,int8,int32,int64 0
uint 0x0
rune 0
byte 0x0
float32, float64 0
bool false
string ""
流程控制:
// if(不用括号)
if x > 10 {
//...
} else {
//...
}
if x := computeValue(); x > 10 { //允许声明一个变量,但是只在该条件内作用域
//...
} else {
//...
}
if x == 3 {
//...
} else if x < 3 {
//
} else {
}
// goto,还是少用不用吧
// for
sum := 0
for idx := 0; index < 10; index++ {
sum += idx
}
sum := 1
for ; sum < 1000; {
sum += sum
}
// 实现 while 功能
sum := 1
for sum < 1000 {
sum += sum
}
// break/continue
// for 配合 range 读取 slice 和 map 数据
for k,v := range map {
fmt.Println("key, value", k, v)
}
// switch
i := 6
switch i {
case 4:
//
fallthrough // 强制执行后边的代码
}
函数: func 声明
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//
return value1, value2
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A + B
Multiplied = A * B
return
}
变参:
func myfunc(arg ...int) {
for _, n := range arg {
fmt.Printf("Add the number is :%d\n", n)
}
} // arg 是一个 int 的 slice
传值和传指针:默认传值,无法修改传入的值。除非使用指针
func add(a int) int {
a = a+1
return a
}
// 如果需要修改值,我们可以传递指针
func add_p(a *int) int {
*a = *a + 1
return *a
} // x :=
/*
传递指针使得多个函数能够操作同一个对象
传指针比较轻量级(8bytes),只是传内存地址,可以用指针传递大结构体。
GO中 channel, slice,map 实现机制类似指针,可以直接传递,而不用取地址。(如果需要修改 slice 长度让需要取地址)
*/
defer: 延迟语句,函数执行到最后时,这些 defer 语句会逆序执行,最后函数返回。用来处理错误,关闭资源
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
函数作为值、类型:go 中函数也是一种变量,可以用 type 定义,类型就是所有拥有相同的参数,相同返回值的一种类型
// type typeName func(input1 inputType1, intpu2 inputType2 [, ...]) (result1 resultType1 [, ...])
// 可以把这个类型的函数当做值来传递
package main
import "fmt"
type testInt func(int) bool // 声明一个函数类型
func isOdd(i int) bool {
if i%2 == 0 {
return false
}
return true
}
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main() {
slice := []int{1, 2, 3, 4}
odd := filter(slice, isOdd)
fmt.Println("Odd:", odd)
}
Panic/Recover:
Panic 是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。
当 F 函数调用 panic,F 的执行逻辑被中断,但是 F 中的延迟函数会正常执行,然后 F 返回调用它的地方。
在调用的地方,F 的行为就像调用了 panic,继续向上直到发生 panic 的 goroutine 中所有调用的函数返回,此时程序退出。
panic 可以手动或者运行时错误产生(越界数组)
recover:内建函数,可以让进入恐慌的流程中的 goroutine 回复过来。recover 仅在延迟函数中有效。
正常逻辑调用 recover 返回 nil。当前的 goroutine 先入恐慌可以用 recover 捕获 panic 的输入值,并且回复正常的执行。
main init 函数:
import: 导入包文件
// 点操作,表示你调用的时候可以省略前缀,比如 fmt.Println("hello") 可以写 Println("Hello")
import (
. "fmt"
)
// 别名操作
import (
f "fmt" //别名,简化调用 f.Println
"os"
)
// _ 操作
import (
_ "github.com/ziutek/mymysql/godrv" //_引入该包,而不直接使用包里的函数,而是调用包里的 init 函数
)
Struct 类型:属性或者字段容器
package main
import (
"fmt"
)
type Person struct {
name string
age int
}
func Older(p1, p2 Person) (Person, int) {
if p1.age > p2.age {
return p1, p1.age - p2.age
}
return p2, p2.age - p1.age
}
func main() {
// 初始化方式
var P Person
P.name = "Astaxie"
P.age = 23
p1 := Person{"Tom", 12}
p2 := Person{age: 10, name: "Tom"}
p := new(Person) // p 是个指针
fmt.Printf("name is ", P.name)
}
2.5 面向对象¶
method: 带有接受者的函数。method 附属在一个给定的类型上,语法和函数的声明语法几乎一样,只是在函数后加上一个 receiver
A method is a funciton with am inplicit first argument, called a reciver - Rob Pike
/*
定义: func (r ReceiverType) funcName(parameters) (results)
虽然 method 名字一样,但是接受者不一样,method 就不一样
method 里可以访问接受者的字段
调用 method 通过 . 访问就像 stuct 里访问字段一样
*/
package main
import (
"fmt"
"math"
)
type Rectangle struct {
width, height float64
}
type Circle struct {
radius float64
}
func (r Rectangle) area() float64 {
return r.width * r.height
}
func (c Circle) area() float64 {
return c.radius * c.radius * math.Pi
}
func main() {
r1 := Rectangle{12, 1}
c1 := Circle{10}
fmt.Println("r1 area", r1.area())
fmt.Println("r1 area", c2.area())
}
指针作为 receiver:
func (r *Rectangle) setHeight(height float64) {
r.height = height //NOTE:注意这里不必非要用 *r.hight = hight,Go 发现参数指针以后帮你自动处理
}
method 继承:如果匿名字段实现了一个 method,那么包含这个匿名字段的 struct 也能调用该 method
method 重写:和匿名字段冲突一样的道理,重写就实现了隐藏匿名字段的 method
2.6 接口 interface¶
interface 是一组 method 签名的组合,通过 interface 来定义对象的一组行为,通过 interface 定义对象的一组行为。 interface 类型定义了一组方法,如果某个对象实现了某个接口的**所有**方法,则此对象实现了这个接口(隐式)。
package main
import (
"fmt"
)
type Human struct {
name string
age int
phone string
}
type Student struct {
Human
school string
loan float32
}
type Employee struct {
Human
company string
money float32
}
func (h *Human) SayHi() {
fmt.Printf("Hi %s %s", h.name, h.phone)
}
func (h *Human) Sing(lyrics string) {
fmt.Println("la la", lyrics)
}
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle", beerStein)
}
func (e *Employee) SayHi() {
fmt.Printf("hi I am %s work at %s call me %s\n", e.name, e.company, e.phone)
}
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount
}
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount
}
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
}
type ElderyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
}
interface 可以被任意对象实现,一个对象也可以实现任意多个 interface。 任意类型都是现了空 interface (interface{}),也就是包含了0个 method 的interface。
interface 值:如果定义一个interface的变量,这个变量里可以存实现这个 interface 的任意类型的对象。 比如定义了一个 Men interface 类型的变量 m,m 可以存 Human, Student, Employee。 因为 m 可以持有三种对象,可以定一个包含 Men 类型的 slice,它可以倍赋予实现了 Men 接口的任意结构的对象。 通过这种方式实现了鸭子类型。
空 interface: 可以存储任意类型数值,类似于 c void*。一个函数把 interface{} 作为参数,可以接受任意类型。同理也可以返回任意类型。
var a interface{}
var i int = 5
s := "hello"
a = i
a = s
interface 的函数参数:interface的变量可以持有任意实现该interface 类型的对象。只要实现接口,我们就可以传入
interface变量存储的类型:如何知道变量里实际存储的类型呢?
- comma-ok
- switch 测试
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List []Element
type Person struct {
name string
age int
}
func (p Person) String() string {
return "(name:" + p.name + "-age:" + strconv.Itoa(p.age) + "years)"
}
func main() {
list := make(List, 3)
list[0] = 1
list[1] = "hello"
list[3] = Person{"Deniss", 30}
for index, element := range list {
switch value := element.(type) { // element.(type) 只能在 switch 里边使用,外边还是要用 comma-ok
case int:
fmt.Printf("int %d %d", index, value)
case string:
fmt.Printf("str %d %d", index, value)
case Person:
fmt.Printf("person %d %d", index, value)
default:
fmt.Printf("list different type %d", index)
}
}
}
嵌入 interface:如果一个 interface1 作为另一个interface2 的嵌入字段,那么interface2 隐式包含了interface1 里的 method
反射: 检查程序运行时状态。reflect¶
t := reflect.TypeOf(i)
v := reflect.ValueOf(i)
并发¶
goroutine: 通过 go 的 runtime 管理的一个线程管理器,通过 go 关键字实现。其实就是一个普通的函数
go hello(a,b,c)
通过 go 关键字启动了一个 goroutine
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched() //让 cpu 让出时间片,下次某个时间继续恢复执行该 goroutine
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
多个 goroutine 运行在同一个进程里边,共享内存数据。 不过设计上需要遵循:不要通过共享来通信,通过通信来共享。
channels:goroutine 共享相同的地址空间,访问共享内存需要做好同步。必须用 make 创建
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
ch <- v //发送数据 v 到 channel ch
v:= <-ch //从 ch 中接受数据,并赋值给v
buffered channels: 允许指定 channel 缓冲区大小。ch := make(chan type, value)
当 value =0,channel 是无缓冲区阻塞读写的,当 value>0,channel 是有缓冲、非阻塞的,知道写满 value 个才阻塞写入。
package main
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total
}
func main() {
a := []int{3, 1, 3, 5, 6, 7}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // 从 c 接收
fmt.Println(x, y, x+y)
}
Range 和 Close: 通过 range,像操作slice 或者 map 一样操作缓存类型的 channel
package main
import "fmt"
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c) // 应该在生产者里关闭,防止 panic。当你没有任何数据要发送,或者想显示结束 range 调用 close
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c { //不断读取知道 channel 被显示关闭
fmt.Println(i)
}
}
Select: 多个 channel 操作。Go提供了select监听 channel 数据流动 Select: 多个 channel。select 可以监听 channel 上的数据流动。select 默认阻塞,只有当监听的 channel 中有发送或接收可以 进行时才会运行,当多个 channel 都准备好的时候随机选择一个执行。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
default:
// 当监听的 channel 没有准备好的时候默认执行(select不再阻塞等待channel)
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
有时候会出现 goroutine 阻塞的情况。还可以设置 select 设置超时。
3 Web基础¶
用 go 实现一个 http server 真滴很简单,支持高并发,内部实现是一个单独的 goroutine 处理请求。
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Println(r.Form)
fmt.Println("path", r.URL.Path)
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "hello") //输出到客户端
}
func main() {
http.HandleFunc("/", sayhelloName)
err := http.ListenAndServe(":9090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
4 表单¶
5 访问数据库¶
5.1 database/sql 接口¶
sql.Register
driver.Driver // 返回的数据库conn 只能用在一个 goroutine
driver.Conn
driver.Stmt // Stmt 是一种准备好的状态,只能用在一个goroutine中
driver.Tx // 事务提交和回滚
driver.Execer /// Conn 选择实现的接口
driver.Result // 执行 update/insert 等操作返回的结果接口定义
driver.Rows // 执行查询返回的结果集接口定义
driver.RowsAffected // int64别名
driver.Value //空接口,容纳任何数据
driver.ValueConverter // 把一个普通值转换成 driver.Value
driver.Valuer // 定义了返回一个 driver.Value 的方式
database/sql
5.2 使用 Mysql¶
使用 “github.com/go-sql-driver/mysql” 演示数据库增删改查
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// Open 打开一个注册过的数据库驱动
db, err := sql.Open("mysql", "root:@/test?charset=utf8")
checkErr(err)
// prepare 返回准备要执行的 sql,然后返回准备完毕的执行状态
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
checkErr(err)
//exec 执行 stmt 准备好的 sql 语句
res, err := stmt.Exec("wnn", "研发", "2012-12-10")
checkErr(err)
id, err := res.LastInsertId()
checkErr(err)
fmt.Println(id)
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
checkErr(err)
res, err = stmt.Exec("wnnupdate", id)
checkErr(err)
affect, err := res.RowsAffected()
checkErr(err)
fmt.Println(affect)
//query 直接执行 sql 返回 rows 结果
rows, err := db.Query("SELECT * from userinfo")
checkErr(err)
for rows.Next() {
var uid int
var username, departname, created string
err = rows.Scan(&uid, &username, &departname, &created)
checkErr(err)
fmt.Println(uid)
fmt.Println(username)
fmt.Println(departname)
fmt.Println(created)
}
stmt, err = db.Prepare("delete from userinfo where uid=?")
checkErr(err)
res, err = stmt.Exec(id)
checkErr(err)
affect, err = res.RowsAffected()
checkErr(err)
fmt.Println(affect)
db.Close()
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
5.5 使用 Beego orm¶
package main
import (
"fmt"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
)
// Model Struct
type User struct {
Id int
Name string `orm:"size(100)"`
}
func init() {
orm.RegisterDataBase("default", "mysql", "root:@/test?charset=utf8", 30)
orm.RegisterModel(new(User))
orm.RunSyncdb("default", false, true)
orm.RegisterDriver("mysql", orm.DRMySQL)
}
func main() {
o := orm.NewOrm()
user := User{Name: "wnn"}
id, err := o.Insert(&user)
user2 := User{Name: "wnn2"}
id, err = o.Insert(&user2)
fmt.Printf("id:%d, err:%v\n", id, err)
user.Name = "wnnupdate"
num, err := o.Update(&user)
fmt.Printf("Num:%d, err:%v\n", num, err)
u := User{Id: user.Id}
err = o.Read(&u)
fmt.Printf("err: %v\n", err)
num, err = o.Delete(&u)
fmt.Printf("num %d, err:%v\n", num, err)
}