Golang 入门到精通
@Author: Solomanxbr | WeChat_ID: Solomanxbr | 微信公众号:

1. 环境与命令

在编译目标程序时,可以设置GOOS和GOARCH环境变量,来配置程序的目标运行平台,实现跨平台编译。

GOOS:设定程序目标运行平台

  • Mac:GOOS=darwin
  • Linux:GOOS=linux
  • Windows:GOOS=windows

GOARCH:设定程序目标运行平台的处理器架构

  • 386:GOARCH=386
  • AMD64:GOARCH=amd64
  • ARM:GOARCH=arm

Go 常用命令

// 查看当前Go环境变量
go env

// 直接运行源代码程序
go run xx.go
go run *.go

// 编译程序,-o 编译结果文件名称
go build -o xx.exe xx.go
go build *.go

// 安装编译好的程序到GOBIN目录下(GOPATH下的bin目录,需要在环境变量中设置GOBIN)
go install

2. 数组与切片

Golang 普通数组为固定长度,而切片就是非定长的数组,其长度和容量会随着元素增多而增大。

package main

import "fmt"

func main() {
    cities := [8]string {"重庆", "成都", "北京", "上海", "广州", "深圳", "杭州", "南京"}

    city1 := cities[2:6]
    fmt.Println("city1: ", city1, "cities: ", cities)
    // 切片引用原数组,可看作原数组部分元素的别名,修改切片会导致修改原数组的元素
    city1[2] = "三亚"
    fmt.Println("city1: ", city1, "cities: ", cities)

    // copy()函数直接复制一份数据,修改新值不会影响原数组
    copyCities := make([]string, len(cities))
    copy(copyCities, cities[:])
    fmt.Println("copySites: ", copyCities, "cities: ", cities)
    copyCities[0] = "山城雾都"
    fmt.Println("copySites: ", copyCities, "cities: ", cities)

    cities[4] = "广州again"
    city2 := cities[:2]
    city3 := cities[6:]
    city4 := cities[2:5]
    fmt.Println("city2: ", city2, "city3: ", city3, "city4: ", city4)

    for index, value := range cities {
        fmt.Println(index+1, ".", value)
    }

    // 第二个参数为切片长度,第三个参数为切片容量
    sites := make([]string, 2, 2)
    fmt.Println("sites: ", sites)
    sites[0] = "www.soloman.vip"
    sites[1] = "www.sundaydesign.top"
    fmt.Println("sites: ", sites, "len: ", len(sites), "cap: ", cap(sites))
    sites = append(sites, "www.golang.org")
    fmt.Println("sites: ", sites, "len: ", len(sites), "cap: ", cap(sites))
    sites = append(sites, "www.python.org")
    sites = append(sites, "www.google.com")
    // 增加元素,会动态改变切片长度和容量大小。len()获取切片长度,cap()获取切片容量
    fmt.Println("sites: ", sites, "len: ", len(sites), "cap: ", cap(sites))

    for _, site := range sites {
        fmt.Println(site)
    }
}

3. 字典

使用make()函数分配空间时,建议指定具体长度,这样性能会更好。集合不存在访问越界,对于不存在的键会返回值对应数据类型的零值(布尔:false、数字:0、字符串:空字符串)

package main

import "fmt"

func main() {
    siteMap := make(map[string]string, 5)
    siteMap["name"] = "Soloman"
    siteMap["domain"] = "soloman.vip"
    siteMap["category"] = "personal site"
    siteMap["developer"] = "Solomanxbr"
    siteMap["technology"] = "Python/Golang/MySQL/Redis/Nginx/Linux"
    printMap(siteMap)

    siteMap["host"] = "www.soloman.vip"
    delete(siteMap, "technology")
    delete(siteMap, "category")
    printMap(siteMap)

    // 通过ok为true/false来判断集合中是否存在该键
    value, ok := siteMap["ip_address"]
    if ok {
        fmt.Println("ip address:", value)
    } else {
        fmt.Println("Doesn't has ip address info:", ok)
    }
}

// 遍历打印集合键值对
func printMap(mapData map[string]string) {
    fmt.Println("------------------")
    for key, value := range mapData {
        fmt.Println(key, ": ", value)
    }
    fmt.Println("------------------")
}

4. 函数

函数名首字母大写,为 public,可在其他包中导入使用;首字母小写的函数为 private, 只能在包内使用。内存逃逸:Golang 编译器制动管理内存,在编译时会判断并将需要的变量从栈移动到堆上。

package main

import "fmt"

func main() {
    score, greeting, flags := funcExample(10, "Soloman", false)
    fmt.Printf("%s! your score is %d. The final exam passed:%t", greeting, score, flags)
}

func funcExample(num int, name string, ok bool) (ret int, msg string, flg bool) {
    ret = num * 10
    msg = "Nice to meet you, " + name
    flg = !ok
    return
}

5. switch case

./switch_case.exe site hello["/Code/switch_case.exe", "site", "hello"]
package main

import (
    "fmt"
    "os"
)

func main() {
    cmds := os.Args
    if len(cmds)<2 {
        fmt.Println("Please input more parameters...", cmds)
    } else {
        for index, cmd := range cmds {
            fmt.Printf("%d %s\n", index, cmd)
        }
        switch cmds[1] {
        case "hi":
            fmt.Println("Hello")
        case "site":
            fmt.Println("www.soloman.vip")
        case "bye":
            fmt.Println("Bye Bye")
        default:
            fmt.Println("I don't know")
        }
    }
}

6. init 和 defer

在导入一个包时,会自动执行该包中的所有 init() 函数。

  • init() 函数没有参数,也没有返回值
  • 一个包可以包含多个 init() 函数,调用顺序不确定
func init() {
    fmt.Println("Do some init operations when this package been imported.")
}

defer 用于定义延迟操作的关键字,确保程序退出当前栈时会执行相关操作。

  • 一般用于定义清理资源的操作,如打开文件后关闭文件、替代锁的作用等
  • 在同一函数中有多个 defer 时,执行顺序类似栈:先进后出
func deferExample() {
    f1, err := os.Open("temp.log")
    // 定义并调用匿名函数
    defer func() {
        fmt.Println("Ready to close file.")
        _ = f1.Close()
    }()

    if err != nil {
        fmt.Println("An error occurred when open file:", err)
    } else {
        buf := make([]byte, 256)
        cnt, _ := f1.Read(buf)
        fmt.Printf("Read content: %d\n%s", cnt, string(buf))
    }

    defer fmt.Println("I am executed before file closed.")
    defer fmt.Println("I am executed on the second.")
    defer fmt.Println("\nI am executed on the first after file content.")
}

/*
Read content: 172
contents in temp.log
I am executed on the first after file content.
I am executed on the second.
I am executed before file closed.
Ready to close file.
*/

7. 类的封装、继承与多态

7.1 封装

Golang 没有 class 关键字,通过结构体来定义类属性并绑定类方法。封装一个 Site 类,以指针和值副本的方式分别绑定 Introduce1() 和 Introduce2() 方法。

package main

import "fmt"

func main() {
    site1 := Site{
        "Soloman",
        "www.soloman.vip",
        "Python/Golang/MySQL/Redis",
        3,
        8888.88,
    }
    site2 := site1

    fmt.Println("使用指针绑定方法")
    fmt.Println("修改之前:", site1)
    site1.Introduce1()
    fmt.Println("修改之后:", site1)

    fmt.Println("使用值副本绑定方法")
    fmt.Println("修改之前:", site2)
    site2.Introduce2()
    fmt.Println("修改之前:", site2)
}

// Site 定义类属性
type Site struct {
    name string
    host string
    technology string
    age int
    price float64
}

// Introduce1 指针形式绑定方法,会修改site1本身
func (this *Site) Introduce1() {
    fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
        this.name, this.host, this.age, this.technology, this.price)
    this.host = "www.sundaydesign,top"
    this.age = 6
    this.price = 9999.66
    fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
        this.name, this.host, this.age, this.technology, this.price)
}

// Introduce2 值副本形式绑定方法,不会修改site2本身
func (this Site) Introduce2() {
    fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
        this.name, this.host, this.age, this.technology, this.price)
    this.host = "www.sundaydesign,top"
    this.age = 8
    this.price = 9999.88
    fmt.Printf("My name is %s, my host is %s, I'm %d years old, built by %s, worth %f dollars.\n",
        this.name, this.host, this.age, this.technology, this.price)
}

7.2 继承

注意区分结构体嵌套与类继承的区别。Golang 中的 public 和 private 都是通过首字母大小写来区分,在 import 导包和类中都是这样。在不同包中,想导入使用其他包中的类属性和类方法,则相关属性和方法首字母必须大写。

package main

import "fmt"

func main() {
    soloman := Student{
        person: Person{
            "Soloman",
            17,
            "男",
            false,
        },
        score: 98,
        class: "高二(1)班",
        school: "清华附中",
    }

    teacherMa := Teacher{
        grade: "教授",
        subject: "量子力学",
        school: "清华大学",
    }
    teacherMa.name = "马老师"
    teacherMa.age = 50
    teacherMa.gender = "男"
    teacherMa.adult = true

    fmt.Println(soloman)
    fmt.Println(teacherMa)
    soloman.Saying()
    teacherMa.Speaking()
}

// Person 定义父类
type Person struct {
    name string
    age int
    gender string
    adult bool
}

// Student 嵌套 Person
type Student struct {
    person Person
    score int
    class string
    school string
}

// Teacher 继承 Person
type Teacher struct {
    Person
    grade string
    subject string
    school string
}

func (stu *Student) Saying() {
    fmt.Printf("My name is %s, I'm a student come from %s, %d years old.\n",
        stu.person.name, stu.school, stu.person.age)
}

func (tch *Teacher) Speaking() {
    fmt.Printf("My name is %s, I'm a %s come from %s, good at %s",
        tch.name, tch.grade, tch.school, tch.subject)
}

7.3 多态

接口可以接受任何数据类型,一般用于根据不同数据类型做不同的程序处理。接口基本使用:

package main

import "fmt"

func main() {
    arr := make([]interface{}, 5)
    arr[0] = "www.soloman.vip"
    arr[1] = 999
    arr[2] = 3.1415926
    arr[3] = false

    for _, value := range arr {
        switch v := value.(type) {
        case int:
            fmt.Printf("Value %d is a number.\n", v)
        case float64:
            fmt.Printf("Value %f is a float,\n", v)
        case string:
            fmt.Printf("Value %s is a string.\n", v)
        case bool:
            fmt.Printf("Value %v is an bool.\n", v)
        default:
            fmt.Println("Not a legal value:", v)
        }
    }
}

接口实现多态:

package main

import "fmt"

func main() {
    //var creature Creature
    soloman := Human{
        name: "Soloman",
        gender: "boy",
        age: 17,
    }
    goldFish := Fish{
        name: "Fairy of Water",
        category: "gold fish",
    }
    eagle := Bird{
        name: "King of the Sky",
        category: "bird",
    }

    //creature = &soloman
    //creature.Move()
    //creature.Speak()
    Introduce(&soloman)
    Introduce(&goldFish)
    Introduce(&eagle)
}

// Creature 接口及其方法
type Creature interface {
    Move()
    Speak()
}

type Human struct {
    name string
    gender string
    age int
}

type Fish struct {
    name string
    category string
}

type Bird struct {
    name string
    category string
}

// 绑定 Human 类到接口
func (man *Human) Move() {
    fmt.Printf("My name is %s, %d years old %s, I can walk run and swimming.\n",
        man.name, man.age, man.gender)
}

func (man *Human) Speak() {
    fmt.Printf("My name is %s, %d years old %s, I can speak Chinese and English.\n",
        man.name, man.age, man.gender)
}

// 绑定 Fish 类到接口
func (fish *Fish) Move() {
    fmt.Printf("My name is %s, I'm a %s, I can swimming fast under water.\n",
        fish.name, fish.category)
}

func (fish *Fish) Speak() {
    fmt.Printf("My name is %s, I'm a %s, I can speak Fishingish: gu gu gu\n",
        fish.name, fish.category)
}

// 绑定 Bird 类到接口
func (bird *Bird) Move() {
    fmt.Printf("My name is %s, I'm a %s, I can fly in the sky.\n",
        bird.name, bird.category)
}

func (bird *Bird) Speak() {
    fmt.Printf("My name is %s, I'm a %s, I can speak Birdish: ji jo ji jo\n",
        bird.name, bird.category)
}

// 通用接口
func Introduce(living Creature) {
    living.Move()
    living.Speak()
}

8. 并发

三种退出方式对比:

  • return 退出当前函数
  • Exit 退出当前进程
  • Goexit 退出当前 routine
package main

import (
    "os"
    "runtime"
)

func main() {
    go func() {
        runtime.Goexit()
    }()
    os.Exit(-1)
    return
}

多 routine 间通信使用管道:

  1. 当缓冲区写满时,写阻塞,当被读取后,恢复写入
  2. 当读取完数据后,读阻塞,当再次有数据写入管道时,恢复读取
  3. 如果管道没有使用 make 分配空间,那默认为 nil,读写都会阻塞,即无法进行读写
  4. 对于一个管道,读写必须次数对等,否则若阻塞在主程序,则程序会崩溃;若阻塞在子 routine,那么会出现内存泄漏
package main

import (
    "fmt"
    "time"
)

func main() {
    intChan := make(chan int, 5)
    go func() {
        for i:=0; i<10; i++ {
            fmt.Println("routine 1 写入数据:", i)
            intChan <- i * 5
        }
    }()

    go func() {
        for i:=10; i<20; i++ {
            fmt.Println("routine 2 写入数据:", i)
            intChan <- i * 5
        }
    }()

    for i:=0; i<20; i++ {
        data := <-intChan
        fmt.Println("main 读取数据:", data)
        time.Sleep(50 * time.Millisecond)
    }
    fmt.Println("main 结束!")
}

为了使对一个管道的读写次数对等,一般使用 for range 遍历管道数据:

  • 使用 close 重复关闭同一个管道程序会崩溃
  • 向已关闭的管道继续写入数据,程序会崩溃
  • 从已关闭的管道读取数据,会返回零值,程序不会崩溃
  • 关闭管道的操作应该在写入数据端,因为读取数据端不知道何时(即写入端还会写入数据吗)关闭管道
package main

import (
    "fmt"
)

func main() {
    intChan := make(chan int, 5)
    go func() {
        for i:=0; i<20; i++ {
            fmt.Println("routine 写入数据:", i)
            intChan <- i
        }
        close(intChan)
    }()

    // for range 遍历未关闭的管道不知道何时停止,会一直等待读取新写入的数据
    // 在写入端 使用 close 关闭管道,for range 遍历完关闭的管道后即退出
    for num := range intChan {
        fmt.Println("main 读取数据:", num)
    }
    fmt.Println("main 结束!")
}
ok-idomnum, ok := <-intChan
package main

import (
    "fmt"
)

func main() {
    intChan := make(chan int, 5)
    go func() {
        for i:=0; i<20; i++ {
            fmt.Println("routine 写入数据:", i)
            intChan <- i
        }
        close(intChan)
    }()

    for {
        num, ok := <-intChan
        if !ok {
            fmt.Println("已取完数据,管道已关闭,main 准备退出...")
            break
        }
        fmt.Println("读取数据:", num)
    }
    fmt.Println("main 退出")
}

单向管道

之前一直定义的都是双向通道,既可以写入,又可以读取。Golang 还支持定义单向通道:

intChan := make(chan int, 5)intChanReadOnly := make(<-chan int, 5)intChanWriteOnly := make(chan<- int, 5)

单向管道一般用于函数参数,这样语义更明确:

package main

import (
    "fmt"
    "time"
)

func main(){
    intChan := make(chan int, 5)
    // 参数为单向只写通道
    go producer(intChan)
    // 参数为单向只读通道
    go consumer(intChan)

    time.Sleep(time.Second)
    fmt.Println("main over!")
}

// 生产者向管道写入数据
func producer(in chan<- int) {
    for i:=0; i<20;i++ {
        fmt.Println("生产者写入:", i)
        in <- i
    }
}

// 消费者从管道取数据
func consumer(out <-chan int) {
    for num := range out {
        fmt.Println("消费者读取:", num)
    }
}

监听通道

select case
package main

import (
    "fmt"
    "time"
)

func main() {
    intChan1 := make(chan int, 5)
    intChan2 := make(chan int, 5)

    // 使用 select case 监视两个 channel
    go func() {
        for {
            fmt.Println("监听通道...")
            select {
            case num1 := <-intChan1:
                fmt.Println("读取到 intChan1 的数据:", num1)
            case num2 := <-intChan2:
                fmt.Println("读取到 intChan2 的数据:", num2)
            default:
                fmt.Println("两个管道都未读取到数据...:")
                time.Sleep(2 * time.Second)
            }
        }
    }()

    // 向 intChan1 写数据
    go func() {
        for i:=0; i<10;i++ {
            fmt.Println("intChan1 写入:", i)
            intChan1 <- i
            time.Sleep(500 * time.Millisecond)
        }
    }()

    // 向 intChan2 写数据
    go func() {
        for i:=10; i<20;i++ {
            fmt.Println("intChan2 写入:", i)
            intChan2 <- i
            time.Sleep(time.Second)
        }
    }()

    for {
        fmt.Println("main over!")
        time.Sleep(5 * time.Second)
    }
}

更多内容