golang 简明语法教程
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 complex1283. 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 可继续执行下一个 case7. 数组和切片(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到3slices 包(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和208. 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) bool12. 结构体 (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 序列化


