Go语言教程(菜鸟教程)

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 响应

http://localhost:8080/hello

返回:

{"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

to go: http://www.runoob.com/go/go-intro.html

正文完
 0
评论(没有评论)