Go语言(Golang)简明语法教程

1. Hello World & 模块

// 初始化模块: go mod init example.com/m
// 运行: go run main.go
// 编译: go build main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

包管理 (Go Modules)

// 常用命令
// 初始化模块(在项目根目录执行)
go mod init github.com/user/project

// 下载依赖并整理 go.mod / go.sum
go mod tidy

// 添加依赖
go get github.com/labstack/echo/v4
go get github.com/labstack/echo/v4@v4.11.0  // 指定版本
go get -u github.com/labstack/echo/v4        // 更新到最新版

// 下载所有依赖(根据 go.mod)
go mod download

// 查看依赖关系
go mod graph

// 清理未使用的依赖
go mod tidy

包导入规则

// 1. 标准库包(无需下载)
import (
    "fmt"
    "os"
    "time"
)

// 2. 第三方包(通过 go get 下载)
import (
    "github.com/labstack/echo/v4"
    "gorm.io/gorm"
)

// 3. 本地项目内的包(相对模块路径)
// 假设模块名是 github.com/user/project,目录结构:
// project/
// ├── main.go
// └── utils/
//     └── helper.go
import "github.com/user/project/utils"

// 4. 别名导入
import (
    f "fmt"                  // f.Println(...)
    . "fmt"                  // 直接使用 Println(...)
    _ "github.com/lib/pq"    // 仅执行包的 init(),不使用其导出内容
)

包声明规则

// main 包:可执行程序的入口
package main

// 普通包:供其他代码导入使用
package utils      // 通常与目录名相同
package mypackage  // 包名可以 != 目录名,但不推荐

// 导出规则:首字母大写 = 导出,小写 = 私有
type Person struct {    // 导出,外部可见
    Name string         // 导出字段
    age  int            // 私有字段
}

func PublicFunc() {}    // 导出函数
func privateFunc() {}   // 私有函数

2. 基本数据类型

bool

string // 只读的字节切片

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte   // uint8 的别名
rune   // int32 的别名,表示一个 Unicode 码点

float32 float64

complex64 complex128

3. String、[]byte 和 string 切片的关系

本质关系

// string 的本质:只读的字节切片,底层是只读的 []byte
// 结构类似于:
type stringHeader struct {
    Data uintptr  // 指向字节数组的指针
    Len  int      // 字节长度
}

string 与 []byte 的转换

s := "hello"

// string -> []byte(会发生内存拷贝)
b := []byte(s)

// []byte -> string(会发生内存拷贝)
s2 := string(b)

// 避免拷贝的hack(了解即可,生产环境慎用)
// 使用 unsafe 包实现零拷贝转换

string 的切片操作 s[a:b]

// string 支持切片操作,返回子字符串
// 注意:返回的是 string,不是 []byte 或 []string
s := "hello world"
sub := s[0:5]   // "hello",左闭右开
sub2 := s[6:]   // "world"
sub3 := s[:5]   // "hello"

// 重要特性:
// 1. 切片操作 O(1),不拷贝底层数据,共享字节数组
// 2. 返回的是 string,依然是不可变的
// 3. 切片基于字节索引,不是字符(rune)索引

// 中文注意:按字节切片可能导致乱码
s = "你好世界"
sub = s[0:3]    // "你" - 中文字符占3字节(UTF-8)
sub = s[0:4]    // "你\xe4" - 乱码!切在字符中间

// 安全处理中文:先转 []rune 再切片
runes := []rune(s)
subRune := string(runes[0:2])  // "你好"

常用操作

// 1. 遍历 string(按 rune,支持中文)
s := "你好Go"
for i, r := range s {
    fmt.Printf("索引:%d, 字符:%c\n", i, r)
}
// 输出:
// 索引:0, 字符:你
// 索引:3, 字符:好
// 索引:6, 字符:G
// 索引:7, 字符:o

// 2. 遍历 string(按字节)
for i := 0; i < len(s); i++ {
    fmt.Printf("索引:%d, 字节:%x\n", i, s[i])
}

// 3. string 切片操作(产生新 string,共享底层数据)
s = "hello world"
sub := s[0:5]  // "hello"
// 注意:切片操作 O(1),不拷贝数据

// 4. strings 包常用操作
import "strings"

strings.Contains(s, "lo")      // true
strings.HasPrefix(s, "he")     // true
strings.HasSuffix(s, "ld")     // true
strings.Index(s, "lo")         // 3
strings.Split(s, " ")          // []string{"hello", "world"}
strings.Join([]string{"a","b"}, "-") // "a-b"
strings.ToUpper(s)             // "HELLO WORLD"
strings.TrimSpace(s)
strings.ReplaceAll(s, "l", "L")

性能陷阱

// 1. 频繁的 string <-> []byte 转换会导致内存拷贝
// 大量字符串拼接应使用 strings.Builder
var builder strings.Builder
for i := 0; i < 1000; i++ {
    builder.WriteString("hello")
}
result := builder.String()

// 2. 字符串拼接的几种方式性能对比
// + 操作符(少量字符串)
s := "a" + "b" + "c"

// fmt.Sprintf(格式化场景)
s = fmt.Sprintf("%s%s", a, b)

// strings.Builder(大量拼接,性能最佳)
// bytes.Buffer(类似 strings.Builder)

// 3. 预分配容量的优化
var builder strings.Builder
builder.Grow(10000)  // 预先分配,避免扩容
for i := 0; i < 1000; i++ {
    builder.WriteString("hello")
}

4. 变量声明

// 标准声明
var a int = 10
var b = 10      // 类型推导
c := 10         // 短变量声明,仅函数内可用

```go
// 批量声明
var (
    name string = "Ada"
    age  int    = 4
    isAI bool   = true
)

变量遮蔽问题

Go允许内层变量遮蔽外层变量,这是常见陷阱。

x := 1
{
    x := 2  // 遮蔽外层 x
    fmt.Println(x) // 输出 2
}
fmt.Println(x) // 输出 1,外层不变

5. 常量

const PI = 3.14
const (
    Monday = iota // 0
    Tuesday       // 1
    Wednesday     // 2
)

6. 流程控制

if

if x := 10; x > 5 {  // 支持初始化语句
    fmt.Println("x 大于 5")
} else {
    fmt.Println("x 不大于 5")
}

for(Go 只有 for 一种循环)

// 1. 经典 for(三个语句:初始化; 条件; 后置)
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// 2. 类似 while(只有条件)
sum := 0
for sum < 100 {
    sum += 10
}

// 3. 无限循环(没有条件)
for {
    // break 或 return 退出
}

// 4. range 遍历数组/切片
nums := []int{1, 2, 3}
for i, v := range nums {  // 索引和值
    fmt.Println(i, v)
}
for i := range nums {     // 只获取索引
    fmt.Println(i)
}
for _, v := range nums {   // 只获取值(用 _ 忽略索引)
    fmt.Println(v)
}
for range nums {           // 只迭代,不获取任何值(用于需要执行固定次数的场景)
    fmt.Println("iteration")
}

// 5. range 遍历 map
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {      // 键和值
    fmt.Println(k, v)
}
for k := range m {        // 只获取键
    fmt.Println(k)
}

// 6. range 遍历字符串(遍历 rune)
s := "你好"
for i, r := range s {     // 字节索引和 rune 值
    fmt.Printf("%d: %c\n", i, r)
}

// 7. range 遍历 channel
ch := make(chan int, 3)
ch <- 1; ch <- 2; ch <- 3
close(ch)
for v := range ch {       // 遍历直到 channel 关闭
    fmt.Println(v)
}

// 8. break 和 continue
for i := 0; i < 10; i++ {
    if i == 3 {
        continue  // 跳过本次迭代
    }
    if i == 7 {
        break     // 退出循环
    }
    fmt.Println(i)
}

// 9. label 配合 break/continue(跳出多层循环)
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer  // 跳出外层循环
        }
        fmt.Println(i, j)
    }
}

// 10. for 初始化语句(支持短变量声明)
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
    fmt.Println(i, j)
}

switch

switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("macOS")
case "linux":
    fmt.Println("Linux")
default:
    fmt.Println("Other")
}
// switch 无需 break,自动中断
// fallthrough 可继续执行下一个 case

7. 数组和切片(Slice)

// 数组(固定长度)
var arr [5]int
arr2 := [3]string{"a", "b", "c"}

// 切片(动态数组)
slice := []int{1, 2, 3}
slice = append(slice, 4)

// make 创建指定长度和容量
s := make([]int, 5, 10) // len=5, cap=10

// 切片操作
sub := slice[1:4] // 从索引1到3

slices 包(Go 1.21+)

import "slices"

// 查找元素
index := slices.Index(slice, 2) // 返回索引,不存在返回-1
contains := slices.Contains(slice, 2) // 返回bool

// 排序
slices.Sort(slice) // 升序
slices.SortFunc(slice, func(a, b int) int { return b - a }) // 自定义排序

// 反转
slices.Reverse(slice)

// 去重(需要先排序)
unique := slices.Compact(slice)

// 比较
equal := slices.Equal(slice, []int{1, 2, 3})

// 复制
copySlice := slices.Clone(slice)

// 删除元素
slice = slices.Delete(slice, 1, 3) // 删除索引1到3的元素

// 插入元素
slice = slices.Insert(slice, 1, 10, 20) // 在索引1处插入10和20

8. Map(字典)

m := make(map[string]int)
m["age"] = 18

m2 := map[string]string{"name": "Ada"}

// 检查键是否存在
value, ok := m["age"] // ok 为 bool

// 删除
delete(m, "age")

maps 包(Go 1.21+)

import "maps"

// 复制
copyMap := maps.Clone(m)

// 比较
equal := maps.Equal(m, map[string]int{"age": 18})

// 清空
maps.Clear(m)

// 合并(覆盖)
maps.Copy(m, map[string]int{"age": 20, "height": 180})

// 获取所有键
keys := maps.Keys(m)

// 获取所有值
values := maps.Values(m)

9. 函数

// 普通函数
func add(a, b int) int {
    return a + b
}

// 多返回值
func div(a, b int) (int, int) {
    return a/b, a%b
}

// 命名返回值
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return // 裸返回
}

// 可变参数
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// defer:延迟执行,常用于资源释放。在函数返回前按 LIFO 顺序执行。
func doSomething() {
    f, _ := os.Open("file.txt")
    defer f.Close()
}

// 闭包用法:函数捕获外部变量,形成闭包
func counter() func() int {
    count := 0  // 被内层函数捕获
    return func() int {
        count++
        return count
    }
}

闭包注意事项

在goroutine中使用循环变量时需注意:

nums := []int{1, 2, 3}
for _, n := range nums {
    go func(x int) {  // 传参避免问题
        fmt.Println(x)
    }(n)
}

10. 指针

var p *int
i := 42
p = &i     // 取地址
fmt.Println(*p) // 解引用

// new 函数分配内存
p2 := new(int)

基于地址的用法(指针高级)

指针常用于函数修改调用者变量,或传递大结构体避免拷贝开销。

// 函数通过指针修改外部变量
func increment(p *int) {
    *p++
}
x := 10
increment(&x)
fmt.Println(x) // 11

// 大结构体用指针接收者
type BigStruct struct {
    data [1000]int
}
func (b *BigStruct) process() {
    b.data[0] = 99
}
bs := BigStruct{}
bs.process()  // 自动取地址
fmt.Println(bs.data[0]) // 99

引用类型无需指针:切片、map、channel、函数、接口是引用,修改自动生效。

11. 类型定义 (Type)

// 1. 类型定义 (Type Definition) - 创建新类型
type MyInt int

// 2. 类型别名 (Type Alias) - 仅起别名,类型相同
type MyAlias = int

// 3. 定义函数类型
type Handler func(int) bool

12. 结构体 (Struct)

type Person struct {
    Name string `json:"name"` // 结构体标签 (Tag)
    Age  int    `json:"age"`
}

// 匿名结构体
point := struct {
    X, Y int
}{10, 20}

方法 (Methods)

func (p Person) SayHello() {  // 值接收者
    fmt.Printf("Hello, I'm %s\n", p.Name)
}

func (p *Person) Birthday() { // 指针接收者,可修改
    p.Age++
}

“继承” (组合/嵌套)

Go 语言没有类继承,通过结构体嵌套(Embedding)实现组合。

type Admin struct {
    Person // 匿名嵌套
    Level  int
}

a := Admin{
    Person: Person{Name: "Linus", Age: 50},
    Level:  1,
}
fmt.Println(a.Name) // 直接访问嵌套字段
a.SayHello()        // 直接调用嵌套方法

空结构体 struct{}

// 空结构体不占用内存空间(大小为 0)
var empty struct{}
fmt.Println(unsafe.Sizeof(empty)) // 0

// 1. 用作 Set(只关心 key,不关心 value)
set := make(map[string]struct{})
set["apple"] = struct{}{}
set["banana"] = struct{}{}

// 检查存在性
if _, ok := set["apple"]; ok {
    fmt.Println("存在")
}

// 删除
delete(set, "apple")

// 2. 用于 channel 的信号通知(只关心事件,不关心数据)
done := make(chan struct{})
go func() {
    // 工作完成,发送信号
    done <- struct{}{}
}()
<-done // 等待信号

// 3. 实现方法接收者(无状态的工具方法)
type Locker struct{}
func (l Locker) Lock()   { /* ... */ }
func (l Locker) Unlock() { /* ... */ }

13. 接口

type Speaker interface {
    Speak() string
}

func Greet(s Speaker) {
    fmt.Println(s.Speak())
}

// any:空接口 interface{} 的别名,可代表任何类型
// Go 1.18+ 引入,与 interface{} 完全等价
var x any = "hello"
var y interface{} = "world" // 等价写法

// 类型断言
s, ok := x.(string)      // 安全断言
s := x.(string)          // 不安全断言,失败会 panic

// 类型 Switch
switch v := x.(type) {
case int:
    fmt.Println("int", v)
case string:
    fmt.Println("string", v)
case nil:
    fmt.Println("nil")
default:
    fmt.Println("其他类型")
}

// any 的使用场景与注意事项
// 1. 适合场景:需要存储任意类型的容器(JSON 解析、缓存等)
func printAny(values ...any) {
    for _, v := range values {
        fmt.Println(v)
    }
}

// 2. 不适合场景:过度使用会失去类型安全
// 错误示例:func add(a, b any) any { ... } // 应避免!

// 3. 从 any 转回原类型需要类型断言
func process(value any) {
    // 需要判断类型后再处理
    switch v := value.(type) {
    case int:
        fmt.Println("整数:", v*2)
    case string:
        fmt.Println("字符串:", v+v)
    }
}

14. 错误处理

func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, errors.New("负数无平方根")
    }
    return math.Sqrt(x), nil
}

// 使用
result, err := sqrt(-1)
if err != nil {
    fmt.Println(err)
}

15. 并发:Goroutine 和 Channel

// 启动 goroutine
go func() {
    fmt.Println("我在 goroutine 中")
}()

// Channel
ch := make(chan int)
go func() {
    ch <- 42  // 发送
}()
value := <-ch // 接收

// 带缓冲 channel
ch2 := make(chan string, 2)
ch2 <- "hello"
ch2 <- "world"

// select 多路复用
select {
case msg := <-ch:
    fmt.Println(msg)
case <-time.After(1 * time.Second):
    fmt.Println("超时")
}

16. 泛型 (Generics)

// 泛型函数
func MapKeys[K comparable, V any](m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

// 泛型结构体
type Stack[T any] struct {
    elements []T
}

17. init 函数

func init() {
    // 包加载时自动执行,早于 main 函数
    // 常用于初始化配置、注册驱动等
}

18. 单元测试

// 文件名必须以 _test.go 结尾
// 函数名必须以 Test 开头
func TestAdd(t *testing.T) {
    got := Add(1, 2)
    if got != 3 {
        t.Errorf("Add(1, 2) = %d; want 3", got)
    }
}

19. 常用标准库

  • fmt:格式化输入输出
  • time:时间处理
  • os / os/exec:系统操作
  • net/http:构建 Web 服务
  • encoding/json:JSON 序列化