结构体的定义
以学生结构体为例,如下:
type Student struct {name stringage int
}
同类型的可以写在一行:
type Student struct {name,course stringage int
}
结构体初始化
方法1:使用var关键字
package mainimport "fmt"type Student struct {name stringage int
}func main() {var s Studentfmt.Printf("s=%#v\n", s)
}
方法2:使用值或键值对初始化
方法2.1:使用值初始化写法
s:= Student{"cc",18,
}
如果{}在一行,逗号可以省略
s:=Student{"cc",18}
方法2.2:使用键值对初始化写法
s:=Student{name:"cc",age:18,
}
方法2.3:对结构体指针进行键值对初始化
s:=&Student{name:"cc",age:18,
}
demo:
package mainimport "fmt"type Student struct {name stringage int
}func main() {//方法1var s=Student{name:"cc",age:18,}//方法2.1s:=Student{"cc",18,}//方法2.2s:=Student{name:"cc",age:18,}fmt.Printf("s=%#v\n", s) //方法2.3:结构体指针s:=&Student{name:"cc",age:18,}fmt.Printf("s=%#v\n", s) }
方法3:给结构体成员赋值的方式进行初始化
demo
package mainimport ("fmt"
)type Student struct {name stringage int
}func main() {var s Studentfmt.Printf("s=%v\n", s)s.name = "cc"s.age = 18fmt.Printf("p1=%v\n", s)fmt.Printf("p1=%#v\n", s)
}
匿名结构体与结构体的匿名字段
在一些临时数据结构等场景下可以使用匿名结构体
import ("fmt"
)func main() {var user struct {Name stringAge int}user.Name = "pprof.cn"user.Age = 18fmt.Printf("%#v\n", user)
}
输出结果:
struct { Name string; Age int }{Name:"pprof.cn", Age:18}
结构体的字段定义时可以没有字段名,这种字段被称为匿名字段
package mainimport ("fmt"
)//Person 结构体
type Person struct {stringint
}func main() {p1 := Person{"pprof.cn",18,}fmt.Printf("%#v\n", p1) //main.Person{string:"pprof.cn", int:18}fmt.Println(p1.string, p1.int) //pprof.cn 18
}
匿名字段 默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中 同种类型的匿名字段只能有一个 。
结构体指针
除了上面几种实例化结构体方式,还可以通过new实例化结构体,返回指向结构体的指针
package mainimport ("fmt"
)type person struct {name stringcity stringage int8
}func main() {var p2 = new(person)fmt.Printf("%T\n", p2) fmt.Printf("p2=%#v\n", p2)
}
输出结果:
*main.person
p2=&main.person{name:"", city:"", age:0}
Go语言中支持对结构体指针直接使用.来访问结构体的成员
package mainimport ("fmt"
)type person struct {name stringcity stringage int8
}func main() {var p2 = new(person) //p2 是一个结构体指针p2.name = "测试" //对结构体指针直接使用`.`来访问结构体的成员p2.age = 18p2.city = "北京"fmt.Printf("p2=%#v\n", p2)
}
输出结果:
p2=&main.person{name:"测试", city:"北京", age:18}
p2.name = "测试"其实在底层是(*p2).name = "测试",这是Go语言帮我们实现的语法糖。
取结构体的地址实例化
使用&对结构体变量进行取地址操作相当于对该结构体类型进行了一次 new 实例化操作。
package mainimport ("fmt"
)type person struct {name stringcity stringage int8
}func main() {p3 := &person{}fmt.Printf("%T\n", p3) //*main.personfmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}p3.name = "博客"p3.age = 30p3.city = "成都"fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"博客", city:"成都", age:30}
}
输出结果:
*main.person
p3=&main.person{name:"", city:"", age:0}
p3=&main.person{name:"博客", city:"成都", age:30}
结构体作为函数参数
- 实例1:将结构体作为函数参数
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int
}func main() {var Book1 Books /* 声明 Book1 为 Books 类型 */var Book2 Books /* 声明 Book2 为 Books 类型 *//* book 1 描述 */Book1.title = "Go 语言"Book1.author = "www.runoob.com"Book1.subject = "Go 语言教程"Book1.book_id = 6495407/* book 2 描述 */Book2.title = "Python 教程"Book2.author = "www.runoob.com"Book2.subject = "Python 语言教程"Book2.book_id = 6495700/* 打印 Book1 信息 */printBook(Book1)/* 打印 Book2 信息 */printBook(Book2)
}func printBook(book Books) {fmt.Printf("Book title : %s\n", book.title)fmt.Printf("Book author : %s\n", book.author)fmt.Printf("Book subject : %s\n", book.subject)fmt.Printf("Book book_id : %d\n", book.book_id)
}
输出结果:
Book title : Go 语言
Book author : www.runoob.com
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.runoob.com
Book subject : Python 语言教程
Book book_id : 6495700
实例2:将结构体指针作为函数参数
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int
}func main() {var Book1 Books /* 声明 Book1 为 Books 类型 */var Book2 Books /* 声明 Book2 为 Books 类型 *//* book 1 描述 */Book1.title = "Go 语言"Book1.author = "www.runoob.com"Book1.subject = "Go 语言教程"Book1.book_id = 6495407/* book 2 描述 */Book2.title = "Python 教程"Book2.author = "www.runoob.com"Book2.subject = "Python 语言教程"Book2.book_id = 6495700/* 打印 Book1 信息 *///地址就是一个指针,指针的实质就是一个地址printBook(&Book1)/* 打印 Book2 信息 */printBook(&Book2)
}
func printBook(book *Books) {fmt.Printf("Book title : %s\n", book.title)fmt.Printf("Book author : %s\n", book.author)fmt.Printf("Book subject : %s\n", book.subject)fmt.Printf("Book book_id : %d\n", book.book_id)
}
结构体内存布局
结构体内存布局是连续的
package mainimport "fmt"type test struct {a int8b int8c int8d int8
}func main() {n := test{1, 2, 3, 4,}fmt.Printf("n.a %p\n", &n.a)fmt.Printf("n.b %p\n", &n.b)fmt.Printf("n.c %p\n", &n.c)fmt.Printf("n.d %p\n", &n.d)
}
输出结果:
n.a 0xc00000a198
n.b 0xc00000a199
n.c 0xc00000a19a
n.d 0xc00000a19b
构造函数
构造函数是一种特殊的函数,用来在对象实例化的时候初始化对象的成员变量。
package mainimport "fmt"type person struct {name stringcity stringage int8
}func main() {p9 := newPerson("pprof.cn", "测试", 90)fmt.Printf("%#v\n", p9)}//构造函数,用于初始化结构体person
func newPerson(name, city string, age int8) *person {return &person{name: name,city: city,age: age,}
}
输出结果:
&main.person{name:"pprof.cn", city:"测试", age:90}
方法和接收器
1、为结构体添加方法
package maintype Bag struct {items []int
}//定义在背包结构体上的名为Insert的方法
func (b *Bag) Insert(itemid int) {b.items = append(b.items, itemid)
}
func main() {b := new(Bag)//在结构体b上调用方法b.Insert(1001)
}
2、接收器——方法作用的对象
2、1指针类型的接收器
指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self。
由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。
在下面的例子,使用定义一个 Property 结构体,为 Property 添加 SetValue() 方法以封装设置属性的过程,通过属性的 Value() 方法可以重新获得属性的数值,使用属性时,通过 SetValue() 方法的调用,可以达成修改属性值的效果。
package main
import "fmt"
// 定义结构体 Property
type Property struct {value int // 属性值
}
// 设置 Property 值
func (p *Property) SetValue(v int) {// 修改p的成员变量p.value = v
}
// 取 Property 值
func (p *Property) Value() int {return p.value
}
func main() {// 实例化 Property p := new(Property)// 设置值p.SetValue(100)// 打印值fmt.Println(p.Value())
}
2、2 非指针类型的接收器
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。需要辅以 return+赋值 实现真正的修改。
package main
import ("fmt"
)
// 定义点结构
type Point struct {X intY int
}
// 非指针接收器的加方法
func (p Point) Add(other Point) Point {// 成员值与参数相加后返回新的结构体return Point{p.X + other.X, p.Y + other.Y}
}
func main() {// 初始化点p1 := Point{1, 1}p2 := Point{2, 2}// 与另外一个点相加result := p1.Add(p2)// 输出结果fmt.Println(result)
}
输出结果:
{3 3}
由于例子中使用了非指针接收器,Add() 方法变得类似于只读的方法,Add() 方法内部不会对成员进行任何修改。
指针和非指针接收器的使用场景
在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器。大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。
什么时候应该使用指针类型接收者?
需要修改接收者中的值接收者是拷贝代价比较大的大对象保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针
package mainimport ("fmt"
)//Address 地址结构体
type Address struct {Province stringCity string
}//User 用户结构体
type User struct {Name stringGender stringAddress Address
}func main() {user1 := User{Name: "pprof",Gender: "女",Address: Address{Province: "黑龙江",City: "哈尔滨",},}fmt.Printf("user1=%#v\n", user1) //user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}
输出结果:
user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
嵌套匿名结构体
package mainimport ("fmt"
)//Address 地址结构体
type Address struct {Province stringCity string
}//User 用户结构体
type User struct {Name stringGender stringAddress //匿名结构体
}func main() {var user2 Useruser2.Name = "pprof"user2.Gender = "女"user2.Address.Province = "黑龙江" //通过匿名结构体.字段名访问user2.City = "哈尔滨" //可以直接访问匿名结构体的字段名fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}
输出结果:
user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
嵌套结构体的字段名冲突
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
package mainimport ("fmt"
)//Address 地址结构体
type Address struct {Province stringCity stringCreateTime string
}//Email 邮箱结构体
type Email struct {Account stringCreateTime string
}//User 用户结构体
type User struct {Name stringGender stringAddressEmail
}func main() {var user3 Useruser3.Name = "pprof"user3.Gender = "女"// user3.CreateTime = "2019" //ambiguous selector user3.CreateTimeuser3.Address.CreateTime = "2000" //指定Address结构体中的CreateTimeuser3.Email.CreateTime = "2000" //指定Email结构体中的CreateTimefmt.Printf("user3=%#v\n", user3)
}
输出结果:
user3=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"", City:"", CreateTime:"2000"}, Email:main.Email{Account:"", CreateTime:"2000"}}
如果直接使用user3.CreateTime = "2000"会报错:ambiguous selector user3.CreateTime
结构体的继承
package mainimport ("fmt"
)//动物结构体
type Animal struct {name string
}//动物结构体的move方法
func (a *Animal) move() {fmt.Printf("%s会动!\n", a.name)
}//狗结构体
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格式1:
{ "firstName":"John" , "lastName":"Doe" }
json格式2:
{"employees": [{ "firstName":"John" , "lastName":"Doe" },{ "firstName":"Anna" , "lastName":"Smith" },{ "firstName":"Peter" , "lastName":"Jones" }]
}
json格式3:
var employees = [{ "firstName":"Bill" , "lastName":"Gates" },{ "firstName":"George" , "lastName":"Bush" },{ "firstName":"Thomas" , "lastName": "Carter" }
];
demo:
package mainimport ("encoding/json""fmt"
)//Student 学生
type Student struct {ID intGender stringName string
}//Class 班级
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("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"}]}`c1 := &Class{}err = json.Unmarshal([]byte(str), c1)if err != nil {fmt.Println("json unmarshal failed!")return}fmt.Printf("%#v\n", c1)
}
输出结果:
json:{"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"}]}
&main.Class{Title:"101", Students:[]*main.Student{(*main.Student)(0xc0000749c0), (*main.Student)(0xc0000749f0), (*main.Student)(0xc000074a20), (*main.Student)(0xc000074a50), (*main.Student)(0xc000074ab0), (*main.Student)(0xc000074ae0), (*main.Student)(0xc000074b10), (*main.Student)(0xc000074b40), (*main.Student)(0xc000074b70), (*main.Student)(0xc000074ba0)}}
结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
例如我们为Student结构体的每个字段定义json序列化时使用的Tag:
package mainimport ("encoding/json""fmt"
)//Student 学生
type Student struct {ID int `json:"id"` //通过指定tag实现json序列化该字段时的keyGender string //json序列化是默认使用字段名作为keyname string //私有不能被json包访问
}func main() {s1 := Student{ID: 1,Gender: "女",name: "pprof",}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":"女"}