Golang基础之结构体
0、前言
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
1、类型别名和自定义类型
自定义类型
在Go语言中有一些基本的数据类型,如string
、整型
、浮点型
、布尔
等数据类型, Go语言中可以使用type
关键字来定义自定义类型。
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:
// 将MyInt定义为int类型
type MyInt int
类型别名
类型别名是Go1.9
版本添加的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type
我们之前见过的rune
和byte
就是类型别名,他们的定义如下:
type byte = uint8
type rune = int32
类型定义和类型别名的区别
类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
//类型定义
type NewInt int
//类型别名
type MyInt = int
func main() {
var a NewInt
var b MyInt
fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
fmt.Printf("type of b:%T\n", b) //type of b:int
}
结果显示a的类型是main.NewInt
,表示main包下定义的NewInt
类型。b的类型是int
。MyInt
类型只会在代码中存在,编译完成时并不会有MyInt
类型。
2、结构体
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct
。 也就是我们可以通过struct
来定义自己的类型了
Go语言中通过struct
来实现面向对象。
3、结构体定义
使用type
和struct
关键字来定义结构体,具体代码格式如下:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
其中:
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- 字段名:表示结构体字段名。结构体中的字段名必须唯一。
- 字段类型:表示结构体字段的具体类型。
举个例子,我们定义一个Person
(人)结构体,代码如下
type Person struct {
name string
city string
age int8
}
同样类型的字段也可以写在一行,
type Person struct {
name,city string
age int8
}
这样我们就拥有了一个person
的自定义类型,它有name
、city
、age
三个字段,分别表示姓名、城市和年龄。这样我们使用这个person
结构体就能够很方便的在程序中表示和存储人信息了。
语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型
4、结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型,我们可以像声明内置类型一样使用var
关键字声明结构体类型。
var 结构体实例 结构体类型
基本实例化
举个例子:
type Person struct {
name string
city string
age int8
}
func main(){
var p1 Person
p1.name = "Jack"
p1.city = "China"
p1.age = 18
fmt.Printf("p1=%v\n", p1) // p1={Jack China 18}
fmt.Printf("p1=%#v\n", p1) // p1=main.Person{name:"Jack", city:"China", age:18}
}
我们通过.
来访问结构体的字段(成员变量),例如p1.name
和p1.age
等。
5、匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package main
import (
"fmt"
)
func main() {
var user struct{Name string; Age int}
user.Name = "小王子"
user.Age = 18
fmt.Printf("%#v\n", user)
}
6、创建指针类型结构体
我们还可以通过使用new
关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:
var p2 = new(person)
fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
从打印的结果中我们可以看出p2
是一个结构体指针。
需要注意的是在Go语言中支持对结构体指针直接使用.
来访问结构体的成员。
var p2 = new(person)
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}
7、结构体实例化
7.1、取结构体的地址实例化
使用&
对结构体进行取地址操作相当于对该结构体类型进行了一次new
实例化操作。
p3 := &person{}
fmt.Printf("%T\n", p3) //*main.person
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
p3.name = "七米"
p3.age = 30
p3.city = "成都"
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"七米", city:"成都", age:30}
p3.name = "七米"
其实在底层是(*p3).name = "七米"
,这是Go语言帮我们实现的语法糖。
7.2、结构体初始化
没有初始化的结构体,其成员变量都是对应其类型的零值。
type person struct {
name string
city string
age int8
}
func main() {
var p4 person
fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}
7.3、使用键值对初始化
使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。
p5 := person{
name: "小王子",
city: "北京",
age: 18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}
也可以对结构体指针进行键值对初始化,例如:
p6 := &person{
name: "小王子",
city: "北京",
age: 18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。
p7 := &person{
city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}
7.4、使用值的列表初始化
初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
p8 := &person{
"沙河娜扎",
"北京",
28,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"沙河娜扎", city:"北京", age:28}
使用这种格式初始化时,需要注意:
- 必须初始化结构体的所有字段。
- 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
- 该方式不能和键值初始化方式混用。
8、构造函数
构造函数约定成俗用new开头,需要考虑构造函数返回的是结构体还是结构体指针,当结构体比较大的时候尽量使用结构体指针,减少程序的运行内存开销
package main
import "fmt"
// 构造函数
type person struct {
name string
age int
}
type dog struct {
name string
}
func newPerson(name string,age int) *person{
return &person{
name: name,
age: age,
}
}
func newDog(name string) *dog {
return &dog{
name: name,
}
}
func main(){
p1 := newPerson("元帅",18)
p2 := newPerson("周林",9000)
fmt.Println(p1,p2)
d1 := newDog("jack")
fmt.Println(d1)
}
9、方法和接受者
Go语言中的方法
是一种作用域特定类型变量的函数。这种特定类型变量叫做接受者
。接受者的概念就类型与其它语言中的this
或者slef
。
方法的定义格式如下:
func (接受者变量 接受者类型) 方法名(参数列表) (返回参数) {
函数体
}
定义一个dog类型,并为该类型定义一个wang方法
package main
import "fmt"
type dog struct {
name string
}
func newDog(name string) *dog {
return &dog{
name: name,
}
}
// 方法是作用于特定类型的函数
// 接受者表示的是调用该方法具体的类型变量,多用类型名首字母小写表示
func (d dog)wang(){
fmt.Printf("%s:汪汪汪\n",d.name)
}
func main(){
p := newDog("jack")
p.wang() // 调用方法
}
输出
jack:汪汪汪
10、嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针
package main
import "fmt"
// 结构体嵌套
type address struct {
province string
city string
}
type person struct{
name string
age int
addr address
}
type company struct{
name string
addr address
}
func main(){
p1 := person{
name: "jack",
age: 20,
addr:address{
province: "广东",
city: "深圳",
},
}
fmt.Println(p1)
fmt.Println(p1.name,p1.addr.city)
}
输出结果
{jack 20 {广东 深圳}}
jack 深圳
匿名嵌套结构体
package main
import "fmt"
// 结构体嵌套
type address struct {
province string
city string
}
type person struct{
name string
age int
address // 匿名嵌套结构体
}
type company struct{
name string
addr address
}
func main(){
p1 := person{
name: "jack",
age: 20,
address:address{
province: "广东",
city: "深圳",
},
}
fmt.Println(p1)
fmt.Println(p1.name,p1.address.city)
fmt.Println(p1.city) // 先在自己结构体找这个字段,找不到就去匿名嵌套的结构体中查找该字段
}
输出结果
{jack 20 {广东 深圳}}
jack 深圳
深圳
注意:如果匿名字段结构体字段冲突后,就需要将字段名写全
11、结构体继承
Go语言中使用结构体也可以实现其它编程语言中面向对象的继承
package main
import "fmt"
// 结构体模式实现其它语言中的"继承"
type animal struct{
name string
}
// 给animal实现一个移动的方法
func (a animal) move(){
fmt.Printf("%s 会动\n",a.name)
}
// 狗类
type dog struct{
feet uint8
animal // animal拥有的方法,dog此时也有了
}
// 给dog实现一个汪汪汪的方法
func (d dog) wang(){
fmt.Printf("%s再叫,汪汪汪\n",d.name)
}
func main(){
d1 := dog{
animal:animal{name:"jack"},
feet:4,
}
fmt.Println(d1)
d1.wang()
d1.move()
}
输出结果:
{4 {jack}}
jack再叫,汪汪汪
jack 会动
12、结构体与JSON
在大多数前后端分离的项目中,后端给前端传的数据格式都为json,那么在Go语言中支持将结构体变量转换成json格式的字符串(序列化),也支持将json格式的字符串转换成结构体变量(反序列化),互相转换
json格式解析网站
package main
import (
"encoding/json"
"fmt"
)
// 结构体与json
// 1.把Go语言中的结构体变量 --> json格式的字符串
// 2.把json格式的字符串 -->Go语言中能够识别的结构体变量
type person struct{
Name string `json:"name"`
Age int `json:"age"`
}
func main(){
p1 := person{
Name: "jack",
Age: 20,
}
// 序列化
b,err := json.Marshal(p1)
if err != nil{
fmt.Println("marshal failed,err:%v",err)
return
}
fmt.Printf("%#v\n",string(b))
// 反序列化
str := `{"name":"xander","age":23}`
var p2 person
json.Unmarshal([]byte(str),&p2) // 必须传指针为了能在json.Unmarshal内部修改p2的值
fmt.Printf("%#v\n", p2)
}
输出结果:
"{\"name\":\"jack\",\"age\":20}"
main.person{Name:"xander", Age:23}