Go 01:环境与基础语法
1. 环境搭建
brew install go
go version
mkdir go-learn && cd go-learn
go mod init example.com/golearn # 生成 go.mod ≈ pyproject.toml
main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
go run main.go # 直接运行 ≈ python main.py
go build -o app # 编译为单个二进制文件
./app
go fmt ./... # 官方统一格式化(≈ black,没有争议)
go vet ./... # 静态检查
| Python | Go |
|---|---|
pip install requests |
go get github.com/xxx/yyy |
requirements.txt / uv.lock |
go.mod / go.sum |
一个.py 文件是一个模块 |
一个目录是一个 package |
_private 约定 |
小写开头 = 私有,大写开头 = 公开 |
| 缩进 | 花括号 + tab 缩进(gofmt 强制) |
2. 变量与类型
var name string = "go"
var count int // 零值:0(Go 中所有变量都有零值,不存在"未初始化")
age := 18 // 短变量声明,类型推断(函数内最常用)
const Pi = 3.14
// 多变量
a, b := 1, "two"
a, c := 2, true // := 左侧至少有一个新变量即可
// 类型转换必须显式
var i int = 42
var f float64 = float64(i)
s := strconv.Itoa(i) // int -> string,需要 import "strconv"
n, err := strconv.Atoi("123")
零值表:int → 0,string → "",bool → false,指针/切片/map/接口 → nil。
Go 没有
None混用问题:string永远不会是 nil。需要"可能为空"时用指针*string或额外的ok bool。
3. 切片(slice)≈ list
nums := []int{1, 2, 3}
nums = append(nums, 4) // append 返回新切片,必须重新赋值
fmt.Println(len(nums), nums[1:3])
// 预分配容量(性能优化)
ids := make([]int, 0, 100)
// 遍历 ≈ for i, v in enumerate(nums)
for i, v := range nums {
fmt.Println(i, v)
}
for _, v := range nums { } // 忽略索引用 _
// Go 1.21+ 标准库 slices(需在文件顶部 import "slices")
slices.Contains(nums, 3)
slices.Sort(nums)
idx := slices.Index(nums, 2)
Go 没有列表推导式,也没有内置 map/filter,直接写 for 循环(这是 Go 的哲学:直白胜于巧妙)。
// [x * 2 for x in nums if x > 1]
var doubled []int
for _, x := range nums {
if x > 1 {
doubled = append(doubled, x*2)
}
}
4. map ≈ dict
scores := map[string]int{"alice": 90, "bob": 80}
scores["carol"] = 85
delete(scores, "bob")
// 取值并判断是否存在 ≈ dict.get / in
if v, ok := scores["alice"]; ok {
fmt.Println("alice:", v)
}
missing := scores["nobody"] // 不存在返回零值 0,不会抛 KeyError
for k, v := range scores { // 注意:map 遍历顺序是随机的
fmt.Println(k, v)
}
// 计数器 ≈ collections.Counter
words := []string{"go", "py", "go"}
counter := map[string]int{}
for _, w := range words {
counter[w]++
}
坑:声明但未初始化的 map 是 nil,写入会 panic:
var m map[string]int; m["a"] = 1 // panic。用make或字面量初始化。
5. 函数
func add(a, b int) int {
return a + b
}
// 多返回值(Go 最常见的模式:结果 + 错误)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
result, err := divide(10, 0)
if err != nil {
fmt.Println("error:", err)
return
}
// 可变参数 ≈ *args
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3)
sum(nums...) // 展开切片
// 函数是一等公民,闭包
func counter() func() int {
c := 0
return func() int {
c++
return c
}
}
next := counter()
next() // 1
next() // 2
6. 控制流
// if 可以带初始化语句
if n, err := strconv.Atoi("42"); err == nil {
fmt.Println(n)
}
// 只有 for,没有 while
for i := 0; i < 3; i++ { }
for count < 10 { count++ } // ≈ while
for { break } // 无限循环
for i := range 5 { } // Go 1.22+ ≈ range(5)
// switch 默认 break,不需要写
switch day := "sat"; day {
case "sat", "sun":
fmt.Println("weekend")
default:
fmt.Println("weekday")
}
// defer:函数返回前执行 ≈ Python 的 with / finally
f, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close()
7. 指针(不用怕,比 C 简单得多)
func increase(n *int) { *n++ }
x := 1
increase(&x) // x == 2
规则:Go 函数传参都是值拷贝。想在函数内修改调用方的数据,传指针。切片、map 内部本身含指针,修改元素无需传指针(但 append 后长度变化需要返回新切片)。
8. 应用场景示例:日志分析小工具
需求:统计 nginx 访问日志中每个状态码出现的次数,以及访问量最高的前 5 个 URL。
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("usage: logstat <access.log>")
os.Exit(1)
}
f, err := os.Open(os.Args[1])
if err != nil {
fmt.Println("open failed:", err)
os.Exit(1)
}
defer f.Close()
statusCount := map[string]int{}
urlCount := map[string]int{}
scanner := bufio.NewScanner(f) // 逐行读取,适合大文件
for scanner.Scan() {
// 示例行: 1.2.3.4 - - [01/Nov/2026:10:00:00 +0800] "GET /blog/1 HTTP/1.1" 200 512
parts := strings.Fields(scanner.Text())
if len(parts) < 9 {
continue
}
urlCount[parts[6]]++
statusCount[parts[8]]++
}
fmt.Println("== status ==")
for code, n := range statusCount {
fmt.Printf("%s\t%d\n", code, n)
}
type kv struct {
URL string
Count int
}
var list []kv
for u, c := range urlCount {
list = append(list, kv{u, c})
}
sort.Slice(list, func(i, j int) bool { return list[i].Count > list[j].Count })
fmt.Println("== top 5 urls ==")
for _, item := range list[:min(5, len(list))] {
fmt.Printf("%-40s %d\n", item.URL, item.Count)
}
}
go run main.go /var/log/nginx/access.log
GOOS=linux GOARCH=amd64 go build -o logstat # 交叉编译,直接拷贝到服务器运行
这就是 Go 的典型优势:写完一个工具,编译出单文件丢到服务器,无需安装 Python 和依赖。
9. 练习
- 给日志工具加上"按小时统计请求数"。
- 写函数
wordFreq(text string) map[string]int,忽略大小写和标点。 - 用
strconv解析日志中的响应字节数,计算总流量。
文章评论 (0)