一、type
关键字的作用
type
定义别名
在基本数据类型中的 byte
和 rune
其实就是 uint8
和 int32
的别名,在源码中这些别名就是使用 type
关键字定义的,当然我们也可以自己定义别名。
// 定义别名,将原名字使用 = 赋值给别名 type alias = oldName 复制代码
func main(){ type bigInt = int64 var balance bigInt = 100000000 fmt.Printf("%T", balance) // int64 } 复制代码
基于已有类型定义新的类型
定义新的类型要在已有类型的基础上进行定义,注意与定义类型别名作区分。
func main(){ type tinyInt int8 var age tinyInt = 8 fmt.Printf("%T", age) // main.tinyInt } 复制代码
type
关键字还可以为函数定义别名、定义结构体以及定义接口。
先来看看什么是结构体以及如何使用 type
关键字定义结构体。
二、结构体 struct
在 Go 中没有类和对象的概念,但是并不代表 Go 无法实现面向对象的三大特征。Go 中通过结构体来实现。
Go 结构体要解决的问题就是如何实现面向对象的三个基本特征 封装、继承、多态
以及 方法重载
和 抽象基类
等特征。
结构体的定义
定义结构体除了使用 type
关键字外,还需要用到结构的标识符 struct
type StructName struct{ attr1 attrType attr2 attrType ... } 复制代码
注意结构体的属性之间是没有逗号的,不要与 Map 混淆。
func main() { // 实例化结构体 var tom Male = Male{ name: "tom", age: 30, address: "tomcat", } fmt.Println(tom.name, tom.age, tom.address) // tom 30 tomcat } // 定义结构体 type Male struct { name string age int address string } 复制代码
Go 是区分大小写的,大写的变量、函数名、接口名或者结构体名等表示可以被外界访问,如果是下小写的则表示私有的,无法被外界(其他包)访问到。Go 的大小写影响其可见性。
一般情况下都会将结构体的属性名大写,使其对外可见。
结构体的实例化
上面的代码中演示过了一种结构体实例化的方式,起始结构体实例化时还可以将属性名省略,但是要保证赋值的顺序与定义的顺序是一一对应的。
func main() { // 第二种实例化方式,省略属性名 allen := Male{"allen", 18, "NYC"} fmt.Println(allen.name, allen.age, allen.address) // allen 18 NYC } 复制代码
结构体实例的属性值不仅可以通过结构体实例来获取,也可以通过结构体指针来直接获取
func main() { // 定义一个结构体指针变量 pennyPoi := &Female{"penny", 18, "Miramar, Florida"} fmt.Printf("%T\n", pennyPoi) // 通过结构体指针获取结构体的属性值 fmt.Println(pennyPoi.Name, pennyPoi.Age, pennyPoi.Address) // 通过指针获取到结构体实例,再通过结构体实例获取属性值 penny := *pennyPoi fmt.Println(penny.Name, penny.Age, penny.Address) } type Female struct { Name string Age int Address string } 复制代码
执行上述代码,输出结果如下:
*main.Female penny 18 Miramar, Florida penny 18 Miramar, Florida 复制代码
注意通过结构体指针获取结构体实例的属性值时不要通过 *pennyPoi.Name
这种方式来获取,因为这样会被误认为 pennyPoi.Name
是一个指针,要么添加括号 (*pennyPoi).Name
,要么先将通过结构体指针获取到的结构体实例赋值到一个变量中,通过变量再去获取属性值。
直接通过结构体指针获取结构体的属性值其实是 Go 的一个语法糖,Go 内部会将 *pennyPoi.Name
转换为 *pennyPoi.Name
。
结构体实例化时如果为空值,那么结构体属性的值会赋值指定类型的默认值
func main() { // 零值 nancy := Female{} fmt.Println(nancy.Name) fmt.Println(nancy.Age) fmt.Println(nancy.Address) } 复制代码
执行上述代码,输出结果如下:
0 复制代码
除了上述方式外,还有其他方式可以零值初始化
func main() { var nancy2 Female fmt.Println(nancy2.Age) // 0 var nancy3Poi *Female = new(Female) fmt.Println(nancy3Poi.Age) // 0 // 这种方式会引起报错 // panic: runtime error: invalid memory address or nil pointer dereference //var nancy4Poi *Female //fmt.Println(nancy4Poi.Age) } 复制代码
指针如果只声明不赋值默认是 nil,会报错,需要通过 new
函数申请内存。而结构体则可以直接通过 var
关键字初始化结构体并自动分配内存。
除了指针之外,还有 Slice Map 初始化时不会自动分配内存的,要使用 new 函数来分配内存。
结构体是值类型
从上面的代码可以确定结构体类型是可以直接通过 var
关键字直接初始化并自动分配内存的,类似的还有 数组 Array,整型 Int,浮点型 Float 以及字符串 String
多可以直接初始化并自动分配内存。
数组 Array,整型 Int,浮点型 Float 以及字符串 String
都是值类型,而 结构体 Struct
是不是也是值类型?
func main() { // 结构体是值类型 p1 := Female{"Penny", 23, "NYC"} p2 := p1 fmt.Printf("%v\n", &(p1.Name)) fmt.Printf("%v\n", &(p2.Name)) // 修改 p2 p2.Name = "PENNY" fmt.Println(p2.Name) fmt.Println(p1.Name) } 复制代码
执行上述代码,输出结果如下:
0xc000098180 0xc0000981b0 PENNY Penny 复制代码
根据输出的 Name 属性的内存地址不同就可以确定结构体是值类型,并且修改 p2 对 p1 没有影响。
因此结构体作为函数参数传递的时候也是,值传递,既复制一个给函数作为参数使用,与原结构体互不影响
结构体占用内存大小
结构体占用内存大小可以使用 unsafe.Sizeof
函数来获取,结构体占用内存大小是固定的,不会应为存的内容的大小而改变
func main() { // 结构体是值类型 p1 := Female{"Penny", 23, "NYC"} p2 := Female{"PennyPennyPennyPennyPennyPenny", 23, "NYC"} fmt.Println(unsafe.Sizeof(p1), unsafe.Sizeof(p2)) // 40 40 } 复制代码
string 和 slice 底层都是结构体。