Go 基础语法
变量与常量、基本类型、零值、类型转换、字符串、指针、控制流与 defer 语句。
前置知识
- Go 概述与环境配置:建议先完成前一篇的学习
学习目标
- 掌握「1. 变量与常量」的核心机制、典型用法与常见陷阱
- 掌握「2. 基本数据类型」的核心机制、典型用法与常见陷阱
- 掌握「3. 字符串」的核心机制、典型用法与常见陷阱
- 掌握「4. 指针」的核心机制、典型用法与常见陷阱
- 掌握「5. 控制流」的核心机制、典型用法与常见陷阱
1. 变量与常量
1.1 变量声明
Go 提供多种变量声明方式,推荐在函数内使用短变量声明:
// 完整声明(可省略类型,由编译器推断)
var name string = "Go"
var age = 15
// 批量声明
var (
x int = 10
y float64 = 3.14
flag bool = true
)
// 短变量声明(仅限函数内,最常用)
city := "Beijing"
count := 100
// 未初始化则使用零值
var score int // 0
var title string // ""(空字符串)
var done bool // false
1.2 常量
// 常量在编译时确定,不能使用 := 声明
const Pi = 3.14159
const Language = "Go"
// 批量声明
const (
StatusOK = 200
StatusError = 500
)
// iota 常量生成器
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
// iota 高级用法:位运算标志
const (
ReadPermission = 1 << iota // 1 (001)
WritePermission // 2 (010)
ExecutePermission // 4 (100)
)
// 跳过值
const (
_ = iota // 跳过 0
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
TB // 1 << 40
)
2. 基本数据类型
2.1 类型一览
| 类别 | 类型 | 说明 |
|---|---|---|
| 布尔 | bool | true 或 false |
| 整数 | int8, int16, int32, int64, int | 有符号整数 |
| 整数 | uint8, uint16, uint32, uint64, uint | 无符号整数 |
| 整数 | byte | uint8 的别名 |
| 整数 | rune | int32 的别名,表示 Unicode 码点 |
| 浮点 | float32, float64 | IEEE 754 浮点数 |
| 复数 | complex64, complex128 | 复数 |
| 字符串 | string | 不可变字节序列 |
| 指针 | *T | 指向类型 T 的指针 |
注意:
int和uint的大小取决于平台(32 位或 64 位),优先使用int。
2.2 零值
Go 中所有变量在声明时若未初始化,会自动赋予零值:
var (
i int // 0
f float64 // 0.0
b bool // false
s string // ""
ptr *int // nil
sl []int // nil
m map[string]int // nil
ch chan int // nil
fn func() // nil
err error // nil
)
零值是 Go 的重要设计理念——变量总是有明确定义的值,不存在”未初始化”状态。
2.3 类型转换
Go 没有隐式类型转换,所有转换必须显式进行:
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
// 字符串与数值转换(使用 strconv 包)
s := strconv.Itoa(42) // int → string: "42"
n, err := strconv.Atoi("42") // string → int: 42
f2 := strconv.FormatFloat(3.14, 'f', 2, 64) // float64 → string: "3.14"
f3, err := strconv.ParseFloat("3.14", 64) // string → float64
// 字符串与字节切片
bytes := []byte("hello") // string → []byte
str := string(bytes) // []byte → string
// rune 与 string
r := '世'
fmt.Printf("%c %U\n", r, r) // 世 U+4E16
3. 字符串
3.1 字符串基础
Go 字符串是不可变的 UTF-8 字节序列:
s := "Hello, 世界"
// 字节长度 vs 字符数
fmt.Println(len(s)) // 13(字节数)
fmt.Println(utf8.RuneCountInString(s)) // 9(字符数)
// 遍历字节
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i]) // 48 65 6c 6c 6f 2c 20 e4 b8 96 e7 95 8c
}
// 遍历 rune(正确处理 Unicode)
for i, r := range s {
fmt.Printf("%d:%c ", i, r) // 0:H 1:e 2:l 3:l 4:o 5:, 6: 7:世 10:界
}
3.2 字符串操作
import "strings"
s := "Hello, World"
// 查找与判断
strings.Contains(s, "World") // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "World") // true
strings.Index(s, "World") // 7
strings.Count(s, "l") // 3
// 变换
strings.ToUpper(s) // "HELLO, WORLD"
strings.ToLower(s) // "hello, world"
strings.TrimSpace(" hi ") // "hi"
strings.Trim("==hi==", "=") // "hi"
strings.Replace(s, "World", "Go", 1) // "Hello, Go"
strings.ReplaceAll(s, "l", "L") // "HeLLo, WorLd"
// 拆分与合并
parts := strings.Split("a,b,c", ",") // ["a", "b", "c"]
joined := strings.Join(parts, "-") // "a-b-c"
fields := strings.Fields(" a b c ") // ["a", "b", "c"]
// 字符串构建(避免频繁拼接)
var b strings.Builder
b.WriteString("Hello")
b.WriteString(", ")
b.WriteString("World")
result := b.String() // "Hello, World"
3.3 原始字符串
// 反引号包围,不处理转义
raw := `C:\Users\name\file.txt`
multi := `
第一行
第二行
第三行
`
4. 指针
4.1 指针基础
x := 42
p := &x // p 是 *int 类型,指向 x 的地址
fmt.Println(p) // 0xc0000b2008(内存地址)
fmt.Println(*p) // 42(解引用,获取地址处的值)
*p = 100 // 通过指针修改值
fmt.Println(x) // 100
4.2 指针的零值
var p *int // nil(零值)
if p != nil {
fmt.Println(*p) // 安全检查
}
// *p = 10 // panic: nil 指针解引用
4.3 指针与函数
// 值传递:函数内修改不影响外部
func doubleVal(n int) {
n *= 2
}
// 指针传递:函数内修改影响外部
func doublePtr(n *int) {
*n *= 2
}
func main() {
x := 10
doubleVal(x)
fmt.Println(x) // 10(未变)
doublePtr(&x)
fmt.Println(x) // 20(已变)
}
4.4 new 函数
// new(T) 分配零值内存并返回指针
p := new(int) // *int 类型,指向 0
*p = 42
fmt.Println(*p) // 42
// 等价于
var v int
p2 := &v
Go 指针 vs C 指针:Go 没有指针运算(不能
p++),更安全。
5. 控制流
5.1 if 语句
// 基本形式(条件不需要括号)
if x > 0 {
fmt.Println("positive")
} else if x < 0 {
fmt.Println("negative")
} else {
fmt.Println("zero")
}
// 初始化语句(变量作用域限定在 if 块内)
if err := doSomething(); err != nil {
fmt.Println("Error:", err)
// err 仅在此块内可见
}
5.2 for 循环
Go 只有 for 一种循环语句,但功能涵盖所有场景:
// 经典三段式
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// while 风格
n := 1
for n < 100 {
n *= 2
}
// 无限循环
for {
if shouldBreak() {
break
}
}
// for-range(遍历集合)
nums := []int{1, 2, 3}
for i, v := range nums {
fmt.Printf("index=%d value=%d\n", i, v)
}
// Go 1.22+ for-range 整数
for i := range 5 {
fmt.Println(i) // 0, 1, 2, 3, 4
}
// 只需要索引或值
for i := range nums { /* 只取索引 */ }
for _, v := range nums { /* 只取值 */ }
5.3 switch 语句
// 基本形式(自动 break,不穿透)
day := "Monday"
switch day {
case "Monday":
fmt.Println("周一")
case "Tuesday":
fmt.Println("周二")
default:
fmt.Println("其他")
}
// 多值匹配
switch color {
case "red", "green", "blue":
fmt.Println("基础颜色")
}
// 穿透(fallthrough)
switch n := 2; n {
case 1:
fmt.Println("一")
fallthrough
case 2:
fmt.Println("二") // 即使匹配 2,也会执行
fallthrough
case 3:
fmt.Println("三") // fallthrough 继续执行
}
// 无条件 switch(替代 if-else 链)
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("D")
}
5.4 break 与 continue
for i := 0; i < 10; i++ {
if i == 3 {
continue // 跳过本次迭代
}
if i == 7 {
break // 退出循环
}
fmt.Println(i) // 0, 1, 2, 4, 5, 6
}
// 标签跳转(跳出外层循环)
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer // 跳出外层循环
}
fmt.Printf("(%d,%d) ", i, j)
}
}
// 输出: (0,0) (0,1) (0,2) (1,0)
6. defer 语句
defer 将函数调用推迟到所在函数返回之前执行,常用于资源清理。
6.1 基本用法
func readFile(path string) {
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close() // 确保文件关闭
// 读取文件...
}
6.2 defer 执行顺序
多个 defer 按**后进先出(LIFO)**顺序执行:
func main() {
defer fmt.Println("第一") // 最后执行
defer fmt.Println("第二") // 第二执行
defer fmt.Println("第三") // 最先执行
fmt.Println("主函数")
}
// 输出:
// 主函数
// 第三
// 第二
// 第一
6.3 defer 参数求值
defer 语句的参数在声明时立即求值,而非执行时:
func main() {
x := 10
defer fmt.Println(x) // 输出 10(声明时求值)
x = 20
fmt.Println(x) // 输出 20
}
// 输出: 20, 10
6.4 defer 与返回值
defer 可以修改命名返回值:
func double() (result int) {
defer func() {
result *= 2 // 修改返回值
}()
return 5 // result = 5,然后 defer 执行 result *= 2
}
// double() 返回 10
6.5 defer 性能考虑
// defer 在循环中可能有性能开销(Go 1.13+ 已大幅优化)
// 简单场景可以直接调用
func process(items []string) {
for _, item := range items {
f, err := os.Open(item)
if err != nil {
continue
}
// 在热路径循环中,直接调用可能比 defer 更高效
processFile(f)
f.Close()
}
}
变量声明
基本写法:显式声明变量类型和初始值
var <变量名> <类型> = <值>
// 显式声明变量类型和初始值
var name string = "Go";
基本写法:类型推断声明
var <变量名> = <值>
// 自动推断类型
var age = 15;
单行写法:批量声明多个变量
var ( <变量1> <类型1> = <值1>; <变量2> <类型2> = <值2> )
// 单行批量声明多个变量
var ( x int = 10; y float64 = 3.14; flag bool = true );
换行写法:批量声明多个变量
var ( ... )
// 换行书写批量声明
var (
x int = 10
y float64 = 3.14
flag bool = true
);
基本写法:短变量声明
<变量名> := <值>
// 仅限函数内使用,自动推断类型
city := "Beijing";
基本写法:零值声明
var <变量名> <类型>
// 未初始化的变量使用零值
var score int; // 0
常量
基本写法:常量声明
const <名称> = <值>
// 声明常量
const Pi = 3.14159;
单行写法:批量声明常量
const ( <名称1> = <值1>; <名称2> = <值2> )
// 单行批量声明常量
const ( StatusOK = 200; StatusError = 500 );
换行写法:批量声明常量
const ( ... )
// 换行书写批量声明
const (
StatusOK = 200
StatusError = 500
);
基本写法:iota 常量生成器
const ( <名称> = iota ... )
// iota 从 0 开始自动递增
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
);
基本写法:iota 位运算标志
const ( <名称> = 1 << iota ... )
// 位运算生成权限标志
const (
ReadPermission = 1 << iota // 1 (001)
WritePermission // 2 (010)
ExecutePermission // 4 (100)
);
基本写法:iota 跳过值
_ = iota
// 跳过 0,从 1024 开始
const (
_ = iota
KB = 1 << (10 * iota) // 1024
MB // 1048576
);
类型转换
基本写法:显式类型转换
<目标类型>(<值>)
// int 转 float64
var i int = 42;
var f float64 = float64(i);
基本写法:int 转 string
strconv.Itoa(<整数>)
// int 转 string
s := strconv.Itoa(42);
基本写法:string 转 int
strconv.Atoi(<字符串>)
// string 转 int
n, err := strconv.Atoi("42");
基本写法:string 转 []byte
[]byte(<字符串>)
// string 转 []byte
bytes := []byte("hello");
基本写法:[]byte 转 string
string(<字节切片>)
// []byte 转 string
str := string(bytes);
字符串操作
基本写法:字符串字节长度
len(<字符串>)
// 字节数(UTF-8 编码)
fmt.Println(len("Hello, 世界")); // 13
基本写法:字符串字符数
utf8.RuneCountInString(<字符串>)
// 字符数(正确处理 Unicode)
fmt.Println(utf8.RuneCountInString("Hello, 世界")); // 9
基本写法:按 rune 遍历字符串
for i, r := range <字符串>
// 按 rune 遍历,正确处理 Unicode
for i, r := range "Hello" {
fmt.Printf("%d:%c ", i, r);
}
基本写法:判断子串是否存在
strings.Contains(<字符串>, <子串>)
// 查找子串
strings.Contains("Hello, World", "World"); // true
基本写法:判断前缀
strings.HasPrefix(<字符串>, <前缀>)
// 判断前缀
strings.HasPrefix("Hello, World", "Hello"); // true
基本写法:判断后缀
strings.HasSuffix(<字符串>, <后缀>)
// 判断后缀
strings.HasSuffix("Hello, World", "World"); // true
基本写法:查找子串位置
strings.Index(<字符串>, <子串>)
// 查找子串位置
strings.Index("Hello, World", "World"); // 7
基本写法:转大写
strings.ToUpper(<字符串>)
// 转大写
strings.ToUpper("Hello"); // "HELLO"
基本写法:转小写
strings.ToLower(<字符串>)
// 转小写
strings.ToLower("Hello"); // "hello"
基本写法:去除首尾空白
strings.TrimSpace(<字符串>)
// 去除首尾空白
strings.TrimSpace(" hi "); // "hi"
基本写法:替换字符串
strings.Replace(<字符串>, <旧>, <新>, <次数>)
// 替换字符串
strings.Replace("Hello", "l", "L", 1); // "HeLlo"
基本写法:拆分字符串
strings.Split(<字符串>, <分隔符>)
// 拆分字符串
parts := strings.Split("a,b,c", ",");
基本写法:合并字符串
strings.Join(<切片>, <分隔符>)
// 合并字符串
joined := strings.Join(parts, "-");
基本写法:字符串构建
strings.Builder
// 使用 Builder 高效构建字符串
var b strings.Builder;
b.WriteString("Hello");
b.WriteString(", World");
result := b.String();
基本写法:原始字符串
`<内容>
// 反引号包围的原始字符串
raw := `C:\Users\name\file.txt`;
指针
基本写法:取地址
&<变量>
// 取地址
x := 42;
p := &x; // p 是 *int 类型
基本写法:解引用
*<指针>
// 解引用获取值
fmt.Println(*p); // 42
基本写法:通过指针修改值
*<指针> = <值>
// 通过指针修改值
*p = 100;
基本写法:nil 指针检查
if <指针> != nil
// nil 指针检查
var p *int;
if p != nil {
fmt.Println(*p);
}
基本写法:指针传递
func <函数名>(<参数> *<类型>)
// 指针传递修改外部变量
func doublePtr(n *int) {
*n *= 2;
}
基本写法:new 函数
new(<类型>)
// new 分配零值内存
p := new(int);
*p = 42;
if 语句
基本写法:基本 if 语句
if <条件> { ... }
// 基本条件判断
if x > 0 {
fmt.Println("positive");
} else if x < 0 {
fmt.Println("negative");
} else {
fmt.Println("zero");
}
基本写法:带初始化的 if
if <初始化>; <条件> { ... }
// 初始化语句中的变量仅在此块可见
if err := doSomething(); err != nil {
fmt.Println("Error:", err);
}
for 循环
基本写法:经典三段式
for <初始化>; <条件>; <后置> { ... }
// 经典 for 循环
for i := 0; i < 10; i++ {
fmt.Println(i);
}
基本写法:while 风格
for <条件> { ... }
// while 风格循环
n := 1;
for n < 100 {
n *= 2;
}
基本写法:无限循环
for { ... }
// 无限循环
for {
if shouldBreak() {
break;
}
}
基本写法:for-range 遍历
for <索引>, <值> := range <集合> { ... }
// 遍历切片
nums := []int{1, 2, 3};
for i, v := range nums {
fmt.Printf("index=%d value=%d\n", i, v);
}
基本写法:Go 1.22+ for-range 整数
for i := range <整数> { ... }
// 遍历 0 到 4
for i := range 5 {
fmt.Println(i);
}
switch 语句
基本写法:基本 switch
switch <表达式> { case ... }
// 基本 switch 语句
switch day {
case "Monday":
fmt.Println("周一");
case "Tuesday":
fmt.Println("周二");
default:
fmt.Println("其他");
}
基本写法:多值匹配
case <值1>, <值2>, <值3>:
// 多值匹配
switch color {
case "red", "green", "blue":
fmt.Println("基础颜色");
}
基本写法:fallthrough 穿透
fallthrough
// fallthrough 继续执行下一个 case
switch n := 2; n {
case 1:
fmt.Println("一");
fallthrough;
case 2:
fmt.Println("二");
fallthrough;
case 3:
fmt.Println("三");
}
基本写法:无条件 switch
switch { case <条件>: ... }
// 无条件 switch
score := 85;
switch {
case score >= 90:
fmt.Println("A");
case score >= 80:
fmt.Println("B");
default:
fmt.Println("D");
}
break 与 continue
基本写法:continue 跳过
continue
// 跳过当前迭代
for i := 0; i < 10; i++ {
if i == 3 {
continue;
}
fmt.Println(i);
}
基本写法:break 退出
break
// 退出循环
for i := 0; i < 10; i++ {
if i == 7 {
break;
}
fmt.Println(i);
}
基本写法:标签跳转
break <标签>
// 标签跳转跳出外层循环
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer;
}
}
}
defer 语句
基本写法:基本 defer
defer <函数调用>
// 确保文件关闭
func readFile(path string) {
file, err := os.Open(path);
if err != nil {
return;
}
defer file.Close();
}
基本写法:defer 执行顺序
defer <函数调用>
// 多个 defer 按后进先出执行
defer fmt.Println("第一"); // 最后执行
defer fmt.Println("第二"); // 第二执行
defer fmt.Println("第三"); // 最先执行
基本写法:defer 参数求值
defer <函数>(<参数>)
// 参数在声明时求值
x := 10;
defer fmt.Println(x); // 输出 10
x = 20;
基本写法:defer 修改命名返回值
defer func() { ... }()
// defer 修改命名返回值
func double() (result int) {
defer func() {
result *= 2;
}();
return 5;
}
Go 1.24+ 新特性
基本写法:Go 1.24 generic type aliases
type <别名>[T] = <类型>[T]
// 泛型类型别名:为泛型类型定义简短别名
type List[T] = []T
type Map[K, V] = map[K]V
type Set[T comparable] = map[T]struct{}
// 使用别名声明变量
var names List[string] = []string{"Go", "Rust"}
var ages Map[string, int] = map[string]int{"Alice": 30}
基本写法:Go 1.23 range-over-func(1.22 为 GOEXPERIMENT=rangefunc 实验)
for <x> := range <func> { }
// range 遍历函数:迭代器函数签名为 func(yield func(T) bool)
// Go 1.22 以 GOEXPERIMENT=rangefunc 预览,Go 1.23 起正式稳定并配套 iter 包
func gen(yield func(int) bool) {
for i := 0; i < 3; i++ {
if !yield(i * 10) {
return
}
}
}
// 使用 range-over-func 直接遍历函数
for v := range gen {
fmt.Println(v) // 依次输出 0 10 20
}
基本写法:Go 1.24 weak pointer 与清理回调
weak.Make(<obj>) / runtime.AddCleanup(<obj>, <cleanup>, <arg>)
// runtime.AddCleanup 注册清理回调,是 SetFinalizer 的安全替代;
// 配套的 weak 包(weak.Make/weak.Pointer)提供不阻止 GC 的弱引用
type Big struct{ data [1024]byte }
obj := &Big{}
obj.data[0] = 42
// 当 obj 被 GC 回收时执行清理函数,arg 作为参数传入
runtime.AddCleanup(obj, func(arg int) {
fmt.Println("对象被回收,传入参数 =", arg)
}, 100)
// 主动解除引用,等待 GC 触发清理
obj = nil
runtime.GC()
基本写法:toolchain 指令(Go 1.21)与 tool 指令(Go 1.24)
// go.mod:toolchain 指令自 Go 1.21 引入,锁定本模块使用的工具链版本;
// GOTOOLCHAIN=auto(默认)时会按需自动下载对应工具链
module example.com/myapp
go 1.24.0
toolchain go1.24.3
// Go 1.24 新增 tool 指令:把 stringer 等开发期工具纳入模块依赖
tool golang.org/x/tools/cmd/stringer
# 添加 tool 依赖后,用 go tool 子命令直接调用
go get -tool golang.org/x/tools/cmd/stringer
go tool stringer -type=Status
基本写法:Go 1.26 new(expr) 内置函数
new(<expr>)
// Go 1.26:new 支持任意表达式,返回指向表达式结果的指针
x := 42
// 直接对表达式结果取地址
p := new(x*2 + 1) // *int,指向值为 85 的内存
fmt.Println(*p) // 输出 85
// 等价于传统写法
// v := x*2 + 1; p := &v