1. 变量与类型
Go 是静态类型语言,变量类型在编译时确定。支持显式声明和类型推断两种方式。
变量声明
package main
import "fmt"
func main() {
// var 声明(显式类型)
var name string = "Go"
var age int = 15
// var 声明(类型推断)
var version = 1.24
var active = true
// 短声明 :=(最常用,仅限函数内部)
lang := "Go"
count := 42
pi := 3.14159
// 多变量声明
var x, y int = 10, 20
a, b := "hello", "world"
// 声明块
var (
host string = "localhost"
port int = 8080
debug bool = true
)
fmt.Println(name, age, version, active)
fmt.Println(lang, count, pi)
fmt.Println(x, y, a, b)
fmt.Println(host, port, debug)
}
基本类型
// 整数类型
var i int = 42 // 平台相关:32位或64位
var i8 int8 = 127 // -128 ~ 127
var i64 int64 = 9999 // 64位整数
var u uint = 42 // 无符号整数
// 浮点数
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793
// 布尔
var ok bool = true
// 字符串
var s string = "Hello, 世界"
// byte 和 rune
var b byte = 'A' // uint8 的别名
var r rune = '中' // int32 的别名,表示 Unicode 码点
零值与类型转换
// Go 的零值:声明但未赋值的变量自动获得零值
var i int // 0
var f float64 // 0.0
var s string // ""(空字符串)
var b bool // false
var p *int // nil
// 类型转换必须显式进行
var x int = 42
var y float64 = float64(x) // int -> float64
var z int = int(y) // float64 -> int(截断小数)
var s string = fmt.Sprintf("%d", x) // int -> string
常量
const Pi = 3.14159
const AppName = "MyApp"
// 常量块
const (
StatusOK = 200
StatusError = 500
)
// iota: 自增常量生成器
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
重要:Go 不允许隐式类型转换。int 和 int64 是不同类型,必须显式转换。未使用的变量会导致编译错误。
2. 字符串
Go 的字符串是不可变的 UTF-8 编码字节序列。
package main
import (
"fmt"
"strings"
)
func main() {
// 基本操作
s := "Hello, 世界"
fmt.Println(len(s)) // 13(字节数,不是字符数)
fmt.Println(len([]rune(s))) // 9(字符数)
// 字符串拼接
greeting := "Hello" + ", " + "Go"
formatted := fmt.Sprintf("版本: %d.%d", 1, 24)
// 多行字符串(反引号,原始字符串)
sql := `SELECT *
FROM users
WHERE age > 18
ORDER BY name`
// strings 包常用函数
fmt.Println(strings.Contains("golang", "go")) // true
fmt.Println(strings.HasPrefix("golang", "go")) // true
fmt.Println(strings.HasSuffix("main.go", ".go")) // true
fmt.Println(strings.ToUpper("hello")) // HELLO
fmt.Println(strings.Split("a,b,c", ",")) // [a b c]
fmt.Println(strings.Join([]string{"a", "b"}, "-")) // a-b
fmt.Println(strings.Replace("foo bar", "foo", "baz", 1)) // baz bar
fmt.Println(strings.TrimSpace(" hello ")) // hello
fmt.Println(greeting, formatted, sql)
}
// rune vs byte
func runeDemo() {
s := "Go语言"
// 按字节遍历
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
// 按字符(rune)遍历
for i, r := range s {
fmt.Printf("索引%d: %c\n", i, r)
}
}
rune vs byte:byte 是 uint8 的别名,处理 ASCII。rune 是 int32 的别名,处理 Unicode 字符。用 range 遍历字符串时自动按 rune 迭代。
3. 数组与切片
Go 有固定长度的数组和动态长度的切片。实际开发中几乎总是使用切片。
package main
import "fmt"
func main() {
// 数组:固定长度,值类型
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{10, 20, 30, 40} // 编译器推断长度
// 切片:动态长度,引用类型
nums := []int{1, 2, 3, 4, 5}
// make 创建切片(长度, 容量)
s := make([]int, 5) // len=5, cap=5
s2 := make([]int, 0, 10) // len=0, cap=10
// 切片操作
sub := nums[1:3] // [2, 3](左闭右开)
head := nums[:3] // [1, 2, 3]
tail := nums[2:] // [3, 4, 5]
// append 追加元素
s2 = append(s2, 1, 2, 3)
s2 = append(s2, nums...) // 展开追加
// 遍历
for i, v := range nums {
fmt.Printf("索引 %d: 值 %d\n", i, v)
}
// 只需要值
for _, v := range nums {
fmt.Println(v)
}
// 只需要索引
for i := range nums {
fmt.Println(i)
}
// copy
dst := make([]int, len(nums))
copy(dst, nums)
fmt.Println(arr, arr2, s, sub, head, tail, dst)
}
🔄 动态数组对比
| 语言 | 类型 | 追加 |
| Go | []int (切片) | append(s, 1) |
| Python | list | lst.append(1) |
| Java | ArrayList | list.add(1) |
| JavaScript | Array | arr.push(1) |
Go 的 append 返回新切片,必须赋值回去:s = append(s, 1)。
4. Map
Map 是 Go 内置的哈希表类型,存储键值对。
package main
import "fmt"
func main() {
// 声明与初始化
m := map[string]int{
"apple": 5,
"banana": 3,
"cherry": 8,
}
// make 创建空 map
scores := make(map[string]int)
// 增 / 改
scores["Alice"] = 95
scores["Bob"] = 87
scores["Alice"] = 98 // 更新
// 查(comma ok 模式)
val, ok := scores["Alice"]
if ok {
fmt.Printf("Alice: %d\n", val)
}
// 检查 key 是否存在
if _, exists := scores["Charlie"]; !exists {
fmt.Println("Charlie 不存在")
}
// 删
delete(scores, "Bob")
// 遍历(顺序不固定)
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
// 获取长度
fmt.Println("元素数量:", len(m))
}
comma ok 模式:访问 map 返回两个值 val, ok。ok 为 true 表示 key 存在。这是 Go 中避免零值歧义的惯用方式。
5. 控制流
if / else
// 基本 if
if x > 0 {
fmt.Println("正数")
} else if x < 0 {
fmt.Println("负数")
} else {
fmt.Println("零")
}
// if 带初始化语句(变量作用域限定在 if 块内)
if err := doSomething(); err != nil {
fmt.Println("错误:", err)
}
// 文件操作中常见
if f, err := os.Open("file.txt"); err != nil {
log.Fatal(err)
} else {
defer f.Close()
// 使用 f...
}
for(Go 唯一的循环)
// 传统 for
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// while 风格
n := 1
for n < 100 {
n *= 2
}
// 无限循环
for {
// break 或 return 退出
break
}
// range 遍历
nums := []int{10, 20, 30}
for i, v := range nums {
fmt.Printf("%d: %d\n", i, v)
}
// 遍历字符串(按 rune)
for i, ch := range "Go语言" {
fmt.Printf("%d: %c\n", i, ch)
}
// 遍历 map
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
fmt.Printf("%s=%d\n", k, v)
}
switch(无需 break)
// 基本 switch(自动 break,不会穿透)
day := "Monday"
switch day {
case "Monday":
fmt.Println("星期一")
case "Saturday", "Sunday":
fmt.Println("周末")
default:
fmt.Println("工作日")
}
// 无条件 switch(替代长 if-else 链)
score := 85
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
// fallthrough 显式穿透到下一个 case
switch 3 {
case 3:
fmt.Println("三")
fallthrough
case 4:
fmt.Println("四(也会执行)")
}
defer
// defer 延迟执行,函数返回时按 LIFO 顺序执行
func readFile() {
f, err := os.Open("data.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close() // 确保文件关闭
// 读取文件...
}
// 多个 defer 按 LIFO 执行
func demo() {
defer fmt.Println("1") // 最后执行
defer fmt.Println("2") // 倒数第二
defer fmt.Println("3") // 最先执行
// 输出: 3, 2, 1
}
Go 的特殊之处:for 是唯一的循环关键字,没有 while 或 do-while。switch 默认不穿透,需要 fallthrough 显式声明。条件表达式不需要括号。
6. 错误处理
Go 没有异常机制(try/catch),使用多返回值 + error 接口进行错误处理。这是 Go 最具争议但也最核心的设计。
package main
import (
"errors"
"fmt"
"strconv"
)
// error 接口只有一个方法
// type error interface {
// Error() string
// }
// 函数返回 error
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// fmt.Errorf 带格式化的错误
func parseAge(s string) (int, error) {
age, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("解析年龄失败 %q: %w", s, err)
}
if age < 0 || age > 150 {
return 0, fmt.Errorf("年龄超出范围: %d", age)
}
return age, nil
}
// 自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证失败 [%s]: %s", e.Field, e.Message)
}
func validateName(name string) error {
if len(name) == 0 {
return &ValidationError{Field: "name", Message: "不能为空"}
}
return nil
}
func main() {
// 基本错误检查模式
result, err := divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("结果:", result)
}
// errors.Is 和 errors.As(Go 1.13+)
age, err := parseAge("abc")
if err != nil {
fmt.Println(err) // 解析年龄失败 "abc": strconv.Atoi: ...
}
// 类型断言检查具体错误类型
err = validateName("")
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Printf("字段: %s, 消息: %s\n", ve.Field, ve.Message)
}
fmt.Println(age, result)
}
🔄 错误处理对比
| 语言 | 方式 |
| Go | 多返回值 val, err := fn(),手动检查 if err != nil |
| Java/Python/JS | try { ... } catch(e) { ... } 异常机制 |
| Rust | Result<T, E> + ? 操作符 |
Go 的 if err != nil 模式虽然冗长,但让错误处理路径清晰可见,不会因忘记 catch 而遗漏错误。
7. 本章要点
📌 变量与类型
:= 短声明最常用,静态类型不允许隐式转换,零值机制避免了未初始化问题。
📌 字符串
不可变的 UTF-8 字节序列。rune 处理 Unicode,strings 包提供丰富的操作函数。
📌 切片与 Map
切片是动态数组,append 追加元素。Map 是哈希表,comma ok 模式检查 key 存在。
📌 控制流
for 是唯一循环,switch 默认不穿透,defer 延迟执行用于资源清理。
📌 错误处理
无异常机制,使用 error 接口 + 多返回值。if err != nil 是 Go 的标志性模式。
📌 iota 与常量
const 定义常量,iota 生成自增枚举值,常用于定义状态码和标志位。