结构体基本实例化
//结构体
//基本实例化
type person struct { //声明一个结构体类型.
name string
city string
age int8
}
func main() {
var p1 person //声明p1变量为一个person的结构体
p1.name = "沙河娜扎"
p1.city = "北京"
p1.age = 18
fmt.Printf("p1=%v\n", p1) //直接显示p1结构体赋值后的内容
fmt.Printf("p1=%#v\n", p1) //按照go语言标准方式显示结构体赋值后的字段名以及值
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
p1={沙河娜扎 北京 18}
p1=main.person{name:"沙河娜扎", city:"北京", age:18}
匿名结构体
//匿名结构体
func main() {
var user struct {
Name string
Age int
} //直接声明一个变量,并将一个结构体赋值给变量.
user.Name = "大爷"
user.Age = 18
fmt.Println(user)
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
{大爷 18}
指针类型结构体使用结构体指针的好处是节省开支,提高性能.(难道与其他代码层面的东西无关吗?)
//指针类型结构体
type person struct { //声明一个结构体类型.
name string
city string
age int8
}
func main() {
var p2 = new(person) //这里p2是结构体指针.但是在go语言里面 取值的时候支持结构体指针+ . 进行取值
fmt.Println(p2)
fmt.Printf("asd%#v", p2.name) //因为只进行了初始化并没进行赋值.所以这里p2.name是空
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_main_go.exe #gosetup
&{ 0}
asd""
取结构体的地址进行实例化
//取结构体的地址进行实例化
func main() {
p3 := &person{} //这里取person结构体的指针赋值给p3
p3.name = "大爷" //赋值结构体中的字段属性
p3.city = "进来玩呀"
p3.age = 66
fmt.Println(p3) //最后取值这里取值的是指针类型,得到的结果是&{}
fmt.Println(*p3) //最后取值这里是从指针里面取到的值.
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
&{大爷 进来玩呀 66} //这里本质还是一个指针类型
{大爷 进来玩呀 66}
结构体初始化
func main() {
p5 := person{ //这里使用的是键值对进行的初始化.
name: "哟",
city: "不错",
age: 22,
}
fmt.Println(p5)
p6 := &person{ //这里使用的指针进行字段键值对初始化
name: "呀",
city: "土豆",
age: 99,
}
fmt.Println(p6)
//某些字段如果没有初始值的话可以不对字段进行赋值.那么该字段的值就是对应类型的零值
p7 := &person{
name: "二大爷",
}
fmt.Println(p7)
p8 := &person{ //这里说明一下,在使用列表进行出生后的时候需要将所有的字段按照规定一一罗列.否则是对应不上的.那么最终的赋值操作就是失败的.
//不推荐使用这种方式进行字段赋值
"二哈",
"喜欢拆家",
77,
}
fmt.Println(p8)
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
{哟 不错 22}
&{呀 土豆 99}
&{二大爷 0}
&{二哈 喜欢拆家 77}
说明一下:
使用指针进行结构体初始化可以节省内存开销提高效率,对于字段比较多的结构体比较适合.
结构体的内存地址布局 空结构体是不占用内存空间的.
//结构体内存布局
type test struct {
a int8
b int8
c int8
d int8
}
func main() {
n := test{
1, 2, 3, 4,
}
fmt.Printf("%d\n,%d\n,%d\n,%d\n,", &n.a, &n.b, &n.c, &n.d)
}
输出结果:
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
824634384472
,824634384473
,824634384474
,824634384475
同一个结构体中的内存地址是连续的,也方便寻址,
请问下面的代码的执行结果
//请问下面的代码执行结果是什么?
type student struct {
name string
age int
}
func main() {
m := make(map[string]*student)
stus := []student{
{name: "小王子", age: 18},
{name: "娜扎", age: 23},
{name: "大王八", age: 9000},
}
for _, stu := range stus { //循环的结果是每一个结构体,stu = {name: "小王子", age: 18}
m[stu.name] = &stu //"大王八" :{name: "大王八", age: 9000}} //当最后一次循环结束的时候 m.[stu.name]的值就变成了大王八.
}
for k, v := range m { //这里键循环.值就是相同的结果了.
fmt.Println(k, "=>", v.name)
}
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
大王八 => 大王八
小王子 => 大王八
娜扎 => 大王八
这里有点模糊的概念就是指针.需要特别特别特别细致的进行梳理.
构造函数
//构造函数
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,
}
}
func main() {
p9 := newperson("asd", "fasd", 11) //这里将构造函数中的参数传入之后,那么对应赋值的变量就成了一个拥有相同结构体类型的被赋值完成的结构体.
fmt.Println(*p9) //可以打印p9 也可打印 *p9
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
{asd fasd 11}
方法
//方法和接收者
//结构体
type person struct { //声明一个结构体类型.
name string
age int8
}
//构造函数
func NewPerson(name string, age int8) person { //这里自己试了一下,返回值是指针和值都是可以的.指针更节省内存开销.
return person{
name: name,
age: age,
}
//上面的内容的目的是将结构体中的字段进行赋值操作.
}
//创建结构体绑定方法
func (p person) Dream() {
fmt.Printf("%s正在学习go语言", p.name)
}
//主函数调用构造函数并传参
func main() {
p1 := NewPerson("大侠", 22)
//调用之前绑定的方法
p1.Dream()
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_main_go.exe #gosetup
大魔王 非常想学好go语言
这里稍微有点点绕.大概顺序就是 ,结构体 ==> 构造函数 ==> 结构体绑定方法==> main函数调用构造函数传值 ==>结构体赋值的变量调用绑定方法==>输出结果.
指针接收者和值接收者的区别
//指针类型的接收者和值类型接收者的区别
type person struct { //声明一个结构体类型.
name string
age int8
}
//构造函数
func NewPerspn(name string, age int8) *person {
return &person{
name: name,
age: age,
}
}
//创建指针接收者类型的方法绑定
func (p *person) SetAge(newAge int8) { //注意这里使用的是*person是一个指针接收者.
p.age = newAge
}
//主函数调用构造函数并传值
func main() {
p1 := NewPerspn("小魔仙", 99)
fmt.Println(p1.age) //99
p1.SetAge(88)
fmt.Println(p1.age) //88
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_main_go.exe #gosetup
99
88
从输出结果发现指针接收者的输出结果是99 88 表明person的结构体字段中的内容被修改生效了.
接下来看下值接收者的结果:
值接收者:
//指针类型的接收者和值类型接收者的区别
type person struct { //声明一个结构体类型.
name string
age int8
}
//构造函数
func NewPerspn(name string, age int8) *person {
return &person{
name: name,
age: age,
}
}
//创建指针接收者类型的方法绑定
func (p person) SetAge(newAge int8) { //注意这里的接收者由原来的*person变成了person
p.age = newAge
}
//主函数调用构造函数并传值
func main() {
p1 := NewPerspn("小魔仙", 99)
fmt.Println(p1.age) //原始值 99
p1.SetAge(88) //调用修改方法
fmt.Println(p1.age) //还是显示原始值 99
}
输出结果:
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
99
99
这里说明一下:
如果是要修改原始构造函数中的值的话,那就使用指针接收者,指针接收者修改的是最原始的结构体值.值接收者是复制了一份构造函数的副本进行绑定.所以如果不修改内容的话是可以使用的.
什么时候应该使用指针类型接收者?
需要修改接收者中的值
接收者是拷贝代价比较大的大对象.
保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
结构体嵌套
//结构体嵌套
type Address struct {
Province string
City string
}
type User struct {
Name string
Gender string
Address Address //这里是一个结构体类型
}
func main() {
user1 := User{
Name: "xiaowangzi",
Gender: "当大侠",
Address: Address{ //嵌入一个新的结构体
Province: "beijing",
City: "beijing",
},
}
fmt.Printf("user1= %#v\n", user1)
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
user1= main.User{Name:"xiaowangzi", Gender:"当大侠", Address:main.Address{Provin
ce:"beijing", City:"beijing"}}
就是结构体中包含结构体
嵌套匿名结构体
//结构体嵌套
type Address struct {
Province string
City string
}
type User struct {
Name string
Gender string
Address //这里是一个结构体类型可是不书写字段名称,默认名称即字段名,
}
func main() {
user1 := User{
Name: "xiaowangzi",
Gender: "当大侠",
Address: Address{ //嵌入一个新的结构体
Province: "山东",
City: "青岛",
},
}
fmt.Printf("user1= %#v\n", user1)
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
user1= main.User{Name:"xiaowangzi", Gender:"当大侠", Address:main.Address{Provin
ce:"山东", City:"青岛"}}
默认就是结构体中的字段名称就是类型名称.如果一个字段在匿名字段中的话,那么会优先找母级结构体字段,如果没有再找子集结构体字段.
结构体嵌套中重名字段中的内容修改和查询
//结构体嵌套过程中字段名称冲突
//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 变量声明成一个结构体类型
user3.Name = "沙河娜扎"
user3.Gender = "男"
user3.Address.CreateTime = "2000" //因为单一结构体中字段的名称是不会出现重复的情况的,所以只要逐级指定结构体字段进行查询就可以找到一个唯一的字段名称.
user3.Email.CreateTime = "1111" //同上
fmt.Println(user3)
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
{沙河娜扎 男 { 2000} { 1111}}
这里说明一下:
因为我们只对其中的某几个字段进行了赋值操作.所以最终显示的结果是赋值字段的结果.其余结果显示该字段类型的零值.
结构体嵌套后是可以继承嵌套结构体的方法的(这里用的是指针形式的嵌套方式,推测使用值类型的也是可以的.无非就是浪费内存啥的.)
//结构体继承
type Animal struct {
name string
}
//首先使用Animal结构体绑定一个move方法
func (a *Animal) move() {
fmt.Printf("%s会动", a.name)
}
//创建一个狗结构体
type Dog struct {
Feet int8
*Animal //这里使用的是* 指针形式的嵌套关系
}
//用狗结构体绑定一个wang方法
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪\n", d.name)
}
func main() {
d1 := &Dog{ //使用指针形式给d1变量赋值Doc结构体的内容并将字段赋值
Feet: 4,
Animal: &Animal{name: "加油,奥利给!!"}, //这里同样使用使用的是指针形式(貌似只有使用指针形式才能携带方法进行继承?这个不太确定.带我验证一下
}
d1.move()
d1.wang()
}
输出结果:
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
加油,奥利给!!会动加油,奥利给!!会汪汪汪
使用值类型的嵌套继承结构体的方法
//结构体继承
type Animal struct {
name string
}
//首先使用Animal结构体绑定一个move方法
func (a *Animal) move() {
fmt.Printf("%s会动", a.name)
}
//创建一个狗结构体
type Dog struct {
Feet int8
Animal //这里使用的是* 指针形式的嵌套关系
}
//用狗结构体绑定一个wang方法
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪\n", d.name)
}
func main() {
d1 := &Dog{ //使用指针形式给d1变量赋值Doc结构体的内容并将字段赋值
Feet: 4,
Animal: Animal{name: "加油,奥利给!!"}, //这里同样使用使用的是指针形式(貌似只有使用指针形式才能携带方法进行继承?这个不太确定.带我验证一下
}
d1.move()
d1.wang()
}
输出结果:
C:\Users\34826\AppData\Local\Temp\___go_build_main_go.exe #gosetup
加油,奥利给!!会动
加油,奥利给!!会汪汪汪
这里说明一下:
值类型和指针类型其实都是可以的,但是指针类型相对来说效率更高,值类型需要拷贝一份新的内容到一个新的内存地址中,所以性能会更低一些.推荐!!!使用指针类型.
结构体json序列化
type Student struct {
ID int
Gender string
Name string
}
type Class struct {
Title string
Students []*Student
}
func main() {
c := &Class{ //结构体赋值,将Class结构体赋值给c变量,这里用的还是指针形式.
Title: "101",
Students: make([]*Student, 0, 200),
}
for i := 0; i < 10; i++ {
stu := &Student{
ID: i,
Gender: "男",
Name: fmt.Sprintf("stu%02d", i),
}
c.Students = append(c.Students, stu) //向结构体赋值的过程.将循环体中的值循环写入到c结构体中的students切片中.
}
//json 序列化
data, err := json.Marshal(c) //json序列化,将结构体中的值转换成json进行输出.
if err != nil { //处理错误
fmt.Println("json marshal failed")
return
}
fmt.Printf("json:%s\n", data) //打印输出结果
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
json:{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"G
ender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gende
r":"男","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":"男","Nam
e":"stu09"}]}
仔细看.每个字段都是用逗号进行的分隔,这里一共两种类型的json 其中一个是title字段.另一个是students切片.因为切片中元素数量比较多.所以显示较多.
json反序列化
type Student struct {
ID int
Gender string
Name string
}
type Class struct {
Title string
Students []*Student
}
func main() {
c := &Class{ //结构体赋值,将Class结构体赋值给c变量,这里用的还是指针形式.
Title: "101",
Students: make([]*Student, 0, 200),
}
for i := 0; i < 10; i++ {
stu := &Student{
ID: i,
Gender: "男",
Name: fmt.Sprintf("stu%02d", i),
}
c.Students = append(c.Students, stu) //向结构体赋值的过程.将循环体中的值循环写入到c结构体中的students切片中.
}
//json 序列化
data, err := json.Marshal(c) //json序列化,将结构体中的值转换成json进行输出.
if err != nil { //处理错误
fmt.Println("json marshal failed")
return
}
fmt.Printf("json:%s\n", data) //打印输出结果
//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) //反序列化过程, 其中json要转换成一个byte的切片才可以进行转换,转换后存到c1中.
//这里注意一下,json.unmarshal返回值只有一个错误信息.没有返回接收者内容信息,而接收者信息在转换过程中就完成了.所以只要处理err即可
if err != nil {
fmt.Println("json unmarshal failed!") //处理错误
return
}
fmt.Printf("title:%v\n", c1.Title) //c1是一个指针,可以直接打印c1结构体中的Title值.这里的用法是结构体的类型为string,所以不用使用* 进行取值.
for _, i := range c1.Students { //同理这里的students的接收者是一个指针类型,但是结构体的指针类型是可以不指定* 的.所以最终的值打印使用指针或者非指针都可以.
fmt.Printf("students%v\n", *i)
}
返回结果:
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
json:{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"G
ender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gende
r":"男","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":"男","Nam
e":"stu09"}]}
title:101
students{0 男 stu00}
students{1 男 stu01}
students{2 男 stu02}
students{3 男 stu03}
students{4 男 stu04}
students{5 男 stu05}
students{6 男 stu06}
students{7 男 stu07}
students{8 男 stu08}
students{9 男 stu09}
其中上半部分是序列化的结果.下半部分是反序列化后循环取值的结果.
结构体tag标签解析json用法
//结构体标签
type Student struct {
ID int `json:"id"` //通过tag标签指定json序列化该字段时的key
Gender string //json序列化默认使用的是结构体字段名作为key
name string
}
func main() {
s1 := Student{ //将student结构体赋值给s1变量.并且将结构体字段进行赋值操作.
ID: 1,
Gender: "男",
name: "沙河娜扎",
}
data, err := json.Marshal(s1) //使用jsonmarshal解析结构体字段信息.并且json.marshal返回两个值,1格式解析完成的json数据,一个是错误值.
if err != nil {
fmt.Println("json marshal falied !")
return //如果错误直接返回.
}
fmt.Printf("json str:%s\n", data) //在json解析完成s1结构体后 原来的ID字段就会直接解析成id字段进行打印.
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_main_go.exe #gosetup
json str:{"id":1,"Gender":"男"}
slice和map对于结构体的用法
//slice和map这两层数据类型都包含了指向底层数据的指针,因此我们在需要复制特们的时候需要特别注意,
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDream(dreams []string) {
p.dreams = dreams //关键问题出在这里,如果直接将dreams传入到p.dreams中的话,那么默认两份切片其实是同一份切片,内存地址是相同的,
}
func main() {
p1 := Person{
name: "小王子",
age: 18,
}
data := []string{"吃饭", "睡觉", "打豆豆"}
p1.SetDream(data)
data[1] = "不睡觉" //因为data是一个字符串类型的切片,所以修改切片的值就相当于修改了内存中源切片的值.
fmt.Println(p1.dreams) //打印结果[吃饭 不睡觉 打豆豆]
//如果想要不修改原切片的内容,那么需要在传入方法的时候先copy一份饭后覆盖一下原来的值.
}
输出如下
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
[吃饭 不睡觉 打豆豆]
正常情况下是在修改结构体中的数据的时候不会将源数据一并修改的.如下所示:
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDream(dreams []string) {
p.dreams = make([]string, len(dreams)) //有点点巧妙,将要定义的结构体的值声明一个字符串类型的空切片.
copy(p.dreams, dreams) //这里实际上是两个参数,1格式
}
func main() {
p1 := Person{
name: "小王子",
age: 18,
}
data := []string{"吃饭", "睡觉", "打豆豆"}
p1.SetDream(data)
data[1] = "不睡觉" //因为data是一个字符串类型的切片,所以修改切片的值就相当于修改了内存中源切片的值.
fmt.Println(p1.dreams) //打印结果[吃饭 不睡觉 打豆豆]
//如果想要不修改原切片的内容,那么需要先将切片copy一分,然后再传值.
}
输出结果
C:\Users\34826\AppData\Local\Temp\___go_build_code_oldboy_com_studygolang_05lesson5_lesson1.exe #gosetup
[吃饭 睡觉 打豆豆]
这里使用的是字符串切片作为复制的对象.如果出现map类型的话同样也是需要做一步复制操作的.
课后作业:
- 学生有id、姓名、年龄、分数等信息
- 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能