基础语法:变量、类型与控制流

🔊

本文基于 Go 1.26。

上一篇我们聊了 Go 的设计理念和第一个 Hello World 程序。这一篇直接上手语法——变量、类型、控制流、函数、复合类型和指针。核心目标只有一个:让你能读懂和写出基本的 Go 代码

Go 的语法设计哲学是简洁胜过繁复——去掉不必要的语法糖,保持清晰直观。如果你从其他语言转过来,会发现 Go 的学习曲线相对平缓,因为它的语法特性不多,但每一个都很实用。

变量与常量

Go 的变量声明非常灵活,提供了多种方式,核心原则是明确意图,避免歧义

var 声明

var 关键字可以声明变量,支持多种形式:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
    // 标准声明(显式类型)
    var name string = "Alice"
    var age int = 25

    // 类型推断
    var city = "Beijing"  // 推断为 string
    var score = 95.5      // 推断为 float64

    // 批量声明(推荐用于相关变量)
    var (
        username string = "user123"
        password string = "secret"
        isActive bool   = true
    )

    fmt.Println(name, age, city, score)
    fmt.Println(username, password, isActive)
}

短声明 :=

Go 最常用的变量声明方式是短声明 :=,它只能在函数内部使用:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func main() {
    // 短声明(自动类型推断)
    name := "Bob"
    count := 42
    pi := 3.14159

    // 多变量同时声明
    x, y := 10, 20

    // 在 if/for 等控制结构中声明临时变量(非常常见)
    if err := doSomething(); err != nil {
        fmt.Println("Error:", err)
    }

    fmt.Println(name, count, pi, x, y)
}

func doSomething() error {
    return nil
}

重要细节:

  • := 只能用于函数内,不能用于包级变量
  • 同一个作用域内,:= 不能重复声明已有变量(除非至少有一个是新变量)
  • := 会自动推断类型,你不需要写类型

常量 const

常量用 const 声明,编译期确定:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const (
    Pi      = 3.141592653589793
    MaxSize = 100
    Version = "1.0.0"
)

const (
    StatusOK    = 200
    StatusError = 500
)

零值初始化:Go 中每个类型都有零值,声明变量但未初始化时自动获得零值:

  • 数值类型:0
  • 布尔类型:false
  • 字符串:""(空字符串)
  • 指针、slice、map、channel、interface、function:nil

基本数据类型

Go 的类型系统清晰直观,没有复杂的类型层级。

基础类型

类型类别Go 类型说明
有符号整数int8, int16, int32, int648/16/32/64 位有符号整数
无符号整数uint8, uint16, uint32, uint648/16/32/64 位无符号整数
平台相关int, uint32 位系统上为 32 位,64 位系统上为 64 位
指针大小uintptr无符号整数,足够存储指针
浮点数float32, float64IEEE 754 标准(推荐用 float64)
复数complex64, complex128实部和虚部都是 float32/float64
布尔值booltrue / false
字符串stringUTF-8 编码的不可变字节序列

类型转换

Go 不支持隐式类型转换,必须显式转换:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func main() {
    var i int = 42
    var f float64 = float64(i)  // 显式转换
    var u uint = uint(f)

    // 字符串转换(小心陷阱)
    var s string = string(i)    // 不是 "42",而是对应 Unicode 码点(42 是 '*')
    var s2 string = string(65)  // "A"(ASCII 65)

    // 正确的整数到字符串转换
    var s3 string = fmt.Sprintf("%d", i)  // "42"

    // 字符串到整数
    var num int
    fmt.Sscanf("42", "%d", &num)  // num = 42

    fmt.Println(f, u, s, s2, s3, num)
}

常见陷阱

  • string(65) 不是 "65",而是 "A"(ASCII 65)
  • string([]byte{'H','i'}) 才是字符串字面量转换的正确方式
  • 整数到字符串用 fmt.Sprintf("%d", num)strconv.Itoa(num)

控制流

Go 的控制流简洁直接——只有 ifforswitch 三种,没有 while

if/else

Go 的 if 语句支持前置语句(常用于错误处理):

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func main() {
    // 标准 if/else
    x := 10
    if x > 5 {
        fmt.Println("x is greater than 5")
    } else {
        fmt.Println("x is not greater than 5")
    }

    // if/else if/else
    score := 85
    if score >= 90 {
        fmt.Println("优秀")
    } else if score >= 60 {
        fmt.Println("及格")
    } else {
        fmt.Println("不及格")
    }

    // 带前置语句的 if(非常常见)
    if err := doWork(); err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Work completed successfully")
}

func doWork() error {
    return nil
}

前置语句的作用域:前置语句声明的变量只在 if 语句块内可见,这避免了命名污染。

for 循环(唯一循环)

Go 只有 for 一种循环,但它可以扮演 C 语言中 forwhiledo-while 的所有角色:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func main() {
    // 1. 标准 C 风格 for 循环
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    // 2. 类似 while 的条件循环(只有条件)
    i := 0
    for i < 10 {
        fmt.Println(i)
        i++
    }

    // 3. 无限循环(死循环)
    count := 0
    for {
        fmt.Println("Tick")
        count++
        if count >= 5 {
            break
        }
    }

    // 4. for range(遍历集合)
    numbers := []int{1, 2, 3, 4, 5}
    for index, value := range numbers {
        fmt.Printf("Index: %d, Value: %d\n", index, value)
    }

    // 只要值,不要索引
    for _, value := range numbers {
        fmt.Println("Value:", value)
    }

    // 只要索引,不要值
    for index := range numbers {
        fmt.Println("Index:", index)
    }

    // 遍历 map
    m := map[string]int{"Alice": 25, "Bob": 30}
    for key, value := range m {
        fmt.Printf("%s: %d\n", key, value)
    }

    // 遍历字符串(按 rune 遍历,不是按字节)
    for index, runeValue := range "Hello, 世界" {
        fmt.Printf("Index: %d, Rune: %c\n", index, runeValue)
    }
}

for range 的坑

  • 遍历字符串时,index 是字节位置(不是字符位置),runeValue 是 Unicode 字符
  • 遍历 slice/array 时,元素是拷贝,不是引用
  • 遍历 map 时,顺序不确定(map 是无序的)

switch

Go 的 switch 不需要 break(不会自动 fallthrough),也可以省略条件(相当于 if-else if-else):

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func main() {
    // 标准 switch
    grade := 'B'
    switch grade {
    case 'A':
        fmt.Println("优秀")
    case 'B':
        fmt.Println("良好")
    case 'C':
        fmt.Println("及格")
    default:
        fmt.Println("其他")
    }

    // 多条件匹配
    num := 3
    switch num {
    case 1, 3, 5, 7, 9:
        fmt.Println("奇数")
    case 2, 4, 6, 8, 10:
        fmt.Println("偶数")
    default:
        fmt.Println("其他")
    }

    // 无条件的 switch(替代 if-else if-else)
    score := 75
    switch {
    case score >= 90:
        fmt.Println("优秀")
    case score >= 60:
        fmt.Println("及格")
    default:
        fmt.Println("不及格")
    }

    // 类型 switch(判断接口类型)
    var i interface{} = "hello"
    switch v := i.(type) {
    case string:
        fmt.Println("String:", v)
    case int:
        fmt.Println("Int:", v)
    default:
        fmt.Println("Unknown type")
    }
}

关键点

  • 不需要 break(除非显式 fallthrough
  • 可以匹配多个值(case 1, 3, 5:
  • 无条件的 switchif-else if-else 的清晰替代品
  • 类型 switch 判断接口的实际类型(接口在后面系列会详细讲)

函数

Go 的函数设计简洁但强大,支持多返回值、命名返回值、可变参数等特性。

基本函数

go
1
2
3
4
5
6
7
8
9
// 基本函数(单返回值)
func add(a int, b int) int {
    return a + b
}

// 多个参数类型相同时可以简写
func add2(a, b int) int {
    return a + b
}

多返回值

Go 原生支持多返回值,这是错误处理的基础:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 多返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// 调用多返回值函数
func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)

    // 忽略某个返回值
    result2, _ := divide(10, 5)  // 忽略错误(不推荐)
    fmt.Println("Result2:", result2)
}

命名返回值

返回值可以命名,函数内可以直接使用这些变量:

go
1
2
3
4
5
6
7
8
9
// 命名返回值
func rectArea(width, height float64) (area float64) {
    area = width * height
    return  // 相当于 return area
}

func main() {
    fmt.Println(rectArea(3, 4))  // 12
}

命名返回值的优势

  • 自动文档化返回值的含义
  • 在复杂函数中减少重复代码
  • 隐式 return 返回所有命名返回值

可变参数

Go 使用 ... 声明可变参数:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 可变参数(类型为 []int)
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

// 调用可变参数函数
func main() {
    fmt.Println(sum(1, 2, 3))        // 6
    fmt.Println(sum(1, 2, 3, 4, 5))  // 15

    // 展开切片
    nums := []int{1, 2, 3}
    fmt.Println(sum(nums...))        // 6
}

关键点

  • 可变参数本质上是一个切片([]int
  • 调用函数时用 ... 展开切片
  • 可变参数必须是参数列表的最后一个

复合类型

Go 提供了数组、切片、映射和结构体等复合类型。

数组和切片

数组是固定长度的,切片是动态长度的(更常用):

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func main() {
    // 数组(固定长度)
    var arr1 [5]int                    // 零值数组 [0 0 0 0 0]
    arr2 := [5]int{1, 2, 3, 4, 5}      // 显式初始化
    arr3 := [...]int{1, 2, 3}          // 自动推断长度([3]int)

    // 切片(动态长度,更常用)
    var s1 []int                       // nil 切片
    s2 := []int{1, 2, 3, 4, 5}         // 直接初始化
    s3 := make([]int, 5)               // make 创建(全 0)
    s4 := make([]int, 5, 10)           // length=5, capacity=10

    // 切片操作
    fmt.Println(s2[0])                 // 访问元素:1
    fmt.Println(s2[1:3])               // 切片:[2 3]
    fmt.Println(len(s2))               // 长度:5
    fmt.Println(cap(s2))               // 容量:5

    // 添加元素(可能触发扩容)
    s5 := []int{}
    s5 = append(s5, 1)
    s5 = append(s5, 2, 3, 4)
    fmt.Println(s5)                    // [1 2 3 4]

    // 复制切片
    s6 := make([]int, len(s5))
    copy(s6, s5)
    fmt.Println(s6)                    // [1 2 3 4]
}

数组和切片的关键区别

  • 数组:固定长度,长度是类型的一部分([3]int[4]int 是不同类型)
  • 切片:动态长度,是对数组的引用(引用类型)
  • 切片的底层:切片包含三个信息:指针(指向数组)、长度、容量

map(映射)

Go 的 map 是无序的键值对集合:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func main() {
    // 创建 map
    m1 := make(map[string]int)           // 空 map
    m2 := map[string]int{"Alice": 25, "Bob": 30}  // 直接初始化

    // 添加/修改
    m1["Charlie"] = 35
    m1["David"] = 40

    // 读取
    fmt.Println(m1["Charlie"])           // 35
    fmt.Println(m2["Alice"])             // 25

    // 读取并判断是否存在
    value, exists := m2["Bob"]
    if exists {
        fmt.Println("Bob's age:", value)
    }

    value2, exists2 := m2["NonExistent"]
    if !exists2 {
        fmt.Println("NonExistent not found, value:", value2)  // 0(零值)
    }

    // 删除
    delete(m2, "Alice")

    // 遍历 map(顺序不确定)
    for key, value := range m1 {
        fmt.Printf("%s: %d\n", key, value)
    }
}

map 的关键点

  • map 是无序的(每次遍历顺序可能不同)
  • 读取不存在的键返回类型的零值
  • 读取时用 value, exists := m[key] 判断键是否存在
  • map 是引用类型(函数内修改会影响原 map)

struct(结构体)

Go 没有类,用 struct 和方法组合实现面向对象:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 定义结构体
type Person struct {
    Name string
    Age  int
}

// 结构体方法(在后面系列会详细讲)
func (p Person) Introduce() string {
    return fmt.Sprintf("My name is %s, I'm %d years old", p.Name, p.Age)
}

func main() {
    // 创建结构体
    p1 := Person{Name: "Alice", Age: 25}
    p2 := Person{"Bob", 30}             // 按顺序初始化
    p3 := Person{}                      // 零值

    // 访问字段
    fmt.Println(p1.Name)                // Alice
    fmt.Println(p2.Age)                 // 30

    // 修改字段
    p1.Age = 26

    // 结构体指针
    p4 := &Person{Name: "Charlie", Age: 35}
    p4.Age = 36                         // 自动解引用

    // 调用方法
    fmt.Println(p1.Introduce())
}

指针基础

Go 有指针,但没有指针算术(出于安全考虑):

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func main() {
    // 声明指针(零值为 nil)
    var p *int

    // 取地址
    x := 42
    p = &x  // p 指向 x 的地址

    // 解引用
    fmt.Println(*p)  // 42

    // 通过指针修改值
    *p = 100
    fmt.Println(x)   // 100

    // 函数参数(值传递 vs 指针传递)
    num := 10
    modifyByValue(num)
    fmt.Println(num)          // 10(不变)

    modifyByPointer(&num)
    fmt.Println(num)          // 20(改变)
}

func modifyByValue(n int) {
    n = 20  // 不影响原变量
}

func modifyByPointer(n *int) {
    *n = 20  // 修改原变量
}

Go 指针 vs C 指针

  • Go 没有:指针算术(p++)、void*、函数指针(用函数值替代)
  • Go 有& 取地址、* 解引用、nil 指针
  • 安全性:Go 的指针不能指向无效地址,降低了内存安全风险

本篇小结

到此我们已经覆盖了 Go 基础语法的核心——变量与常量、基本数据类型、控制流、函数、复合类型和指针。你现在已经能读懂大部分 Go 代码了。

如果记住一句话就够了:Go 的语法简洁直观,变量声明灵活(var/:=),类型转换必须显式,控制流只有 for 循环一种,函数支持多返回值,切片和 map 是最常用的复合类型,指针安全但无算术。

下一篇我们深入 Go 的面向对象特性——struct、方法、接口,以及 Go 最独特的错误处理模式(error 接口、panic/recover)。