1.自定义类型
Go语言中可以使用type关键字来定义自定义类型。
type MyInt intfunc main() {var a MyInt=100fmt.Printf("%T",a) //main.MyInt}//通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性
2.类型别名
我们之前见过的rune和byte就是类型别名,他们的定义如下
type TypeAlias = Type
type byte = uint8type rune = int32
type NewInt int //类型定义type MyInt = int //类型别名func main() {var a NewIntvar b MyIntfmt.Printf("%T\n",a) //main.NewIntfmt.Printf("%T\n",b) //int}
a的类型是main.NewInt,表示main包下定义的NewInt类型。
b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。
3.结构体的实例化
3.1结构体的定义
使用type和struct关键字来定义结构体
type 类型名 struct {字段名 字段类型字段名 字段类型…}
type person struct {name stringcity stringage int8}
type person struct {name,city stringage int8}
3.2基本实例化
type person struct {name,city stringage int8}func main() {var p personp.name = "cheng"p.city = "北京"p.age = 18fmt.Println(p) //{cheng 北京 18}fmt.Printf("%v\n",p) //{cheng 北京 18}fmt.Printf("%#v",p) //main.person{name:"cheng", city:"北京", age:18}}
type person struct {name,city stringage int8}func main() {p :=person{name: "cheng",city: "汕头",age: 18}fmt.Println(p)}
p :=person{}p.name = "cheng"p.city = "北京"p.age = 18fmt.Println(p)
3.3匿名结构体
var user struct{Name string;Age int}user.Name = "cheng"user.Age = 18fmt.Println(user)
3.4指针结构体
type person struct {name,city stringage int8}func main() {var p = new(person)fmt.Printf("%T\n",p) //*main.personfmt.Printf("%v\n",*p) //{ 0}fmt.Printf("%v\n",p) //&{ 0}}
var p = new(person)p.name = "cheng"p.city = "汕头"p.age = 18fmt.Println(p) //&{cheng 汕头 18}fmt.Println(*p) //{cheng 汕头 18}fmt.Printf("%#v",p) //&main.person{name:"cheng", city:"汕头", age:18}
3.5地址实例化
使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作
p1 :=&person{}p2 := new(person)fmt.Println(p1) //&{ 0}fmt.Println(p2) //&{ 0}p1.name="cheng"p1.age = 18p1.city = "广州"fmt.Println(p1) //&{cheng 广州 18}
4.结构体初始化
没有初始化的结构体,其成员变量都是对应其类型的零值
var p personfmt.Printf("%#v",p) //main.person{name:"", city:"", age:0}
4.1使用键值对初始化
p := person{name:"cheng",age:18,city:"汕头",}fmt.Println(p)
p := &person{name:"cheng",city:"深圳",age:18,}fmt.Println(p)
4.2使用值的列表初始化
p := &person{"cheng","深圳",18,}fmt.Println(p)
p := person{"cheng","深圳",18,}fmt.Println(p)
使用这种格式初始化时,需要注意:
- 必须初始化结构体的所有字段。
- 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
- 该方式不能和键值初始化方式混用。
4.3结构体内存布局
//结构体占用一块连续的内存type test struct {a int8b int8c byted byte}func main() {n :=test{1,2,3,4,}fmt.Printf("%p\n",&n.a) //0xc00000a088fmt.Printf("%p\n",&n.b) //0xc00000a089fmt.Printf("%p\n",&n.c) //0xc00000a08afmt.Printf("%p\n",&n.d) //0xc00000a08b}
//空结构体是不占用空间的var v struct{}func main() {fmt.Println(unsafe.Sizeof(v))}
5.构造函数
Go语言的结构体没有构造函数,我们可以自己实现。
struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大
所以该构造函数返回的是结构体指针类型
type person struct {name stringcity stringage int8}func newPerson(name,city string,age int8) *person{return &person{name: name,city: city,age: age,}}
p :=newPerson("cheng","汕头",18)fmt.Println(p)
6.方法和接收者
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。
接收者的概念就类似于其他语言中的this或者 self
6.1方法
//结构体type Person struct {name stringage int8}//构造函数func NewPerson(name string, age int8) *Person {return &Person{name:name,age:age,}}//方法func (p Person) Eat() {fmt.Println("吃饭")}func main() {p :=NewPerson("cheng",20)p.Eat()}
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型
6.2指针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self
func (p *Person) SetAge(newAge int8) {p.age = newAge}
p :=NewPerson("cheng",20)fmt.Println(p)p.SetAge(18)fmt.Println(p)
6.3值类型的接收者
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
func (p Person) SetAge(age int8) {p.age = age}func main() {p :=NewPerson("cheng",20)p.Eat()fmt.Println(p.age) //20p.SetAge(18)fmt.Println(p.age) //20}
7.任意类型添加方法
int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法
type MyInt intfunc (m MyInt) sayHello() {fmt.Println("HelloMyInt")}func main() {var my MyIntmy.sayHello() //HelloMyIntmy = 100fmt.Printf("%#v %T\n",my,my) //100 main.MyInt}
8.结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段
type Person struct {stringint}func main() {p := Person{"cheng",18,}fmt.Println(p) //{cheng 18}fmt.Println(p.int) //18fmt.Println(p.string) //cheng}
9.嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针
//地址结构体type Address struct {Province stringCity string}//用户结构体type User struct {Name stringGender stringAddress Address}func main() {user :=User{Name: "cheng",Gender: "男",Address: Address{Province: "广东",City: "汕头",},}fmt.Println(user)fmt.Printf("%#v\n",user)}
嵌套匿名字段
//地址结构体type Address struct {Province stringCity string}//用户结构体type User struct {Name stringGender stringAddress //匿名字段}func main() {var user Useruser.Name = "cheng"user.Gender= "男"user.Address.Province = "广东" // 匿名字段默认使用类型名作为字段名user.City="汕头" // 匿名字段可以省略fmt.Println(user)}
嵌套结构体的字段名冲突
//地址结构体type Address struct {Province stringCity stringCreateTime string}type Email struct {Account stringCreateTime string}//用户结构体type User struct {Name stringGender stringAddress //匿名字段}func main() {var user Useruser.Name = "cheng"user.Gender="男"//user.CreateTime="2021"user.Address.CreateTime = "2020"user.Email.CreateTime="2020"fmt.Println(user)}
10.结构体的继承
使用结构体也可以实现其他编程语言中面向对象的继承
type Animal struct {name string}func (a *Animal) move() {fmt.Printf("%s移动\n",a.name)}type Dog struct {Age int8Animal}func (d *Dog) wang() {fmt.Printf("%s汪汪汪\n",d.name)}func main() {d :=Dog{Age: 10,Animal:Animal{name: "狗",},}d.wang()d.move()fmt.Println(d)}
type Animal struct {name string}func (a *Animal) move() {fmt.Printf("%s移动\n",a.name)}type Dog struct {Age int8*Animal}func (d *Dog) wang() {fmt.Printf("%s汪汪汪\n",d.name)}func main() {d :=&Dog{Age: 10,Animal:&Animal{name: "狗",},}d.wang()d.move()fmt.Println(d)}
11.结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
12.JSON序列化
type Student struct {ID intGender stringName string}type Class struct {Title stringStudents []*Student}func main() {c :=&Class{Title: "110",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)}fmt.Println(c)//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"}]}`class :=&Class{}err = json.Unmarshal([]byte(str), class)if err!=nil {fmt.Println("json unmarshal failed!!!")return}fmt.Printf("%#v\n",class)}
type Student struct {ID intGender stringName string}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("%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"}]}`class:=&Class{}error := json.Unmarshal([]byte(str), class)if error!=nil {fmt.Println("json unmarshal failed!!!")return}fmt.Println(class)}
13.结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
type Student struct {ID int `json:"id"` //通过指定tag实现json序列化该字段时的keyGender string //json序列化是默认使用字段名作为keyname string //私有不能被json包访问}func main() {s:=Student{ID: 1,Gender: "男",name: "cheng",}data, err := json.Marshal(s)if err != nil {fmt.Println("json marshal failed!!!")return}fmt.Printf("%s\n",data)}
结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误。
14.结构体和方法补充知识点
因为slice和map这两种数据类型都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意。
type Person struct {name stringage int8dreams []string}func (p *Person) SetDreams(dreams []string) {p.dreams=dreams}func main() {p := Person{name:"cheng",age:18}data := []string{"Java","Golang","Python"}p.SetDreams(data)fmt.Println(p.dreams) //[Java Golang Python]data[0]="C#"fmt.Println(p.dreams) //[C# Golang Python]}
func (p *Person) SetDreams(dreams []string) {//p.dreams=dreamsp.dreams = make([]string,len(dreams))copy(p.dreams,dreams)}p := Person{name:"cheng",age:18}data := []string{"Java","Golang","Python"}p.SetDreams(data)fmt.Println(p.dreams) //[Java Golang Python]data[0]="C#"fmt.Println(p.dreams) //[Java Golang Python]
同样的问题也存在于返回值slice和map的情况,在实际编码过程中一定要注意这个问题
