Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
总结:
-非本地类型不能定义方法,也就是我们不能给别的包的类型定义方法
-通过结构体变量 点 机构体成员的方式,访问结构体
-指针类型结构体 也可以直接通过 点 访问成员,因为go实现了一个语法糖 &T.name 相当于 (*T).name
-结构体中字段没有初始化,字段的值就是当前类型的零值
-接收者变量推荐使用接收者类型的首字母
-接收者可以是值类型或者指针类型,推荐使用指针类型,可以修改接收者结构体的成员,如果结构体过大时值拷贝浪费性能
-结构体的匿名字段默认使用类型作为字段名,所以同一个结构体中同种类型的匿名字段只能有一个
-结构体中字段如果首字母大写代表可见,首字母小写表示私有,只在当前包中可用
1.1. 类型别名和自定义类型
1.1.1. 自定义类型
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以基于struct定义的类型。例如:
//将MyInt定义为int类型type MyInt int
通过Type关键字的定义,MyInt就是一种新的类型,它具有int的特性。
1.1.2. 类型别名
类型别名,本质上说还是同一个类型,只是给这个类型起了一个名字而已
type TypeAlias = Type
我们之前见到的byte和rune就是类型别名
type byte = uint8type rune = int32
1.1.3. 类型定义和类型别名的区别
自定义类型已经是一个全新的类型,只是拥有相同的特性。类型别名只是取了一个新的名字,但是本质还是一样的,在代码编译完成后是不会存在类型别名
通过下面的代码验证:
//类型定义type NewInt int//类型别名type MyInt = intfunc main() {var a NewIntvar b MyIntfmt.Printf("type of a:%T\n", a) //type of a:main.NewIntfmt.Printf("type of b:%T\n", b) //type of b:int}
结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。
1.2. 结构体
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。
Go语言中通过struct来实现面向对象。
1.2.1. 结构体的定义
使用type和struct关键字来定义结构体,具体代码格式如下:
type 类型名 struct {字段名 字段类型字段名 字段类型…}
其中:
1.类型名:标识自定义结构体的名称,在同一个包内不能重复。2.字段名:表示结构体字段名。结构体中的字段名必须唯一。3.字段类型:表示结构体字段的具体类型。
定义一个Person(人)结构体:
type person struct {name stringcity stringage int8}
同样类型的字段也可以写在一行,
type person1 struct {name, city stringage int8}
这样我们就拥有了一个person的自定义类型,它有name、city、age三个字段,分别表示姓名、城市和年龄。这样我们使用这个person结构体就能够很方便的在程序中表示和存储人信息了。
语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型
1.2.2. 结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。
var 结构体实例 结构体类型
1.2.3. 匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package mainimport ("fmt")func main() {var user struct{Name string; Age int}user.Name = "pprof.cn"user.Age = 18fmt.Printf("%#v\n", user)}
1.2.4.创建指针类型结构体
使用new关键字对机构体进行实例化
var p2 = new(person)fmt.Printf("%T\n", p2) //*main.personfmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
使用&取地址符,对结构体进行实例化,相当于进行了一次new
p3 := &person{}fmt.Printf("%T\n", p3) //*main.personfmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
1.2.5.结构体初始化
1.使用键值对初始化:
p5 := person{name: "pprof.cn",city: "北京",age: 18,}fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。
p7 := &person{city: "北京",}fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}
2.使用值的列表初始化
初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
p8 := &person{"pprof.cn","北京",18,}fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"pprof.cn", city:"北京", age:18}
1.必须初始化结构体的所有字段。2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。3.该方式不能和键值初始化方式混用。
1.2.6.构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
func newPerson(name, city string, age int8) *person {return &person{name: name,city: city,age: age,}}
调用构造函数
p9 := newPerson("pprof.cn", "测试", 90)fmt.Printf("%#v\n", p9)
1.2.7.方法和接收者
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
方法的定义格式如下:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体}
其中,
1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。
例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
2.接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
3.方法名、参数列表、返回参数:具体格式与函数定义相同。
举个例子:
//Person 结构体type Person struct {name stringage int8}//NewPerson 构造函数func NewPerson(name string, age int8) *Person {return &Person{name: name,age: age,}}//Dream Person做梦的方法func (p Person) Dream() {fmt.Printf("%s的梦想是学好Go语言!\n", p.name)}func main() {p1 := NewPerson("测试", 25)p1.Dream()}
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
1.2.8.指针类型接受者和值类型接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。
// SetAge 设置p的年龄// 使用指针接收者func (p *Person) SetAge(newAge int8) {p.age = newAge}
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
// SetAge2 设置p的年龄// 使用值接收者func (p Person) SetAge2(newAge int8) {p.age = newAge}
什么时候使用指针接收者
1.需要修改接收者中的值
2.接收者是拷贝代价比较大的大对象
3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
1.2.9.任意类型添加方法
在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。
//MyInt 将int定义为自定义MyInt类型type MyInt int//SayHello 为MyInt添加一个SayHello的方法func (m MyInt) SayHello() {fmt.Println("Hello, 我是一个int。")}func main() {var m1 MyIntm1.SayHello() //Hello, 我是一个int。m1 = 100fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt}
注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。
1.2.10.结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
//Person 结构体Person类型type Person struct {stringint}func main() {p1 := Person{"pprof.cn",18,}fmt.Printf("%#v\n", p1) //main.Person{string:"pprof.cn", int:18}fmt.Println(p1.string, p1.int) //pprof.cn 18}
匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
1.2.11.嵌套结构体
一个结构体可以嵌套包含结构体或者结构体指针
//Address 地址结构体type Address struct {Province stringCity string}//User 用户结构体type User struct {Name stringGender stringAddress Address}func main() {user1 := User{Name: "pprof",Gender: "女",Address: Address{Province: "黑龙江",City: "哈尔滨",},}fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}}
1.2.12.嵌套匿名结构体
//Address 地址结构体type Address struct {Province stringCity string}//User 用户结构体type User struct {Name stringGender stringAddress //匿名结构体}func main() {var user2 Useruser2.Name = "pprof"user2.Gender = "女"user2.Address.Province = "黑龙江" //通过匿名结构体.字段名访问user2.City = "哈尔滨" //直接访问匿名结构体的字段名fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}}
可以直接访问匿名结构体中的成员,在当前结构体中找不到,才会到匿名结构体中寻找
1.2.13.嵌套结构体的字段名冲突
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
//Address 地址结构体type Address struct {Province stringCity stringCreateTime string}//Email 邮箱结构体type Email struct {Account stringCreateTime string}//User 用户结构体type User struct {Name stringGender stringAddress}func main() {var user3 Useruser3.Name = "pprof"user3.Gender = "女"// user3.CreateTime = "2019" //ambiguous selector user3.CreateTimeuser3.Address.CreateTime = "2000" //指定Address结构体中的CreateTimeuser3.Email.CreateTime = "2000" //指定Email结构体中的CreateTime}
1.2.14.结构体的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
//Animal 动物type Animal struct {name string}func (a *Animal) move() {fmt.Printf("%s会动!\n", a.name)}//Dog 狗type Dog struct {Feet int8*Animal //通过嵌套匿名结构体实现继承}func (d *Dog) wang() {fmt.Printf("%s会汪汪汪~\n", d.name)}func main() {d1 := &Dog{Feet: 4,Animal: &Animal{ //注意嵌套的是结构体指针name: "乐乐",},}d1.wang() //乐乐会汪汪汪~d1.move() //乐乐会动!}
1.2.15. 结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
1.2.16.结构体于json序列化
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号””包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。
| json.Unmarshal | json反序列化 |
|---|---|
| json.Marshal | json序列化 |
//Student 学生type Student struct {ID intGender stringName string}//Class 班级type Class struct {Title stringStudents []*Student}func main() {c := &Class{Title: "101",Students: make([]*Student, 0, 200),}for i := 0; i < 10; i++ {stu := &Student{Name: fmt.Sprintf("stu%02d", i),Gender: "男",ID: i,}c.Students = append(c.Students, stu)}//JSON序列化:结构体-->JSON格式的字符串data, err := json.Marshal(c)if err != nil {fmt.Println("json marshal failed")return}fmt.Printf("json:%s\n", data)//JSON反序列化:JSON格式的字符串-->结构体str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`c1 := &Class{}err = json.Unmarshal([]byte(str), c1)if err != nil {fmt.Println("json unmarshal failed!")return}fmt.Printf("%#v\n", c1)}
