在Go语言中,类型名称、变量或者函数名首字母大写,表示其是可导出的,对于这个知识点我们都不陌生。例如,如果一个函数名首字母是大写的,我们称之为公有方法,相反如果首字母是小写,则称为私有方法。其实用“公有”和“私有”来描述并不是很准确,准确的理解是:当一个标识符可以被导出时(首字母大写),表示可以被其他包直接访问,当一个标识符不能被导出时(首字母小写),则无法被其他包 直接 访问。📢标识符不能被导出并不意味着它完全不能在包外被访问,只是不能直接访问。本文将介绍在Go语言中关于标识符是否导出的一些冷门知识,欢迎点赞收藏。
下面通过一个可导出类型举例说明, 项目目录结构如下,在test文件夹下的exportedtypes1.go中定义了一个 AlertCounter类型,它虽然是基于int的一个别名,但完全是一个新类型,并且类型的首字母A大写,表明它可以直接导出在其他包中使用。
package counters
type AlertCounter int
在main包中编写代码进行验证,代码如下:
package main
import (
"fmt"
"identifier/test"
)
func main() {
counter := counters.AlertCounter(10)
fmt.Printf("Counter: %d\n", counter)
}
编译运行输出:Counter: 10, 没有任何问题。
现在将类型AlertCounter首字母改为小写的a, 即不可导出,看看会出现什么问题。
package counters
type alertCounter int
可以看到,编辑器直接提示错误。强行编译会失败,提示未找到 counters.alertCounter 类型定义。
./main.go:9:22: undefined: counters.alertCounter
尽管我们不能直接访问 alertCounter 类型,因为它是不可导出的,但是有一种方法可以在包外创建并且使用此未导出类型的变量,实现如下。
在 counters 包中定义一个可导出函数 NewAlertCounter,该函数返回值为 alertCounter 类型。
package counters
type alertCounter int
func NewAlertCounter(value int) alertCounter {
return alertCounter(value)
}
在main中调用上述代码,可以正常编译,运行输出10。
func main() {
counter := counters.NewAlertCounter(10)
fmt.Printf("Counter: %d\n", counter)
}
同理对于结构体类型,如果结构体中的字段首字母是大写的,表示其是可导出的能够在包外直接访问,如果首字母是小写的。是不可导出的不能直接在包外访问。
下面的 Dog 结构体含有两个可导出字段和一个不可导出字段,如果在main函数中给age赋值,会产生错误。
package animals
type Dog struct {
Name string
BarkStrength int
age int
}
main函数中定义一个Dog类型变量,然后打印。
编译会报错:
go run main.go
./main.go:12:3: unknown field age in struct literal of type animals.Dog
现在对Dog结构体做点调整,拆分成如下两个结构体,内嵌一个animal。
type animal struct{
Name string
Age int
}
type Dog struct{
animal
BarkStrength int
}
由于结构体animal首字母小写是不可导出类型,所以在main包中无法引用,产生错误。
但是可以通过下面的方法对不可导出类型animal中的Name和Age进行初始。
func main() {
dog := animals.Dog{
BarkStrength: 10,
}
dog.Name = "Chole"
dog.Age = 1
}
Go标准库time包中的 Time 结构体是一个很好的范例,该结构体中的字段是小写即不可导出,从而隐藏内部实现。达到封装性、安全性和兼容性目的。
type Time struct {
wall uint64
ext int64
loc *Location
}
只能通过Time提供的方法操作Time对象,例如 Time.Add进行时间加减,Time.Format进行格式化,Time.Compare进行时间比较。