GO语言结构体
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
类型别名与自定义类型
自定义类型
Go语言中可以使用type关键字来定义自定义类型。
//NewInt是一种新的类型,具有int类型的特性。
type NewInt int
func main() {
var a NewInt
fmt.Println(a) //初始值为 0
fmt.Printf("%T\n",a) //main.NewInt 类型是自己定义的NewInt
}
类型别名
类型别名是Go1.9版本添加的新功能。
类似于软链,本质上与type是同一个类型。
类型别名只在代码编写过程中生效,编译完不存在。
//类型别名,ares就是bool,编译完后不存在ares类型
type ares = bool
func main() {
var b ares //定义b为ares类型
fmt.Println(b) //bool类型默认只false
fmt.Printf("%T\n",b) //bool
}
rune和byte其实就是类型别名。
type byte = uint8
type rune = int32
结构体
Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。
Go语言中通过struct来实现面向对象。
结构体定义
语法:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
类型名:标识自定义结构体的名称,在同一个包内不能重复。
字段名:表示结构体字段名。结构体中的字段名必须唯一。
字段类型:表示结构体字段的具体类型。
示例:
type student struct {
name string
id ,age int //相同类型可以写在一行
hobby []string
sex string
}
结构体实例化
结构体必须实例化才可以使用结构体的字段(分配内存)。
var 结构体实例 结构体类型
基本实例化
示例:
func main() {
//实例化方法1,使用键值对初始化
var ares = student{
name: "ares",
id: 1,
age: 28,
sex: "man",
hobby: []string{"自行车","爬山"},
}
fmt.Println(ares) //{ares 1 28 [自行车 爬山] man}
//结构体支持使用.来访问属性
fmt.Println(ares.name) //ares
fmt.Println(ares.age) //28
fmt.Println(ares.hobby) //[自行车 爬山]
//实例化方法2
var ares1 student
ares1.name = "ares1"
ares1.id = 2
fmt.Println(ares1) //{ares1 2 0 [] }
type ares struct {
age int
id int
name string
}
//方法三
p1 := ares{}
p1.name = "ares"
fmt.Println(p1) //{0 0 ares}
//方法4
p2 := ares{name: "ares", age: 1}
fmt.Println(p2) //{1 0 ares}
}
如果初始化没有赋值,那么就为默认值!
指针型结构体
使用new关键字对结构体进行实例化,得到的是结构体的地址。
//指针型结构体
var ares2 = new(student)
ares2.id = 3
ares2.name = "ares2"
(*ares2).age = 18 //等同于ares2.age = 18
fmt.Printf("%T\n",ares2) //*main.student 指针类型
fmt.Println(ares2) //&{ares2 3 18 [] }
//结构体指针支持使用.来访问属性
fmt.Println(ares2.id) //3
fmt.Println(ares2.age) //18
取结构体的地址初始化
使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。
//取结构体地址初始化
var ares3 = &student{} //ares3 := &student{}
fmt.Printf("%T\n",ares3) //*main.student
ares3.name = "ares3"
fmt.Println(ares3) //&{ares3 0 0 [] }
初始化结构体简写
初始化结构体的时候可以不写键,但是必须初始化结构体所有字段并且顺序一致,不能与键值对初始化混用!
//初始化键值对简写
ares4 := &student{
"ares4",
1,
25,
[]string{"play"},
"man",
}
fmt.Println(ares4) //&{ares4 1 25 [play] man}
结构体内存布局
结构体在内存上是占用一块连续的内存。
type con struct {
a int8
b int8
c int8
d string
e string
}
n := con{
10, 21, 35, "a","b",
}
fmt.Printf("n.a %p\n", &n.a) //n.a 0xc0000ae000
fmt.Printf("n.b %p\n", &n.b) //n.b 0xc0000ae001
fmt.Printf("n.c %p\n", &n.c) //n.c 0xc0000ae002
fmt.Printf("n.d %p\n", &n.d) //n.d 0xc0000ae008
fmt.Printf("n.e %p\n", &n.e) //n.e 0xc0000ae018
构造函数
指针是值类型,如果结构体比较复杂,值拷贝性能开销会比较大,所以可以使函数返回结构体指针类型。
type student struct {
name string
id ,age int //相同类型可以写在一行
hobby []string
sex string
}
//自己实现一个构造函数
func newStudent(name ,sex string, id ,age int,hobby []string) student {
return student{
name : name,
sex:sex,
id:id,
age:age,
hobby:hobby,
}
}
//自己实现一个返回指针的构造函数
func newStudentp(name ,sex string, id ,age int,hobby []string) *student {
return &student{
name : name,
sex:sex,
id:id,
age:age,
hobby:hobby,
}
}
func main() {
ares := newStudent("ares","男",1,28,[]string{"play"})
ares1 := newStudentp("ares1","男",1,28,[]string{"play"})
fmt.Println(ares) //{ares 1 28 [play] 男}
fmt.Println(ares.name) //ares
fmt.Println(ares1) //&{ares1 1 28 [play] 男}
fmt.Println(ares1.name) //ares1
}
方法和接收者
函数是谁都可以调用的。
方法就是某个特定的类型才能调用的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
格式:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
接收者变量:在go语言中约定成俗不用this也不用self,而是使用后面类型的首字母的小写
值接收者
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
//定义一个persion结构体
type persion struct {
name string
age int
sex string
}
//定义一个newPersion的构造函数
func newPersion(name ,sex string,age int) persion {
return persion{
name:name,
age:age,
sex:sex,
}
}
//定义一个值类型接收者方法
func (p persion) Dream() {
p.age = 18 //无法修改接受者变量本身
fmt.Printf("%s 的梦想是有钱!\n",p.name)
}
func main() {
var ares = persion{
name:"ares",
age:28,
sex:"男",
}
ares.Dream() //ares 的梦想是有钱!
fmt.Println(ares.age) //28
}
指针类型接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的.
//定义一个指针类型接收者方法
func (p1 *persion) Dream1() {
p1.age = 18
fmt.Printf("%s 的梦想是有大钱!\n",p1.name)
}
func main() {
var ares = persion{
name:"ares",
age:28,
sex:"男",
}
ares.Dream1() //ares 的梦想是有大钱!相当于(&ares).Dream1()
fmt.Println(ares.age) //18
}
结构体默认是值拷贝,除非使用指针
func main() {
type Person struct {
name string
age int
id int
}
p1 := Person{"Ares", 18, 1}
//值拷贝,开辟新内存地址,不会修改原结构体
p2 := p1
p2.name = "ares1"
fmt.Println(p1) //{Ares 18 1}
fmt.Println(p2) //{ares1 18 1}
//指针拷贝,拷贝内存地址,原结构体也会修改
p3 := &p1
p3.name = "ares2"
fmt.Println(p1) //{ares2 18 1}
fmt.Println(*p3) //{ares2 18 1}
}
什么场景需要使用指针类型
- 需要修改接收者中的值。
- 接收者是拷贝代价比较大的大对象。
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
###任意类型添加方法
在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。
可以为任意类型追加方法,但不能给别的包定义的类型添加方法。
//可以为任意类型追加方法,不能给别的包定义的类型添加方法
//将int类型定义为自定义的MyInt类型
type MyInt int
//为MyInt添加一个sayhi方法
func (m MyInt) sayhi() {
fmt.Println("hihihi")
}
func main() {
var m1 MyInt
m1.sayhi() //hihihi
m1 = 10
fmt.Println(m1) //10
fmt.Printf("%T\n",m1) //main.MyInt
}
结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
//定义一个匿名persion结构体
type persion1 struct {
//name string
string //没有字段名的就是匿名字段,匿名字段类型不能重复
int
}
func main() {
ares := persion1{
//name:"ares", //不能混用
"男",
18,
}
fmt.Println(ares.string) //男
fmt.Println(ares.int) //18
}
匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
结构体的嵌套
一个结构体中可以嵌套包含另一个结构体或结构体指针。
//People结构体
type People struct {
name string
age int
sex string
Address Address //嵌套Address结构体
}
type People1 struct {
name string
age int
sex string
Address *Address //嵌套Address结构体指针
}
//Address结构体
type Address struct {
city string
province string
}
func main() {
ares := People{
name: "ares",
age: 28,
Address: Address{
city: "北京",
province: "beijing",
},
}
fmt.Println(ares) //{ares 28 {北京 beijing}}
fmt.Println(ares.name) //ares
fmt.Println(ares.Address.province) //beijing
//拷贝一份结构体
ares1 := ares
ares1.Address.province = "bj"
fmt.Println(ares) //{ares 28 {北京 beijing}} 值拷贝,开辟新内存,不会修改老结构体
fmt.Println(ares1) //{ares 28 {北京 bj}}
p2 := People{
name: "ares",
age: 28,
Address: Address{
city: "北京",
province: "beijing",
},
}
fmt.Println(p2) //{ares 28 {北京 beijing}}
//复制结构体内存地址
p3 := &p2
fmt.Println(p3) //&{ares 28 {北京 beijing}}
p3.Address.province = "bj" //因为是一块内存,所以原结构体也会修改
fmt.Println(p2) //{ares 28 {北京 bj}}
fmt.Println(*p3) //{ares 28 {北京 bj}}
}
匿名结构体
没有名字的结构体即为匿名结构体。创建结构体结构时同时创建对象。
func main() {
p1 := struct {
name string
id int
age int
}{"Ares", 1, 18}
fmt.Println(p1) //{Ares 1 18}
}
当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
如果嵌套多个匿名结构体,打印时需明确指出是哪个匿名结构体中字段。
//People结构体
type People struct {
name string
age int
sex string
Address //嵌套Address匿名结构体
}
只有匿名结构体,匿名字段才支持直接访问!
当匿名字段有冲突时必须显示调用!
结构体继承
类似于其他语言中面向对象的继承。
//结构体模拟继承
//定义一个animal结构体
type animal struct {
name string
}
//定义一个动物会动的方法
func (a *animal) move() {
fmt.Printf("%s会动\n",a.name)
}
//定义一个狗的结构体
type dog struct {
feet int
animal
}
//定义一个狗会叫的方法
func (d *dog) wnag() {
fmt.Printf("%s会叫。\n",d.name)
}
func main() {
var jinmao = dog{
feet:4,
animal:animal{
name:"旺财",
},
}
fmt.Println(jinmao) //{4 {旺财}}
jinmao.move() //旺财会动
jinmao.wnag() //旺财会叫。
}
结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
结构体与结构体tag与JSON序列化
tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
//json序列化
//定义stu的结构体
type stu struct {
ID int
Name string
Sex string
}
//默认序列化为,字段还是大些,如果需要序列化为小写,需要定义元信息:json tag
type stu11 struct {
ID int `json:"id"`
Name string `json:"name"`
Sex string `json:"sex"`
}
func main() {
var stu1 = stu{
ID:1,
Name:"ares",
Sex:"男",
}
//序列化,把编程语言中的数据转换成JSON格式的字符串
v,err := json.Marshal(stu1)
if err != nil{
fmt.Println("JSON序列化失败!")
fmt.Println(err)
}
fmt.Println(v) //[123 34 73 68 34 58 49 44 34 78 97 109 101 34 58 34 97 114 101 115 34 44 34 83 101 120 34 58 34 231 148 183 34 125]
fmt.Printf("v的类型是:%T\n",v) //v的类型是:[]uint8 []byte类型
//把[]byte类型转换成string
fmt.Printf("%v\n",string(v)) //{"ID":1,"Name":"ares","Sex":"男"}
fmt.Printf("%#v\n",string(v)) //"{\"ID\":1,\"Name\":\"ares\",\"Sex\":\"男\"}"
//反序列化:把满足JSON格式的字符串转换成 当前编程语言里面的对象
str := "{\"ID\":1,\"Name\":\"ares\",\"Sex\":\"男\"}"
var stu2 = &stu{}
json.Unmarshal([]byte(str),stu2)
fmt.Println(stu2) //&{1 ares 男}
fmt.Printf("stu2的类型是:%T\n",stu2) //stu2的类型是:*main.stu
var stu22 = stu11{
ID:2,
Name:"ares",
Sex:"男",
}
stu222,err := json.Marshal(stu22)
fmt.Println(stu222)
//此时序列化后为小写
fmt.Println(string(stu222)) //{"id":2,"name":"ares","sex":"男"}
}