类型别名和自定义类型
自定义类型
Go语言使用type来自定义类型,自定义类型是定义了一个全新的类型
type NewInt int
类型别名
使用type和=来进行类型别名定义
type TypeAlisa = Type
自定义类型和类型别名区别
//NewInt 基于int的自定义类型
type NewInt int
//MyInt 为类型别名
type MyInt = int
func main() {
var a NewInt
var b MyInt
fmt.Printf("%T %v\n", a, a) // main.NewInt 0
fmt.Printf("%T %v\n", b, b) // int 0
}
结构体
Go语言中的结构体相当于其他语言的类,表示一些事物的基本属性。使用type和struct来定义结构体。
结构体定义
type 类型名 struct{
字段名, 字段类型
...
}
type person struct{
name, city string
age int8
}
结构体实例化
只有结构体实例化后,才会真正的分配内存。也就是只有实例化之后才可以使用结构体的字段。
var 结构体实例 结构体类型
func main() {
var p1 person
p1.name = "小三"
p1.city = "北京"
p1.age = 18
fmt.Printf("p1=%v\n", p1) // p1={小三 北京 18}
fmt.Printf("p1=%#v\n", p1) // p1=main.person{name:"小三", city:"北京", age:18}
}
匿名结构体
在定义一些临时数据结构等场景下可以使用匿名结构体
func main() {
var user struct {
Name string
Age int8
}
user.Name = "小四"
user.Age = 44
fmt.Printf("%#v\n", user) // struct { Name string; Age int8 }{Name:"小四", Age:44}
}
创建指针类型结构体
使用new关键字对结构体进行实例化,得到的是结构体地址。
func main() {
var p2 = new(person)
p2.name = "小三" // 这里其实应该是(*p2).name Go做了封装可以简写
p2.city = "上海"
fmt.Printf("%T\n", p2) // *main.person
fmt.Printf("%v\n", p2) // &{小三 上海 0}
}
取结构体的地址实例化
使用&对结构体进行取地址操作,相当于对结构体进行了一次new实例化操作
func main() {
p3 := &person{}
fmt.Printf("%T\n", p3) // *main.person
p3.name = "小四"
p3.city = "广州"
p3.age = 44
fmt.Printf("%#v", p3) // &main.person{name:"小四", city:"广州", age:44}
}
使用键值对初始化
func main() {
p3 := person{
name: "小三",
city: "北京",
age: 19,
}
fmt.Println(p3)
}
也可以对结构体指针进行健值初始化
func main() {
p3 := &person{
name: "小三",
city: "北京",
age: 19,
}
fmt.Println(p3)
}
使用值的列表初始化
初始化结构体的时候可以简写,也就是不写健,直接写值
func main() {
p3 := &person{
"小三",
"北京",
19,
}
fmt.Println(p3)
}
构造函数
Go中结构体没有构造函数,可以自己实现。一般构造结构体使用指针,来提升性能。一般使用New+结构体名字
func NewPerson(name, city string, age int8) *person {
return &person{
name: name,
city: city,
age: age,
}
}
调用构造体函数
func main() {
p3 := NewPerson("张三", "上海", 19)
fmt.Println(p3)
}
方法和接收者
Go语言中的方法(Method)
是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)
。接收者的概念就类似于其他语言中的this
或者 self
。
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数){
函数体
}
- 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母
- 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型
- 方法名、参数列表、返回参数:具体格式与函数定义相同
// Person 为构造体
type Person struct {
name string
age int8
}
// NewPerson 为构造函数——函数不属于任何类型
func NewPerson(name string, age int8) *Person {
return &Person{
name: name,
age: age,
}
}
// Dream 为Person的做梦方法——方法属于特定的类型
func (p Person) Dream() {
fmt.Printf("%s在做梦", p.name)
}
func main() {
p1 := NewPerson("小红", 18)
p1.Dream()
}
指针类型的接收者
指针类型的接收者有一个构造体的指针组成,调用该方法可以修改任意成员的变量。
func (p *Person) setAge(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("小红", 18)
p1.setAge(20)
fmt.Println(p1.age) // 20
}
值类型的接收者
当方法作用于值类型的接收者时,Go会在代码执行时候将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
func (p Person) setAge2(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("小红", 18)
p1.setAge2(20)
fmt.Println(p1.age) // 20
}
什么时候只用指针类型接收者
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的对象
- 保持一致性,如果某个方法使用了指针接收者,那么其他方法也应该使用指针接收者
任意类型添加方法
在Go中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以有方法。
//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
}
嵌套结构体
一个结构体能够嵌套另一个结构体或结构体指针
// Address 为结构体
type Address struct {
city string
}
// User 为结构体
type User struct {
name string
age int8
Address Address
}
func main() {
u := User{
name: "用户a",
age: 18,
Address: Address{
city: "北京",
},
}
fmt.Printf("%#v", u)
// main.User{name:"用户a", age:18, Address:main.Address{city:"北京"}}
}
嵌套匿名结构体
// Address 为结构体
type Address struct {
city string
country string
}
// User 为结构体
type User struct {
name string
Address
}
func main() {
var u User
u.name = "小二"
u.Address.country = "China" // 通过匿名结构体.字段名访问
u.city = "北京" // 直接访问匿名结构体的字段名
fmt.Printf("%#v", u)
// main.User{name:"小二", Address:main.Address{city:"北京", country:"China"}}
}
嵌套结构体的字段冲突
// Address 为结构体
type Address struct {
name string
city string
}
// Email 为结构体
type Email struct {
name string
}
// User 为结构体
type User struct {
name string
Address
Email
}
func main() {
u := User{
name: "小二",
Address: Address{
name: "四街",
city: "上海",
},
Email: Email{
name: "xiaoer@qq.com",
},
}
fmt.Println(u.name, u.Email.name, u.Address.name)
}
结构体中的继承
type animal struct {
name string
}
type dog struct {
*animal
}
func (a *animal) move() {
fmt.Printf("%s会跑\n", a.name)
}
func (d *dog) wang() {
fmt.Printf("%s会汪汪叫", d.name)
}
func main() {
d1 := &dog{
animal: &animal{
name: "小黄",
},
}
d1.move()
d1.wang()
}
结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
结构体与json序列化
package main
import (
"encoding/json"
"fmt"
)
// Student 学生结构体
type Student struct {
ID int8
Name string
}
// Class 班级结构体
type Class struct {
Title string
Students []Student
}
// NewStudent 为Student的构造函数
func NewStudent(id int8, name string) Student {
return Student{
ID: id,
Name: name,
}
}
func main() {
c1 := Class{
Title: "高中一班",
Students: make([]Student, 0, 20),
}
for i := 0; i < 10; i++ {
ns := NewStudent(int8(i), fmt.Sprintf("stu%02d", i))
c1.Students = append(c1.Students, ns)
}
// JSON序列化
data, err := json.Marshal(c1)
if err != nil {
fmt.Println("json序列化失败")
return
}
fmt.Printf("序列化结果为%s\n:", data)
// JSON反序列化
str := `{"Title":"高中一班","Students":[{"ID":0,"Name":"stu00"},{"ID":1,"Name":"stu01"},{"ID":2,"Name":"stu02"},{"ID":3,"Name":"stu03"}]}`
// c2 := &Class{}
var c2 Class
err = json.Unmarshal([]byte(str), &c2)
if err != nil {
fmt.Println("json反序列化失败")
return
}
fmt.Println("反序列化结果为",c2)
}
因为上边定义的变量都为大写的,所以反序列化能够成功显示,如果为小写,那就对外部不可见,反序列化后小写字段是修改不成功的。
结构体Tag标签
Tag
是结构体的元信息,可以在运行的时候通过反射的机制读取出来。 Tag
在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag
时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}
func main() {
s1 := Student{
ID: 1,
Gender: "男",
name: "唐僧",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json marshal failed!")
return
}
fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}