Go 基础语法

5 min入门

变量与常量、基本类型、零值、类型转换、字符串、指针、控制流与 defer 语句。

前置知识

学习目标

  • 掌握「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 类型一览

类别类型说明
布尔booltrue 或 false
整数int8, int16, int32, int64, int有符号整数
整数uint8, uint16, uint32, uint64, uint无符号整数
整数byteuint8 的别名
整数runeint32 的别名,表示 Unicode 码点
浮点float32, float64IEEE 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