Go 语言完整入门指南
Go(又称 Golang)是 Google 开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。本指南旨在为初学者提供一套系统、完整的 Go 语言入门教程,涵盖从环境搭建、基本语法、控制流,到结构体、接口、错误处理及并发编程等核心内容。
1. Go 语言简介与环境搭建
1.1 核心特点
- 简单易学:语法极其精简,只有 25 个关键字。
- 高并发支持:原生支持协程(Goroutine)和通道(Channel),并发编程心智负担低。
- 编译型语言:直接编译成机器码,执行效率极高,且部署时只需分发单个二进制文件。
- 内置垃圾回收 (GC):自动管理内存,避免手动释放的风险。
1.2 环境搭建
- 从 Go 官方网站 下载对应系统的安装包安装,或使用包管理器(如 macOS 下运行
brew install go)。 - 安装完成后,在终端运行以下命令验证:
go version
1.3 编写第一个程序 (Hello World)
- 创建一个新目录并进入:
mkdir hello-go && cd hello-go - 初始化 Go 模块(用于管理依赖包):
go mod init hello-go - 创建名为
main.go的文件,写入以下内容:package main import "fmt" func main() { fmt.Println("Hello, World!") } - 运行程序:
go run main.go - 编译程序:
go build main.go ./main-go # 运行编译生成的二进制文件
2. 变量、常量与基本数据类型
2.1 变量声明
Go 提供了多种变量声明方式:
package main
import "fmt"
func main() {
// 1. 声明并指定类型
var a int = 10
// 2. 声明并自动推导类型
var b = "Golang"
// 3. 简短变量声明(最常用,只能在函数体内使用)
c := true
// 4. 批量声明
var (
x int = 1
y string = "hello"
z float64 = 3.14
)
fmt.Println(a, b, c, x, y, z)
}2.2 常量
使用 const 声明常量,常量的值在编译期确定且不可修改。Go 提供了 iota 关键字用于快速生成递增的常量:
const PI = 3.14159
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)2.3 基本数据类型
- 布尔型:
bool(值为true或false) - 整型:有符号(
int,int8,int16,int32,int64)和无符号(uint,uint8,uint16,uint32,uint64)。其中byte是uint8的别名,rune是int32的别名(用于表示一个 Unicode 字符)。 - 浮点型:
float32,float64 - 复数型:
complex64,complex128 - 字符串:
string(双引号表示解释型字符串,反引号 ` 表示原生多行字符串)
3. 容器类型:数组、切片与映射
3.1 数组 (Array)
数组是长度固定且类型相同的元素序列。在 Go 中,数组的长度是其类型的一部分(如 [5]int 和 [10]int 是不同类型)。
var numbers [5]int // 默认初始化为 [0, 0, 0, 0, 0]
numbers[0] = 10
primes := [3]int{2, 3, 5}3.2 切片 (Slice)
切片是对数组一个连续片段的引用,长度动态可变。在实际开发中,切片的使用率远高于数组。
// 1. 基于数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 包含 arr[1], arr[2], arr[3],即 [2, 3, 4]
// 2. 使用 make 函数创建切片 (长度为 3, 容量为 5)
s := make([]int, 3, 5)
// 3. 追加元素 (当容量不足时,切片会自动扩容)
s = append(s, 100)3.3 映射 (Map)
映射是一种无序的键值对(Key-Value)集合,键的类型必须支持比较运算符。
// 1. 创建 Map (必须初始化后才能使用)
userAges := make(map[string]int)
userAges["Alice"] = 25
// 2. 字面量创建
scores := map[string]int{
"Math": 95,
"English": 88,
}
// 3. 查询键是否存在
score, exists := scores["Math"]
if exists {
fmt.Println("分数是:", score)
}
// 4. 删除键值对
delete(scores, "English")4. 控制结构
4.1 条件语句 (if-else)
Go 的 if 条件不需要括号,且支持在条件判断前执行一条初始化语句:
if num := 9; num < 0 {
fmt.Println(num, "是负数")
} else if num < 10 {
fmt.Println(num, "是个位数")
} else {
fmt.Println(num, "是多位数")
}4.2 条件分支 (switch)
Go 的 switch 分支默认不需要写 break,执行完一个分支后会自动退出。如果想继续执行下一个分支,需使用 fallthrough。
switch day := 2; day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
default:
fmt.Println("其他")
}4.3 循环语句 (for)
Go 语言中只有 for 循环,没有 while 或 do-while。
- 标准三段式循环:
for i := 0; i < 5; i++ { fmt.Println(i) } - 替代 while 循环:
n := 1 for n < 5 { n++ } - 无限循环:
for { // 循环体,需内部 break break } - 遍历容器 (for-range):
items := []string{"apple", "banana", "orange"} for index, value := range items { fmt.Printf("索引: %d, 值: %s\n", index, value) }
5. 函数与包管理
5.1 函数定义
Go 支持函数多返回值,也支持命名返回值:
// 定义一个计算面积和周长的函数,返回两个值
func calc(width, height float64) (float64, float64) {
area := width * height
perimeter := (width + height) * 2
return area, perimeter
}
// 命名返回值写法
func divide(a, b int) (result int, err error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
result = a / b
return // 自动返回 result 和 err
}5.2 包与导出规则 (Capitalization Rule)
- Go 以文件夹为单位组织包。每个 Go 源文件的第一行必须声明所属的包名(如
package main)。 - 导出规则:如果结构体、函数、变量、常量的名字以大写字母开头,表示它是公开的(Public),可以被其他包导入访问;如果以小写字母开头,则表示它是私有的(Private),仅在当前包内可见。
6. 指针、结构体与方法
6.1 指针 (Pointer)
指针存储的是变量的内存地址。Go 提供了指针,但不支持指针运算(无法像 C 语言那样对指针进行自增或偏移),这极大地保证了内存安全。
val := 42
p := &val // p 是指向 val 的指针 (& 获取地址)
fmt.Println(*p) // 42 (* 访问地址存储的值)6.2 结构体 (Struct)
结构体是由一系列类型相同的或不同的成员变量组合而成的集合,用于模拟面向对象中的实体。
type User struct {
Name string
Age int
Email string
}
func main() {
// 实例化结构体
u := User{
Name: "Bob",
Age: 30,
Email: "bob@example.com",
}
fmt.Println(u.Name)
}6.3 方法 (Method)
方法是一个包含了接收者的函数。接收者可以是结构体的值或指针。
// 值接收者:修改接收者的属性不会影响原结构体
func (u User) SayHello() {
fmt.Printf("你好,我是 %s\n", u.Name)
}
// 指针接收者:可以通过方法直接修改原结构体的属性
func (u *User) SetAge(newAge int) {
u.Age = newAge
}7. 接口 (Interface)
接口定义了一组行为规范。在 Go 中,接口的实现是隐式的:一个类型只要实现了接口中定义的所有方法,它就自动实现了该接口,无需像其他语言那样显式声明 implements。
package main
import "fmt"
// 定义接口
type Speaker interface {
Speak() string
}
type Dog struct{}
// Dog 实现了 Speak 方法,因此隐式实现了 Speaker 接口
func (d Dog) Speak() string {
return "汪汪"
}
type Cat struct{}
func (c Cat) Speak() string {
return "喵喵"
}
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
MakeSound(Dog{}) // 输出: 汪汪
MakeSound(Cat{}) // 输出: 喵喵
}空接口 (interface{} 或 any)
空接口不包含任何方法,因此任何类型都实现了空接口。空接口可以用来保存任意类型的数据:
var obj any // Go 1.18 起,any 是 interface{} 的别名
obj = 42
obj = "hello"8. 错误处理与延迟执行
8.1 错误处理机制
Go 推荐显式处理错误。函数将可能发生的错误作为最后一个值返回,调用者通过检查 err 是否为 nil 来判断操作是否成功。
func parseData(data string) (int, error) {
if data == "" {
return 0, fmt.Errorf("数据不能为空")
}
// 解析逻辑...
return 100, nil
}8.2 延迟执行 (defer)
defer 语句会将其后跟随的函数延迟到当前函数执行结束(包括因 panic 退出)前再执行。常用于释放锁、关闭连接、关闭文件句柄等清理工作,遵循后进先出(LIFO)的顺序。
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
return
}
// 保证在 readFile 函数退出前,文件一定会被关闭,避免内存泄露
defer file.Close()
// 读取文件的逻辑...
}8.3 Panic 与 Recover (异常捕获)
panic用于引发运行时恐慌(类似于抛出致命异常),程序会立刻中断执行并向上层冒泡。recover用于捕获panic引起的恐慌,它必须在defer函数中调用才有效。
func safeDivide(a, b int) int {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到恐慌:", r)
}
}()
if b == 0 {
panic("除数不能为零") // 触发 panic
}
return a / b
}9. 并发编程:Goroutine 与 Channel
Go 并发的核心哲学是:“不要通过共享内存来通信,而要通过通信来共享内存”。
9.1 协程 (Goroutine)
协程是 Go 运行时的轻量级线程,由 Go 运行时管理,其创建和切换开销微乎其微。使用 go 关键字即可开启一个协程:
func printMessage() {
fmt.Println("并发执行中...")
}
func main() {
go printMessage() // 开启协程并发运行
}