《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
测试一个数是不是 NaNmath.Pi
2024.8.3
for
循环几种写法-
range
关键字- 变量的定义
2024.8.5
:=
语言规则
:=
必须在函数体内使用。- 简短声明变量的语句。将声明变量与变量赋值合并。
x:=2
- 多变量定义。
x,y,z := 1,2,3
- 类型推断。
name := "wangzhy"
- 注意,在使用
:=
时必须至少有一个新变量。
func main(){
x := 5
x,y := x+2, x*2
fmt.Println(x,y)
}
make(map[string]int)
语法
map[string]int
map
go 内置数据结构string
map 的key
的类型int
map 的value
的类型
map[string]int
表示一个 key 为 string 类型,value 为 int 类型的 map。
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 类型。
- 执行了 fmt.Printf 方法时,会输出
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
关键字用 法
- 遍历数组和切片
numbers := []int{10,20,30}
for index,value := range numbers{
fmt.Printf("Index: %d, Value: %d \n",index,value)
}
- 遍历数组
- 遍历字符串
- 遍历通道 channel
- 仅需要键或值
rune
的用法
rune 是一种数据类型,表示一个 unicode 字符,实际上是 int32 的别名。
数组与 slice 的区别
数组:定长 slice: 变长
2024.12.04
go mod
版本管理
go mod download
指针本质上也是一个变量,保存的是另一个变量的内存地址
.
指针的作用:
- 让函数修改我们传入变量的值
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 的用法
- 函数可变参数
接收任意数量的 int 参数。
func foo(a ...int){
// ...
}
- 数组长度推导
arr := [...]int{1,2,3}
- 将切片展开为单个参数
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",
}
结构体字面量需要遵守的规则
- A key must be a field name declared in the struct type. | key 的名称必须是已经声明的字段。
- 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 的元素列表必须按照字段声明顺序为每个结构体字段列出对应元素。
- If any element has a key, every element must have a key. | 若任一元素包含键,则所有元素都必须包含键。
- 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.|含键的元素列表无需为每个结构体字段提供元素,未列出的字段将自动获得该字段类型的零值。
- A literal may omit the element list; such a literal evaluates to the zero value for its type. | 字面量可省略元素列表, 此类字面量将自动求值为其类型的零值。
- 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)
}
数组、切片字面的规则
- Each element has an associated integer index marking its position in the array. | 每个元素都有一个关联的整型索引,用于标记其在数组中的位置。
- 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 类型值表示的非负常量;若指定类型则必须为整型。
- 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