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

一个 Go 程序的执行起点是位于 main 包中的 main() 函数。要构建合法程序,必须遵循以下守则:
package 包名 语句来设定;main 包能够定义作为程序入口的 main() 函数;import 指令,它同时支持单行与多行两种导入格式。// 单行导入
import "fmt"
// 多行导入(推荐,更清晰)
import (
"fmt"
"math"
)
// 程序入口:无参数、无返回值
func main() {
fmt.Println("Hello, Go!") // 输出:Hello, Go!
}
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 []
}
常量的定义离不开 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 的数据可划分为值类型(按值拷贝传递)和引用类型(通过指针传递)。以下是其核心类型概览:
| 类型类别 | 具体类型 | 说明 |
|---|---|---|
| 整数类型 | 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
}
数组是固定长度、元素类型相同的集合。其长度是类型的一部分,因此 [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]
切片是一种动态长度的“数组”,其底层依托于数组实现。它的核心结构包含指针+长度+容量三部分,声明时无需指定长度。
// 声明方式
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=容量)
映射(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 表示键是否存在)
结构体是一种自定义的复合类型,它允许我们将多个不同类型的字段封装在一起,以形成一个新的类型。
// 定义结构体
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
指针是存储另一个变量内存地址的变量。它的主要用途在于修改函数参数的值,因为值类型在函数参数传递时默认是拷贝传递的。
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 // 修改指针指向的变量值
}
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("合格")
}
}
Go 语言中,循环结构仅有 for 这一种,它主要支持以下四种用法:
i := 0
for i < 5 { // 条件满足时循环
fmt.Println(i)
i++
}
for j := 0; j < 3; j++ {
fmt.Println(j) // 输出:0 1 2
}
count := 0
for { // 等价于 for ; ; {}
count++
if count == 3 {
break // 跳出循环
}
fmt.Println(count) // 输出:1 2
}
该结构专门用于遍历数组、切片、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)
}
break:用于立即跳出当前的循环或 switch 块;continue:用于跳过当前循环迭代中的后续代码,直接进入下一次迭代;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
}
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("合格")
}
}
其核心语法结构为: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
}
}
在声明返回值时即指定其名称,函数体内可直接对这些变量赋值,最后的 return 语句无需再带参数,使代码更简洁。
func calc(a, b int) (sum, sub int) {
sum = a + b
sub = a - b
return // 裸返回,自动返回 sum 和 sub
}
若参数列表的最后一个参数以 ...类型 的形式声明,则该函数可以接受任意数量的该类型参数,在函数内部,这些参数会被当作一个切片来处理。
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
在 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)来实现多态,其核心理念是“鸭子类型”,即“如果它看起来像鸭子,走起来像鸭子,那它就是鸭子”。
接口是由一组方法签名组成的集合,它只定义“做什么”,而不定义“怎么做”(即不包含具体实现)。
// 定义接口
type Animal interface {
Speak() string // 方法签名:无参数,返回 string
Move() string
}
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)
}
接口类型的变量可以存储任何实现了该接口的结构体实例。在调用接口变量的方法时,程序会自动执行该实例所对应的具体实现。
func main() {
var animal Animal // 接口变量
animal = Dog{Name: "旺财"}
fmt.Println(animal.Speak()) // 输出:旺财 汪汪叫
fmt.Println(animal.Move()) // 输出:旺财 跑起来
animal = Cat{Name: "咪宝"}
fmt.Println(animal.Speak()) // 输出:咪宝 喵喵叫
}
不包含任何方法签名的接口被称为空接口。它可以用来存储任意类型的值(其作用类似于 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 类型值来处理程序中的错误。
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("年龄合法")
}
通过让结构体实现 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 在语言层面原生支持并发。它通过goroutine(轻量级线程)和channel(通道)实现了“通信顺序进程(CSP)”模型,这种方式既简单又高效。
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) // 简单等待(不推荐)
}
Channel 用于 goroutine 之间的通信,它既能解决数据竞争问题,又能实现“同步 + 传值”的功能。通道主要分为无缓冲通道和有缓冲通道两类。
ch <- val)会阻塞当前 goroutine,直到有另一个 goroutine 准备好接收数据;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) // 输出:发送完成
}
func main() {
ch := make(chan int, 2) // 有缓冲通道,容量 2
ch <- 1 // 发送成功(缓冲区未满)
ch <- 2 // 发送成功(缓冲区未满)
// ch <- 3 // 阻塞(缓冲区满)
fmt.Println(<-ch) // 接收 1,缓冲区剩余 1
fmt.Println(<-ch) // 接收 2,缓冲区空
}
close(ch) 可以关闭一个通道。通道关闭后,不能再向其中发送数据,但可以继续从中接收数据;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
}
}
除了使用 channel,sync 包也提供了多种工具(如互斥锁、等待组)来实现同步。
等待组是 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("所有工作完成")
}
互斥锁主要用于解决多个 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.Args、os.Open()、os.Exit() |
io/ioutil | 文件读写(简化版) | ioutil.ReadFile()、ioutil.WriteFile() |
bufio | 带缓冲的读写(高效) | bufio.NewReader()、bufio.Scanner |
net/http | HTTP 客户端 / 服务端 | http.Get()、http.HandleFunc() |
encoding/json | JSON 序列化 / 反序列化 | json.Marshal()、json.Unmarshal() |
time | 时间处理 | time.Now()、time.Sleep()、time.Parse() |
Name 和 name 会被视为两个完全不同的变量或函数名;make 函数或直接赋值的方式来初始化它们;本文从基础结构到并发模型,全面梳理了 Go 语言的核心语法,为学习与实践提供了清晰路径。掌握这些要点,便能在工程开发中有效运用其简洁高效的特性。