浅谈Go语言中类的方法与函数
前言
我们知道Go语言有着自己的一套独特的面向对象编程的方法, 当然更多人称之为面向接口编程, 但是这里因为和函数长得过于相似, 所以也存在着一些异同点, 其中最容易混淆的概念就是Go语言设计者设计的一些简化特性, 本文会对此一些独特的特性作一些总结
Go中的“类”
一. 结构体
Go语言中去除了方法重写, 覆盖, 构造函数等一些老牌面向对象编程语言共有的东西, 其结构体其实就等价于其他语言中的类的概念
以下是一个结构体示例:
type Person struct {
Name string
Age int
}
这里其实没有什么好说的, 但是要注意, 如果我们想要跨包引用这个结构体, 首字母就要大写
二. 方法
我们可以发现Go语言中的成员方法其实是对结构体编写对应的“函数”
如下是一个例子:
type Person struct {
Name string
Age int
}
func (p Person) calc(i int) int {
return p.Age / i
}
func main() {
p := Person{"john", 10}
fmt.Println(p.calc(2))
}
我们对于类Person 编写了一个calc 方法, 仔细对比Go语言中的函数, 可以发现, 其实我们就是在函数名前加入了对应的一个类的“实例”, 可以看成传入了一个对象, 并对其进行操作
注意:
- Go语言对于方法的结构体传入是“值拷贝”, 也就是说我们传入的
Person进行了复制, 对传入函数的p操作并不影响 main 函数的p - 但是我们知道值拷贝在某些情况下相对于直接引用地址慢很多, 因为我们需要重新复制一份, 这个时候就有了另外一种更优秀的方案:
func (p *Person) calc(i int) int {
return p.Age / i
}
这里我们传入了结构体指针, 也就是指针类型, 这个时候相当于将地址传入进函数中, 并不需要拷贝, 相对地, 我们操作函数中的结构体变量, 会影响到main函数中的变量, 因为这两者其实在堆中指向的是同一块内存
3. 我们之前就知道Go语言设计者简化了指针引用的类型对应的访问变量问题, 对于Person结构体变量, 我们可以不使用*运算符而直接访问
func (p *Person) calc(i int) int {
// p.Age 等价于 (*p).Age
return p.Age / i
}
当然, 在方法传参中, Go语言设计者仍然做出了简化, 根据默认的规则, 我们定义的方法其实是结构体指针的方法, 但是在这里我们仍然可以使用结构体变量来直接访问结构体指针的方法, 也就是
func main() {
// 应该是(*p).calc(10), 但是使用以下语句调用方法仍然是可以的
p.calc(10)
}
两者对比
类的方法与函数在大致上是一样的, 但是也存在着一些细节上的不同
- 传参不同, 我们来看以下代码
对于函数
func test01(p Person) {
fmt.Println(p.Name)
}
func test02(p *Person) {
fmt.Println(p.Name)
}
我们发现函数体内, 因为语法简化的原因, 我们传入参数不同, 但是使用同样的语句达成了同样的效果.
这里可以发现函数传参是”严格“的, 意思就是我们不能将指针传入一个接受值类型的函数中
func main() {
p := Person{"john", 10}
test01(p)
test02(&p) // 相对应地, test01(&p) 与 test02(p) 是错误的
}
对于方法
我们同样编写两个方法来进行测试
func (p Person) test01() {
fmt.Println(p.Name)
}
func (p *Person) test02() {
fmt.Println(p.Name)
}
这里和函数大同小异, 这两个方法依旧相当于接收指针以及传入值类型, 但是我们在main函数中测试之后发现, 对于接收值类型的方法, 我们可以使用指针来调用, 对于接收指针的方法, 我们也可以使用值类型来调用, 也就是这两者是通用的
func main() {
p := Person{"john", 10}
p.test01()
p.test02()
(&p).test02()
(&p).test01()
}
- 对于方法来说传入值或者地址是通用的, 而且传入的值和地址并不影响方法的类型
具体地说
func (p Person) test01() {
p.Name = "tom"
fmt.Println(p.Name)
}
func main() {
p := Person{"john", 10}
p.test01()
(&p).test01()
}
这里我们虽然看似传入了一次值, 一次地址, 但是方法类型为值拷贝, 所以就算传入地址, 也相当于值拷贝, 所以调用这两次test01()之后p的Name并没有发生变化, 还是tom
总结
因为Go语言设计者设计的一些简化特性, 使得结构体指针和结构体变量之间产生了某种意义上的混淆, 首先一定要分清楚其中的异同
- 作为函数传入的参数必须严格遵守类型
- 结构体指针可以直接访问指向的结构体变量的成员
- 结构体指针或者结构体变量都可以直接调用方法
- 辨别值拷贝还是地址引用, 重点在于方法名前面是否是指针方法, 和调用方法的变量无关, 是结构体变量还是指针变量并不重要

浙公网安备 33010602011771号