Go语言基本语法 | 青训营

109 阅读5分钟

在Go中,如果一个名称以大写字母开头,则它为已导出的名称

在导入一个包后,只能使用这个包中已导出的名字,任何未导出的名字在这个包外无法访问

函数

func add(x int, y int) int {
    return x + y
}

函数可以返回任意数量的返回值

func swap(x, y string) (string, string) {
    return y, x
}

变量

var用于声明一个变量列表

var a, b, c bool

简洁赋值语句 := 可以在类型明确的地方代替 var 声明

a, b, c := true, false, "giao"

:=不能在函数外使用

Go 数据类型

bool

string

int int8 int16 int32 int64

uint uint8 uint16 uint32 uint64 uintptr

byte(alias of uint8rune(alias of int32float32 float64

complex64 complex128

变量声明可以写成一个语法块

var (
    a bool = true
    b int = 123
    c complex128 = cmplx.Sqrt(1 + 2i)
)

类型转换

a := 42
b := float64(a)
u := uint(b)

类型推导

声明一个变量时,如果不指定其类型,则类型由右值推导而出

var i int
j := i	// j 为 int

常量

常量使用const关键字声明

常量不能使用:=声明

循环

for i := 0; i < 10; i++ {
    ...
}

for 循环 range 形式

for i, v := range pow {
    ...
}

range 每次迭代都会返回两个值,一个是当前元素的下标,另一个是对应元素的副本

if

if x < 0 {
    ...
} else {
    ...
}

// 前置语句
if v := 0; v < 0 {
    ...
}

v 的作用范围只在 if 内

switch

无条件switch

t := time.Now()
	
switch t {
    case t.Hour() < 12:
        println("good morning")
    case t.Hour() = 12:
        println("good noon")
    case t.Hour() < 18:
        println("good afternoon")
    default:
        println("good evening")
}

defer

defer 语句会将函数的执行推迟到外层函数返回之后

推迟的函数调用会被放入栈中,当外层函数返回时,被推迟的函数会按照后进先出的顺序调用

func main() {
    println("counting")
    for i := 0; i < 10; i++ {
        defer println(i)
    }
}

指针

指针保存值的内存地址

    var p *int

对变量使用 & 会生成一个指向该变量得指针

int i := 123
p = &i

通过指针读取或赋值

println(*p)
*p = 456

Go 没有指针运算

结构体

一个结构体就是一组字段

type Vertex struct {
    X int
    Y int
}

v := Vertex{1, 2}
print(v)
print(v.X)
print(v.Y)

结构体指针

vp = &v
print(*vp)
print(vp.X)
print(vp.Y)

数组

var a [10]int
var b []int = {..., ..., ...} 

数组的长度是固定的

切片

切片通过两个下标来确定,即切片的上界和下界

a[1 : 4]	// 其包含a中下标从13的元素

切片不存储任何数据,它类似对应数组的引用,更改切片的元素会导致对应数组的元素也被更改

数组文法

[3]bool{true, true, false}

切片文法

[]bool{true, true, false}

以上会创建一个和相同的数组,然后构建一个引用了它的切片

对于var a [10]来说

a[0:10]
a[:10]
a[0:]
a[:]

以上切片是等价的

切片的长度

len(s)

切片的容量

cap(s)

切片的零值

nil	// 切片的长度和容量为0且没有底层数组

使用make创建切片

a := make([]int, 5)		// 创建一个元素为0值的数组,并返回一个引用了它的切片

a := make([]int, 0, 5)		// 第三个参数指定切片的容量

b = b[:cap(b)]

为切片追加新的元素

append(s, ...)	// 第一个参数是切片,其余为待添加的元素

映射

var m map[string]Vertex

初始化

m = make(map[string]Vertex)

获取元素

m["giao"] = Vertex{1, 2}

删除元素

delete(m, "giao")

通过双赋值检查元素是否存在

e, ok := m["giao"]

"giao"m中,则oktrue

"giao"不在m中,则okfalse

从映射中读取某个不存在的键时,结果为映射的元素类型的零值

方法

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}

接口

隐式实现

type Greeter interface {
    Greet() string
}

type EnglishGreeter struct {}

func (g *EnglishGreeter) Greet() string {
    return "Hello"
}

空接口

var i interface {}

实现 Stringer 内建接口

type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    p := Person{"giao", 123}
    println(p)
}

实现 Error 内建接口

type error interface {
    Error() string
}

type GiaoError struct {
    When time.Time
    What string
}

func (e *GiaoError) Error() string {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}

类型断言

t := i.(T)

如果 i 没有保存 T 类型的值,该语句会触发一个 panic

t, ok := i.(T)

如果 i 没有保存 T 类型的值,t 将为 T 类型的零值,ok 为 false

类型选择

func do(i interface{}) {
    switch v := i.(type) {
        case int:
            fmt.Printf("Twice %v is %v\n", v, v*2)
        case string:
            fmt.Printf("%q is %v bytes long\n", v, len(v))
        default:
            fmt.Printf("I don't know about type %T!\n", v)
    }
}

func main() {
    do(21)
    do("hello")
    do(true)
}

Goroutine

go f(x)

信道

队列式数据结构

ch := make(chan int)
ch <- v		// 将 v 发送至 ch 信道


func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, j)
        time.Sleep(time.Second)
        fmt.Printf("Worker %d finished job %d\n", id, j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // 启动 3 个 worker
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // 发送 9 个 jobs 到 jobs 通道中
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)		// 通道关闭

    // 读取所有结果
    for a := 1; a <= 9; a++ {
        <-results
    }
}

信道容量为0时,该信道为无缓冲信道,在发送数据时,必须要求被立即接收

信道容量为1时,该信道只能存一个数据,若再往里发送数据,会造成阻塞,可以用来做锁

信道容量大于1时,该信道可以存放多个数据,可以作为多个协程之间的通信管道共享资源

select

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
            case c <- x:
                x, y = y, x+y
            case <-quit:
                fmt.Println("quit")
                return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {		// 匿名 Goroutine
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

当select中的其他分支都没有准备好时,default分支会执行

为了在发送或接收时不产生阻塞(非阻塞通道操作),可以使用default分支

可用于超时处理

通道同步

func main() {
    done := make(chan bool, 1)
    go worker(done)

    <- done		// 程序将在这里阻塞
}

通道遍历

func main() {
    queue := make(chan string, 2)
    queue <- "one"
    queue <- "two"

    close(queue)	// 关闭通道

    // 如果没有关闭通道,循环将会一直阻塞执行,等待第三个值
    for e := range queue {
        fmt.Println(e)
    }
}

定时器

func main() {
    timer := time.NewTimer(time.Second * 2)
    fmt.Println("start")
    <- timer.C		// 阻塞
    fmt.Println("end")	
}