Go语言基础之结构体

107 阅读4分钟
# 类型别名和自定义类型
//将MyInt定义为int类型
type MyInt int
通过`type`关键字的定义,`MyInt`就是一种新的类型,它具有`int`的特性


TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型
type TypeAlias = Type


## 类型定义和类型别名的区别
//类型定义
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
}


Go语言中的基础数据类型可以表示一些事物的基本属性,
但是当我们想表达一个事物的全部或部分属性时,
这时候再用单一的基本数据类型明显就无法满足需求了,
Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体
## 结构体的定义
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

## 结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段
var 结构体实例 结构体类型

### 匿名结构体
var user struct{Name string; Age int}

### 创建指针类型结构体
通过使用`new`关键字对结构体进行实例化,得到的是结构体的地址
在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}

### 取结构体的地址实例化
使用`&`对结构体进行取地址操作相当于对该结构体类型进行了一次`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 = "zhanasn"`其实在底层是`(*p3).name = "zhanasn"` ,这是Go语言帮我们实现的语法糖

## 结构体初始化
没有初始化的结构体,其成员变量都是对应其类型的零值
### 使用键值对初始化
### 使用值的列表初始化

## 结构体内存布局
结构体占用一块连续的内存
空结构体是不占用空间的
var v struct{}
fmt.Println(unsafe.Sizeof(v))  // 0


## 方法和接收者
Go语言中的`方法(Method)`是一种作用于特定类型变量的函数。
这种特定类型变量叫做`接收者(Receiver)`。接收者的概念就类似于其他语言中的`this`或者 `self`
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型
### 指针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,
调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
	p.age = newAge
}
### 值类型的接收者


## 任意类型添加方法
在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 
举个例子,我们基于内置的`int`类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法
//MyInt 将int定义为自定义MyInt类型
type MyInt int
//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
	fmt.Println("Hello, 我是一个int。")
}
func main() {
	var m1 MyInt
	m1.SayHello() //Hello, 我是一个int。
	m1 = 100
	fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt
}

## 结构体的匿名字段
这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,
结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个
//Person 结构体Person类型
type Person struct {
	string
	int
}

func main() {
	p1 := Person{
		"小王子",
		18,
	}
	fmt.Printf("%#v\n", p1)        //main.Person{string:"北京", int:18}
	fmt.Println(p1.string, p1.int) //北京 18
}


## 嵌套结构体
### 嵌套匿名字段
当访问结构体成员时会先在结构体中查找该字段,找不到再去嵌套的匿名字段中查找

## 结构体的“继承”
//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() //乐乐会动!
}

## 结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

## 结构体与JSON序列化
//JSON序列化:结构体-->JSON格式的字符串
data, err := json.Marshal(c)
//JSON反序列化:JSON格式的字符串-->结构体
err = json.Unmarshal([]byte(str), c1)

## 结构体标签(Tag)
`Tag`是结构体的元信息,可以在运行的时候通过反射的机制读取出来
`key1:"value1" key2:"value2"`