编程语言

Go教程(一)环境与基础语法

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. 练习

  1. 给日志工具加上"按小时统计请求数"。
  2. 写函数 wordFreq(text string) map[string]int,忽略大小写和标点。
  3. 用 strconv 解析日志中的响应字节数,计算总流量。

文章评论 (0)