https://www.runoob.com/go/go-tutorial.html
Go 简介
Go 语言吉祥物是一只名为 Gopher 的土拨鼠(接地气?),官方口号是 Simple, Fast, Reliable,这三个词概括了它全部的设计取舍。
- 原生支持并发编程,无需第三方库(内置 goroutine 和 channel,并发如同写顺序代码)
- 语法最少化,没有类和继承(极简语法,强制格式化,一种问题只有一种写法)
- 自动垃圾回收(GC),避免手动内存管理的风险
- Go 支持类型推断——编译器可以自动推导变量类型,开发者无需反复声明类型名。这种设计在保持类型安全的同时,让代码写起来像动态语言一样简洁。
并发是 Go 语言最引人注目的特性。Goroutine 是一种比操作系统线程轻量得多的执行单元——每个 Goroutine 的初始栈大小仅为约 2 KB,而操作系统线程通常在 1 MB 以上。可以在一台普通机器上同时运行数十万个 Goroutine,这是传统线程模型无法做到的。
Channel(通道)是 Goroutine 之间的通信管道,遵循 Go 的并发哲学:
不要通过共享内存来通信,而要通过通信来共享内存。(Don’t communicate by sharing memory; share memory by communicating.)
package main
import "fmt"
func main() {
// 完整声明:显式指定类型
var name string = "runoob"
// 类型推断:编译器自动推导类型
var version = 1.24 // 推导为 float64
count := 100 // 短变量声明,推导为 int
message := "Hello Go" // 推导为 string
// 多变量声明
x, y := 10, 20 // 同时声明多个变量
fmt.Println(name, version, count, message, x, y)
//runoob 1.24 100 Hello Go 10 20
}
变量与常量:Go 提供多种变量声明方式,适应不同场景。常量使用 const 关键字声明。
package main
import "fmt"
func main() {
// 方式一:标准声明(显式类型)
var age int = 25
// 方式二:声明后赋值(类型在声明时指定)
var score float64
score = 98.5
// 方式三:短变量声明(最常用,仅限函数内部)
name := "runoob"
// 方式四:多变量同时声明
x, y := 10, 20
// 常量声明(编译时确定,不可修改)
const Pi = 3.14159
const AppName = "RUNOOB"
fmt.Println(name, age, score, x, y, Pi, AppName)
//runoob 25 98.5 10 20 3.14159 RUNOOB
}
控制流程:Go 的控制流程精简为 if、for 和 switch 三种。Go 没有 while 关键字,所有循环均使用 for 实现。
package main
import "fmt"
func main() {
// if 语句:条件表达式不需要括号,但大括号必须有
score := 85
if score >= 90 {
fmt.Println("RUNOOB 评级: A")
} else if score >= 60 {
fmt.Println("RUNOOB 评级: B")
} else {
fmt.Println("RUNOOB 评级: C")
}
//RUNOOB 评级: B
// for 循环一:标准三要素形式(类似 C 的 for)
fmt.Print("计数: ")
for i := 0; i < 5; i++ {
fmt.Print(i, " ")
}
fmt.Println()
//计数: 0 1 2 3 4
// for 循环二:仅条件(类似 while)
sum := 0
n := 1
for n <= 10 {
sum += n
n++
}
fmt.Println("1 到 10 之和:", sum)
//1 到 10 之和: 55
// for 循环三:range 遍历(最常用)
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
fmt.Printf("索引 %d: %s\n", index, fruit)
}
//索引 0: apple
//索引 1: banana
//索引 2: cherry
// switch 语句:每个 case 自动 break,无需手动添加
day := "周一"
switch day {
case "周一":
fmt.Println("新的一周开始了")
case "周五":
fmt.Println("周末快到了")
default:
fmt.Println("普通的一天")
}
//新的一周开始了
}
函数:Go 函数使用 func 关键字声明,支持多返回值——这是 Go 错误处理机制的基础。
package main
import (
"errors"
"fmt"
)
// add 函数:两个参数同为 int 类型,返回一个 int
func add(a, b int) int {
return a + b
}
// divide 函数:返回两个值——结果和错误
// Go 惯用 (result, error) 模式进行错误处理
func divide(a, b float64) (float64, error) {
if b == 0 {
// 返回零值和自定义错误
return 0, errors.New("除数不能为零")
}
return a / b, nil // nil 表示没有错误
}
// swap 函数:多返回值用于返回多个计算结果
func swap(x, y string) (string, string) {
return y, x
}
func main() {
// 调用普通函数
sum := add(10, 20)
fmt.Println("10 + 20 =", sum)
//10 + 20 = 30
// 调用多返回值函数并检查错误
result, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
} else {
fmt.Println("10 / 2 =", result)
}
//10 / 2 = 5
// 测试除零情况
_, err = divide(10, 0) // _ 用于忽略不需要的返回值
if err != nil {
fmt.Println("除零错误:", err) // 预期输出此错误
}
//除零错误: 除数不能为零
// 多返回值交换
first, second := swap("RUNOOB", "Hello")
fmt.Println("交换后:", first, second)
//交换后: Hello RUNOOB
}
Go 没有 try-catch 异常机制,而是通过函数返回 error 值来显式处理错误。这种设计让错误处理路径一目了然,避免了隐式控制流。
结构体与方法:Go 没有类的概念,使用 struct(结构体)组织数据,通过为类型定义方法来实现面向对象风格的行为。方法是带有接收者(receiver)的函数。
package main
import "fmt"
// User 定义用户结构体(类似于其他语言中的类)
type User struct {
Name string // 首字母大写 = 公开字段(可被外部包访问)
Email string
age int // 首字母小写 = 私有字段(仅包内可访问)
}
// Greet 是为 User 类型定义的方法
// (u User) 是值接收者,方法内对 u 的修改不会影响原始值
func (u User) Greet() string {
return fmt.Sprintf("你好,我是 %s,邮箱是 %s", u.Name, u.Email)
}
// Birthday 使用指针接收者,方法内可以修改原始值
func (u *User) Birthday() {
u.age++ // 通过指针直接修改原始结构体的 age 字段
}
func main() {
// 创建结构体实例
user := User{
Name: "runoob",
Email: "runoob@example.com",
age: 25,
}
// 调用方法
fmt.Println(user.Greet())
//你好,我是 runoob,邮箱是 runoob@example.com
fmt.Printf("生日前年龄: %d\n", user.age)
//生日前年龄: 25
user.Birthday() // 使用指针接收者,age 被加 1
fmt.Printf("生日后年龄: %d\n", user.age)
//生日后年龄: 26
}
接口:Go 的接口是隐式实现的——只要一个类型实现了接口中所有方法,它就自动满足该接口,无需显式声明 implements。这种设计极大地降低了代码耦合度。
package main
import (
"fmt"
"math"
)
// Shape 接口:定义几何形状的行为规范
type Shape interface {
Area() float64 // 计算面积
Perimeter() float64 // 计算周长
}
// ====================== 圆形 ===================
// Circle 圆形结构体
type Circle struct {
Radius float64
}
// Circle 实现 Shape 接口(隐式实现,无需显式声明)
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// ====================== 矩形 ===================
// Rectangle 矩形结构体
type Rectangle struct {
Width, Height float64
}
// Rectangle 也隐式实现了 Shape 接口
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// PrintShape 接受任何实现了 Shape 接口的类型
func PrintShape(s Shape) {
fmt.Printf("面积: %.2f, 周长: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 5} // 半径为 5 的圆
r := Rectangle{Width: 4, Height: 6} // 宽 4 高 6 的矩形
fmt.Println("圆形 (RUNOOB 示例):")
PrintShape(c)
// 圆形 (RUNOOB 示例):
//面积: 78.54, 周长: 31.42
fmt.Println("矩形 (RUNOOB 示例):")
PrintShape(r)
//矩形 (RUNOOB 示例):
//面积: 24.00, 周长: 20.00
}
Go 的接口设计鼓励「小接口」——通常只包含 1-3 个方法。标准库中的 io.Reader(仅一个 Read 方法)和 io.Writer(仅一个 Write 方法)是最经典的例子。
HTTP 服务器示例
使用标准库 net/http 即可搭建一个功能完整的 Web 服务,无需任何第三方框架。
// 文件路径:server.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// Response 定义 JSON 响应的数据结构
// 本结构体像一个容器,规范了每一次 API 请求返回的数据长什么样。无论请求是成功还是失败,都统一返回包含这三个基本要素的对象。
type Response struct {
// 在字段类型后面,用反引号括起来的部分(如 `json:"code"`)叫做结构体标签。
// Go 语言有严格的命名规则:字段名首字母必须大写(如 Code),外部包(比如 JSON 序列化工具)才能访问它。
// 但是,前端 JavaScript / 传统的 JSON 命名规范更习惯使用小写字母(如 code)。
// 结构体标签(如 `json:"code"`)本身只是底层的元数据(Metadata)
// 在实际开发中,必定会有某个地方调用了序列化函数,这些标签才会生效。
Code int `json:"code"` // 结构体标签指定 JSON 字段名
Message string `json:"message"`
// omitempty 是一个非常高级且实用的 JSON 标签控制指令,字面意思:omit empty(如果为空则忽略)
// 当有数据返回时:比如获取用户信息成功,Data 里面有内容;当没有数据返回时(例如密码错误、操作失败、或者单纯的“删除成功”不需要回显数据):此时 Data 的值是 nil(空)
// 如果没有加 omitempty:输出会带着 "data": null,即:key “data” 和 对应的值
// 加了 omitempty:Go 在生成 JSON 时会自动抹去/不生成 data 这个 key,输出结果非常干净
// 这不仅能帮前端省去判断 null 的麻烦,还能减少网络传输的数据量
Data any `json:"data,omitempty"` // omitempty:值为空时不输出该字段
// any 类型:这是 Go 1.18 引入的关键字,等价于空接口 interface{}。
// 这意味着 Data 字段可以装载任何类型的数据(比如一个用户对象、一个商品列表、或者一个简单的数字和布尔值),极具灵活性。
}
// helloHandler 处理 /hello 路由的请求
func helloHandler(w http.ResponseWriter, r *http.Request) {
// 仅允许 GET 请求
if r.Method != http.MethodGet {
http.Error(w, "仅支持 GET 请求", http.StatusMethodNotAllowed)
return
}
// 从查询参数获取 name,默认为 "RUNOOB"
name := r.URL.Query().Get("name")
if name == "" {
name = "RUNOOB"
}
// 构建响应数据
resp := Response{
Code: 200,
Message: fmt.Sprintf("你好,%s!欢迎访问 Go HTTP 服务", name),
}
// 设置响应头并返回 JSON
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(resp)
}
func main() {
// 注册路由处理函数
http.HandleFunc("/hello", helloHandler)
addr := ":8080"
fmt.Printf("RUNOOB 服务器启动,监听地址: http://localhost%s\n", addr)
fmt.Printf("访问示例: http://localhost%s/hello?name=Go开发者\n", addr)
// 启动 HTTP 服务(阻塞,直到服务停止)
log.Fatal(http.ListenAndServe(addr, nil))
}
启动服务:
go run main.go
访问对应地址即可获得 JSON 响应
返回:
{"code":200,"message":"你好,RUNOOB!欢迎访问 Go HTTP 服务"}
http://localhost:8080/hello?name=%E5%BC%A0%E4%B8%89
返回:
{"code":200,"message":"你好,张三!欢迎访问 Go HTTP 服务"}
如果端口被站用,windows下杀掉对应的进程:
netstat -ano | findstr 8080
taskkill /F /PID 13316