Go 语法笔记 | 青训营

63 阅读3分钟

本篇笔记是笔者在第六届字节跳动青训营期间学习 Go 语言的语法笔记,记录了一些 Go 语言中常见的语法

1. 数据类型

1. 类型转化与编码

· Go 为强类型语言,即使是 int 系列,也需要进行显式强转【不像 C 语言一样支持自动运算强转】

var a int
var b int64
a = int64(b)
c := float32(b) / float32(a)

· Go 中的 rune 本质上为 int32,其是一种加强版的 byte,用来表示 unicode 编码,中文默认使用 3 个字节来进行表示。将含有中文的字符串 []rune 转换可以正确打印原字符串内容。值得一提的是,for range 对字符串直接操作也可以正确打印,举例:

    str := "你好,世界"
    fmt.Println("normal print:")
    for i := 0; i < len(str); i++ {
        fmt.Printf("%c", str[i])
    }
    fmt.Println()
    fmt.Println("for_range print:")
    for _, v := range str {
        fmt.Printf("%c", v)
    }
    fmt.Println()
    fmt.Println("convert into []rune print:")
    runes := []rune(str)
    for i := 0; i < len(runes); i++ {
        fmt.Printf("%c", runes[i])
    }
    fmt.Println()

输出结果如下:

normal print:
ä½ å¥½ï¼ä¸ç
for_range print:
你好,世界
convert into []rune print:
你好,世界
2.数组、切片、结构体和接口

· Go 中数组(array)和结构体(struct)是类型,切片(slice)和接口(interface)都是引用类型,使用类型名或接口名 + {} 可以直接开辟空间。Go中开辟空间实在堆区还是栈区是根据上下文由编译器自动推导的。笔者目前从未遇到任何因为堆区栈区开辟所导致的问题。切片和结构体举例如下:

sliceEmpty, sliceInit := []int{}, []int{1, 2, 3}
​
type structDefault struct {
    name string
    age int
}
stu1 := structDefault{"jack", 18}
stu2 := structDefault{name: "mary", age: 19}

· 切片底层实现有三个域:引用"数组"位置,切片大小,切片容量。一般会初始化切片空间以达到像其他编程语言中直接对数组的使用,示例如下。切片容量一般不需要考虑

func solution(n int, something []int) {
    nums := make([]int, n)
    for i := 0; i < n; i++ {
        nums[i] = something[i] // len(somthing) >= n
    }
}

· 切片常用 append() 进行动态扩充,有趣的是:当 append() 方法传入的切片为 nil 时,其会十分“智能”地自动开辟空间,并作为该切片的指向空间

· 空结构体为 struct{},可以将其理解成一种特殊的数据类型:结构体中的 bool 类型,在特殊的场合中使用最小的花销起到标记作用,常见于哈希存储中,示例如下:

type student struct {
    name string
    age int
}
​
func solution(stus []student) {
    nameExistTable := map[student]struct{}{}
    for _, v := range stus {
        if _, ok := nameExistTable[v]; !ok {
            nameExistTable[v] = struct{}{}
        }
    }
}

· 空接口为 interface{},由于其中没有任何方法,因此可以将所有类型(包括自定义类型)视为 interface{} 类型。通常可以定义空接口切片:[]interface{},来存储不同的数据类型

2. 文件操作

· Go 语言中打开文件并返回文件指针 *File 通常会用到 os 包下的两个函数,分别为 os.Open() 和 os.OpenFile()

os.Open()
func Open(name string) (*File, error)

os.Open() 只能以只读模式打开文件,并且文件打开后无法进行写入操作。

os.OpenFile()
func OpenFile(name string, flag int, perm FileMode) (*File, error)
  • 参数 flag: 设置打开文件的模式,使用常量与或可以进行设置打开模式,如 os.O_RDONLY | os.O_CREATE

  • 参数 perm: 设置文件的权限,其为一个八进制数,而 Go 中八进制数的表示以 0 开头。三位依此表示文件所有者其他用户。比如 0755 的含义如下:

    • 7 == 4 + 2 + 1,从左至右依此为 r, w, x (读,写,执行),表示文件所有者拥有读、写和执行权限
    • 5 == 4 + 1,表示组和其他用户没有写权限,只有读和执行权限

3. 命令行参数使用

os.Args []string

在运行 go 程序时可以对其传入参数,存储在 os.Args 字符串切片中,其类型为 []string,注意:os.Args[0] 保存的不是传入的参数,具体如下:

func main() {
    fmt.Println("There is", len(os.Args), "args in the using...")
    for i, v := range os.Args {
        fmt.Printf("[%v] arg is %v\n", i + 1, v)
    }
}

输出结果:

bash: go build -o fileArgs.exe fileArgs.go 
bash:./fileArgs.exe first 01 23 127.0.0.1
There is 5 args in the using...
[1] arg is ./fileArgs.exe
[2] arg is first
[3] arg is 01
[4] arg is 23
[5] arg is 127.0.0.1
flag 包

为了使得命令行传入的参数支持乱序、指定输入与默认值,Go 中提供了 flag 包来实现这一步骤,具体示例如下:

func main() {
    var usr, pwd, ip string
    var port int
    flag.StringVar(&usr, "u", "", "user name")
    flag.StringVar(&pwd, "p", "", "password")
    flag.StringVar(&ip, "h", "127.0.0.1", "host ip")
    flag.IntVar(&port, "P", 3306, "connect port")
​
    flag.Parse()
​
    allParse := []interface{}{usr, pwd, ip, port}
    for _, v := range allParse {
        fmt.Println(v)
    }
}   

输出结果如下:

./test.exe -p 123 -P 8080 -u anonym -h 224.1.1.2
anonym
123
224.1.1.2
8080

flag.StringVar() 与 flag.IntVar() 方法表明命令行传入的值 go 程序应该以怎样的“视角”来进行看待。以flag.StringVar()为例,参数说明如下:

func flag.StringVar(p *string, name string, value string, usage string)

p *string: 解析后存储的位置

name string: 调用者通过 -[name] 指定传入参数名

value string: 默认值,用于调用者未传参数时

usage string: 用于描述该参数,举例如下:

./test.exe -p 123 -P 8080 -u anonym -h
flag needs an argument: -h
Usage of ./test.exe:
  -P int
        connect port (default 3306)
  -h string
        host ip (default "127.0.0.1")
  -p string
        password
  -u string
        user name