GoClub Pro

Go-基础

最近更新:2026-10-24
本页是手写/批注类笔记,直接按原始笔记截图呈现(转成文字会乱序);原始笔记文件见「Go语言相关笔记」页的原始链接。
onenote-go-basics 原始笔记截图 1
附件 PDF 全文:《蓝山工作室——Golang 第一节课》(23 页)

蓝山工作室——Golang 第一节课

关于Golang 语言

Go 语言由 Robert Griesemer, Rob Pike, Ken Thompson 于 2007 年末开发,

并于 2009 年开源。它旨在提供一种简单、可靠且高效的编程语言,特别适合

构建高性能网络服务和分布式系统。

认识一下Go 程序

一个基础的 Go 程序通常包含以下几个部分:

codeGo

package main // 声明主包,表示这是一个可执行程序

import "fmt" // 导入 fmt包,用于格式化输入输出

func main() { // 程序入口函数

fmt.Println("Hello, World!") // 调用 fmt 包的 Println 函数打印字符串

}

• package main: 定义了程序的入口点。

• import "fmt": 导入标准库中的 fmt 包,提供打印等功能。

• func main(): 程序的执行从这里开始。

Go 语法基础

数据类型

Go 语言支持以下基本数据类型:

1. 布尔型 (bool): true 或 false。

2. 数字类型:

o 整型: int8, int16, int32, int64 (有符

号); uint8, uint16, uint32, uint64 (无符号)。

o int: 根据操作系统架构,可以是 32 或 64 位。

o byte: uint8 的别名。

o rune: int32 的别名,用于表示 Unicode 码点。

o 浮点型: float32, float64。

o 复数型: complex64, complex128。

3. 字符串类型 (string): 底层是不可变的 UTF-8 编码字节数组。

4. 派生类型: 包括指针、数组、结构体、Channel、函数、切片、接口和

Map。

变量声明

Go 语言变量必须先声明再使用,且同一作用域内不可重复声明,声明后也必须

使用。

• 完整声明: var 变量名 类型 = 表达式

codeGo

var a = "first" // 类型推导

var d int8 = 1 // 指定类型

var e float64 // 声明

e = 1.0 // 赋值

• 短变量声明: 变量名 := 表达式 (只能用于函数内部)

codeGo

f := 114514 // 自动推导类型并赋值

常量

常量是程序运行期间不可改变的值,使用 const 关键字声明,定义时必须赋

值。

codeGo

const s string = "constant"

const h = 500000000 // 类型自动推导

循环

Go 语言只有 for 一个循环关键字。

1. 三段式: for 初始化; 条件; 循环表达式 { ... }

codeGo

for i := 0; i < 3; i++ {

fmt.Println(i)

}

o 各部分可省略,如 for ; i < 5; { ... }

2. 一段式 (只带条件): for 条件 { ... }

codeGo

i := 0

for i < 5 {

fmt.Println(i)

i++

}

3. 无限循环: for { ... }

codeGo

for {

fmt.Println("loop")

break // 需要手动跳出,否则无限循环

}

• break: 跳出当前循环。

• continue: 跳过当前循环的剩余部分,进入下一次循环。

条件判断 if

codeGo

if 条件表达式 {

// 条件为 true 时执行

} else {

// 条件为 false 时执行

}

• 支持 else if 多分支。

• if 语句可以带一个短声明,声明的变量作用域仅限于 if/else if/else

块内。

codeGo

if num := 9; num < 0 {

fmt.Println(num, "is negative")

} else if num < 10 {

fmt.Println(num, "has 1 digit")

} else {

fmt.Println(num, "has multiple digits")

}

多分支选择 switch

用于处理多个条件分支,比 if-else if 更简洁。

• case 匹配成功后会自动跳出,无需 break (与 C/C++不同)。

• 使用 fallthrough 可以强制执行下一个 case。

• 可以同时匹配多个值: case 4, 5: ...

• switch 后可不跟表达式,直接在 case 中写条件。

codeGo

a := 2

switch a {

case 1:

fmt.Println("one")

case 2:

fmt.Println("two") // 匹配到此并跳出

// fallthrough // 如果加上这句,会继续执行 case 3

case 3:

fmt.Println("three")

default:

fmt.Println("other")

}

t := time.Now()

switch { // 没有 switch 表达式,case 直接是条件

case t.Hour() < 12:

fmt.Println("It's before noon")

default:

fmt.Println("It's after noon")

}

函数 func

函数是组织代码的基本单位,用于实现特定功能。

1. 声明与定义: func 函数名(参数列表) 返回值列表 { ... }

codeGo

func add(x int, y int) int {

return x + y

}

o 参数类型相同时可简写: func add(x, y int) int { ... }

o 可返回多个值: func swap(x, y int) (int, int) { return y,

x }

2. 调用: result := add(3, 5)

3. 函数是一等公民: 函数可以像普通变量一样被赋值、作为参数传递、作

为返回值返回。这使得高阶函数和闭包成为可能。

codeGo

type MathOperation func(int, int) int // 定义函数类型

func add(a, b int) int { return a + b }

func main() {

var op MathOperation = add // 将函数赋值给变量

fmt.Println(op(1, 2)) // 调用函数变量

}

fmt 包

fmt 包提供了格式化输入输出功能。

• fmt.Println(): 打印值后换行。

• fmt.Printf(): 格式化打印。

o %v: 默认格式打印。

o %+v: 打印结构体时显示字段名。

o %#v: 打印值的 Go 语法表示。

o %.2f: 浮点数格式,保留两位小数。

codeGo

type point struct { x, y int }

p := point{1, 2}

fmt.Printf("p=%v\n", p) // p={1 2}

fmt.Printf("p=%+v\n", p) // p={x:1 y:2}

fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

f := 3.14159

fmt.Printf("%.2f\n", f) // 3.14

第 02 节课

数组 & 切片

数组和切片是 Go 语言中用于存储同类型元素序列的两种数据结构。

数组-array

• 固定长度: 数组的长度在定义后不可改变,是类型的一部分。

[3]int 和 [4]int 是不同类型。

• 值类型: 数组是值类型,传递时会创建一份完整的副本。

• 初始化:

codeGo

var a [3]int // 声明一个长度为 3 的 int 数组,元素默认为零值

[0 0 0]

b := [3]int{1, 2} // [1 2 0],未初始化元素为零值

c := [...]int{1, 2, 3, 4} // 通过初始化值确定长度为 4

d := [5]int{2: 100, 4: 200} // [0 0 100 0 200],通过索引初始化

• 多维数组: 支持多维数组。

codeGo

e := [2][2]int{{1, 2}, {3, 4}} // [[1 2] [3 4]]

• 访问: 通过索引 arr[index] 访问,索引从 0 到 len-1。

codeGo

arr := [3]string{"a", "b", "c"}

fmt.Println(arr[0]) // 输出: a

• 遍历: for i, v := range arr { ... }

codeGo

for i, v := range arr {

fmt.Printf("arr[%d] = %s\n", i, v)

}

切片-slice

• 可变长度: 切片是动态可变的,底层引用一个数组。

• 引用类型: 切片是引用类型,传递时只复制切片头信息(指针、长度、

容量),指向同一个底层数组。

• 切片三要素: 一个切片由指针(指向底层数组的起始位置)、长度

(len,当前可见元素数量)和容量(cap,从指针位置到底层数组末尾

的元素数量)组成。

• 初始化:

codeGo

var s1 []int // 声明一个 nil 切片,长度和容量都为 0

s2 := []int{} // 声明一个空切片,长度和容量都为 0

s3 := []int{1, 2, 3} // 初始化并赋值

s4 := make([]int, 5) // 创建一个长度为 5,容量为 5 的切片,元素为零值

s5 := make([]int, 0, 10) // 创建一个长度为 0,容量为 10 的切片

• 操作:

o len(s): 获取切片长度。

o cap(s): 获取切片容量。

o s[n]: 访问索引为 n 的元素。

o s[low:high]: 切片操作,从 low 到 high-1。新切片长度 high-

low,容量原cap-low。

o s[low:high:max]: 切片操作,指定底层数组的最大容量。新切片

长度 high-low,容量 max-low。

codeGo

nums := []int{10, 20, 30, 40, 50}

subSlice := nums[1:3] // subSlice: [20 30], len: 2, cap: 4 (从20

开始到末尾)

fullSlice := nums[0:len(nums):len(nums)] // 完整切片,限制容量等于长度

• append(): 向切片末尾添加元素,返回新切片。当容量不足时,会重新

分配一个更大的底层数组(通常是 2 倍),并复制旧数据。

codeGo

s := []int{1, 2}

s = append(s, 3, 4) // s: [1 2 3 4]

切片与数组的关系

切片是底层数组的一个“视图”。多个切片可以引用同一个底层数组的不同部

分。对一个切片的修改可能会影响到引用同一底层数组的其他切片。

codeGo

s3 := []int{1, 2, 3, 4, 5, 6, 7, 8} // len=8, cap=8

s4 := s3[3:6] // s4: [4 5 6], len=3, cap=5 (从索引 3 到 8)

s6 := append(s4, 9) // s6: [4 5 6 9], len=4, cap=5。如果 s4 的 cap 不

足,append 会创建新底层数组。

// 否则,s6 会修改 s3 中 s4 底层数组对应位置的

值。

字典-map

Map 是键值对的无序集合,通过键快速查找对应的值。键必须是可比较的类型

(不能是函数、切片、map 本身)。

• 初始化:

codeGo

// 使用 make 函数

ages := make(map[string]int) // 创建一个 string 到 int 的 map

// 使用字面量

grades := map[string]string{

"Alice": "A",

"Bob": "B",

}

• 增/改元素: map[key] = value

codeGo

ages["Alice"] = 25

ages["Bob"] = 30

• 查询元素: value, exists := map[key]

codeGo

aliceAge, exists := ages["Alice"]

if exists {

fmt.Printf("Alice's age: %d\n", aliceAge)

} else {

fmt.Println("Alice not found.")

}

• 遍历: for key, value := range map { ... }

codeGo

for name, age := range ages {

fmt.Printf("%s is %d years old.\n", name, age)

}

• 删除元素: delete(map, key)

codeGo

delete(ages, "Bob")

函数进阶

函数是一等公民

Go 语言中,函数可以被赋值给变量、作为参数传递给其他函数、作为返回值返

回。

• 函数类型: 可以像其他类型一样定义函数类型。

codeGo

type Printer func(contents string) (n int, err error) // 定义一个函数类型

func printToStd(contents string) (bytesNum int, err error) {

return fmt.Println(contents)

}

func main() {

var p Printer = printToStd // 将函数赋值给函数类型变量

p("something")

}

o 函数签名:指函数的参数列表和结果列表的类型和顺序。只要签

名一致,函数就可以互相赋值。

高阶函数

满足以下任一条件即可称为高阶函数:

1. 接受其他函数作为参数。

2. 把其他函数作为结果返回。

codeGo

type Operate func(x, y int) int // 定义一个操作函数类型

// 接受函数作为参数的高阶函数

func calculate(x, y int, op Operate) (int, error) {

if op == nil { return 0, errors.New("invalid operation") }

return op(x, y), nil

}

// 返回函数作为结果的高阶函数(通常伴随闭包)

type CalculateFunc func(x, y int) (int, error)

func genCalculator(op Operate) CalculateFunc {

return func(x int, y int) (int, error) { // 这是一个闭包

if op == nil { return 0, errors.New("invalid operation") }

return op(x, y), nil

}

}

闭包

闭包是一个函数值,它引用了其函数体外部的变量。这个外部变量被称为“自由

变量”。闭包会“记住”它被创建时的环境,即使该环境已不再存在。

• 作用: 延迟逻辑实现、动态生成函数、状态共享(如迭代器)、回调函

数、函数工厂等。

codeGo

// 例子:函数工厂

func CalculationFactory(operation string) func(int, int) int {

switch operation {

case "add":

return func(a, b int) int { return a + b }

case "subtract":

return func(a, b int) int { return a - b }

default:

return nil

}

}

func main() {

addFunc := CalculationFactory("add")

fmt.Println(addFunc(5, 3)) // 输出: 8

}

引用类型 & 值类型

• 值类型: int, float, bool, string, array, struct。函数传参时会复制一

份完整的副本。修改副本不会影响原值。

• 引用类型: slice, map, channel。函数传参时会复制其引用/指针信息

(如切片的底层数组指针),但它们引用的底层数据是共享的。修改引

用类型可能影响原数据。

codeGo

func modifyArray(a [3]string) { // 数组是值类型,传入的是副本

a[1] = "x"

}

func main() {

array1 := [3]string{"a", "b", "c"}

modifyArray(array1)

fmt.Println(array1) // 输出: [a b c] (未改变)

}

func modifySlice(s []string) { // 切片是引用类型,底层数据共享

s[1] = "x"

}

func main() {

slice1 := []string{"a", "b", "c"}

modifySlice(slice1)

fmt.Println(slice1) // 输出: [a x c] (已改变)

}

Go Test

Go 语言内置了丰富的测试支持,用于编写单元测试、基准测试和示例测试。

• 测试文件命名: xxx_test.go。

• 测试函数命名:

o 功能测试: func TestXxx(t *testing.T)

o 基准测试: func BenchmarkXxx(b *testing.B)

o 示例测试: func ExampleXxx() (通常用于展示用法和预期输出)

• 运行测试: 在终端运行 go test 或 go test -v (显示详细信息)。

codeGo

// my_math.go

func Add(a, b int) int {

return a + b

}

// my_math_test.go

package main

import "testing"

func TestAdd(t *testing.T) {

if Add(1, 2) != 3 {

t.Errorf("Add(1, 2) = %d; want 3", Add(1, 2))

}

}

Git

Git 是分布式版本控制系统,用于管理代码版本和团队协作。

• 安装: 访问 Git 官网下载安装。

• 基本操作: git init, git add, git commit, git push, git pull, git

clone。

• IDE 集成: 大部分 IDE(如 Goland)都内置了 Git 图形界面或命令行工

具,方便操作。

• 学习资源: Learn Git Branching (一个交互式 Git教程)。

包

包介绍

Go 语言使用包(package)来组织代码,实现模块化和代码复用。每个.go 文件

都必须属于一个包。

定义包

• 一个包通常对应一个文件夹,文件夹内的所有.go 文件都声明同一个包

名。

• 包名为 main 的包是可执行程序的入口点。

• 包名通常应简洁且有意义,例如 config, logger, utils。

• 禁止循环导入: 包之间不能形成循环依赖。

可见性

Go 语言通过标识符(变量、常量、函数、类型等)的首字母大小写来控制可见

性:

• 首字母大写: 对外可见(可导出),其他包可以访问。

• 首字母小写: 包内可见(不可导出),只能在当前包内部访问。

codeGo

var PublicVar string // 对外可见

var privateVar string // 包内可见

func PublicFunc() {} // 对外可见

func privateFunc() {} // 包内可见

包的引入

使用 import 关键字在当前文件引入其他包。

• 语法: import "path/to/package"

codeGo

import "fmt" // 导入标准库包

import "myproject/mypackage" // 导入自定义模块的包(路径基于 go.mod)

• 批量引入:

codeGo

import (

"fmt"

"net/http"

)

• 别名导入: import alias "path/to/package",用于解决包名冲突或简

化长包名。

codeGo

import f "fmt" // 使用 f.Println() 代替 fmt.Println()

• 匿名导入: import _ "path/to/package"。

o 只执行包的 init 函数,不导入包中的其他标识符。

o 常用于引入数据库驱动等,只为初始化其内部状态。

o init 函数会在 main 函数之前执行,多个 init 函数按声明顺序执

行。

codeGo

import _ "github.com/go-sql-driver/mysql" // 注册数据库驱动

• 获取外部包: go get "module/path"。

• 清理依赖: go mod tidy。

指针

指针地址和指针类型

• 地址: 内存中存储变量的位置。

• 取地址符 &: 获取变量的内存地址。

• 指针类型: *T 表示指向 T 类型变量的指针。

codeGo

a := 10

b := &a // b 是指向 a 的指针,类型为 *int

fmt.Printf("a 的值: %d, a 的地址: %p\n", a, &a)

fmt.Printf("b 的值 (a 的地址): %p, b 的类型: %T\n", b, b)

指针取值

• 取值符 *: 根据指针指向的地址,获取存储在该地址上的值。

codeGo

a := 10

b := &a // b 存储 a 的地址

fmt.Println(*b) // 输出: 10 (通过 b 获取 a 的值)

*b = 20 // 通过b 修改 a 的值

fmt.Println(a) // 输出: 20

• &和*是一对互补操作符。

指针的作用

• 修改原变量: 函数接收指针作为参数时,可以直接修改原变量的值,而

非其副本。

codeGo

func updateValue(p *int) {

*p++ // 直接修改指针 p 指向的变量的值

}

func main() {

x := 0

updateValue(&x) // 传入 x 的地址

fmt.Println(x) // 输出: 1

}

“指针就做指针的事”

Go 语言中的指针不能进行偏移和运算 (如 p+1)。这是为了:

• 内存安全: 避免越界访问,Go 通过切片和边界检查提供更安全的内存操

作。

• 可移植性: 不同架构下的指针算术可能表现不同。

• GC (垃圾回收) 友好: GC需要精确追踪哪些是有效指针,指针运算会使

GC 难以判断。

• 简化复杂性: 鼓励使用切片等更高级的抽象来处理内存块。

结构体,方法,接口——面向对象思想

在 Go 中的浪漫诠释

Go 语言没有传统意义上的“类”和“继承”,但通过结构体、方法和接口的组合,

实现了强大的面向对象编程(OOP)思想。

前置知识

自定义类型

使用 type 关键字定义一个全新的类型,它具有原类型的特性但被视为不同类

型。

codeGo

type MyInt int // MyInt 是一个新类型,底层是 int

类型别名

使用 type 别名 = 类型定义一个类型别名,它与原类型是完全相同的。

codeGo

type MyString = string // MyString 只是 string 的一个别名

• 区别: 自定义类型是新类型,别名是同类型。自定义类型可用于为基础

类型添加方法。

结构体 (struct)

结构体是自定义的复合数据类型,用于将多个不同类型的数据字段组合在一

起,表示一个事物的完整属性。

• 定义:

codeGo

type Person struct {

Name string

Age int

}

• 初始化:

codeGo

// 键值对初始化

p1 := Person{Name: "Alice", Age: 30}

// 字段顺序初始化 (不推荐,容易出错)

p2 := Person{"Bob", 25}

// 声明后赋值

var p3 Person

p3.Name = "Charlie"

p3.Age = 22

• 零值: 未初始化的结构体字段会自动赋值为对应类型的零值(如 string

为"",int 为 0)。

• 嵌套结构体: 一个结构体可以包含另一个结构体作为字段。

codeGo

type Address struct {

City string

Street string

}

type Employee struct {

Person // 匿名嵌套,Employee 直接拥有 Person 的字段

Address // 匿名嵌套

ID string

}

func main() {

emp := Employee{

Person: Person{Name: "David", Age: 35},

Address: Address{City: "Beijing", Street: "Main St"},

ID: "E001",

}

fmt.Println(emp.Name) // 直接访问内嵌结构体的字段 (字段提升)

fmt.Println(emp.City) // 也可以直接访问 Address 的字段

}

方法 (Method)

方法是作用于特定类型的函数,这种特定类型被称为“接收者”。

• 定义: func (接收者变量 接收者类型) 方法名(参数列表) (返回值列表)

{ ... }

codeGo

type Circle struct {

Radius float64

}

// 值接收者方法 (不会修改 Circle 本身)

func (c Circle) Area() float64 {

return 3.14 * c.Radius * c.Radius

}

// 指针接收者方法 (可以修改 Circle 本身)

func (c *Circle) SetRadius(r float64) {

c.Radius = r

}

• 接收者类型:

o 值接收者: 方法内部操作的是接收者变量的副本,不会修改原变

量。适用于读操作或接收者是小对象。

o 指针接收者: 方法内部操作的是接收者变量的指针,可以修改原

变量。适用于写操作、接收者是大型对象(避免拷贝开销)或需

要保持一致性时。

• 调用: myCircle.Area() 或 myCircle.SetRadius(10)。

• 非本地类型不能定义方法: 不能为其他包定义的类型添加方法。

接口 (Interface)

接口定义了一组方法的集合。它关注“能做什么”,而不是“是什么”。

• 定义:

codeGo

type Shape interface {

Area() float64

Perimeter() float64

}

• 实现接口: 一个类型只要实现了接口中定义的所有方法,就被认为实现

了该接口。Go 语言的接口实现是隐式的,无需显式声明。

codeGo

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) }

func main() {

var s Shape // s 是一个接口类型变量

s = Rectangle{Width: 3, Height: 4} // Rectangle 类型的值可以赋值给 Shape

接口变量

fmt.Println("Area:", s.Area())

}

• 接口是动态类型: 接口变量可以持有任何实现了该接口的具体类型的

值。

• 接口的意义:

1. 多态性: 不同类型实现相同接口,可被统一处理。

2. 解耦合: 模块间通过接口而非具体实现进行交互,降低依赖。

3. 扩展性: 添加新功能时,只需实现接口即可,无需修改原有代

码。

4. 接口断言: 在运行时检查接口变量持有的具体类型。

面向对象思想 (OOP)

Go 语言的 OOP 核心思想是:用结构体承载状态,用方法定义行为,用接口抽

象能力。

OOP 三要素在Go 中的体现:

1. 封装 (Encapsulation)

o 定义: 隐藏内部实现细节,只通过公开接口暴露必要功能。

o Go 实现: 通过标识符首字母大小写(大写导出,小写不导出)控

制字段和方法的可见性。外部只能通过大写的方法访问或修改小

写(私有)的字段。

codeGo

type Account struct {

balance int64 // 小写,包外不可直接访问

}

func (a *Account) Deposit(amount int64) { // 大写方法,对外暴露

a.balance += amount

}

func (a *Account) Balance() int64 { // 大写方法,对外暴露

return a.balance

}

2. 组合代替继承 (Composition over Inheritance)

o 定义: Go 没有传统的类继承,通过结构体内嵌来实现代码复用和

功能扩展。

o Go 实现: 将一个结构体作为另一个结构体的匿名字段嵌入,被嵌

入的结构体字段和方法会自动“提升”到外层结构体。

codeGo

type Logger struct {

prefix string

}

func (l *Logger) Log(msg string) {

fmt.Printf("%s: %s\n", l.prefix, msg)

}

type Server struct {

Logger // 匿名内嵌 Logger,Server 拥有 Logger 的字段和方法

Addr string

}

func main() {

s := Server{

Logger: Logger{prefix: "[SERVER]"},

Addr: ":8080",

}

s.Log("Server started.") // 直接调用内嵌 Logger 的方法

// 等同于 s.Logger.Log("Server started.")

}

3. 多态 (Polymorphism)

o 定义: 不同类型的对象,通过实现同一接口,能够以统一的方式

被操作。

o Go 实现: 通过接口和隐式实现来达到。函数或方法可以接收接口

类型参数,从而接受任何实现了该接口的具体类型。

codeGo

type Greeter interface {

Greet() string

}

type EnglishSpeaker struct { Name string }

func (e EnglishSpeaker) Greet() string { return "Hello, " + e.Name }

type SpanishSpeaker struct { Name string }

func (s SpanishSpeaker) Greet() string { return "Hola, " + s.Name }

func SayHello(g Greeter) { // 接收接口类型

fmt.Println(g.Greet())

}

func main() {

eng := EnglishSpeaker{Name: "Alice"}

spa := SpanishSpeaker{Name: "Bob"}

SayHello(eng) // 输出: Hello, Alice

SayHello(spa) // 输出: Hola, Bob

}

泛型

Go 语言在 1.18 版本引入了泛型,使得代码可以更灵活地处理多种类型,同时

保持类型安全和性能。泛型是“语法糖级别的工具”,让你能够写一份代码,适

配多种类型。

类型参数列表 / 泛型函数

泛型函数通过类型参数列表定义,通常用 T 表示一个或多个类型。类型参数列

表放在函数名后的方括号[]中。

• 类型约束: 使用接口或自定义的类型集来约束类型参数所能代表的具体

类型。

codeGo

// Max 函数接受任何实现了 constraints.Ordered 接口的类型T (int, float 等)

func Max[T comparable](a, b T) T { // T 必须是可比较类型 (==, !=)

if a > b { // 这里要求 T 支持 > 运算符

return a

} else {

return b

}

}

// 自定义类型约束

type Number interface {

~int | ~int8 | ~float32 // ~表示包含底层类型为 int/int8/float32 的所有类

型,包括自定义类型

}

func Sum[T Number](nums []T) T { // Sum 函数接受切片,元素类型 T 需满足

Number 约束

var total T

for _, n := range nums {

total += n

}

return total

}

func main() {

fmt.Println(Max(10, 20)) // T 被推导为int

fmt.Println(Max(3.14, 2.71)) // T 被推导为float64

fmt.Println(Sum([]int{1, 2, 3})) // T 被推导为int

}

o comparable: 内置接口,约束类型必须支持==和!=操作符。

o ~ (波浪号): 在类型约束中,~表示允许包含底层类型为指定类型

的所有类型,包括基于该底层类型定义的自定义类型。

泛型结构体

结构体也可以使用类型参数,使其可以存储不同类型的数据。

codeGo

type List[T any] struct { // T 可以是任何类型 (any 是 interface{}的别名)

data []T

}

type Map[K comparable, V any] struct { // K 必须是可比较类型,V 可以是任何类型

hashMap map[K]V

}

func main() {

myIntList := List[int]{data: []int{1, 2, 3}}

myStringMap := Map[string, string]{hashMap: make(map[string]string)}

myStringMap.hashMap["name"] = "Alice"

}

泛型底层简单解析 (看看即可)

• 编译时优化 (Shape Stenciling): Go 编译器会为具有相同内存布局和

GC 指针位图的类型参数(即同一“shape”)生成一份共享的机器码,从

而实现性能接近手写代码。例如,int, int32, float64 等标量类型通常

可以共享同一份泛型代码。

• 运行时字典: 对于需要特定类型操作(如 map 的哈希函数、接口方法调

用、比较操作==/<等)的类型参数,编译器会传入一个算法字典或生成

专用辅助函数。这使得泛型在保持类型安全的同时,仍能处理复杂类型

特有的行为。

附件原文:GO 笔记 PDF(点击下载)

转换文字版(供搜索 / 复制)
内容反馈

发现错误或想补充内容?

GoClub Pro 是静态知识库,评论功能不再加载第三方脚本。你可以在 GitHub 仓库提交 Issue,反馈会直接进入项目协作流程。

提交 GitHub 反馈