跳到主要内容

《Go 语言圣经》

书籍地址:https://golang-china.github.io/gopl-zh/index.html

内置包

flag

flag 包提供了一种解析命令行选项和参数的方法

  • flag.Bool var n = flag.Bool("n", false, "omit frailing newline")
  • flag.String var sep = flag.String("s"," ","separator")

range

go 的关键字,用于迭代各种数据结构

1、数组

numbers := []int {1,2,3,4,5}
for index,value := range numbers {
fmt.Printf("Index: %d, Value: %d",index,value)
}
如果只需获取值,可以使用空白标识符 _ 来忽略索引
for _,value := range numbers{
fmt.Printf("Value: %d",value)
}

2、字符串

str := "hello,world"
for index,runeValue := range str {
fmt.Printf("Index: %d,Rune: %c",index,runeValue)
}

3、map

m := map[string]int{"a":1,"b":2,"c":3}
for key,value := range str {
fmt.Printf("Key: %d,Value: %c",key,value)
}

4、channel

ch := make(chan int,5)
go func(){
ch <- 1
ch <- 2
ch <- 3
close(ch)
}()

for value := range ch{
fmt.Printf("Received:" value)
}

math

  • math.Exp(x float64) e的x次幂
  • math.NaN 返回一个“非数”(NaN)
  • math.IsNaN 测试一个数是不是 NaN
  • math.Pi

2024.8.3

  • for 循环几种写法
  • -
  • range 关键字
  • 变量的定义

2024.8.5

:= 语言规则

  1. := 必须在函数体内使用。
  2. 简短声明变量的语句。将声明变量与变量赋值合并。 x:=2
  3. 多变量定义。 x,y,z := 1,2,3
  4. 类型推断。 name := "wangzhy"
  5. 注意,在使用 := 时必须至少有一个新变量。
func main(){
x := 5
x,y := x+2, x*2
fmt.Println(x,y)
}

make(map[string]int) 语法

  1. map[string]int
  • map go 内置数据结构
  • string map 的 key 的类型
  • int map 的 value 的类型

map[string]int 表示一个 key 为 string 类型,value 为 int 类型的 map。

  1. make 语法

make 返回一个已经分配内存的引用。

input := bufio.NewScanner(os.Stdin)

  • bufio: go 的标准库包,提供 I/O 功能。
  • NewScanner: 创建一个新的 Scanner
  • Scanner: 逐行输入。
  • os: go 标准包,提供操作系统相关的功能。
  • os.Stdin: io.Reader 类型,标准的输入流。

运行 go

  • 直接运行 go 文件
go run xxx.go [param1, param2 ...]
  • 先编译,后运行
go build xxx.go
./xxx param1 param2 ...

2024.8.19

  • 命名

    • 大写开头: 可以被外部的包访问。 fmt.Printf
    • 小写开头: 包内部访问。
  • 声明

    • var 变量
    • const 常量
    • type 类型
    • func 函数
  • 指针

    • p &p *p
  • 问题

    • 执行了 fmt.Printf 方法时,会输出%!(EXTRA float64=100)%。 原因: 使用了 %d、%g 占位符,但是传递的参数类型不匹配。
    • %d:匹配 int 类型,%g:匹配 float 类型。

2024.9.22

  • 变量
    • var 名称 类型 = 表达式
    • var s string
    • var i,j,k int
    • var b,f,s = true,2.3 ,"four"
    • var f,err = os.Open(name)
  • 简短变量生命
    • t:=0.0
    • i,j := 0,1
    • i,j := j,i 交换 i 和 j 的值
  • 指针
    • 一个指针的值是另一个变量的地址。
    • 一个指针对应变量在内存中的存储位置。
    • var x int &x *int
x:=1
p := &x
*p = 2
var x,y int
&x == &x // true
&x == &y // false
&x == nil // false
var p = f()
func f() *int{
v :=1
return &v
}

f() == f() // false
func incr(p *int) int{
*p++
return *p
}
v := 1
incr(&v) // v = 2
fmt.Println(incr(&v)) // v = 3
  • new 函数
    • p := new(int)
    • *p = 2
  • 变量的生命周期
    • 包一级别:和整个程序的运行周期是一致的。
    • 局部变量:从创建变量到该变量不再被引用。
  • 赋值
    • x = 1
    • *p = true
    • person.name = "bob"
    • count[x] = count[x] * scale count[x] *= scale
  • 元组赋值
    • x,y = y,x
func gcd(x,y int) int{
for y != 0 {
x,y = y,x%y
}
return x
}
func fjb(n int)int{
x,y := 0,1
for i := 0; i<n;i++ {
x,y = y,x+y
}
return x
}
  • 可赋值性

    • medals := []string{"gold","silver","bronze"}
  • 类型

    • 变量、表达式
    • type 类型名字 底层类型
  • 包和文件

  • 作用域

func f() {} 
var g = "g"
func main(){
f := "f"
fmt.Println(f) // "f"; local var f shadows package-level func f
fmt.Println(g) // "g"; package-level var
fmt.Println(h) // compile error: undefined: h
}
func main(){
x := "hello" // 定义第一个 x 变量
for i:=0; i<len(x); i++{
x := x[i] // 定义第二个 x 变量
if x != '!'{
x := x + 'A' - 'a' //定义第三个 x 变量
fmt.Printf("%c",x) // 输出第三个 x 变量的值
}
}
}
if x:= f(); x == 0 {  // 声明 x 变量
fmt.Println(x)
} else if y := g(x); x == y { // 在这里可以使用 x 变量
fmt.Println(x,y)
} else {
fmt.Println(x,y) // 这里可以使用 x,y 变量
}
// fmt.Println(x,y) // compile error

错误代码

if f,err := os.Open(fname); err != nil {
return err
}

f.ReadByte() // compile error: f 是在 if 条件里面创建的,在此不能使用。
f.Close()

推荐

f,err := os.Open(fname)
if err != nil {
return err
}
f.ReadByte()
f.Close()

不推荐

if f, err := os.Open(fname); err != nil {
return err
} else {
f.ReadByte()
f.Close()
}

基础数据类型

  • 数字
    • 整形
      • int8
      • int16
      • int32
      • int64
      • uint8
      • uint16
      • uint32
      • uint64
      • int
      • uint
      • uintptr
    • 浮点数
      • float32
      • float64
    • 复数
      • complex64
      • complex128
  • 布尔
    • true
    • false
  • 字符串
    • 不可变的字节序列
    • Unicode
    • UTF-8
    • 字符串和 Byte 切片
      • bytes
      • strings
        • 查询
        • 替换
        • 比较
        • 截断
        • 拆分
        • 合并
      • strconv
      • unicode
    • 字符串和数字的转换
    • 常量
      • const pi = 3.1415926

复合数据类型

  • 数组
    • var a [3]int
    • var q [3]int = [3]int{1,2,3}
    • q := [...]int{1,2,3}
    • r := [...]int{99:-1} // 定义了一个含有100个元素的数组r,最后一个元素被初始化为-1,其它元素都是用0初始化。
  • slice
    • 变长的序列,每个元素类型相同。
    • []T
    • 指针、长度、容量
    • months := [...]string{1: "January", /* ... */, 12: "December"}
    • s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。
    • slice 之间不能比较。(slice 可以与 nil 进行比较)
    • slice 判空 len(s) == 0
    • make([]T,len)
    • make([]T,len,cap)
    • append 函数
  • map
    • 是一个无序的key/value对的集合
    • map[K]V
    • map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。
    • ages := make(map[string]int) // mapping from strings to ints
    • map[string]int{}
    • Go语言中并没有提供一个set类型
  • 结构体
    • 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。
  • JSON
  • 文本和HTML模板

引用类型

  • 指针
  • 切片
  • 字段
  • 函数
  • 通道

2024.12.03

range 关键字用法

  1. 遍历数组和切片
numbers := []int{10,20,30}
for index,value := range numbers{
fmt.Printf("Index: %d, Value: %d \n",index,value)
}
  1. 遍历数组
  2. 遍历字符串
  3. 遍历通道 channel
  4. 仅需要键或值

rune 的用法

rune 是一种数据类型,表示一个 unicode 字符,实际上是 int32 的别名。

数组与 slice 的区别

数组:定长 slice: 变长

2024.12.04

go mod

版本管理

go mod download

指针本质上也是一个变量,保存的是另一个变量的内存地址.

指针的作用:

  1. 让函数修改我们传入变量的值

2024.12.24

变量声明

var name string
var (
a string
b int
c bool
d float32
)

var name string = "wangzhy"
var fullName , age = "wangzhiyuan", 18

// 类型推导
var firstName = "wang"
var realAge = 27

// 简短变量声明 必须在函数内部
lastName :="zhiyuan"

2025.7.10

... 在 go 的用法

  1. 函数可变参数

接收任意数量的 int 参数。

func foo(a ...int){
// ...
}
  1. 数组长度推导
arr := [...]int{1,2,3}
  1. 将切片展开为单个参数
a:=[]int{1,2,3,4,5}
b:=[7,8,9]
a = appent(a,b...)
// 将 arr 切片展开为单个参数
foo(arr...)

复合字面量

https://go.dev/ref/spec#Composite_literals

由字面量类型(struct, array, slice, map)后跟大括号包裹的元素列表组成。

m := map[string]string{
"name" : "wangzhy",
}

结构体字面量需要遵守的规则

  1. A key must be a field name declared in the struct type. | key 的名称必须是已经声明的字段。
  2. An element list that does not contain any keys must list an element for each struct field in the order in which the fields are declared.| 没有 key 的元素列表必须按照字段声明顺序为每个结构体字段列出对应元素。
  3. If any element has a key, every element must have a key. | 若任一元素包含键,则所有元素都必须包含键。
  4. An element list that contains keys does not need to have an element for each struct field. Omitted fields get the zero value for that field.|含键的元素列表无需为每个结构体字段提供元素,未列出的字段将自动获得该字段类型的零值。
  5. A literal may omit the element list; such a literal evaluates to the zero value for its type. | 字面量可省略元素列表, 此类字面量将自动求值为其类型的零值。
  6. It is an error to specify an element for a non-exported field of a struct belonging to a different package. | 为属于不同包的结构体非导出字段指定元素将导致错误。
type Point3D struct {
x, y, z float64
}

type Line struct {
p, q Point3D
}

func main() {

origin := Point3D{}
line := Line{
p: origin,
q: Point3D{ // 会自动为 x 赋值
y: -4,
z: 12.3,
},
}

fmt.Println(line)
}

数组、切片字面的规则

  1. Each element has an associated integer index marking its position in the array. | 每个元素都有一个关联的整型索引,用于标记其在数组中的位置。
  2. An element with a key uses the key as its index. The key must be a non-negative constant representable by a value of type int; and if it is typed it must be of integer type. | 带键的元素使用该键作为索引。键必须是能用 int 类型值表示的非负常量;若指定类型则必须为整型。
  3. An element without a key uses the previous element's index plus one. If the first element has no key, its index is zero. | 无键元素的索引等于前一个元素索引加一。若首元素无键,则其索引为零。

在 array、slice、map 复合字面量中,如果元素或key 元复合字面量的元素或 key 相同时,可以省略字面量

[...]Point{{1.5,-3.5}, {0, 0}}
[][]int{{1,2,3},{4,5,6}}
[][]Point{{{0, 1}, {1,3}}}
map[string]Point{"orig":{0,0}}
map[Point]string{{0,1}:"orig"}

type PPoint *Point
[2]*Point{{1.5, -3.5},{}} // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5},{}} // same as [2]PPoint{&Point{1.5, -3.5}, PPoint(&Point{})}

有效的 array、map、slice 字面量示例

// slice
prims := []int{2,3,5,7,9,2147483647}

// array
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1} // -1 0 0 0 -0.1 -0.1 0 0 0 -1

// map
noteFrequency := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87, // 需要以逗号结尾
}

2025.7.11

defer 执行时机

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:

案例1

package main

import "fmt"

func ff1() int { // return 5
x := 5
defer func() { // 操作的是局部变量x, 并不是函数返回值
x++
}()
return x
}
func ff2() (x int) { // return 6
defer func() { // 操作的是函数返回值 x
x++
}()
return 5 // defer 函数可以访问和修改命名函数值
}

func ff3() (y int) { // return 5
x := 5
defer func() { // 操作的是函数内局部变量 x ,而不是函数返回值 y
x++
}()
return x // 函数已经定义了函数返回值 y, return x 表示,将 x 赋值给 y
}
func ff4() (x int) { // return 5
defer func(x int) { // x 传递的是副本, 并不是函数返回值
x++
}(x)
return 5
}

// defer 延迟调用
func main() {
fmt.Println(ff1()) // 5
fmt.Println(ff2()) // 6
fmt.Println(ff3()) // 6
fmt.Println(ff4()) // 6
}

案例2

import "fmt"

func calcFuncDefer(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}

func main() {
x := 1
y := 2
// 1. calcFuncDefer("A", x, y) 会立即执行, 输出 A 1 2 3
// 4. calcFuncDefer("AA", x, 3) AA 1 3 4
defer calcFuncDefer("AA", x, calcFuncDefer("A", x, y)) // 外层传递的是 x 的值引用, 此时 x = 1
x = 10
// 2. calcFuncDefer("B", x, y) 会立即执行 B 10 2 12
// 3. calcFuncDefer("BB", x, 12) BB 10 12 22
defer calcFuncDefer("BB", x, calcFuncDefer("B", x, y))
y = 20
}

init 函数

一个包中可以有多个 init 函数(不能被手动调用),根据定义顺序,依次调用。

包初始化

在包内部,包级变量初始化按照步骤进行,每一步选择声明顺序最早且不依赖于未初始化变量的变量。

var x = a
var a, b = f() // a and b are initialized together, before x is initialized

类型断言

value, ok := x.(T)
// x 接口类型的变量
// T 目标类型
// 若 x 与 T 匹配, value 返回 T 类型的值, ok = false