Basic
Packages, variables, and functions
- Packages
-
每个 Go 程序都由包(package)组成。
-
程序从
main包开始运行。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}
- Exported
在 Go 中,如果一个名称以大写字母开头,则它是导出的(exported)。
导入一个包时,你只能引用它的导出名称。 任何“未导出”的名称在包外都无法访问。
- Functions
类型写在变量名之后。(点击了解)
func add(x int, y int) int {
return x + y
}
当两个或多个连续的已命名函数参数共享同一类型时,除最后一个外可以省略类型。
func add(x, y int) int {
return x + y
}
- Multiple results
函数可以返回任意数量的结果。
func swap(x, y string) (string, string) {
return y, x
}
- Variables
var 语句声明一个变量列表;与函数参数列表一样,类型在最后。
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
- Variables with initializers
- var 声明可以包含初始值,每个变量对应一个。
- 如果存在初始值,可以省略类型;变量会采用初始值的类型。
- 变量声明也可以像导入语句一样“分组”成块。
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
- Short variable declarations
-
在函数内部,可以用 := 短赋值语句代替带隐式类型的 var 声明。
-
在函数外,每条语句都以关键字开头(var 、 func 等),因此无法使用 := 结构。
-
在
:=声明中,允许重复赋值,前提是:-
初始化中的相应值可赋值给
v。 -
声明至少创建了一个其他变量。
-
- Basic types
Go 的基本类型有:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
// 常以[]byte形式出现
rune // alias for int32
// 常用来处理 UTF-8 字符串
float32 float64
complex64 complex128
int 、 uint和uintptr类型在 32 位系统上通常为 32 位宽,在 64 位系统上为 64 位宽。当你需要一个整数值时,应使用
int,除非你有特定理由使用定宽或无符号整数类型。
- Zero values
声明时没有显式初始值的变量会被赋予 零值(zero value)。
| 类型 | 零值 |
|---|---|
| 数值类型 | 0 |
| 布尔类型 | false |
| 字符串类型 | “” |
| 指针 | nil |
| 切片 | nil |
| 映射 | nil |
- Type conversions
表达式 T(v) 将值 v 转换为类型 T。
f := float64(i)在 Go 中不同类型的项之间赋值需要显式转换
字符串转整数:
n, err := strconv.Atoi("123")
整数转字符串:
s := strconv.Itoa(123)
- Constants
- 使用
const声明常量,不能使用:=。 iota在每个const块中从0开始,每项加1。- 省略表达式时,会重复上一项的表达式。
type ByteSize int
const (
_ = iota // 0,忽略
KB ByteSize = 1 << (10 * iota) // 1 << 10
MB // 1 << 20
GB // 1 << 30
)
fmt.Println(KB) // 1024
fmt.Println(MB) // 1048576
- Numeric Constants
const MaxRetry = 3 // 无类型常量
const Timeout int = 30 // 有明确类型
- 无类型数值常量在编译期可以表示很大、很高精度的值;只有赋给或转换为具体类型时,编译器才检查该类型能否表示它。
- 无类型常量会根据上下文采用所需类型;没有明确上下文时,会采用默认类型,如整数默认 int、浮点数默认 float64。
实际开发中,普通固定值通常写成无类型常量;只有 API、协议或业务明确要求具体类型时,才显式标注类型。
const n = 3 // 无类型整数常量
var a int64 = n // 根据上下文采用 int64
b := n // 无明确上下文,默认采用 int
const big = 1 << 100 // 无类型常量可以表示这个大数
var c int64 = big // 编译错误:big 超出 int64 的范围
- format verb
| 动词 | 用途 | 常见场景 |
|---|---|---|
%v |
按默认格式输出任意值 | 日志、临时调试 |
%+v |
输出结构体字段名和值 | 查看完整结构体 |
%s |
字符串 | 拼接提示、日志信息 |
%q |
带引号的字符串 | 检查空格、换行等特殊字符 |
%d |
十进制整数 | ID、数量、状态码 |
%f / %.2f |
浮点数 | 金额、比例、小数 |
%t |
布尔值 | 开关状态、判断结果 |
%T |
值的类型 | 调试接口和类型转换 |
%w |
包装错误 | fmt.Errorf 返回带上下文的错误 |
%x |
十六进制 | 字节、哈希、二进制数据 |
- init function
init()用于包的初始化,无参数、无返回值,由 Go 自动调用。- 每个源文件可以定义多个
init()。 - 执行顺序:
导入包初始化
→ 包级变量初始化
→ init()
→ main()
常用于程序运行前的检查、补充默认值或注册配置:
var user = os.Getenv("USER")
var home = os.Getenv("HOME")
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
}
func main() {
// init 执行完成后才会进入 main
}
Flow control statements
- For
基本的 for 循环有三个由分号分隔的组成部分:
- 初始化语句:在第一次迭代之前执行
- 条件表达式:在每次迭代之前求值
- 后置语句:在每次迭代结束时执行
初始化语句通常是一个短变量声明,在那里声明的变量 仅在 for 语句的作用域内可见。
- For is Go’s “while”
其中,初始化语句和后置语句是可选的。
for sum < 1000 {
sum += sum
}
- Forever
如果省略循环条件,它会永远循环,因此可以很紧凑地表达无限循环。
func main() {
for {
}
}
- If
- Go的if 语句允许在条件之前加以一个简短语句开始执行。
- 在 if 简短语句中声明的变量,在任何对应的 else 块中也可用。
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// can't use v here, though
return lim
}
- Switch
switch 语句是编写一连串 if - else 语句的更短方式。 它会运行第一个值等于条件表达式的 case。
- Go 只运行被选中的 case,而不会继续执行后面的 case。
类似C语言中每个 case 末尾所需的 break 语句 在 Go 中是自动提供的。
- Go 的 switch case 不必是常量, 所涉及的值也不必是整数。
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
fallthrough 关键字可以强制执行下一个 case:
switch 2 {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2") // 输出:2
fallthrough // 强制执行下一个case
case 3:
fmt.Println("3") // 输出:3(因fallthrough被执行)
}
- Swich with no condition
没有条件的 switch 与 switch true 相同。 这种结构可以干净地写出很长的 if-then-else 链。 实际开发中,无条件 switch 很适合表示多个互斥的范围判断:
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
等价于
if t.Hour() < 12 {
fmt.Println("Good morning!")
} else if t.Hour() < 17 {
fmt.Println("Good afternoon.")
} else {
fmt.Println("Good evening.")
}
switch true更加优雅
- Defer
- defer 语句会将函数的执行推迟到外围函数返回时。
- 被推迟调用的参数会立即求值,但函数调用本身直到外围函数返回时才会执行。
- 被推迟的函数调用会压入一个栈中。当函数返回时, 其被推迟的调用按后进先出的顺序执行。
More types
自定义type
// 基于int创建自定义类型MyInt(全新类型)
type MyInt int
// 基于[]int创建自定义类型IntSlice(全新类型)
type IntSlice []int
// 基于struct创建自定义类型User(结构体类型)
type User struct {
Name string
Age int
}
// 基于函数类型创建自定义类型MathFunc
type MathFunc func(int) int
Points
定义
指针保存一个值的内存地址。
与 C 不同,Go 没有指针运算。
&操作符会生成指向其操作数的指针。
i := 42
p = &i
*操作符表示指针的底层值。
fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p
这称为“解引用”(dereferencing)或“间接引用”(indirecting)。
new 和 make
func new(Type) *Type
创建变量,将变量初始化为该类型的零值,返回指针。
func make(t Type, size ...IntegerType) Type
初始化 slice、map、channel,返回值本身。
new(int) // *int,指向零值 0
new(string) // *string,指向零值 ""
new([]int) // *[]int,指向一个 nil 切片,较少使用
new(int64(300)) // *int64,指向初始值 300(Go 1.26+)
make([]int, 10, 100) // 长度 10、容量 100 的切片
make(map[string]int, 10) // 创建 map,10 是初始容量提示
make(chan int, 10) // 创建缓冲区大小为 10 的通道
Structs
定义
struct 是一组字段(field)的集合。
字段通常由“字段名 + 字段类型”组成。
type Rectangle struct {
height, width, area int
color string
}
实例化
programmer := Programmer{
Name: "jack",
Age: 19,
Job: "coder",
Language: []string{"Go", "C++"},
}
访问
-
结构体字段使用点号访问。
-
结构体字段可以通过结构体指针访问。
对结构体指针使用
v.X时,Go 会自动将它理解为(*v).X。
v := &Vertex{1, 2}
v.X = 1e9 // (*v).X
Slices
Arrays
类型 [n]T 是由 n 个类型为 T 的值组成的数组。
var a [10]int
数组的长度是其类型的一部分,因此数组不能调整大小
初始化:
arr := [3]int{1,2,3}或arr := […]int{0:1, 1:23, 2:34, 3:43, 5:53}
Slices
- 切片(slice)是对数组元素的动态大小、灵活视图。
- 类型 []T 是元素类型为 T 的切片。
type sliceHeader struct {
ptr *[]byte // 指向底层数组的指针
len int // 切片长度
cap int // 切片容量
}
切片通过指定两个下标形成,即一个下界和一个上界,用冒号分隔
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] // 左闭右开
也可以省略上界或下界,改用默认值。
对于数组var a [10]int,以下切片表达式是等价的
a[0:10]
a[:10]
a[0:]
a[:]
Slices are like references to arrays
切片本身不存储任何数据, 它只描述底层数组的一段。
更改切片的元素会修改其底层数组中对应的元素。
共享同一底层数组的其他切片也会看到这些更改。
package main
import "fmt"
func main() {
arr := [4]int{10, 20, 30, 40}
a := arr[0:3] // 对应 arr[0] 到 arr[2]
b := arr[1:4] // 对应 arr[1] 到 arr[3]
a[1] = 99 // 修改的是底层数组的 arr[1]
fmt.Println(arr) // [10 99 30 40]
fmt.Println(a) // [10 99 30]
fmt.Println(b) // [99 30 40]
}
Slice literals
切片字面量类似于没有长度的数组字面量
对于[3]bool{true, true, false}
Go会先创建与上面相同的底层数组, 然后构建一个引用它的切片:
[]bool{true, true, false}
Slice length and capacity
-
切片既有 长度(length),也有 容量(capacity)。
-
切片的长度是它包含的元素个数。通过
len()获取 -
切片的容量是底层数组中、从切片第一个元素起算的元素个数。通过
cap()获取
Creating a slice with make
切片可以用内建的 make 函数创建。
make 函数会分配一个清零的数组, 并返回引用该数组的切片:
a := make([]int, 5) // len(a)=5
要指定容量,向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
Appending to a slice
Go 提供了内建的 append 函数。
func append(s []T, vs ...T) []T
append 的第一个参数 s 是类型为 T 的切片,其余是要追加到该切片的 T 值。
append 的结果是一个包含原切片所有元素以及所提供值的切片。
如果 s 的后备数组太小,装不下所有给定值,就会分配一个更大的数组。返回的切片将指向新分配的数组。
将元素附加到切片的末尾并返回结果:
x := []int{1,2,3}
x = append(x, 4, 5, 6)
fmt.Println(x)
将一个切片附加到另一个切片:
x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)
当函数的最后一个参数是可变参数
...T时,可以使用slice...将一个切片展开成多个参数传入。
slice of slices
func Pic(dx, dy int) [][]uint8 {
// 先创建外层切片,长度为 dy。
// 每个元素的类型是 []uint8,初始值都是 nil。
result := make([][]uint8, dy)
for i := range dy {
// 为第 i 行创建一个长度为 dx 的 uint8 切片。
// 至此,二维切片拥有 dy 行,每行 dx 列。
result[i] = make([]uint8, dx)
for j := range dx {
// 设置第 i 行、第 j 列的元素。
result[i][j] = uint8(i ^ j)
}
}
return result
}
Range
for 循环的 range 形式会遍历Slices或Map。
在对Slices使用 range 时,每次迭代会返回两个值。 第一个是下标,第二个是该下标处元素的副本。
在对Map使用 range 时,每次迭代会返回两个值。 第一个是key,第二个是value。
for i, v := range pow{
}
可以通过赋给 _ 来跳过下标或值。
for _ , v := range pow{
}
只需要下标,可以省略第二个变量。
for i := range pow{
}
不需要变量,可以直接:
for range 10{
}
Maps
Map
映射(map)将键映射到值。
映射的零值是 nil。 nil 映射没有键,也不能添加键。
type hmap struct {
count int // 哈希表中实际存储的键值对总数
B uint8 // 桶数组的大小指数(桶数量 = 2^B,比如B=3则有8个桶)
buckets unsafe.Pointer // 指向桶数组(bmap数组)的指针
hash0 uint32 // 哈希种子(随机数,避免哈希碰撞攻击)
// 其他辅助字段:溢出桶指针、扩容标记、迭代器状态等
}
make 函数返回给定类型的映射, 已初始化并可供使用。
m = make(map[string]Vertex)
Map literals
映射字面量类似结构体字面量,但键是必需的。
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
Mutating Maps
在映射 m 中插入或更新元素:
m[key] = elem
获取元素:
elem = m[key]
删除元素:
delete(m, key)
即使键已从映射中缺失,执行此操作也是安全的。
用双赋值测试某个键是否存在:
v, ok := m[key]
如果 key 在 m 中,v为m[key],ok 为 true。
如果 key 不在映射中,则 elem 是该映射元素类型的零值, ok 为 false。
Function
Function values
函数也是值。它们可以像其他值一样传递。
函数值可以用作函数参数和返回值。
函数变量也有类型,比如func add(a, b int) int { return a + b }类型是func(int, int) int
package main
import "fmt"
func add(a, b int) int {
return a + b
}
// 接收函数作为参数,并返回这个函数
func use(f func(int, int) int) func(int, int) int {
fmt.Println(f(1, 2))
return f
}
func main() {
fn := use(add) // add 作为参数传入
fmt.Println(fn(3, 4)) // 函数作为返回值:7
}
Function closures
-
闭包是一个函数值,它引用了函数体之外的变量。
-
该函数可以访问并赋值这些被引用的变量;从这个意义上说,函数被“绑定”到了这些变量上。
闭包不是单独一段函数代码,而是“函数代码 + 被捕获变量的环境”。
闭包让局部变量的生命周期脱离原函数栈帧,改为跟随闭包本身。
package main
import "fmt"
func fibonacci() func() int {
prenum,num:=0,1
return func() int {
result:=prenum
prenum,num=num,num+prenum
return result
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}