Go语言入门指南:快速了解 Golang 基础
Go 语言,通常被称为 Golang,是由 Google 设计和开发的一种开源编程语言。自 2009 年发布以来,Go 凭借其简洁性、高效率、强大的并发处理能力以及丰富的标准库,在云计算、微服务、网络编程、DevOps 等领域获得了广泛应用。对于希望学习一门现代、高效且实用的编程语言的开发者来说,Go 是一个绝佳的选择。本指南将带你深入了解 Go 语言的基础知识,助你快速入门。
1. Go 语言简介与特性
为什么选择 Go?
- 简洁性 (Simplicity): Go 的语法设计力求简单,关键字数量少(仅 25 个),没有复杂的继承体系(采用组合和接口),使得代码易于阅读、编写和维护。
- 高效率 (Efficiency): Go 是编译型语言,可以直接编译成机器码,执行速度快。其编译器优化良好,编译速度也非常快,大大提升了开发效率。
- 强大的并发模型 (Concurrency): Go 在语言层面原生支持并发。其
goroutine
(轻量级线程)和channel
(通道)机制使得编写高并发程序变得异常简单和高效,能够充分利用现代多核处理器的性能。 - 内存管理 (Memory Management): Go 拥有自动垃圾回收机制,开发者无需手动管理内存,降低了内存泄漏的风险。
- 丰富的标准库 (Rich Standard Library): Go 提供了一个强大且设计良好的标准库,涵盖了网络、I/O、加密、文本处理等常用功能,开发者通常无需依赖大量第三方库即可构建复杂的应用。
- 静态类型与编译时检查 (Static Typing): Go 是静态类型语言,变量类型在编译时确定,有助于在早期发现类型错误,提高代码的健壮性。
- 跨平台编译 (Cross-Compilation): Go 支持轻松地交叉编译到不同的操作系统和架构,方便部署。
- 活跃的社区与生态 (Active Community & Ecosystem): Go 拥有一个庞大且活跃的社区,提供了大量的开源库和工具,生态系统日益完善。
2. 环境搭建
在开始编写 Go 代码之前,你需要安装 Go 开发环境。
- 下载安装包: 访问 Go 官方网站 (https://golang.org/dl/ 或国内镜像 https://golang.google.cn/dl/),根据你的操作系统(Windows, macOS, Linux)下载对应的安装包。
- 安装: 运行安装包,按照提示完成安装。默认情况下,Go 会安装在
/usr/local/go
(Linux/macOS) 或c:\Go
(Windows)。安装程序通常会自动配置好必要的环境变量。 - 验证安装: 打开终端或命令提示符,输入以下命令:
bash
go version
如果看到类似go version go1.x.y os/arch
的输出,表示安装成功。 -
环境变量 (可选了解):
GOROOT
: 指向 Go 的安装目录,通常由安装程序自动设置。GOPATH
: 在 Go 1.11 版本之前,这是存放 Go 源码、编译后库和可执行文件的工作区目录。Go 1.11 引入了 Go Modules 后,GOPATH
的重要性大大降低,但仍用于存放go install
安装的二进制文件等。可以设置多个路径。GOPROXY
: (推荐设置) 用于配置 Go 模块代理,加速依赖下载。可以设置为国内镜像,如https://goproxy.cn,direct
。PATH
: 需要将 Go 的bin
目录(如/usr/local/go/bin
或c:\Go\bin
)添加到系统的PATH
环境变量中,这样才能在任何地方执行go
命令。安装程序通常也会自动完成此步骤。
你可以使用
go env
命令查看当前的 Go 环境变量设置。
3. 第一个 Go 程序:Hello, World!
让我们从经典的 “Hello, World!” 程序开始。
- 创建项目目录: 在你的工作区(任何你喜欢的地方,推荐使用 Go Modules)创建一个新的目录,例如
helloapp
。
bash
mkdir helloapp
cd helloapp - 初始化 Go Modules: (推荐) 在项目根目录下执行:
bash
go mod init helloapp
这会创建一个go.mod
文件,用于管理项目依赖。helloapp
是模块路径,可以根据你的项目名或代码托管路径修改。 -
创建源文件: 创建一个名为
main.go
的文件,并输入以下内容:“`go
package main // 声明包名为 main,表示这是一个可执行程序import “fmt” // 导入 fmt 包,用于格式化输入输出
// main 函数是程序的入口点
func main() {
fmt.Println(“Hello, World!”) // 调用 fmt 包的 Println 函数打印字符串
}
“` -
代码解析:
package main
: 每个 Go 程序都由包(package)组成。package main
特殊声明,告诉编译器这个包需要被编译成可执行文件,而不是库。可执行程序的入口点必须是main
包下的main
函数。import "fmt"
: 导入标准库中的fmt
包。fmt
包提供了格式化 I/O(输入/输出)的功能,类似于 C 的printf
和scanf
。func main()
: 定义main
函数。当程序执行时,main
函数会被首先调用。它没有任何参数,也没有返回值。fmt.Println("Hello, World!")
: 调用fmt
包中的Println
函数。这个函数会将括号内的字符串打印到标准输出(通常是控制台),并在末尾自动添加一个换行符。
-
运行程序:
- 直接运行: 在
helloapp
目录下,使用go run
命令可以直接编译并运行源文件:
bash
go run main.go
你将在终端看到输出:Hello, World!
- 编译后运行: 使用
go build
命令编译生成可执行文件:
bash
go build
这会在当前目录下生成一个名为helloapp
(Windows下可能是helloapp.exe
) 的可执行文件。然后直接运行它:
bash
./helloapp # Linux/macOS
.\helloapp.exe # Windows
同样会输出Hello, World!
。go build
更适合用于最终发布。
- 直接运行: 在
4. Go 基础语法
4.1 变量 (Variables)
Go 是静态类型语言,变量在使用前必须声明。
- 标准声明: 使用
var
关键字,可以指定类型,也可以让编译器推断。
go
var name string = "Alice" // 显式指定类型并初始化
var age int // 声明变量,默认为零值 (int 的零值是 0)
age = 30 // 赋值
var city = "New York" // 类型推断 (string)
var ( // 批量声明
country string = "USA"
zipcode int = 10001
) - 短变量声明: 在函数内部(不能在包级别使用),可以使用
:=
操作符进行声明和初始化,编译器会自动推断类型。这是最常用的方式。
go
func someFunc() {
message := "Hello from short declaration" // 类型推断为 string
count := 10 // 类型推断为 int
isValid := true // 类型推断为 bool
fmt.Println(message, count, isValid)
} - 零值 (Zero Values): 如果变量声明时没有显式初始化,它会被赋予其类型的零值:
- 数值类型(int, float 等):
0
- 布尔类型 (bool):
false
- 字符串类型 (string):
""
(空字符串) - 指针、接口、切片、映射、通道、函数类型:
nil
- 数值类型(int, float 等):
4.2 常量 (Constants)
常量使用 const
关键字声明,其值在编译时确定,不能被修改。
“`go
const Pi float64 = 3.1415926535
const Greeting = “你好, 世界” // 类型推断为 string
const (
StatusOK = 200
NotFound = 404
ServerError = 500
)
// iota: 特殊常量,可以被编译器修改。
// 在一个 const 声明块中,iota 从 0 开始,每增加一行(或一个常量声明)加 1。
const (
Sunday = iota // 0
Monday // 1 (自动使用上一行的表达式和类型)
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
“`
4.3 基本数据类型
Go 提供了丰富的内置数据类型:
- 布尔型 (Boolean):
bool
(值为true
或false
) - 字符串型 (String):
string
(UTF-8 编码的字符序列,不可变) - 整型 (Integer):
- 有符号整型:
int8
,int16
,int32
,int64
- 无符号整型:
uint8
(别名byte
),uint16
,uint32
,uint64
,uintptr
- 平台相关整型:
int
,uint
(大小取决于操作系统,32位或64位)
- 有符号整型:
- 浮点型 (Floating-Point):
float32
,float64
- 复数型 (Complex):
complex64
,complex128
- 其他:
byte
(uint8 的别名),rune
(int32 的别名,表示一个 Unicode 码点)
go
var isGoCool bool = true
var message string = "Go is awesome!"
var score int = 95
var price float64 = 19.99
var firstChar byte = 'G' // 存储 'G' 的 ASCII 值
var chineseChar rune = '世' // 存储 '世' 的 Unicode 码点
4.4 运算符
Go 支持常见的运算符:
- 算术运算符:
+
,-
,*
,/
,%
(取模) - 关系运算符:
==
,!=
,>
,<
,>=
,<=
- 逻辑运算符:
&&
(逻辑与),||
(逻辑或),!
(逻辑非) - 位运算符:
&
,|
,^
,<<
,>>
- 赋值运算符:
=
,+=
,-=
,*=
,/=
,%=
,&=
,|=
,^=
,<<=
,>>=
- 其他运算符:
&
(取地址),*
(指针解引用)
4.5 控制流
-
if-else
语句:
“`go
score := 75
if score >= 90 {
fmt.Println(“Grade: A”)
} else if score >= 80 {
fmt.Println(“Grade: B”)
} else if score >= 70 {
fmt.Println(“Grade: C”)
} else {
fmt.Println(“Grade: D”)
}// if 语句可以包含一个短变量声明,其作用域仅限于 if-else 块
if num := rand.Intn(10); num < 5 { // 生成 0-9 随机数
fmt.Println(“Number”, num, “is small”)
} else {
fmt.Println(“Number”, num, “is big”)
}
// fmt.Println(num) // 错误:num 在这里不可见
``
if
*注意:Go 的和
else后面的大括号
{}是必需的,即使只有一条语句。条件表达式不需要括号
()`。* -
for
循环: Go 只有for
这一种循环结构,但它有多种形式。
“`go
// 1. 标准 for 循环 (类似 C/Java)
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(“Sum:”, sum)// 2. 条件循环 (类似 while)
n := 1
for n < 100 { // 条件为 true 时循环
n *= 2
}
fmt.Println(“n:”, n)// 3. 无限循环 (需要 break 或 return 退出)
// for {
// fmt.Println(“Looping forever…”)
// time.Sleep(1 * time.Second)
// }// 4. for-range 循环 (用于遍历数组、切片、字符串、map、通道)
nums := []int{2, 3, 4}
total := 0
for index, value := range nums { // index 是索引,value 是元素值
fmt.Printf(“Index: %d, Value: %d\n”, index, value)
total += value
}
fmt.Println(“Total:”, total)// 如果只需要值,可以用 _ 忽略索引
for _, value := range nums {
total += value
}// 遍历 map
kvs := map[string]string{“a”: “apple”, “b”: “banana”}
for k, v := range kvs {
fmt.Printf(“%s -> %s\n”, k, v)
}// 遍历字符串 (按 Unicode 码点)
for index, char := range “Go语言” { // char 是 rune 类型
fmt.Printf(“%d: %c\n”, index, char)
}
“` -
switch
语句: 功能更强大的switch
。
“`go
day := “Monday”
switch day {
case “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”: // 可以匹配多个值
fmt.Println(“Workday”)
case “Saturday”, “Sunday”:
fmt.Println(“Weekend”)
default:
fmt.Println(“Invalid day”)
}// switch 后面可以没有表达式,相当于 if-else if 链
hour := time.Now().Hour()
switch {
case hour < 12:
fmt.Println(“Good morning!”)
case hour < 17:
fmt.Println(“Good afternoon!”)
default:
fmt.Println(“Good evening!”)
}// 类型断言 switch (后面接口部分会讲)
var i interface{} = “hello”
switch v := i.(type) {
case int:
fmt.Println(“It’s an int:”, v)
case string:
fmt.Println(“It’s a string:”, v)
default:
fmt.Printf(“Unknown type: %T\n”, v)
}
``
switch
*注意:Go 的默认在每个
case结束后自动
break,不需要显式写
break。如果需要继续执行下一个
case(不推荐),可以使用
fallthrough` 关键字。*
5. 复合数据类型
5.1 数组 (Arrays)
数组是具有固定长度且包含相同类型元素的序列。长度是类型的一部分。
“`go
var arr1 [5]int // 声明一个包含 5 个 int 的数组,元素默认为 0
arr1[0] = 100 // 赋值
arr2 := [3]string{“Apple”, “Banana”, “Cherry”} // 声明并初始化
fmt.Println(“arr2:”, arr2, “Length:”, len(arr2))
arr3 := […]int{1, 2, 3, 4} // 使用 … 让编译器自动计算长度
fmt.Println(“arr3 Length:”, len(arr3))
// 数组是值类型,赋值或传参时会复制整个数组
arr4 := arr3
arr4[0] = 99
fmt.Println(“arr3:”, arr3) // 输出 [1 2 3 4]
fmt.Println(“arr4:”, arr4) // 输出 [99 2 3 4]
“`
由于固定长度和值传递的特性,数组在 Go 中使用不如切片(Slice)普遍。
5.2 切片 (Slices)
切片是对底层数组的一个动态的、灵活的视图。切片比数组更常用。
-
声明与创建:
“`go
// 1. 基于数组创建
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] // 创建一个切片,包含 primes[1] 到 primes[3] (不含 primes[4])
// s 现在是 [3 5 7]// 2. 字面量创建 (会隐式创建一个底层数组)
letters := []string{“a”, “b”, “c”, “d”}// 3. 使用 make 创建 (推荐用于创建空切片或预分配空间)
// make([]T, length, capacity)
slice1 := make([]int, 5) // 创建长度为 5,容量为 5 的 int 切片,元素为 0
slice2 := make([]int, 0, 10) // 创建长度为 0,容量为 10 的 int 切片
* **长度 (Length) 与 容量 (Capacity):**
go
* `len(s)`: 切片中元素的数量。
* `cap(s)`: 从切片的第一个元素开始,到底层数组末尾的元素数量。容量表示切片在不重新分配内存的情况下可以增长到的最大长度。
* **常用操作:**
s := []int{1, 2, 3}
fmt.Println(“Length:”, len(s), “Capacity:”, cap(s)) // 3, 3// 添加元素 (append)
s = append(s, 4, 5) // append 返回一个新的切片,可能指向新的底层数组
fmt.Println(“Appended:”, s, “Length:”, len(s), “Capacity:”, cap(s)) // [1 2 3 4 5], 5, 6 (容量通常会翻倍增长)// 复制切片 (copy)
dst := make([]int, len(s))
numCopied := copy(dst, s) // copy 返回复制的元素数量
fmt.Println(“Copied:”, dst, “Num copied:”, numCopied)// 切片截取 (Slicing)
l := s[2:5] // [3 4 5] (包含索引 2, 3, 4)
m := s[:3] // [1 2 3] (从开头到索引 2)
n := s[3:] // [4 5] (从索引 3 到结尾)// 切片是引用类型,修改切片元素会影响底层数组和其他共享该数组的切片
original := []int{10, 20, 30, 40, 50}
sliceA := original[1:3] // [20 30]
sliceB := original[2:4] // [30 40]
sliceA[0] = 22 // 修改 sliceA 会影响 original 和 sliceB
fmt.Println(“Original:”, original) // [10 22 30 40 50]
fmt.Println(“Slice A:”, sliceA) // [22 30]
fmt.Println(“Slice B:”, sliceB) // [30 40]
“`
5.3 映射 (Maps)
Map 是一种无序的键值对集合,类似于其他语言中的哈希表或字典。键必须是可比较的类型(如 string, int, float, bool, 指针, 结构体等,不能是 slice, map, function)。
-
创建与初始化:
“`go
// 使用 make 创建
m1 := make(map[string]int) // 创建一个 key 为 string,value 为 int 的空 map// 字面量初始化
m2 := map[string]string{
“name”: “Bob”,
“city”: “London”,
}
* **操作:**
go
m := make(map[string]int)// 插入或更新键值对
m[“age”] = 25
m[“score”] = 90// 获取值
age := m[“age”] // 25
score := m[“score”] // 90// 删除键值对
delete(m, “score”)// 检查键是否存在
value, exists := m[“city”] // 如果键 “city” 存在,exists 为 true,value 为对应值;否则 exists 为 false,value 为零值
if exists {
fmt.Println(“City:”, value)
} else {
fmt.Println(“City key not found.”)
}// 遍历 map (注意:遍历顺序是不确定的)
for key, val := range m2 {
fmt.Printf(“%s: %s\n”, key, val)
}// Map 是引用类型
m3 := m2
m3[“name”] = “Charlie”
fmt.Println(“m2 name:”, m2[“name”]) // 输出 Charlie
“`
5.4 结构体 (Structs)
结构体是用户定义的类型,可以将不同类型的数据字段聚合在一起。
- 定义:
go
type Person struct {
Name string // 字段名大写表示可导出(公有)
Age int
city string // 字段名小写表示不可导出(私有,仅包内可见)
} -
创建与使用:
“`go
// 1. 使用 var 声明 (字段为零值)
var p1 Person
p1.Name = “Alice”
p1.Age = 30
// p1.city = “…” // 只能在定义 Person 的包内访问// 2. 使用字面量 (按顺序) – 不推荐,容易出错
p2 := Person{“Bob”, 25, “Paris”}// 3. 使用字面量 (指定字段名) – 推荐
p3 := Person{Name: “Charlie”, Age: 35} // 未指定的字段为零值// 4. 使用 new 返回指针
p4 := new(Person) // p4 是 Person 类型,字段为零值
p4.Name = “David” // Go 自动解引用 (p4).Name = “David”// 5. 使用 & 取地址返回指针
p5 := &Person{Name: “Eve”, Age: 28} // p5 是 *Person 类型fmt.Println(p1, p2, p3, p4, p5)
fmt.Println(“P3’s Name:”, p3.Name)
“`
6. 函数 (Functions)
函数是 Go 程序的基本构建块。
-
定义:
“`go
// 基本函数
func add(x int, y int) int { // 参数类型写在变量名后
return x + y
}// 参数类型相同可以简写
func multiply(x, y int) int {
return x * y
}// 多返回值
func swap(a, b string) (string, string) {
return b, a
}// 命名返回值 (可以像变量一样使用,通过裸 return 返回)
func divide(dividend, divisor int) (quotient, remainder int) {
if divisor == 0 {
// 通常使用 error 处理,这里简化
return 0, 0
}
quotient = dividend / divisor
remainder = dividend % divisor
return // 裸 return,返回 quotient 和 remainder 的当前值
}
* **调用:**
go
sum := add(10, 20)
product := multiply(5, 6)
s1, s2 := swap(“hello”, “world”)
q, r := divide(10, 3)
fmt.Println(sum, product, s1, s2, q, r) // 30 30 world hello 3 1
* **可变参数 (Variadic Functions):**
go
func sumAll(nums …int) int { // nums 是一个 int 切片
total := 0
for _, num := range nums {
total += num
}
return total
}total1 := sumAll(1, 2, 3) // 6
total2 := sumAll(10, 20, 30, 40) // 100
slice := []int{5, 5, 5}
total3 := sumAll(slice…) // 将切片展开传入
fmt.Println(total1, total2, total3) // 6 100 15
* **匿名函数与闭包 (Anonymous Functions & Closures):**
go
// 匿名函数直接调用
func() {
fmt.Println(“I am an anonymous function!”)
}() // 末尾的 () 表示立即执行// 将匿名函数赋值给变量
greet := func(name string) {
fmt.Println(“Hello,”, name)
}
greet(“Go”) // Hello, Go// 闭包:函数引用了其外部作用域的变量
func intSeq() func() int {
i := 0
return func() int { // 返回的函数 “记住” 了 i
i++
return i
}
}nextInt := intSeq()
fmt.Println(nextInt()) // 1
fmt.Println(nextInt()) // 2
fmt.Println(nextInt()) // 3newInts := intSeq() // 创建新的闭包实例
fmt.Println(newInts()) // 1
“`
7. 方法 (Methods)
方法是附加到特定类型(通常是结构体)上的函数。接收者(receiver)出现在 func
关键字和方法名之间。
“`go
type Rectangle struct {
Width, Height float64
}
// 为 Rectangle 类型定义 Area 方法
// (r Rectangle) 是接收者,表示这个方法作用于 Rectangle 类型的值
func (r Rectangle) Area() float64 { // 值接收者 (Value Receiver)
return r.Width * r.Height
}
// 指针接收者 (Pointer Receiver) – 可以修改接收者的状态
func (r Rectangle) Scale(factor float64) {
r.Width = factor
r.Height *= factor
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(“Area:”, rect.Area()) // 调用方法
rect.Scale(2) // Go 自动处理值和指针:(&rect).Scale(2)
fmt.Println("Scaled Area:", rect.Area()) // 现在 Width=20, Height=10
// 也可以显式使用指针
rectPtr := &Rectangle{Width: 3, Height: 4}
fmt.Println("Ptr Area:", rectPtr.Area()) // Go 自动解引用 (*rectPtr).Area()
rectPtr.Scale(10)
fmt.Println("Ptr Scaled Area:", rectPtr.Area())
}
“`
* 值接收者 vs 指针接收者:
* 值接收者操作的是接收者的一个副本,方法内部的修改不影响原始值。
* 指针接收者操作的是接收者的指针,方法内部的修改会影响原始值。
* 如果方法需要修改接收者的状态,或者接收者是大型结构体(避免复制开销),应使用指针接收者。
8. 指针 (Pointers)
指针存储了一个变量的内存地址。
&
操作符:获取变量的地址。*
操作符:- 用在类型前(如
*int
):表示一个指向该类型的指针。 - 用在指针变量前:解引用,获取指针指向地址处的值。
- 用在类型前(如
“`go
a := 42
var p *int // 声明一个指向 int 的指针,初始值为 nil
p = &a // p 指向 a 的内存地址
fmt.Println(“Value of a:”, a) // 42
fmt.Println(“Address of a:”, &a) // 内存地址 (e.g., 0x…)
fmt.Println(“Value of p:”, p) // 内存地址 (与 &a 相同)
fmt.Println(“Value pointed to by p:”, *p) // 42 (解引用)
*p = 21 // 通过指针修改 a 的值
fmt.Println(“New value of a:”, a) // 21
// 指针在函数传参中用于修改外部变量或避免大对象复制
func increment(n int) {
n++ // 修改指针指向的值
}
num := 10
increment(&num)
fmt.Println(“Incremented num:”, num) // 11
“`
Go 中的指针比 C/C++ 更安全,不支持指针算术运算。
9. 接口 (Interfaces)
接口是 Go 语言的核心特性之一,它定义了一组方法的集合。一个类型只要实现了接口中定义的所有方法,就被认为隐式地实现了该接口,无需显式声明(像 Java 的 implements
)。
“`go
// 定义一个接口 Shape,包含一个 Area() 方法
type Shape interface {
Area() float64
}
// Circle 类型
type Circle struct {
Radius float64
}
// Circle 实现 Shape 接口的 Area() 方法
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// Rectangle 类型 (之前已定义)
// func (r Rectangle) Area() float64 { … } // Rectangle 也实现了 Area()
// 接受任何实现了 Shape 接口的类型作为参数
func printArea(s Shape) {
fmt.Printf(“Area of shape: %.2f\n”, s.Area())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 4, Height: 6}
printArea(c) // Circle 实现了 Shape 接口
printArea(r) // Rectangle 也实现了 Shape 接口
shapes := []Shape{c, r, Circle{Radius: 2}} // 可以创建接口类型的切片
for _, shape := range shapes {
printArea(shape)
}
}
* **空接口 (`interface{}`)**: 表示不包含任何方法的接口。任何类型都隐式地实现了空接口,因此 `interface{}` 可以用来表示任意类型的值,类似于 Java 的 `Object` 或 C 的 `void*`。
go
var i interface{}
i = 42
fmt.Printf(“Type: %T, Value: %v\n”, i, i) // Type: int, Value: 42
i = “hello”
fmt.Printf(“Type: %T, Value: %v\n”, i, i) // Type: string, Value: hello
i = Circle{Radius: 1}
fmt.Printf(“Type: %T, Value: %v\n”, i, i) // Type: main.Circle, Value: {1}
* **类型断言 (Type Assertion):** 用于检查接口变量底层存储的具体类型,并获取该类型的值。
go
var val interface{} = “Go”
// 形式1: value, ok := i.(T)
str, ok := val.(string)
if ok {
fmt.Println("It's a string:", str) // It's a string: Go
} else {
fmt.Println("Not a string")
}
// 形式2: value := i.(T) - 如果类型不匹配会 panic
num, ok := val.(int)
if !ok {
fmt.Println("Not an int") // Not an int
// num = val.(int) // 这行会引发 panic
}
// 配合 switch 使用 (见 switch 部分的例子)
```
10. 错误处理 (Error Handling)
Go 使用显式的、基于返回值的方式处理错误。通常,可能出错的函数会返回一个额外的 error
类型的值。如果操作成功,error
值为 nil
;如果出错,error
值包含错误信息。
“`go
import (
“errors”
“fmt”
“strconv”
)
// 一个可能出错的函数
func divideChecked(a, b int) (int, error) {
if b == 0 {
// errors.New 创建一个简单的错误
return 0, errors.New(“division by zero”)
}
return a / b, nil // 成功,返回 nil 错误
}
func main() {
result, err := divideChecked(10, 2)
if err != nil { // 检查错误
fmt.Println(“Error:”, err)
} else {
fmt.Println(“Result:”, result) // Result: 5
}
result, err = divideChecked(10, 0)
if err != nil {
fmt.Println("Error:", err) // Error: division by zero
} else {
fmt.Println("Result:", result)
}
// 标准库函数也遵循这个模式
numStr := "123a"
num, err := strconv.Atoi(numStr) // 尝试将字符串转为整数
if err != nil {
// fmt.Errorf 创建格式化的错误
fmt.Printf("Error converting '%s': %v\n", numStr, err)
// Output: Error converting '123a': strconv.Atoi: parsing "123a": invalid syntax
} else {
fmt.Println("Parsed number:", num)
}
}
``
if err != nil` 的模式在 Go 代码中非常常见,强调了对错误的显式检查和处理。*
*这种
11. 并发 (Concurrency) – Goroutines 和 Channels
Go 的并发是其核心优势之一。
- Goroutine: 轻量级的执行单元,由 Go 运行时管理。创建成本极低,可以轻松启动成千上万个。使用
go
关键字启动。 - Channel: 用于 Goroutine 之间通信和同步的管道。通道是类型化的,只能传递指定类型的数据。
“`go
import (
“fmt”
“time”
)
func say(s string, ch chan string) {
for i := 0; i < 3; i++ {
fmt.Println(s)
time.Sleep(100 * time.Millisecond)
}
ch <- s // 向通道发送完成信号 (字符串)
}
func main() {
// 创建一个字符串类型的通道
messageChannel := make(chan string)
// 启动两个 goroutine
go say("Hello", messageChannel)
go say("World", messageChannel)
// 从通道接收两次信号,等待两个 goroutine 完成
// 接收操作 (<-ch) 会阻塞,直到有数据发送到通道
result1 := <-messageChannel
result2 := <-messageChannel
fmt.Println("Goroutine 1 finished:", result1)
fmt.Println("Goroutine 2 finished:", result2)
fmt.Println("Main function finished.")
// ---- 缓冲通道 ----
bufferedChan := make(chan int, 2) // 创建容量为 2 的缓冲通道
bufferedChan <- 1 // 发送不会阻塞,因为容量 > 0
bufferedChan <- 2
// bufferedChan <- 3 // 这会阻塞,因为通道满了
fmt.Println(<-bufferedChan) // 1
fmt.Println(<-bufferedChan) // 2
}
``
sync` 包实现)。*
*Go 的并发模型基于 CSP (Communicating Sequential Processes) 理论,鼓励通过通信来共享内存,而不是通过共享内存来通信(尽管后者也可以通过
12. 包管理 (Package Management) – Go Modules
自 Go 1.11 起,Go Modules 是官方推荐的依赖管理系统。
- 初始化: 在项目根目录运行
go mod init <module-path>
,例如go mod init myproject
或go mod init github.com/yourusername/myproject
。这会创建go.mod
文件。 go.mod
文件: 定义了模块路径、Go 版本和依赖项及其版本。go.sum
文件: 包含了依赖项的校验和,确保依赖的完整性和安全性。- 添加依赖: 当你在代码中
import
一个新的第三方包时,下次运行go build
,go test
或go run
时,Go 会自动下载该依赖并更新go.mod
和go.sum
。也可以手动使用go get <package-path>
。 - 常用命令:
go build
: 编译包和依赖。go run
: 编译并运行 Go 程序。go test
: 运行测试。go get
: 添加、更新或移除依赖。go list -m all
: 列出所有依赖。go mod tidy
: 清理未使用的依赖,添加需要的依赖。go mod download
: 下载依赖到本地缓存。
13. 常用标准库一瞥
Go 拥有强大的标准库,覆盖了广泛的功能:
fmt
: 格式化 I/O。net/http
: 实现 HTTP 客户端和服务器。os
: 平台无关的操作系统功能接口(文件操作、环境变量等)。io
: I/O 原语接口,如Reader
,Writer
。encoding/json
: JSON 编码和解码。database/sql
: 通用的 SQL(或类 SQL)数据库接口。sync
: 提供基本的同步原语,如Mutex
,WaitGroup
,Cond
。time
: 时间相关的操作。strings
: 字符串操作。strconv
: 字符串和其他基本类型的转换。log
: 简单的日志记录。
14. Go 工具链
Go 提供了一套强大的命令行工具:
go build
: 编译包和依赖。go run
: 编译并运行 Go 程序。go install
: 编译并安装包(可执行文件到$GOPATH/bin
或$GOBIN
)。go test
: 运行测试和基准测试。go fmt
: 格式化 Go 代码,强制统一风格。go vet
: 代码静态分析,检查可疑构造。go doc
: 显示包或符号的文档。godoc
: 运行本地文档服务器。go mod
: 管理模块依赖。
结语
本指南覆盖了 Go 语言的核心基础知识,包括环境搭建、基本语法、复合类型、函数、方法、接口、错误处理、并发模型以及包管理等。Go 语言以其简洁、高效和强大的并发特性,成为了现代软件开发的热门选择。
要真正掌握 Go,最好的方式是动手实践。尝试编写小程序,解决实际问题,阅读优秀的开源项目代码,并深入探索其丰富的标准库。祝你在 Go 语言的学习旅程中一帆风顺!