go语言语法基础全面梳理:适合入门

作者:袖梨 2026-06-04

Go,亦称 Golang,作为一种静态编译型语言,以其简洁性、高并发安全性和贴近工程实践的语法设计著称。本文旨在系统梳理其核心语法,既适合初学者构建知识地图,也可作为开发者的日常速查手册。

go语言语法基础的全面梳理(适合入门)

一、基础语法:程序结构与变量常量

1. 程序入口与包声明

一个 Go 程序的执行起点是位于 main 包中的 main() 函数。要构建合法程序,必须遵循以下守则:

  1. 每个源文件都归属于一个特定的包,这通过文件首行的 package 包名 语句来设定;
  2. 唯有 main 包能够定义作为程序入口的 main() 函数;
  3. 引入外部依赖包时需使用 import 指令,它同时支持单行与多行两种导入格式。
// 单行导入
import "fmt"
// 多行导入(推荐,更清晰)
import (
    "fmt"
    "math"
)
// 程序入口:无参数、无返回值
func main() {
    fmt.Println("Hello, Go!") // 输出:Hello, Go!
}

2. 变量声明

Go 语言提供了四种变量声明方式,其核心规则可总结为:强类型,且声明即自动初始化(赋予默认零值)。

声明方式语法示例适用场景
完整声明var 变量名 类型 = 值类型明确,需显式指定
类型推导(推荐)var 变量名 = 值编译器自动推导类型,简洁
短变量声明(仅函数内)变量名 := 值函数内快速声明,最常用
批量声明var (a int = 1; b string = "test")多变量集中声明,减少重复代码

零值规则:但凡未显式赋值的变量,都会被赋予其类型对应的默认零值。例如,int 类型的零值是 0,string 是空字符串 "",bool 是 false,而指针则为 nil。

func main() {
    var a int = 10          // 完整声明
    var b = "hello"         // 类型推导(string 类型)
    c := 3.14               // 短变量声明(float64 类型)
    var (
        d bool              // 零值:false
        e []int             // 零值:nil(切片)
    )
    fmt.Println(a, b, c, d, e) // 输出:10 hello 3.14 false []
}

3. 常量声明

常量的定义离不开 const 关键字,其值必须在编译期就能确定,通常为数字、字符串或布尔值。此外,const 也支持批量声明,并能与 iota 配合实现枚举效果。

// 基本常量
const PI = 3.14159
const NAME string = "Go"

// 批量声明
const (
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
)

// iota 枚举(自增常量,默认从 0 开始,每行+1)
const (
    A = iota // 0
    B = iota // 1
    C = 100  // 手动赋值,打破自增
    D = iota // 3(恢复自增,继续计数)
)

二、数据类型:值类型与引用类型

Go 的数据可划分为值类型(按值拷贝传递)和引用类型(通过指针传递)。以下是其核心类型概览:

1. 基本类型(值类型)

类型类别具体类型说明
整数类型int8/int16/int32/int64、uint8/uint16...int 的位数与系统位数(32/64 位)一致,uint 同理
浮点类型float32(精度约 6 位)、float64(默认)float64 精度更高,推荐优先使用
布尔类型bool取值 true/false,不可用 0/1 替代
字符串类型string不可变(修改需重新生成),UTF-8 编码
字符类型rune(别名 int32,表示 Unicode 字符)区别于 byte(uint8,ASCII 字符)
func main() {
    var age int = 25
    var weight float64 = 68.5
    var isStudent bool = true
    var name string = "张三"
    var char rune = '中' // 注意:单引号表示字符,双引号表示字符串
    
    fmt.Printf("%T %T %T %T %Tn", age, weight, isStudent, name, char)
    // 输出:int float64 bool string int32
}

2. 复合类型

(1)数组(值类型)

数组是固定长度、元素类型相同的集合。其长度是类型的一部分,因此 [3]int[5]int 被视为两种截然不同的类型。

// 声明方式
var arr1 [3]int = [3]int{1, 2, 3} // 完整声明
arr2 := [3]int{4, 5, 6}           // 类型推导
arr3 := [...]int{7, 8, 9}         // 自动推导长度

// 访问与修改
fmt.Println(arr1[0]) // 输出:1
arr2[1] = 10
fmt.Println(arr2) // 输出:[4 10 6]

(2)切片(引用类型)

切片是一种动态长度的“数组”,其底层依托于数组实现。它的核心结构包含指针+长度+容量三部分,声明时无需指定长度。

// 声明方式
var s1 []int               // 零值:nil
s2 := []int{1, 2, 3}       // 直接初始化
s3 := make([]int, 3, 5)    // make(类型, 长度, 容量):长度 3,容量 5

// 常用操作
s4 := append(s2, 4, 5)     // 追加元素(容量不足时自动扩容)
s5 := s2[1:3]              // 切片截取:[start:end),左闭右开
fmt.Println(len(s3), cap(s3)) // 输出:3 5(len=长度,cap=容量)

(3)映射(map,引用类型)

映射(map)是一个无序的键值对集合。作为键的类型必须是“可比较的”,例如 int、string、bool 等,而切片、map 等则不能作为键。

// 声明与初始化(必须用 make 或直接赋值,否则为 nil,无法添加元素)
m1 := make(map[string]int)       // 空 map
m2 := map[string]string{
    "name": "李四",
    "age":  "28",
}

// 增删改查
m1["score"] = 90                // 新增/修改
fmt.Println(m1["score"])        // 查询:90
delete(m1, "score")             // 删除
v, ok := m1["score"]            // 安全查询(避免键不存在时返回零值)
fmt.Println(v, ok)              // 输出:0 false(ok 表示键是否存在)

(4)结构体(值类型)

结构体是一种自定义的复合类型,它允许我们将多个不同类型的字段封装在一起,以形成一个新的类型。

// 定义结构体
type Person struct {
    Name string
    Age  int
    Sex  string
}

// 初始化
p1 := Person{Name: "王五", Age: 30, Sex: "男"} // 指定字段名(推荐)
p2 := Person{"赵六", 26, "女"}                // 按字段顺序(不推荐,易出错)

// 访问字段
fmt.Println(p1.Name) // 输出:王五
p2.Age = 27

(5)指针(引用类型)

指针是存储另一个变量内存地址的变量。它的主要用途在于修改函数参数的值,因为值类型在函数参数传递时默认是拷贝传递的。

func main() {
    a := 10
    var p *int = &a // &a:取变量 a 的地址;p 是 *int 类型指针
    fmt.Println(*p) // *p:通过指针访问变量的值,输出:10
    
    modify(p)       // 传递指针
    fmt.Println(a)  // 输出:20(变量被修改)
}

// 函数接收指针参数
func modify(x *int) {
    *x = 20 // 修改指针指向的变量值
}

三、流程控制:条件、循环、跳转

1. 条件语句(if-else)

  1. 包裹条件表达式不需要括号;
  2. 条件表达式的计算结果必须是 bool 类型,不能用 0 或 1 来替代;
  3. 支持在条件前书写“初始化语句”,该语句中声明的变量作用域仅限于整个 if 块内。
func main() {
    age := 18
    // 基础用法
    if age >= 18 {
        fmt.Println("成年")
    } else {
        fmt.Println("未成年")
    }
    
    // 带初始化语句
    if score := 95; score >= 90 {
        fmt.Println("优秀")
    } else if score >= 80 {
        fmt.Println("良好")
    } else {
        fmt.Println("合格")
    }
}

2. 循环语句(for)

Go 语言中,循环结构仅有 for 这一种,它主要支持以下四种用法:

(1)普通循环(类似 while)

i := 0
for i < 5 { // 条件满足时循环
    fmt.Println(i)
    i++
}

(2)经典 for 循环(初始化 + 条件 + 自增)

for j := 0; j < 3; j++ {
    fmt.Println(j) // 输出:0 1 2
}

(3)无限循环(无条件)

count := 0
for { // 等价于 for ; ; {}
    count++
    if count == 3 {
        break // 跳出循环
    }
    fmt.Println(count) // 输出:1 2
}

(4)循环遍历(for range)

该结构专门用于遍历数组、切片、map 和字符串等,书写更简洁,效率也更高:

// 遍历切片
s := []int{1, 2, 3}
for index, value := range s {
    fmt.Printf("索引:%d,值:%dn", index, value)
}

// 遍历 map(无序)
m := map[string]int{"a":1, "b":2}
for key, val := range m {
    fmt.Printf("键:%s,值:%dn", key, val)
}

// 遍历字符串(按 rune 遍历,支持中文)
str := "Hello 世界"
for i, c := range str {
    fmt.Printf("索引:%d,字符:%cn", i, c)
}

3. 跳转语句(break、continue、goto)

  1. break:用于立即跳出当前的循环或 switch 块;
  2. continue:用于跳过当前循环迭代中的后续代码,直接进入下一次迭代;
  3. goto:用于跳转到当前函数内指定的标签位置(需谨慎使用,以免破坏代码的清晰结构)。
// break 示例(跳出循环)
for i := 0; i < 5; i++ {
    if i == 3 {
        break
    }
    fmt.Println(i) // 输出:0 1 2
}

// goto 示例
func main() {
    fmt.Println("1")
    goto label
    fmt.Println("2") // 不会执行
label:
    fmt.Println("3") // 输出:1 3
}

4. 选择语句(switch)

Go 的 switch 语句更为灵活,它支持任意类型,且无需显式书写 break(默认自动跳出,如需穿透到下一个 case 需使用 fallthrough)。

func main() {
    num := 2
    switch num {
    case 1:
        fmt.Println("一")
    case 2:
        fmt.Println("二")
    case 3:
        fmt.Println("三")
    default:
        fmt.Println("其他")
    }

    // 带初始化语句
    switch score := 85; { // 条件可以是表达式(类似 if-else 链)
    case score >= 90:
        fmt.Println("优秀")
    case score >= 80:
        fmt.Println("良好")
    default:
        fmt.Println("合格")
    }
}

四、函数:声明、参数、返回值

1. 函数声明

其核心语法结构为:func 函数名(参数列表) (返回值列表) { 函数体 }

// 无参数、无返回值
func sayHello() {
    fmt.Println("Hello, Go Function!")
}

// 有参数、有返回值(单返回值)
func add(a int, b int) int { // 参数类型相同可简写为 (a, b int)
    return a + b
}

// 多返回值(Go 特色,常用于返回结果+错误)
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为 0") // 返回错误
    }
    return a / b, nil // 无错误返回 nil
}

// 调用函数
func main() {
    sayHello()
    sum := add(10, 20)
    fmt.Println(sum) // 输出:30
    
    res, err := divide(10, 2)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(res) // 输出:5
    }
}

2. 函数进阶特性

(1)命名返回值

在声明返回值时即指定其名称,函数体内可直接对这些变量赋值,最后的 return 语句无需再带参数,使代码更简洁。

func calc(a, b int) (sum, sub int) {
    sum = a + b
    sub = a - b
    return // 裸返回,自动返回 sum 和 sub
}

(2)可变参数

若参数列表的最后一个参数以 ...类型 的形式声明,则该函数可以接受任意数量的该类型参数,在函数内部,这些参数会被当作一个切片来处理。

func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

// 调用:可传递任意个 int 类型参数
fmt.Println(sum(1, 2, 3))       // 输出:6
fmt.Println(sum(4, 5, 6, 7))    // 输出:22

(3)函数作为参数 / 返回值(高阶函数)

在 Go 中,函数被视为“一等公民”,这意味着它们可以像其他类型一样,被当作参数传递给其他函数,也可以作为返回值从函数中返回。

// 函数作为参数
func operate(a, b int, f func(int, int) int) int {
    return f(a, b)
}

// 函数作为返回值
func getAddFunc() func(int, int) int {
    return func(a, b int) int {
        return a + b
    }
}

// 调用
addFunc := getAddFunc()
fmt.Println(operate(5, 3, addFunc)) // 输出:8

五、接口与面向对象

Go 语言并不具备传统的类与继承机制,而是通过接口(interface)来实现多态,其核心理念是“鸭子类型”,即“如果它看起来像鸭子,走起来像鸭子,那它就是鸭子”。

1. 接口声明

接口是由一组方法签名组成的集合,它只定义“做什么”,而不定义“怎么做”(即不包含具体实现)。

// 定义接口
type Animal interface {
    Speak() string // 方法签名:无参数,返回 string
    Move() string
}

2. 接口实现

Go 中并不需要显式声明“实现某一接口”。只要某个结构体实现了接口中定义的所有方法,它就自动被认为实现了该接口,这是一种非侵入式的设计。

// 定义结构体
type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

// Dog 实现 Animal 接口(实现所有方法)
func (d Dog) Speak() string {
    return fmt.Sprintf("%s 汪汪叫", d.Name)
}
func (d Dog) Move() string {
    return fmt.Sprintf("%s 跑起来", d.Name)
}

// Cat 实现 Animal 接口
func (c Cat) Speak() string {
    return fmt.Sprintf("%s 喵喵叫", c.Name)
}
func (c Cat) Move() string {
    return fmt.Sprintf("%s 跳起来", c.Name)
}

3. 接口使用(多态)

接口类型的变量可以存储任何实现了该接口的结构体实例。在调用接口变量的方法时,程序会自动执行该实例所对应的具体实现。

func main() {
    var animal Animal // 接口变量
    animal = Dog{Name: "旺财"}
    fmt.Println(animal.Speak()) // 输出:旺财 汪汪叫
    fmt.Println(animal.Move())  // 输出:旺财 跑起来
    
    animal = Cat{Name: "咪宝"}
    fmt.Println(animal.Speak()) // 输出:咪宝 喵喵叫
}

4. 空接口(interface {})

不包含任何方法签名的接口被称为空接口。它可以用来存储任意类型的值(其作用类似于 Java 中的 Object),常用于处理类型未知的情况。

// 空接口变量
var any interface{}
any = 10          // 存储 int
any = "hello"     // 存储 string
any = Dog{Name: "旺财"} // 存储结构体

// 类型断言(判断空接口存储的实际类型)
val, ok := any.(Dog)
if ok {
    fmt.Println(val.Name) // 输出:旺财
}

六、错误处理

Go 并未采用 try-catch 机制来捕获异常,而是遵循“错误也是值”的原则,通过显式地返回 error 类型值来处理程序中的错误。

1. 错误类型与创建

error 是一个内置的接口类型,其定义如下:type error interface { Error() string }。在实际开发中,我们常使用 fmt.Errorf()errors.New() 来创建错误实例。

import (
    "errors"
    "fmt"
)

func checkAge(age int) error {
    if age < 0 || age > 120 {
        // 创建错误:两种方式
        return errors.New("年龄必须在 0-120 之间")
        // return fmt.Errorf("年龄 %d 非法:必须在 0-120 之间", age) // 带格式化信息
    }
    return nil // 无错误返回 nil
}

// 调用:必须检查错误
func main() {
    err := checkAge(150)
    if err != nil {
        fmt.Println("错误:", err) // 输出:错误:年龄必须在 0-120 之间
        return
    }
    fmt.Println("年龄合法")
}

2. 自定义错误(进阶)

通过让结构体实现 error 接口,我们可以创建自定义错误类型,从而携带更丰富的错误信息,例如错误码。

// 自定义错误结构体
type MyError struct {
    Code    int
    Message string
}

// 实现 error 接口的 Error() 方法
func (e *MyError) Error() string {
    return fmt.Sprintf("错误码:%d,信息:%s", e.Code, e.Message)
}

// 返回自定义错误
func doSomething() error {
    return &MyError{Code: 500, Message: "服务器内部错误"}
}

// 调用
err := doSomething()
if err != nil {
    fmt.Println(err) // 输出:错误码:500,信息:服务器内部错误
    // 类型断言获取自定义错误信息
    if myErr, ok := err.(*MyError); ok {
        fmt.Println("错误码:", myErr.Code) // 输出:500
    }
}

七、并发编程(Go 核心特性)

Go 在语言层面原生支持并发。它通过goroutine(轻量级线程)channel(通道)实现了“通信顺序进程(CSP)”模型,这种方式既简单又高效。

1. Goroutine(协程)

  1. 这是一种由 Go 运行时(runtime)而非操作系统内核管理的轻量级线程;
  2. 它的启动成本极低(初始栈大小仅有 2KB,且能动态扩容),因此可以轻松支持百万级别的并发;
  3. 只需在函数或方法调用前加上 go 关键字即可启动一个 goroutine。
func sayHello(name string) {
    fmt.Printf("Hello, %sn", name)
}

func main() {
    // 启动 3 个 goroutine
    go sayHello("A")
    go sayHello("B")
    go sayHello("C")
    
    // 主 goroutine 退出后,子 goroutine 会被强制终止,需等待
    time.Sleep(100 * time.Millisecond) // 简单等待(不推荐)
}

2. Channel(通道)

Channel 用于 goroutine 之间的通信,它既能解决数据竞争问题,又能实现“同步 + 传值”的功能。通道主要分为无缓冲通道有缓冲通道两类。

(1)无缓冲通道(同步通道)

  1. 发送操作(ch <- val)会阻塞当前 goroutine,直到有另一个 goroutine 准备好接收数据;
  2. 接收操作(val := <-ch)同样会阻塞,直到有 goroutine 向该通道发送数据。
func send(ch chan int) {
    ch <- 10 // 发送 10 到通道,阻塞直到接收
    fmt.Println("发送完成")
}

func main() {
    ch := make(chan int) // 无缓冲通道(容量默认 0)
    
    go send(ch)          // 启动发送 goroutine
    
    val := <-ch          // 接收通道数据,阻塞直到发送
    fmt.Println("接收值:", val) // 输出:接收值:10
    
    time.Sleep(100 * time.Millisecond) // 输出:发送完成
}

(2)有缓冲通道(异步通道)

  1. 此类通道拥有一个大于 0 的容量,发送操作只有在缓冲区已满时才会阻塞;
  2. 而接收操作则只会在缓冲区为空时阻塞。
func main() {
    ch := make(chan int, 2) // 有缓冲通道,容量 2
    
    ch <- 1 // 发送成功(缓冲区未满)
    ch <- 2 // 发送成功(缓冲区未满)
    // ch <- 3 // 阻塞(缓冲区满)
    
    fmt.Println(<-ch) // 接收 1,缓冲区剩余 1
    fmt.Println(<-ch) // 接收 2,缓冲区空
}

(3)通道关闭与遍历

  1. 使用 close(ch) 可以关闭一个通道。通道关闭后,不能再向其中发送数据,但可以继续从中接收数据;
  2. 使用 for range 可以遍历一个通道,该循环会持续接收数据,直到通道被关闭。
func send(ch chan int) {
    for i := 0; i < 3; i++ {
        ch <- i
    }
    close(ch) // 发送完成后关闭通道
}

func main() {
    ch := make(chan int, 3)
    go send(ch)
    
    // 遍历通道,直到关闭
    for val := range ch {
        fmt.Println(val) // 输出:0 1 2
    }
}

3. sync 包(同步原语)

除了使用 channel,sync 包也提供了多种工具(如互斥锁、等待组)来实现同步。

(1)等待组(sync.WaitGroup)

等待组是 time.Sleep 的优雅替代品,用于等待多个 goroutine 完成任务。

func work(id int, wg *sync.WaitGroup) {
    defer wg.Done() // goroutine 完成后调用(计数器-1)
    fmt.Printf("工作 %d 开始n", id)
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("工作 %d 结束n", id)
}

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1) // 启动 goroutine 前计数器+1
        go work(i, &wg) // 传递指针(避免拷贝)
    }
    
    wg.Wait() // 阻塞,直到计数器为 0
    fmt.Println("所有工作完成")
}

(2)互斥锁(sync.Mutex)

互斥锁主要用于解决多个 goroutine 在并发修改共享变量时可能产生的数据竞争问题。

var (
    count int
    mutex sync.Mutex // 互斥锁
)

func increment() {
    mutex.Lock()         // 加锁
    defer mutex.Unlock() // 延迟解锁(确保函数退出时释放)
    count++
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("count:", count) // 输出:1000(无数据竞争)
}

八、常用标准库

Go 拥有一个功能强大的标准库。以下是入门阶段一些必不可少的核心库及其常用功能:

库名核心功能常用函数 / 类型
fmt输入输出格式化Println()Printf()Scanln()
os操作系统交互(文件、环境变量等)os.Argsos.Open()os.Exit()
io/ioutil文件读写(简化版)ioutil.ReadFile()ioutil.WriteFile()
bufio带缓冲的读写(高效)bufio.NewReader()bufio.Scanner
net/httpHTTP 客户端 / 服务端http.Get()http.HandleFunc()
encoding/jsonJSON 序列化 / 反序列化json.Marshal()json.Unmarshal()
time时间处理time.Now()time.Sleep()time.Parse()

九、语法核心注意事项

  1. 大小写敏感Namename 会被视为两个完全不同的变量或函数名;
  2. 语句结尾无分号:Go 编译器会自动添加分号,因此通常情况下,一行只写一个语句。如果要在同一行写多个语句,则必须用分号分隔;
  3. 变量 / 导入未使用:如果声明的变量或导入的包未被使用,Go 编译器会报错,这一机制有助于避免产生冗余代码;
  4. 值类型 vs 引用类型:值类型在赋值或传递时是拷贝传递,而引用类型则传递指针,意味着对引用类型的修改会影响到原数据;
  5. map 和切片必须初始化:一个 nil 的 map 或切片是无法直接添加元素的。必须通过 make 函数或直接赋值的方式来初始化它们;
  6. 并发安全:当多个 goroutine 并发地操作同一个共享变量时,必须使用互斥锁 (mutex) 或通道 (channel) 来同步数据访问,以避免出现数据竞争。

总结 

本文从基础结构到并发模型,全面梳理了 Go 语言的核心语法,为学习与实践提供了清晰路径。掌握这些要点,便能在工程开发中有效运用其简洁高效的特性。

相关文章

精彩推荐