一.类型别名和自定义类型
1.自定义类型
//将AgeInt定义为int类型
type AgeInt int
通过Type关键字的定义,AgeInt就是一种新的类型,它具有int的特性。
2.类型别名
//定义age为int类型的别名
type age = int
使用:
type (
// BBB 自定义类型
name string
// AAA 类型别名
age = int
)
func main() {
var a name
var b age
fmt.Printf("type of a:%T\n", a) //type of a:main.name
fmt.Printf("type of b:%T\n", b) //type of b:int
}
二.结构体
1.基本定义
type 类型名 struct {
字段名 字段类型
字段名 字段类型
...
}
运用
type ID struct {
name, address string //内存对齐
age, Telephone int
}
同:
type ID struct {
name string
address string
age int
Telephone int
}
2.结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。结构体本身也是一种类型,我们可以像声明内置类型一样使用var
关键字声明结构体类型。
var 结构体实例 结构体类型
例子
type person struct {
name, city string
age, Telephone int
}
func main() {
var a person
a.name = "晓天"
a.city = "重庆"
a.age = 20
a.Telephone = 123456789
fmt.Printf("%v\n", a) //{晓天 重庆 20 123456789}
fmt.Printf("%#v\n", a) //main.person{name:"晓天", city:"重庆", age:20, Telephone:123456789}
fmt.Printf("%v\n", a.name) //晓天
}
通过.
来访问结构体的字段(成员变量)
3.匿名结构体
func main() {
var b struct {
Name string
Age int
}
b.Age = 30
...
}
4.指针类型结构体
var 结构体指针 = new(结构类型)
例子
type person struct {
name, city string
age, Telephone int
}
func main() {
var a = new(person)
a.name = "晓天"
a.city = "重庆"
a.age = 20
a.Telephone = 123456789
fmt.Printf("%v\n", a) //&{晓天 重庆 20 123456789}
fmt.Printf("%#v\n", a) //&main.person{name:"晓天", city:"重庆", age:20, Telephone:123456789}
fmt.Printf("%v\n", a.name) //晓天
}
使用&
对结构体进行取地址操作相当于对该结构体类型进行了一次new
实例化操作。
a := &person{}
5.结构体初始化和简写
func main() {
a := &person{
"晓天",
"重庆",
20,
12345667890,
}
fmt.Printf("%v\n", a) //&{晓天 重庆 20 123456789}
fmt.Printf("%#v\n", a) //&main.person{name:"晓天", city:"重庆", age:20, Telephone:123456789}
fmt.Printf("%v\n", a.name) //晓天
}
6.构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型
func newPerson(name string, age int8) *person {
return &person{
name: name,
age: age,
}
}
调用
p9 := newPerson("晓天", 20)
fmt.Printf("%#v\n", p9)
7.方法和接收者
Go语言中的方法(Method)
是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)
。接收者的概念就类似于其他语言中的this
或者 self
。
7.1.基本语法
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
例子
//person结构体
type person struct {
name string
age int
}
// Asr 构造函数
func Asr(name string, age int) *person {
return &person{
name: name,
age: age,
}
}
// Dream 方法
func (p person) Dream() {
fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}
func main() {
p1 := Asr("晓天", 20)
p1.Dream()
}
7.2.指针类型接收者和值类型接收者
指针类型:
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this
或者self
。 例如我们为Person
添加一个SetAge
方法,来修改实例变量的年龄。
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}
调用:
func main() {
p1 := NewPerson("小王子", 20)
fmt.Println(p1.age) // 20
p1.SetAge(30)
fmt.Println(p1.age) // 30 改变了值
}
值类型:
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("晓天", 20)
p1.Dream()
fmt.Println(p1.age) // 20
p1.SetAge2(30) // (*p1).SetAge2(30)
fmt.Println(p1.age) // 20 没有改变值
}
7.3.什么时候用指针类型
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
8.任意类型接收者
在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 = 10
fmt.Printf("%#v %T\n", m1, m1) //10 main.MyInt
}
9.结构体匿名字段
//Person 结构体Person类型
type Person struct {
string
int
}
func main() {
p1 := Person{
"晓天",
20,
}
fmt.Printf("%#v\n", p1) //main.Person{string:"晓天", int:20}
fmt.Println(p1.string, p1.int) //晓天 20
}
10.嵌套结构体
10.1 一个结构体中可以嵌套包含另一个结构体或结构体指针
//Address 地址结构体
type Address struct {
Province string
City string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address Address
}
func main() {
user1 := User{
Name: "晓天",
Gender: "男",
Address: Address{
Province: "中国",
City: "重庆",
},
}
fmt.Printf("user1=%#v\n", user1) //user1=main.User{Name:"晓天", Gender:"男", Address:main.Address{Province:"中国", City:"重庆"}}
}
10.2嵌套结构体字段冲突
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
//Address 地址结构体
type Address struct {
Province string
City string
CreateTime string
}
//Email 邮箱结构体
type Email struct {
Account string
CreateTime string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address
Email
}
func main() {
var user3 User
user3.Name = "晓天"
user3.Gender = "男"
// user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
user3.Address.CreateTime = "1999" //指定Address结构体中的CreateTime
user3.Email.CreateTime = "2000" //指定Email结构体中的CreateTime
fmt.Println(user3) //{晓天 男 { 1999} { 2000}}
}
11.结构体的继承
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() //大黄会动!
}
12.结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
13.结构体与json序列化
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。
//Student 学生
type Student struct {
ID int
Gender string
Name string
}
//Class 班级
type Class struct {
Title string
Students []*Student
}
func main() {
c := &Class{
Title: "19级一班",
Students: make([]*Student, 0, 200),
}
for i := 0; i <= 2; i++ {
stu := &Student{
Name: fmt.Sprintf("学生%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:{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"学生00"},{"ID":1,"Gender":"男","Name":"学生01"},{"ID":2,"Gender":"男","Name":"学生02"}]}
//JSON反序列化:JSON格式的字符串-->结构体
str := `{"Title":"19级1班","Students":[{"ID":0,"Gender":"男","Name":"学生00"},{"ID":1,"Gender":"男","Name":"学生01"},{"ID":2,"Gender":"男","Name":"学生02"}]}`
c1 := &Class{}
err = json.Unmarshal([]byte(str), c1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
fmt.Printf("%#v\n", c1) //&main.Class{Title:"19级1班", Students:[]*main.Student{(*main.Student)(0xc000110870), (*main.Student)(0xc0001108a0), (*main.Student)(0xc0001108d0)}}
}
14.结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
例子
//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":"女"}
}
15.删除map类型结构体和实现map有序输出
15.1删除map类型结构体
type student struct {
id int
name string
age int
}
func main() {
ce := make(map[int]student)
ce[1] = student{1, "晓天", 22}
ce[2] = student{2, "小王", 23}
fmt.Println(ce)
delete(ce, 2)
fmt.Println(ce)
}
15.2map有序化输出
func main() {
map1 := make(map[int]string, 5)
map1[1] = "我是一号"
map1[2] = "我是二号"
map1[5] = "我是五号"
map1[3] = "我是三号"
map1[4] = "我是四号"
sli := []int{}
for k, _ := range map1 {
sli = append(sli, k)
}
sort.Ints(sli)
for i := 0; i < len(map1); i++ {
fmt.Println(map1[sli[i]])
}
}