Go 基础

基础


[TOC]

特性

  • Go 并发编程
    • 采用CSP模型
    • 不需要锁,不需要callback
    • 并发编程 vs 并行计算

安装Go环境

  • 安装编译器 https://studygolang.com/dl
  • 设置环境变量 GO_HOME,PATH
  • idea 安装 go 插件

Go 命令

命令格式
go command [arguments]
编译(生成一个exe文件)
go build HelloWorld.go
获取第三方库
# 1.
go get url
# 2. 下载七牛开发的gopm包管理工具,方便国内环境下载
go get -u github.com/gpmgo/gopm
Go 学习工具
go tool tour
检查数据并发冲突
func main(){
    wg := sync.WaitGroup{}
    for i := 0; i < 10; i++ {  //write i
        wg.Add(1)
        go func() {
            fmt.Println(i)  //read i
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("main over.")
}

# 检查数据并发冲突
go run -race xxx.go
测试
  • 表格测试 (测试数据与算法分离)
  • http 测试
性能
文档
// 启动内置的文档web服务
godoc -http :8888

内置HTTP Server

import (
    "net/http"
    "fmt"
)

func main(){
    //路由映射
    http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
        //获取http 元数据
        agent := request.UserAgent()
        //获取name参数值
        name := request.FormValue("name")
        //输出给客户端
        fmt.Fprintf(writer,"agent:[%s]",agent)
        fmt.Fprintf(writer,"msg:[Hello %s]",name)
    })

    //启动server,并监听8888端口
    http.ListenAndServe("localhost:8888", nil)
}

语法特性

"*""&"

  • "*" 作用
    • 指针变量声明
    • 指针取值
  • "&" 作用
    • 值取指针

  • 一个目录相当于一个包,包名建议与目录名一致
  • main 方法只能在 main 包中

变量

类型显示转换
int64(52)
变量声明赋值
a := 1 //声明并赋值,只能用于函数内部
var a int = 1  //可在函数外部定义变量
var a, b = 1, "b"   
const (
    a=1
    b="b"
)
变量值替换
a := "a"
b := "b"
a,b = b,a
fmt.Println("a=",a,",b=",b)
interface{}

相当与 Java 的 Object

类型断言

针对于 interface{} 类型的变量

/**类型断言**/
val := interface{}(123) //转为interface{},否则无法断言
fmt.Printf("Type=%T,Value=%v\n",val,val)
//v,c := val.(int)  //如果断言类型与val原类型不一致,v为断言类型的默认值,c=false;否则v=val,c=true
v,c := val.(int64)  //如果断言类型与val原类型不一致,v为断言类型的默认值,c=false;否则v=val,c=true
if !c {fmt.Printf("类型断言失败,val 不是 %T 类型\n",v)}
fmt.Printf("Type=%T,Value=%v\n",v,v)

/**类型转换**/
vv := int64(v)
fmt.Printf("Type=%T,Value=%v\n",vv,vv)
创建
  • 内置函数
    • new (适用与任何类型,用于分配内存,返回一个0值指针)
    • make (适用与slice, map, chan, 用于内存分配,初始化,返回一个值)

控制结构

for 替代 while
if-else 分支共享变量
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //创建底层数组和slice
for _, v := range arr {  // _表示忽略index,v为元素值
    //if-else每个分支都能访问到 val 变量
    if val := map[string]string{"even": "偶数", "odd": "奇数"}; v%2 == 0 {
        fmt.Println(v, val["even"])
    } else {
        fmt.Println(v, val["odd"])
    }
}
swith 代替 if-elseif-else

找到第一个匹配的 case 后自动break

level :=1
var scope int
//1
switch level {
    case 1: scope=100
case 2: scope=90
case 3: scope=80
case 4: scope=60
default:
    scope=0
}
//2
switch {
    case scope >= 60:
    fmt.Println("及格")
    case scope >= 80:
    fmt.Println("良好")
    case scope >= 95:
    fmt.Println("优秀")
    default:
    fmt.Println("不及格")
}
//3
var x interface{}
switch i := x.(type) {
    case nil:      
    fmt.Printf(" x 的类型 :%T",i)                
    case int:      
    fmt.Printf("x 是 int 型")                       
    case float64:
    fmt.Printf("x 是 float64 型")           
    case func(int) float64:
    fmt.Printf("x 是 func(int) 型")                      
    case bool, string:
    fmt.Printf("x 是 bool 或 string 型" )       
    default:
    fmt.Printf("未知型")     
} 

函数

defer 栈
  • 类比 java finallly
  • defer func(){err := recover()}() //捕获处理panic抛出的error
func fn(){
    //在fn函数结束前打印出 "hello world"
    defer fmt.Print("world")
    ...
    defer fmt.Print("hello")
    ...
}
函数闭包
  • 所谓闭包是指内层函数引用了外层函数中的变量或称为引用了自由变量的函数,其返回值也是一个函数
  • 闭包可以用来对一个函数进行 功能增强(类似于aop)
func fib() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    fn := fib() //执行fib(),返回匿名函数给fn,a,b变量作用域扩展到main
    // Function calls are evaluated left-to-right.
    for i := 0; i < 10; i++ {
        fmt.Print(fn() , "  ")
    }
}
支持多个函数返回值
func main(){
    i,j := fn() //获取两个返回值
    m,_ := fn() //只获取第一个返回值,忽略第二个
    _,n := fn() //忽略第一个,获取第二个
    fmt.Print(i,j,m,n) 
}
func fn() (int, string) {
    a := 1
    b := "b"
    return a, b
}

面向对象

封装
  • 结构体与方法
func main(){
    p := Point{11.11, 22.22}
    fmt.Println("old point=", p)
    xy_Map := p.ToMap()
    xy_Map["x"] += xy_Map["y"] //x=x+y
    
    //获取p指针赋给pp
    pp := &p
    pp.FromMap(xy_Map)
    fmt.Println("new point=", p)    
}
type Point struct {
    X, Y float64
}

//定义Point方法
func (p Point) ToMap() map[string]float64 {
    return map[string]float64{"x": p.X, "y": p.Y}
}

//定义Point*方法
func (p *Point) FromMap(xy_map map[string]float64) {
    p.X = xy_map["x"]
    p.Y = xy_map["y"]
}
继承(假)
  • 使用匿名组合实现继承
    • 可继承方法
    • 可扩展方法
    • 可继承属性,不过在初始化属性值时还是需要传递
    • 可扩展属性
  • 使用别名1实现继承
    • 不可继承方法,可通过强转为源类型调用方法(应为Go 必须显式强转)
    • 可扩展方法
    • 可继承属性
    • 不可扩展属性(- 与真继承不一样的地方)
  • 别名1 和别名2的区别:
    • 别名1 type AA A 中 AA 是在 A的基础上的新类型
    • 别名2 (v1.9 新特性) type AAA = A
      • AAA 就是 A,真别名,不能用来实现继承
      • 对于内建类型A,AAA不能扩展方法,否则会破坏整个类型;对于 自定义类型 A 时,AAA扩展方法===A扩展方法
  • 由于 Go 是假继承,所以没有 Java 中抽象函数,类多态,所以 Java 中的一些涉及到继承的设计模式在 Go 也是无法实现的;例如:模板模式
  • struct 嵌套 (组合)
func main() {
    lang := &Go{Lang:&Lang{Author:"Ken",Version:"v1.10"},Oop:true,Fp:true}
    fmt.Println(lang)
    //“匿名组合特点,可直接获取组合Lang的属性”
    // 等价于 fmt.Println(lang.Lang.Author)
    fmt.Println(lang.Author)  
    fmt.Printf("%T supports oop: %v",lang,lang.Oop)
    fmt.Printf("%T supports Fp: %v",lang,lang.Fp)
}

type Lang struct {
    Author  string
    Version string
}
func (this *Lang) String() string {
    return fmt.Sprintf("%T is a designed by %s,current version is %s", this, this.Author, this.Version)
}
//"匿名组合"
type Go struct {
    *Lang  //此处没有变量名,此种方式是“匿名组合”
    Oop bool  //是否支持面向对象
    Fp bool //是否支持函数式
}
  • 别名 1
func main() {
    lang := &Go{Author:"Ken",Version:"v1.10"}
    fmt.Println(lang)  //不会去调用func (this *Lang) String() string,说明别名调用不了原类型的方法,只是继承了属性
    fmt.Println((*Lang)(lang)) //如果需要去调用源类型方法,需要类型强转
    fmt.Println(lang.GetVersion()) //访问扩展方法
}

type Lang struct {
    Author  string
    Version string
}
func (this *Lang) String() string {
    return fmt.Sprintf("%T is a designed by %s,current version is %s", this, this.Author, this.Version)
}
//别名
type Go Lang
//扩展方法
func (this *Go) GetVersion() string{
    return this.Version
}
  • 别名2 (v1.9 新特性)
func main() {
    aa := AA{"aa"} //AA("a")==A("a")
    fmt.Println(aa.a())
    a := A{"a"}
    fmt.Println(a.a())
}

type A struct {
    A string
}
// AA 真别名 A
type AA = A
//这里 (this AA) === (this A)
func (this AA) a() string{
    return this.A
}

面向接口

接口与多态
  • 只要结构体的方法签名和接口方法签名一致,就代表实现了该接口,无需显式指定
//声明一个接口
type Runable interface{
    run()
}

接口作为参数实现多态时,这个参数为指针

func main(){
    /** 直接创建struct,返回值 **/
    car := Car{"GTR"}
    machine := Machine{"空调"}
    Run(&car)
    Run(&machine)
    Run(&car)
    Run(&machine)

    /** new(Type) 创建返回指针 **/
    car2 := new(Car)
    car2.Brand="GTR"
    machine2 := new(Machine)
    machine2.Name="空调"
    Run(car2)
    Run(machine2)
    Run(car2)
    Run(machine2)
}
/** 多态,参数runnable必须为指针 **/
func Run(runnable Runnable) {
    runnable.Run()
}
type Machine struct {
    Name string
}
func (m *Machine) Run() {  //Machine implements Runnable
    fmt.Printf("%T %s run.\n", m, m.Name)
    m.Name="洗衣机"
}
type Car struct {
    Brand string
}
func (c *Car) Run() {  //Car implements Runnable
    fmt.Printf("%T %s run.\n", c, c.Brand)
    c.Brand="BMW"
}

枚举

Go中没有枚举,只是使用 constant 常量来代替

iota 的含义是:每当声明一个 const 常量, iota 就会自增1,默认值为 0

type Season int
const (
    SPRING Season = iota //0
    SUMMER //1
    FALL //2
    _  //3
    WINTER //4
)
func main() {
    fmt.Println("Here is main.")
    fmt.Println(SPRING,SUMMER,FALL,WINTER)
}

并发与goroutine

goroutine - n:1 - thread

thread 调度 goroutine

  • 调度上下文数据结构P ,存在一个可被调度运行的 RunnableQueue,当 go func() 的时候,会构建一个gorounine 并进入 RunnbaleQueue, 随后 go 调度线程会从这个队列中取一个放在Thread 中执行
  • gorountine 在运行时对 chan 操作遇到阻塞,当前 goroutine 不再存在与 RunnableQueue 中,同时构建 sudog 数据结构来存储 gorountine 的现场并作为这个gorountine 的代理被 hchanrecvq/sendq 指向;
  • chan数据非空/非满时,会将recvq/sendq 指向的sudog对应的gorountine 重新放入 RunnableQueue 中;

go 关键字

go func() //开启一个goroutine 执行函数

chan 通道

关闭chan

  • 同一个goroutine 重复close(ch) 会panic
  • 写:panic
  • 读:直到chan 中无数据时,第二个返回值为false

超时

  • select {case data:=<-ch: case <- time.After(3*time.Second):return false}
  • 用于 goroutine 间通信的共享数据结构;
  • make函数在创建channel的时候会在该进程的heap区申请一块内存,创建一个hchan结构体,返回执行该内存的指针,所以获取的的ch变量本身就是一个指针,在函数之间传递的时候是同一个channel ;
  • hchan结构体使用一个环形队列来保存groutine之间传递的数据(如果是缓存channel的话),使用两个list保存像该chan发送和从该chan接收数据的goroutine,还有一个mutex来保证操作这些结构的安全 ;
type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz        elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}

详见

(http://www.iigrowing.cn/bing_fa_zhi_tong_threadgo_yu_yan_de_goroutineakka_de_actor.html)

(https://blog.csdn.net/kongdefei5000/article/details/75209005)

  • e.g. chan 接口多态
func main() {
    ch := make(chan A)
    wg := &sync.WaitGroup{}
    wg.Add(2)
    go func() {
        a := <-ch
        a.run()
        a = <-ch
        a.run()
        wg.Done()
    }()

    go func() {
        b := B{}
        c := C{}
        ch <- &b
        ch <- &c
        wg.Done()
    }()
    wg.Wait()
}

type A interface {
    run()
}
type B struct {
}
func (this *B) run() {
    log.Println("B run.")
}

type C struct {
}
func (this *C) run() {
    log.Println("C run.")
}

对Unix pipeline 的仿真

func pipe(app1 func(in io.Reader,out io.Writer),
         app2 func(in io.Reader,out io.Writer)
         ) func(in io.Reader,out io.Writer) {
    return func(in io.Reader,out io.Writer){
        pr,pw := io.Pipe()
        defer pw.Close()
        go func(){
            defer pr.Close()
            app2(pr,out)
        }()
        app1(in,pw)
    }
}

func pipe(apps... func(in io.Reader,out io.Writer))
    func(in io.Reader,out io.Writer){
        app := apps[0]
        len := len(apps)
        for i:=1;i

Note

  • 尽量用指针,指针的默认值为nil,如果是结构体则是一个字段全为默认值的struct,不利于 if 判断
  • Go 没有Java 在使用匿名函数访问外部自由变量的时候,必须为final的安全限制
  • chan 其中一方没相应会阻塞
  • 使用 select 在一些情况下可避免,chan 造成循环等待
  • 单例: Go 没有构造方法,防止外部直接strcut{},struct 应定义为私有(首字母小写),所以 sync.Once.Do{} 写单例时,应写在函数内,不是自身方法内(除非工厂类的方法)

你可能感兴趣的:(Go 基础)