一文速通go语言类型系统
# 写在文章开头
go语言是静态编译型语言,这意味着在编译时即可知晓变量的类型从而完成内存分配和优化,在提升执行效率的同时,还尽可能的避免了一些潜在的风险。本文会对自定义类型声明、方法、接口、设计模式以及标识符进行实践和介绍,希望对你有帮助。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 详解自定义类型
# 自定义类型
自定义类型我们完全可以理解为Java中的class,其语法格式如下:
type user struct {
id int
age int
name string
isAdult bool
}
2
3
4
5
6
如果希望声明这个变量的初始值,可直接用var表达式进行声明:
func main() {
var u user
fmt.Println(u)
}
2
3
4
默认情况下输出的自定义类型各个成员变量都是其类型的0值:
{0 0 false}
2
查看输出结果我们可以发现,自定义类型都有一个默认值,这正是go语言的特点,所有的变量在默认情况下都会赋予对应类型的0值:
{0 0 false}
2
如果你希望对自定义变量进行初始化,也可以通过短变量声明符,对应的语法如下所示:
func main() {
u := user{
id: 1,
age: 18,
name: "xiaoming",
isAdult: true,
}
fmt.Println(u)
}
2
3
4
5
6
7
8
9
10
11
输出结果如下:
{1 18 xiaoming true}
2
当然,如果你还想在简写,也可以按照下面这种格式,只要你严格按照自定义类型内部成员变量的顺序进行声明值,你就可以这种非键值对格式的语法创建类型:
func main() {
//按照字段顺序略去字段名的声明
u := user{1, 18, "xiaowang", false}
}
2
3
4
5
6
7
# 如何嵌套
面对思想最重要的一点就是类型的复用,所以使用go语言时,如果你希望对某个类型能够进行复用,那么我们也可以按照下面这种格式对type进行嵌套,可以看到user类作为admin的成员变量存在:
type user struct {
id int
age int
name string
isAdult bool
}
type admin struct {
no int
person user
}
2
3
4
5
6
7
8
9
10
11
用短变量进行声明时,同样可以使用键值对的方式进行声明:
func main() {
//按照键值对的格式声明
a := admin{
no: 1,
person: user{
id: 1,
age: 18,
name: "xiaoming",
isAdult: true,
},
}
fmt.Println(a)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里我们也贴出一下输出结果:
{1 {1 18 xiaoming true}}
2
# 自定义类型的声明的错误示例
如果我们声明了一个自定义的类型Duration ,如下所示,可以看到它其实就是int64,初学go语言的读者可能会认为它和int64是等价的,实际上这种说法是错误的:
type Duration int64
如果是用这种语法,编译是无法通过的
dur=int64(1)
正确的方式应该是下面这样:
var dur Duration
//正确的做法
dur = 5
fmt.Println(dur)
2
3
4
5
6
# 详解go语言方法
# 值接收方法
通过方法前方的括号即可指明这个方法属于哪个类型的,意味参数为user的值而不是指针,这就意味着该方法是一个值接收方法,user调用该方法时,该方法会拷贝user的副本。
type user struct {
name string
}
// 修改user名字
func (u user) modify() {
u.name = "modify"
fmt.Println(u.name, " notify")
}
func main() {
//创建user类型并调用方法modify
u := user{"xiaoming"}
u.modify()
fmt.Println("after modify,the user name is ", u.name)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
从输出结果可以看出,方法内部对user的改造对这个方法不起任何作用:
modify notify
after modify,the user name is xiaoming
2
3
和Java这些语言有点不同,go语言的方法支持按照值或者按照地址传参的,按值传参的示例如下,该方法被执行时就会拷贝这个值的副本,这意味着方法内部对对象的操作是对调用者不起任何作用:

接收者为值的方法,调用者一样可以是指针,我们强调过go语言是静态编译型语言,所以在接收者为值的情况下,使用user指针进行调用,go语言会在编译器对其进行转换,所以我们指针调用最终会被编译成(*u).modify()。
//创建user类型并调用方法notify
u := &user{"xiaoming"}
u.modify()
fmt.Println("after modify,the user name is ", u.name)
2
3
4
当然因为方法是接收值方法,所以输出结果还是不变的:
modify notify
after modify,the user name is xiaoming
2
3
# 接收指针方法
我们只需将值接收方法上加一个*号就变为接收指针的方法,和值接收方法不同调用该方法时方法不同,接收指针的方法会拿到指针所指向的类型,这意味着接受指针的方法的修改操作会直接反映在对象上:

就下面这段代码的,方法内部对于姓名的修改会直接反映在u上:
type user struct {
name string
}
// 创建user的方法
func (u *user) modify() {
u.name = "modify"
fmt.Println(u.name, " notify")
}
func main() {
//创建user类型并调用方法notify
u := &user{"xiaoming"}
u.modify()
fmt.Println("after modify,the user name is ", u.name)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
所以最终输出结果是name的值为xiaoming:
modify notify
after modify,the user name is modify
2
3
同理我们调用者也可以是值,原因和上述同理,go语言编译时会将语法转为(&u).modify:
//创建user类型并调用方法notify
u := user{"xiaoming"}
u.modify()
fmt.Println("after modify,the user name is ", u.name)
2
3
4
# 如何使用
当我们希望操作的结果能够改变调用者的时候用指针,如果希望调用该方法后得到一个新值,保证调用者的原始性的话,则用值接收方法。
# go语言中的三种类型
# 内置类型
内置类型也就是常见的数值、字符串、布尔等类型,下面这种go语言为我们提供的默认类型,因此对于这些类型进行操作时,这些方法都会创建一个新的值:
func Trim(s, cutset string) string {
if s == "" || cutset == "" {
return s
}
if len(cutset) == 1 && cutset[0] < utf8.RuneSelf {
return trimLeftByte(trimRightByte(s, cutset[0]), cutset[0])
}
if as, ok := makeASCIISet(cutset); ok {
return trimLeftASCII(trimRightASCII(s, &as), &as)
}
return trimLeftUnicode(trimRightUnicode(s, cutset), cutset)
}
2
3
4
5
6
7
8
9
10
11
12
# 引用类型
而引用类型就是数组、切片、map等,将他们作为入参时,其底层的header都包含这些结构的指针,所以方法操作结果会直接反应在引用类型上。
以map为例,一旦map作为参数传入,形参就会拿到引用类型的header以及header中的指针,从而修改内部的map内部的键值对:

对应代码如下:
func modify(m map[int]string) {
m[1] = "string"
}
func main() {
m := map[int]string{1: "str"}
modify(m)
fmt.Println(m[1])
}
2
3
4
5
6
7
8
9
10
输出结果直接变为string。
# 结构类型
结构类型,该类型比较灵活,也就是类似user这种自定义类型,它可以根据是否需要更改类型上的值决定接收者是值还是指针,大部分情况下是非原始的,即要传指针,方法接受者为指针:
type user struct {
name string
}
// 创建user的方法
func (u *user) modify() {
u.name = "modify"
fmt.Println(u.name, " notify")
}
func main() {
//创建user类型并调用方法notify
u := user{"xiaoming"}
u.modify()
fmt.Println("after modify,the user name is ", u.name)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 小结
我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 参考
《go语言实战》