1.自定义类型
Go语言中可以使用type关键字来定义自定义类型。
type MyInt int
func main() {
var a MyInt=100
fmt.Printf("%T",a) //main.MyInt
}
//通过type关键字的定义,MyInt就是一种新的类型,它具有int的特性
2.类型别名
我们之前见过的rune和byte就是类型别名,他们的定义如下
type TypeAlias = Type
type byte = uint8
type rune = int32
type NewInt int //类型定义
type MyInt = int //类型别名
func main() {
var a NewInt
var b MyInt
fmt.Printf("%T\n",a) //main.NewInt
fmt.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 string
city string
age int8
}
type person struct {
name,city string
age int8
}
3.2基本实例化
type person struct {
name,city string
age int8
}
func main() {
var p person
p.name = "cheng"
p.city = "北京"
p.age = 18
fmt.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 string
age int8
}
func main() {
p :=person{name: "cheng",city: "汕头",age: 18}
fmt.Println(p)
}
p :=person{}
p.name = "cheng"
p.city = "北京"
p.age = 18
fmt.Println(p)
3.3匿名结构体
var user struct{Name string;Age int}
user.Name = "cheng"
user.Age = 18
fmt.Println(user)
3.4指针结构体
type person struct {
name,city string
age int8
}
func main() {
var p = new(person)
fmt.Printf("%T\n",p) //*main.person
fmt.Printf("%v\n",*p) //{ 0}
fmt.Printf("%v\n",p) //&{ 0}
}
var p = new(person)
p.name = "cheng"
p.city = "汕头"
p.age = 18
fmt.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 = 18
p1.city = "广州"
fmt.Println(p1) //&{cheng 广州 18}
4.结构体初始化
没有初始化的结构体,其成员变量都是对应其类型的零值
var p person
fmt.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 int8
b int8
c byte
d byte
}
func main() {
n :=test{
1,2,3,4,
}
fmt.Printf("%p\n",&n.a) //0xc00000a088
fmt.Printf("%p\n",&n.b) //0xc00000a089
fmt.Printf("%p\n",&n.c) //0xc00000a08a
fmt.Printf("%p\n",&n.d) //0xc00000a08b
}
//空结构体是不占用空间的
var v struct{}
func main() {
fmt.Println(unsafe.Sizeof(v))
}
5.构造函数
Go语言的结构体没有构造函数,我们可以自己实现。
struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大
所以该构造函数返回的是结构体指针类型
type person struct {
name string
city string
age 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 string
age 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) //20
p.SetAge(18)
fmt.Println(p.age) //20
}
7.任意类型添加方法
int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法
type MyInt int
func (m MyInt) sayHello() {
fmt.Println("HelloMyInt")
}
func main() {
var my MyInt
my.sayHello() //HelloMyInt
my = 100
fmt.Printf("%#v %T\n",my,my) //100 main.MyInt
}
8.结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段
type Person struct {
string
int
}
func main() {
p := Person{
"cheng",
18,
}
fmt.Println(p) //{cheng 18}
fmt.Println(p.int) //18
fmt.Println(p.string) //cheng
}
9.嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针
//地址结构体
type Address struct {
Province string
City string
}
//用户结构体
type User struct {
Name string
Gender string
Address Address
}
func main() {
user :=User{
Name: "cheng",
Gender: "男",
Address: Address{
Province: "广东",
City: "汕头",
},
}
fmt.Println(user)
fmt.Printf("%#v\n",user)
}
嵌套匿名字段
//地址结构体
type Address struct {
Province string
City string
}
//用户结构体
type User struct {
Name string
Gender string
Address //匿名字段
}
func main() {
var user User
user.Name = "cheng"
user.Gender= "男"
user.Address.Province = "广东" // 匿名字段默认使用类型名作为字段名
user.City="汕头" // 匿名字段可以省略
fmt.Println(user)
}
嵌套结构体的字段名冲突
//地址结构体
type Address struct {
Province string
City string
CreateTime string
}
type Email struct {
Account string
CreateTime string
}
//用户结构体
type User struct {
Name string
Gender string
Address //匿名字段
}
func main() {
var user User
user.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 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)
}
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 int
Gender string
Name string
}
type Class struct {
Title string
Students []*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 int
Gender string
Name string
}
type Class struct {
Title string
Students []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序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name 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 string
age int8
dreams []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=dreams
p.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的情况,在实际编码过程中一定要注意这个问题