iota
iota 是常量计数器,在const关键字中被重置为0,随后每出现一行常量定义就会自动加1。
iota 除了赋值表达式来实现,还可以隐式重复。
如果 iota 进行了匿名使用,也会进行跳值与占位。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package main
import "fmt"
const (
// 实现简洁的枚举
a = iota // 从 0 开始
b = iota
c = iota
_ // 跳值与占位
d
)
func main() {
fmt.Println(b)
fmt.Println(d)
}
|
iota 的高级用法是位运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package main
import "fmt"
const (
open = 1 << iota // 1 << 0 = 1
close = 1 << iota // 1 << 1 = 2
execute = 1 << iota // 1 << 2 = 4
)
func main() {
fmt.Println(open)
fmt.Println(close)
fmt.Println(execute)
fmt.Printf("%T\n", close)
}
|

我们使用 iota 有几个原因,其一是类型安全,配合类型定义 type Priority int,可以创建具有特定含义的枚举。然后是易于维护,如果我们在中间插入一个新的变量,后续所有的 iota 的值都会自动更新,不需要手动更改。最后,也是 go 语言本身的特点,那就是代码简洁,用法简单。
Rune
Rune 就是 Go 语言中的 Unicode 字符,它与 byte 类型的区别在于:Rune 可以表示 32 位,而 byte 只能表示 8 位字符,所以 byte 用于表示 ASCII, 而 Rune 表示 Unicode,更适合处理中文字符。
Rune -> int32
byte -> uint8
1
2
3
4
5
6
7
8
9
| func main() {
s := "Go语言"
bytes := []byte(s)
fmt.Println(len(bytes))
runes := []rune(s)
fmt.Println(len(runes))
}
|

Composition
Go 里面没有继承的概念,而是使用组合来实现,也就是 Composition。Go 里的“继承”不由接口实现者决定,而是由使用者决定。只要 一个对象实现了相关接口的所有方法,就实现了“继承”。
结构体也是如此。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| package main
import (
"fmt"
)
type Engine struct {
Horsepower int
}
func (e *Engine) Start() {
fmt.Printf("Max power: %d hp\n", e.Horsepower)
}
type Car struct {
Engine // extends Engine
Model string
}
// 实现 Start 方法
func (c *Car) Start() {
fmt.Printf("%s is running...\n", c.Model)
c.Engine.Start()
fmt.Println("Car is ready!")
}
func main() {
Tesla := Car{
Engine: Engine{Horsepower: 300},
Model: "CyberTruck",
}
// 1. 直接访问提升后的方法
Tesla.Start()
// 2. 直接访问提升后的字段
// 等价于 Tesla.Engine.Horsepower
fmt.Println("Tesla's hp is: ", Tesla.Horsepower)
}
|

结构体和接口的区别在于:
本质上,结构体是具体实现,用来定义字段和状态,而接口是抽象的协议,用于定义一组方法的签名。而在存储内容上,结构体是占用实际内存的,存储具体的数据和字段,而接口不存储数据字段,只持有指向具体实现和数据的指针。在实例化方面,我们可以直接实例化,比如用car := Car{},但我们不能直接实例化接口,而必须由某个结构体来实现它。
最后,结构体可以通过组合来实现复用,而接口可以隐式实现,不需要 implements 关键字。
interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| package main
import "fmt"
type Usber interface {
start()
stop()
}
type Phone struct {
Name string
}
func (p Phone) start() {
fmt.Println(p.Name, "start")
}
func (p Phone) stop() {
fmt.Println(p.Name, "stop")
}
type Camera struct {
}
func (c Camera) start() {
fmt.Println("start")
}
func (c Camera) stop() {
fmt.Println("stop")
}
func main() {
p := Phone{
Name: "iPhone",
}
p.start()
var p1 Usber // golang中接口就是一个数据类型
p1 = p
p1.start()
p1.stop()
// 实现接口必须实现接口所带的所有方法
c := Camera{}
var c1 Usber = c
c1.start()
c1.stop()
}
|

总结一下: 结构体负责封装数据和具体的实现逻辑,而接口负责定义行为和实现多态。
匿名字段的结构体
类型断言的语法
使用 int(i) 进行类型转换,它适用于两种情况:底层类型相同或具有相同的底层结构。int(i) 不能用于接口,因为接口变量 i 是一个“盒子”,里面装着具体的值和它的类型信息。int(i) 是试图强行把“盒子”作为某个目标类型来处理,这在语法上是不成立的。
但是可以用 i.(int) 来处理,这就是类型断言。
类型断言的过程是:检查-提取-报错(return bool/panic)
1
2
3
4
5
6
7
| // 将一个接口变量 i 转换为具体的 int 类型
// v 是提取出的值,ok 是一个布尔值,告诉你断言是否成功
if v, ok := i.(int); ok {
fmt.Println("断言成功,值是:", v)
} else {
fmt.Println("断言失败,i 里面装的不是 int")
}
|
关于别名(alias)
golang 里的别名定义用 “=” 符号
1
2
| // T2 是 T1 的别名
type T1 = T2
|
关于泛型
Go 1.18 引入的泛型中,any 关键字等价于 interface{}。
使用泛型的一般形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| package main
import "fmt"
type Number interface {
int | int64 | float64
}
func Sum[T Number](list []T) T {
var total T
for _, v := range list {
total += v
}
return total
}
func main() {
ints := []int{1, 2, 3}
floats := []float64{1.1, 2.2, 3.3}
// 调用时,Go 的编译器通常能自动推到 T 的类型
fmt.Println(Sum(ints))
fmt.Println(Sum(floats))
}
|

使用泛型结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| package main
import "fmt"
type Container[T any] struct {
Data T
}
func main() {
// 实例化时需要指定具体类型
c1 := Container[string]{Data: "Hello Go"}
c2 := Container[int]{Data: 100}
fmt.Println(c1.Data, c2.Data)
}
|

什么时候使用泛型?
当我们在实现一个通用容器、通用切片或映射操作,以及对底层类型执行相同逻辑的时候,适合使用泛型。
而当我们只是在调用一个方法或处理两个逻辑完全不同的类型时,就不推荐使用了。
泛型约束
常见约束:comparable
1
2
3
4
5
6
7
8
9
10
11
| // 使用内置约束 comparable 来实现切片内的查找函数
// comparable 支持 == 和 != 操作
func Contains[T comparable](list []T, target T) bool {
for _, v := range list {
if v == target {
return true
}
}
return false
}
|
尝试用泛型写一个简单的栈(Stack)数据结构