网站首页 文章专栏 Go 语言基本语法
Go 语言基本语法
创建于:2021-07-04 08:46:04 更新于:2024-11-23 10:13:15 羽瀚尘 546
Go Go

[TOC]

go基本语法

变量

使用 var 定义变量, 变量类型

// 一般形式
// var 变量名 变量类型
// 注意不需要分号

// 正确的使用方式1
var v1 int = 10
// 正确的使用方式2,编译器可以自动推导出v2的类型
var v2 = 10 
// 正确的使用方式3,编译器可以自动推导出v3的类型
v3 := 10

布尔

bool

整型

int8 byte int16 int uint uintptr

var v1 int

浮点类型

float32 float64

复数类型

complex64 complex128

字符串

string

var v2 string

字符类型

rune

指针

pointer

var v6 *int

数组

array

var v3 [10]int 

切片

slice

// 数组切片var 
// 切片并不存储任何数据,它只是描述了底层数组中的一段。
// 切片支持默认,与 python 相同
// 切片是左闭右开
var v4 []int = v3[1:3]
append 用于给切片添加元素
var s []int
s = append(s, 0)

计算长度

// 计算现有长度
len(x)

// capacity
cap(x)

字典

map

// map,key为string类型,value为int类型
var v7 map[string]int 

// 通过双赋值检测某个键是否存在:
elem, ok = m[key]

// 遍历字典
for k, v := range scene {
    fmt.Println(k, v)
}

信道

func test(c chan int) {
        c <- 1
}

c := make(chan int)
go test(c)
go test(c)
x, y := <-c, <-c // 从 c 中接收

// 信道可以带缓冲
ch := make(chan int, 2)

// 发送者可以用 close 关闭信道
// 关闭后 ok 将为 false
close(c)
v, ok := <-ch

// 可以使用 range 从信道读取数据
for i := range c {
}

// select 可以阻塞到某个分支可以继续执行为止
// 可以写入也可以读出
// 同时准备好时随机选取一个
select {
    case c <- x:
        x, y = y, x+y
    case <-quit:
        fmt.Println("quit")
        return
    default:
        fmt.Println("Default option")
        time.Sleep(50 * time.Millisecond)
}

要点:
- 信道必须使用 make 创建
- 信道必须先在接收端就绪,然后才能写入,也就是说必须使用 go 程才能完成信道的示例

结构体

struct 
v5 struct {
    f int
}


type Vertex struct {
        X int
        Y int
}
// 结构体

var (
        v1 = Vertex{1, 2}  // 创建一个 Vertex 类型的结构体
        v2 = Vertex{X: 1}  // Y:0 被隐式地赋予
        v3 = Vertex{}      // X:0 Y:0
        p  = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
// 结构体初始化

接口

类型断言

var i interface{} = "hello"
// 如果没有ok,且类型不对,会引发panic
f, ok := i.(float64)
fmt.Println(f, ok)

类型选择

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)
    }
}

错误

Error
是一个内建的接口

type error interface {
    Error() string
}

一个错误处理的例子

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}

Error 接口无限循环实例

例子见 https://tour.go-zh.org/methods/20
分析过程参考 https://www.jianshu.com/p/c77730e03393

type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
        // 这里如果只用 fmt.Sprintf(e) 会让代码陷入死循环
        return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

因为e变量是一个通过实现Error()的接口函数成为了error类型,那么在 fmt.Sprint(e) 时就会调用e.Error() 来输出错误的字符串信息,于是函数相当于

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprint(e.Error())
}

nil

var s []int
s == nil

常量

使用const关键字

const Pi float64 = 3.14159265358979323846

逻辑运算

与 &&

或 ||

算术运算

流程控制

if

if a < 5 {
	return 0
} else {
	return 1
}

要点:

  1. 条件语句不需要使用括号将条件包含起来();
  2. 无论语句体内有几条语句,花括号{}都是必须存在的;
  3. 左花括号{必须与 if 或者 else 处于同一行;
  4. 在 if 之后,条件语句之前,可以添加变量初始化语句,使用;间隔;
  5. 在有返回值的函数中,不允许将“最终的”return 语句包含在 if…else…结构中,

否则会编译失败:

switch

switch i {
    case 0:
    	fmt.Printf("0")
    case 1:
    	fmt.Printf("1")
    case 2:
    	fallthrough
    case 3:
    	fmt.Printf("3")
    case 4, 5, 6:
    	fmt.Printf("4, 5, 6")
    default:
    	fmt.Printf("Default")
}

要点:

  1. 左花括号{必须与 switch 处于同一行;
  2. 条件表达式不限制为常量或者整数;
  3. 单个 case 中,可以出现多个结果选项;
  4. 与 C 语言等规则相反,Go 语言不需要用 break 来明确退出一个 case;
  5. 只有在 case 中明确添加 fallthrough 关键字,才会继续执行紧跟的下一个 case;
  6. 可以不设定 switch 之后的条件表达式,在此种情况下,整个 switch 结构与多个

if…else…的逻辑作用等同。

for

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

要点:

  1. 左花括号{必须与 for 处于同一行。
  2. Go 语言中的 for 循环与 C 语言一样,都允许在循环条件中定义和初始化变量,唯一的区别

是,Go 语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多
个变量。

  1. Go 语言的 for 循环同样支持 continue 和 break 来控制循环,但是它提供了一个更高级的

break,可以选择中断哪一个循环

函数

package mymath
import "errors"
func Add(a int, b int) (ret int, err error) {
	if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
		err= errors.New("Should be non-negative numbers!")
		return
	}
	return a + b, nil // 支持多重返回值
}

要点:

  1. 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用

将函数作为参数时,可以用 func(),下面是一个例子

// 注册一个回掉函数
// 注意返回值类型要相同
func registerFunc(myFunc func() error){

}

作用域

多文件

面向对象编程

特定方法

defer

延迟语句的执行,到 return 之前都不会执行。

package main

import "fmt"

func main() {
        fmt.Println("counting")

        for i := 0; i < 10; i++ {
                defer fmt.Println(i)
        }

        fmt.Println("done")
}

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

range

每次遍历时会返回一个indexvalue

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
}

delete

delete(m, key)

闭包

闭包的关键在于参数在生成闭包时确定,返回值在调用时确定。

package main

import "fmt"

func adder() func(int) int {
        sum := 0
        return func(x int) int {
                sum += x
                return sum
        }
}

func main() {
        pos, neg := adder(), adder()
        for i := 0; i < 10; i++ {
                fmt.Println(
                        pos(i),
                        neg(-2*i),
                )
        }
}

Reader

func (T) Read(b []byte) (n int, err error)

Reader 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。示例代码创建了一个 strings.Reader 并以每次 8 字节的速度读取它的输出。

r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
        n, err := r.Read(b)
        if err == io.EOF {
                break
        }
}

自己实现 reader 方法时,注意在函数内对切片赋值,返回字节数和错误值。

func (r MyReader) Read(b []byte) (int, error) {
        b[0] = byte('A')
        return 1, nil
}

Reader 数据流处理,见 https://tour.go-zh.org/methods/23

// 该例子中,使用 rot13Reader 并在其中使用了 r 来顺序处理数据流
type rot13Reader struct {
        r io.Reader
}

func (this *rot13Reader) Read(b []byte) (int, error) {
        n, e := this.r.Read(b)
        for i := 0; i < n; i++ {
                b[i] = rot13(b[i])
        }
        return n, e
}

Chan

go f(x, y, z)

互斥锁

mux sync.Mutex

mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问
// some options
mux.Unlock()