一直都觉得自己没入门,学不会golang,不如去学java.进公司也有一年多了,从实习就一直呆在这家公司,2024.10-2025.4似乎刚好一年半. 现在看也许就是编程语言的意识没有切换过来吧. 稍微总结吐槽下自己.万幸,卒获有所闻.
其实golang是门被阉割的语言,OO思想不深的人和深谙OO的人最好切换这种思想.但像我这半吊子,写起来真的是很别扭.记录一下常用的golang语法,方便各位看官看源码的时候方便一些.
Point1: 组合思想
最重要的一点就是组合大于继承,甚至可以夸张到说一句暴论:只有组合没有继承. 直接影响了golang的代码编写风格.
为什么这么说?
是因为Golang的继承就是一种组合,在子类中添加一个父类对象,就算实现了所谓的父类.可是这就是单纯的组合,所以以后别人再和我说用go继承一下,我真的会暴起.
因为之前写的CPP和Java都是会有转型的概念,可以理解为父类指针指向子类对象(将子类对象赋给父类对象),golang没有,这么写就会报错,毕竟是为了解耦合,取消继承是大趋势,Rust好像也是组合优先,这很正常,符合软件开发原则组合优于继承.
go
type ParentClass struct {
// 父结构体的字段
}
type ChildClass struct {
Parent // 嵌套父结构体
// 子结构体的字段
}
func main() {
c := ChildClass{} // 创建一个 Child 对象
// p := ParentClass(c) // 强制转型 父类指针指向子类对象 golnag不支持,报错
}
这同样导致另外一个蛋疼的现象: 多态以隐式实现接口的形式出现.
Java的接口是用implements来实现的,Golang是只管定义,实现了方法就算实现接口了.也就是你可能莫名其妙地就实现了一个接口.这就导致用golang开发的时候,智能提示去找相关的接口实现会给你飙到一些风马牛不相及的地方.
这是一个简单的接口实现,网络协议族的接口NetworkProtocol 以TCP UDP通过实现该接口的三个函数来实现网络协议的接口.
golang
package main
import "fmt"
// NetworkProtocolIF 网络协议接口,包含三个方法
type NetworkProtocolIF interface {
Send(data string) error
Receive() (string, error)
GetProtocolName() string
}
// TCP 结构体代表 TCP 协议
type TCP struct {
port int
}
func (t *TCP) Send(data string) error {
fmt.Printf("TCP sending data '%s' on port %d\n", data, t.port)
return nil // 模拟发送成功
}
func (t *TCP) Receive() (string, error) {
receivedData := fmt.Sprintf("TCP received data on port %d", t.port)
fmt.Println(receivedData)
return receivedData, nil // 模拟接收成功
}
func (t *TCP) GetProtocolName() string {
return "TCP"
}
// UDP 结构体代表 UDP 协议
type UDP struct {
port int
}
func (u *UDP) Send(data string) error {
fmt.Printf("UDP sending data '%s' on port %d\n", data, u.port)
return nil // 模拟发送成功
}
func (u *UDP) Receive() (string, error) {
receivedData := fmt.Sprintf("UDP received data on port %d", u.port)
fmt.Println(receivedData)
return receivedData, nil // 模拟接收成功
}
func (u *UDP) GetProtocolName() string {
return "UDP"
}
func processProtocol(protocol NetworkProtocolIF) {
fmt.Println("Processing protocol:", protocol.GetProtocolName())
err := protocol.Send("Hello, network!")
if err != nil {
fmt.Println("Error sending:", err)
}
_, err = protocol.Receive()
if err != nil {
fmt.Println("Error receiving:", err)
}
fmt.Println()
}
func main() {
processProtocol(&TCP{port: 8080})
processProtocol(&UDP{port: 9000})
}
从上面代码和可以看出,processProtocol(protocol NetworkProtocolIF )
方法的参数是NetworkProtocolIF
接口,而调用该方法的两个地方传入的是TCP
和UDP
对象指针,这正是朴素上的多态体现,实现了相同接口从而达到一个函数被多个不同类型所调用的现象.
关于结构体方法的接收者类型和使用注意事项:
- 接收者类型选择:
- 结构体方法的接收者可以是值类型(普通对象)或指针类型
- 建议保持一致:一个结构体的所有方法最好统一使用值接收者或指针接收者
- 方法调用灵活性(go对指针的特殊处理):
- 无论接收者是值类型还是指针类型,都可以通过值或指针来调用方法
- 关键区别:当接收者为指针类型 时,方法内部对结构体的修改会反映到调用者
- 接口实现规则:
- 如果方法使用值接收者,实现接口时可以传值或指针
- 如果方法使用指针接收者,实现接口时只能传指针
Part2 万能类型空接口
记得大学时学C/C++, 爱说的万能类型是nil,万物皆可nil,万物皆可new. Golang似乎也有万能类型就是空接口.
万能类型其实就是空接口(不含任何方法的接口),可以把任何对象赋值给空接口对象(实例化空接口),类似 Java 中的Object
.
golang
// any 和 interface{} 等价,都可以表示空接口
// `interface{}` 是 Go 语言内置的空接口类型
// `any` 是 Go 1.18 引入的预定义类型别名:`type any = interface{}` 属于语法糖
type any = interface{}
var a1 interface{} // 定义一个空接口变量 a1
var a2 any // 定义一个空接口变量 a2
-
空接口 (
interface{}
/any
) :- 使用
eface
表示 - 不包含方法集信息
- 空接口有额外的内存开销(两个指针大小)
- 使用
- 涉及动态类型检查,比静态类型慢
- 非空接口 :
- 使用
iface
结构表示 (定义在同一个文件中) - 包含方法表信息
- 使用
空接口的底层实现:
golang
// src/runtime/runtime2.go
type eface struct {
_type *_type // 类型指针
data unsafe.Pointer // 数据指针
}
接口可以通过断言判断其动态类型,说明接口中保存了赋值变量的类型,即:_type *_type
接口保存的是变量值,则修改后不影响,接口保存的是指针,则会影响num值
interface{}注意点
golang
var dataSlice []int = foo()
var interfaceSlice []interface{} = dataSlice
cannot use dataSlice (type []int) as type []interface { } in assignment
[]interface{}
那么问题是,"当我可以将任何类型分配给 时,为什么不能将任何切片分配给interface{}
?"
-
万能类型是
interface{}
,不是[]interface{}
,[]int
可以赋值给interface{}
,但不能赋值给[]interface{}
-
从内存的角度,假设长度为
N
的[]interface{}
,那么所占空间就是N*2
,因为一个interface{}
中含有两个指针;而对于长度为N
的[]int
,那么所占空间就是N*sizeof(MyType)
interface{}与...语法糖的坑
上述问题,单看还算好理解,一旦和别的掺杂在一起就比较恶心了. ...语法用在函数定义等价于规整为切片,在使用上等价切片打散拆分为单个子元素
golang
package main
import "fmt"
func f(a ...int) {
fmt.Printf("参数类型: %T, 值: %v\n", a, a)
}
func g(b ...interface{}) {
fmt.Printf("参数类型: %T, 值: %v\n", b, b)
}
func main() {
// 情况1: 直接传递多个int参数
f(1, 2, 3, 4) // 参数类型: []int, 值: [1 2 3 4]
// 情况2: 展开int切片
f([]int{1, 2, 3, 4}...) // 参数类型: []int, 值: [1 2 3 4]
// 情况3: 尝试展开int8切片 - 会编译错误
// f([]int8{1, 2, 3, 4}...) // 错误: cannot use []int8{...} as []int
// 情况4: 使用interface{}可变参数
g(1, "hello", true) // 参数类型: []interface {}, 值: [1 hello true]
// 情况5: 展开任意类型切片到interface{}
ints := []int{1, 2, 3, 4}
g(ints...) // 参数类型: []interface {}, 值: [1 2 3 4]
// 情况6: 展开interface{}切片
mix := []interface{}{1, "world", 3.14}
g(mix...) // 参数类型: []interface {}, 值: [1 world 3.14]
// 情况7: 展开字符串切片
strs := []string{"a", "b", "c"}
g(strs...) // 参数类型: []interface {}, 值: [a b c]
// 情况8: []int{1, 2, 3, 4}...
f([]int{1, 2, 3, 4}...) // 数会转换成[][]interface{}{{1, 2, 3, 4}}
// 情况9: []int{1, 2, 3, 4}...
f([]int{1, 2, 3, 4}...) 报错
因为被拆分为 int, int ,而不是interfce{},interface{}
}