0


10-Go语言结构体

结构体

Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或者部分属性时,这时候在用单一的基本数据类型就无法满足要求了,G哦语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫做结构体,英文

struct

Go语言中通过

struct

来实现面向对象。

结构体定义

使用

type

struct

关键字来定义结构体,具体代码格式如下:

type 类型名 struct{
      字段名 字段类型
      字段名 字段类型
      ...}

其中:

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

举个例子,我们定一个

Person

的结构体,代码如下:

type Person struct{
  name string
  city string
  age int8}//同样的类型可以写在一行:type Person1 struct{
  name,city string
  age 
}

这样我们就有了一个

preson

的自定义类型,他有

name
city
age

三个字段。这样我们就可以使用这个

peeson

结构体就能方便的在程序中表示和存储人信息了。

//定义一个Student的结构体type Student struct{
    name     string
    age      int8
    married  bool
    mapScore map[string]int}//定义一个order的结构体type Order struct{
    id         int64
    proID      int64
    userId     int64
    createTime int64}

结构体实例化

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

结构体本身也是一种类型,我们可以像声明内置类型一样使用

var

关键字声明结构体类型。

var 结构体实例 结构体类型
基本实例化:
type Person struct{
    name string
    city string
    age  int8}funcstruDemo1(){var p1 Person
    p1.name ="沙河娜扎"
    p1.city ="北京"
    p1.age =18

    fmt.Printf("p1=%+v\n", p1)
    fmt.Printf("p1=%#v\n", p1)}//结果
p1={name:沙河娜扎 city:北京 age:18}
p1=main.Person{name:"沙河娜扎", city:"北京", age:18}

我们通过

.

来访问结构体的字段(成员变量),例如

p1.name

p1.age

等。

匿名结构体

在定义一些临时数据结构等场景下还可以使用匿名结构体。

//匿名结构体funcstruDemo2(){var user struct{
        name string
        age  int}
    user.name ="小王子"
    user.age =18
    fmt.Printf("%#v\n", user)}//结果:struct{ name string; age int}{name:"小王子", age:18}
创建指针类型结构体

我们还可以通过使用

new

关键字对结构体进行实例化,得到的是结构体的地址。格式如下:

//创建指针类型结构体funcstruDemo3(){var p2 =new(Person)
    fmt.Printf("%T\n", p2)// *main.Person
    fmt.Printf("p2=%#v\n", p2)// p2=&main.Person{name:"", city:"", age:0}}//从打印结果中我们可以看出p2是一个结构体指针。//需要注意的是在go语言中支持对结构体指针直接使用.来访问funcstruDemo3(){var p2 =new(Person)

    p2.name ="小孩子"//等价于(*p2).name
    p2.age =28
    p2.city ="上海"
    fmt.Printf("%T\n", p2)
    fmt.Printf("p2=%#v\n", p2)}
取结构体地址实例化
funcstruDemo4(){
    p3 :=&Person{}//等价于 var p3 = new(Person)
    fmt.Printf("%T\n", p3)
    fmt.Printf("p3=%#v\n", p3)

    p3.name ="黑煤球"
    p3.city ="北京"
    p3.age =27
    fmt.Printf("p3=%+v\n", p3)}//结果:*main.Person
p3=&main.Person{name:"", city:"", age:0}
p3=&{name:黑煤球 city:北京 age:27}
p3.name = "黑煤球"

其实在底层是

(*p3).name = "黑煤球"

,这是Go语言帮我们实现的语法糖。

结构体初始化

没有初始化的结构体,其成员变量都是对应类型的零值。

funcstruDemo5(){var p4 Person

    fmt.Printf("p4=%#v\n", p4)}//运行结果:
p4=main.Person{name:"", city:"", age:0}
使用键值对初始化

使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

unc struDemo6(){
    p5 := Person{
        name:"不小孩",
        city:"北京",
        age:28,}

    fmt.Printf("p5=%#v\n", p5)//p5=main.Person{name:"不小孩", city:"北京", age:28}}

也可以对结构体指针进行键值对初始化,例如:

funcstruDemo7(){
    p6 :=&Person{
        name:"小王子",
        city:"北京",
        age:18,}

    fmt.Printf("p6=%#v\n", p6)// p6=&main.Person{name:"小王子", city:"北京", age:18}}

当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

p7 :=&person{
    city:"北京",}
fmt.Printf("p7=%#v\n", p7)//p7=&main.person{name:"", city:"北京", age:0}

eg:

//结构体字面量初始化funcstruDemo8(){
    stu1 := Student{
        name:"王磊",
        age:26,
        mapScore:map[string]int{"语文":6,"数学":7,},}

    fmt.Printf("%+v\n", stu1)

    stu2 := Student{}// map[string]int{}//stu2.mapScore["英语"] = 6 //会报错,因为map没有make
    fmt.Printf("%#v\n", stu2)

    stu3 :=&Student{}//取地址  --》 new(Student) --> 结构体指针
    stu3.name ="玩" Go语言中提供的语法糖,支持 结构体指针类型.属性 简写(*stu3).age =34
    fmt.Printf("%+v\n", stu3)var stu4 =&Student{}
    stu4.name ="json"
    fmt.Printf("%+v\n", stu4)}//结果:{name:王磊 age:26 married:false mapScore:map[数学:7 语文:6]}
main.Student{name:"", age:0, married:false, mapScore:map[string]int(nil)}&{name:玩 age:34 married:false mapScore:map[]}&{name:json age:0 married:false mapScore:map[]}
值列表初始化

初始化结构体的时候可以简写,也就是初始化不写键,直接写值:

// 列表初始化// 必须按结构体定义时候的属性顺序依次赋值funcstruDemo9(){var stu6 = Student{"胡子",24,false,map[string]int{"语文":100},}

    fmt.Printf("%+v\n", stu6)}

使用这种格式初始化时,需要注意:

  1. 必须初始化结构体的所有字段。
  2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  3. 该方式不能和键值初始化方式混用。
结构体指针初始化
// 结构体字面量初始化funcdemo5(){
    stu4 :=&Student{}// 取地址  --》 new(Student) --> 结构体指针(*stu4).name ="李硕"
    stu4.age =18// Go语言中提供的语法糖,支持 结构体指针类型.属性 简写
    fmt.Printf("%+v\n", stu4)// var stu5 *Student // nil// var stu5 = new(Student)var stu5 =&Student{}
    stu5.name ="jade"// (*nil).name =
    fmt.Printf("%+v\n", stu5)
    stu5 =&Student{
        name:"大都督",}
    stu5 =new(Student)// var x *int       // nilvar x =new(int)*x =100// (*nil) = 100
    fmt.Println(x)}
空结构体

空结构体不占用空间.

//空结构体funcstruDemo10(){var v struct{}
    fmt.Println(unsafe.Sizeof(v))//0}

结构体的内存布局(进阶)

结构体大小

结构体占用连续的内存空间。

结构体占用的内存大小是由每个属性的大小和内存对齐决定的。

type Foo struct{
    a int8//1byte
    b int8//1byte
    c int8//1byte}//结构体大小funcstruDemo11(){var f Foo
    fmt.Println(unsafe.Sizeof(f))//3byte}//结果3
内存对齐

内存对齐的原理:CPU读取内存是以word size(字长)为单位,避免出现一个属性CPU分多次读取的问题。

内存对齐是编译器帮我们根据CPU和平台来自动处理的。

//内存对齐type Bar struct{
    a int32//4
    b int64//8
    c bool//1}funcstruDemo12(){var b1 Bar
    fmt.Println(unsafe.Sizeof(b1))//24}

有的同学可能会认为结构体变量

b1

的内存布局如下图所示,那么问题来了,结构体变量

b1

的大小怎么会是24呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7UGudskD-1656580917572)(/Users/alblue/Library/Application Support/typora-user-images/image-20220127093929478.png)]

很显然结构体变量

b1

的内存布局和上图中的并不一致,实际上的布局应该如下图所示,灰色虚线的部分就是内存对齐时的填充(padding)部分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeG0Gyx9-1656580917573)(/Users/alblue/Library/Application Support/typora-user-images/image-20220127094031194.png)]

因为 CPU 访问内存时,并不是逐个字节访问,而是以字(word)为单位访问。比如 64位CPU的字长(word size)为8bytes,那么CPU访问内存的单位也是8字节,每次加载的内存数据也是固定的若干字长,如8words(64bytes)、16words(128bytes)等。

对齐保证

我们利用对齐的规则合理的减小结构体的体积。

对齐系数:对于 struct 类型的变量 x,计算 x 每一个字段 f 的

unsafe.Alignof(x.f)

unsafe.Alignof(x)

等于其中的最大值。

我们可以通过内置的

unsafe

包的

sizeof

函数获取一个变量的大小,此外我们可以通过内置的

unsafe

Alignof

函数获取一个变量的对齐系数,例如:

//结构体变量b1的对齐系数
    fmt.Println(unsafe.Alignof(b1))//8//b1每个字段的对齐系数
    fmt.Println(unsafe.Alignof(b1.a))//4: 表示此字段按4的倍数对齐
    fmt.Println(unsafe.Alignof(b1.b))//8:表示此字段按8的倍数对齐
    fmt.Println(unsafe.Alignof(b1.c))//1:表示此字段按1的倍数对齐unsafe.Alignof()的规则如下:
unsafe.Alignof()

的规则如下:

  • 对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1。
  • 对于 struct 类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f)unsafe.Alignof(x) 等于其中的最大值。
  • 对于 array 类型的变量 x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐倍数。

在了解了上面的规则之后,就可以调整结构体字段来减小结构体大小。

//对齐保证type Bar2 struct{
    x int32//4
    z bool//1
    y int64//8}funcstruDemo13(){var b2 Bar2
    fmt.Println(unsafe.Sizeof(b2))//16}

此时结构体 Bar2 变量的内存布局示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DDzLYvId-1656580917573)(/Users/alblue/Library/Application Support/typora-user-images/image-20220127095343474.png)]

总结一下:在了解了Go的内存对齐规则之后,我们在日常的编码过程中,完全可以通过合理地调整结构体的字段顺序,从而优化结构体的大小。

方法

构造函数

Go语言中的结构体没有构造函数,我们可以自己实现。例如,下方的代码就实现了一个person的构造函数。因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销比较大,所以该构造函数返回的是结构体指针类型。

type Person struct{
    name string
    city string
    age  int8}//构造函数funcstruDemo14(name, city string, age int8)*Person {return&Person{
        name: name,
        city: city,
        age:  age,}}//调用构造函数
newPerson :=struDemo14("王磊","北京",28)
    fmt.Printf("%#v\n", newPerson)//&main.Person{name:"王磊", city:"北京", age:28}

方法的定义

Go语言中的

方法method

是一种作用于特定类型变量的函数。这种特定类型变量叫做接受者(receiver)。接收者的概念类似于其他语言中的this或者self。

方法定义的格式如下:

func(接受者变量 接受者类型) 方法名(参数列表)(返回参数){
  函数体
}

其中:

  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是selfthis之类的命名。例如,Person类型的接收者变量应该命名为 pConnector类型的接收者变量应该命名为c等。
  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。

举个例子:

//方法//People结构体type People struct{
    name string
    age  int8}//构造函数funcnewPeople(name string, age int8)*People {return&People{
        name: name,
        age:  age,}}//People的Dream方法func(p People)Dream(){
    fmt.Printf("%s的梦想是在%v学好GO语言\n", p.name, p.age)}//调用
p1 :=newPeople("不小孩",25)
    p1.Dream()// 不小孩的梦想是在25学好GO语言

接收者

  1. 值接收者当方法作用于值类型接受者时,Go语言会在代码运行时将接受者的值复制一份。在值类型接受者的方法中可以获取接收者的成员值,但修改的只是针对副本,无法修改接受者变量本身。//值类型接受者//定义值类型接受的方法func(p People)SetAge2(newage int8){ p.age = newage fmt.Printf("副本的年龄:%v\n", p.age)}//调用方法:p1 :=newPeople("不小孩",25) fmt.Println(p1.age)//25 p1.SetAge2(30)//副本的年龄:30 fmt.Println(p1.age)//25,原来变量的值仍旧25
  2. 指针接收者指针类型接受者由一个结构体的指针组成。由于指针的特性,调用方法时修改接受者指针的任意成员变量,在方法结束后,修改都是有效的。//指针类型的接受者//定义方法func(p *People)SetAge(newage int8){ p.age = newage}//调用p1 :=newPeople("不小孩",25) fmt.Println(p1.age)//25 p1.SetAge(30)//30 fmt.Println(p1.age)//30 原来的变量已经被更改了
  3. 什么时候该使用指针类型的接受者1. 需要修改接收者中的值2. 接收者是拷贝代价比较大的大对象3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

任意类型添加方法

在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的

int

类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

//MyInt 将int定义为自定义MyInt类型type MyInt int//SayHello 为MyInt添加一个SayHello的方法func(m MyInt)SayHello(){
    fmt.Println("Hello, 我是一个int。")}funcmain(){var m1 MyInt
    m1.SayHello()//Hello, 我是一个int。
    m1 =100
    fmt.Printf("%#v  %T\n", m1, m1)//100  main.MyInt}

结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就叫做匿名字段。

注意:这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。

//结构体的匿名字段type Teacher struct{stringint}functeaDemo(){
    t := Teacher{"小王子",30,}
    fmt.Printf("%#v\n", t)//main.Teacher{string:"小王子", int:30}
    fmt.Println(t.string, t.int)//小王子 30}

嵌套结构体

一个结构体可以嵌套包含一个结构体或结构体指针,就像下面的示例代码那样。

//嵌套结构体//Address结构体type Address struct{
    Province string
    City     string}//User用户结构体type User struct{
    Name    string
    Gender  string
    Address Address //嵌套结构体}funcstrudemo15(){
    user := User{
        Name:"小王子",
        Gender:"男",
        Address: Address{
            Province:"北京",
            City:"北京",},}
    fmt.Println(user)//{小王子 男 {北京 北京}}}
嵌套匿名字段

上面的user结构体嵌套的address结构体,也可以采取匿名字段的方式,例如:

//嵌套匿名字段type User struct{
    Name    string
    Gender  string
    Address //匿名字段}funcstudemo16(){var user1 User
    user1.Name ="小昂子"
    user1.Gender ="女"
    user1.Address.Province ="山西"// 匿名字段默认使用类型名作为字段名
    user1.City ="大同"// 匿名字段可以省略
    fmt.Println(user1)//{小昂子 女 {山西 大同}}}

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

嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。

//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
}funcmain(){var user3 User
    user3.Name ="沙河娜扎"
    user3.Gender ="男"// user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
    user3.Address.CreateTime ="2000"//指定Address结构体中的CreateTime
    user3.Email.CreateTime ="2000"//指定Email结构体中的CreateTime

结构体的继承

go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

//结构体继承//Anmial 结构体type Animal struct{
    name string}func(a *Animal)move(){
    fmt.Printf("%s会动\n", a.name)}//Dogtype Dog struct{
    Feet    int8*Animal //继承Anmial结构体}func(d *Dog)wang(){
    fmt.Printf("%s会汪汪\n", d.name)}//调用funcAnmil(){
    d1 := Dog{
        Feet:4,
        Animal:&Animal{
            name:"小强",},}
    d1.wang()//小强会汪汪
    d1.move()//强会动}

结构体字段的可见性

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

结构体与JSON序列化

JSON是一种轻量级数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON的键值对是用来保存JS对象的一种方式,键值对组合中的键名写在前面并用双引号

"

包裹,使用冒号

:

分隔,然后紧接着值;多个键值之间使用英文,分隔。

//JSON与结构体序列化和反序列化//Student学生type Student struct{
    Id     int
    Gender string
    Name   string}//Class 班级type Class struct{
    Title    string
    Students []*Student
}funcJson(){
    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("fail")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)}

结构体标签(Tag)

Tag

是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

Tag

在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

`key1:"value1" key2:"value2"`

结构体tag由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

注意事项: 为结构体编写

Tag

时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

例如我们为

Student

结构体的每个字段定义json序列化时使用的Tag:

//Student 学生type Student struct{
    ID     int`json:"id"`//通过指定tag实现json序列化该字段时的key
    Gender string//json序列化是默认使用字段名作为key
    name   string//私有不能被json包访问}funcmain(){
    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":"男"}}

补充

切片的源码

在这里插入图片描述

练习

  1. 下面的代码执行结果为?为什么?type student struct{//定义一个名为student的结构体 name string age int}funcmain(){ m :=make(map[string]*student)//定义一个map:key是string,value为student结构体指针类型 stus :=[]student{//定义一个元素类型为student结构体类型的切片,并且初始化了3个元素在里面{name:"小王子", age:18},{name:"娜扎", age:23},{name:"大王八", age:9000},}for_, stu :=range stus {//在每次循环的过程中操作map,添加新的键值对key为切片元素的name,value为当次循环中for range的内部变量的地址 m[stu.name]=&stu //stu地址不变,但是值会不断的变化,循环到最后一个值为{name: "大王八", age: 9000},所以内存地址指的就是这个值}for k, v :=range m { fmt.Println(k,"=>", v.name)}}//运行结果:小王子 => 大王八娜扎 => 大王八大王八 => 大王八//原因分析:1、新键值对的value是存储内部变量stu的指针,那么就意味着,每次循环所创建的心键值对的value都指向了同一块内存地址&stu2、那么就知道为啥输出这个样子的,因为stu的指针指向内存地址,每次循环的时候,值都是会变的。循环到最后一项,内部value为最后一项的元素``````//变行1:type student struct{ name string age int}funcex(){ m :=make(map[string]*student) stus :=[]student{{name:"小王子", age:18},{name:"娜扎", age:23},{name:"大王八", age:9000},}for i, stu :=range stus { m[stu.name]=&stus[i]//这样每次对应的内存地址就不一样了// fmt.Println(m)}for k, v :=range m { fmt.Println(k,"=>", v.name)}}
  2. 编写学生管理系统1. 获取用户输入:2. 使用“面向对象”的思维方式编写一个学生信息管理系统。 1. 学生有id、姓名、年龄、分数等信息2. 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能package mainimport("fmt""os")/*使用“面向对象”的思维方式编写一个学生信息管理系统。1、学生有id、姓名、年龄、分数等信息2、程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能*///1、Student结构体type Student struct{ ID int Name string Age int Score int}//考虑有添加和编辑,考虑maptype Class struct{ Stulist map[int]*Student}//2、提供展示学生列表、添加学生、编辑学生信息、删除学生等方法//查看学生列表方法func(c *Class)showList(){iflen(c.Stulist)==0{ fmt.Println("学生列表为空哦~")}else{for k, v :=range c.Stulist { fmt.Printf("学生id:%d 学生名字:%s 学生年龄: %d 学生分数:%d\n", k, v.Name, v.Age, v.Score)}}}//添加学生func(c *Class)addStudent(){var( id int name string age int score int) fmt.Print("输入学生ID:") fmt.Scan(&id)_, ok :=(c.Stulist)[id]if ok { fmt.Println("该学生已经存在,不能重复添加")return} fmt.Print("输入学生名字:") fmt.Scan(&name) fmt.Print("输入学生年龄:") fmt.Scan(&age) fmt.Print("输入学生分数:") fmt.Scan(&score) stu :=&Student{ ID: id, Name: name, Age: age, Score: score,} c.Stulist[id]= stu fmt.Printf("%s同学添加成功~\n", stu.Name)}//编辑学生信息func(c *Class)editStudent(){var( id int name string age int score int) fmt.Print("请输入修改学生的id:") fmt.Scan(&id)_, ok := c.Stulist[id]if!ok { fmt.Println("该学生id无效,请重新输入有效id")return} fmt.Print("请输入编辑后的学生名字,年龄,分数") fmt.Scan(&name,&age,&score) stu :=&Student{ Name: name, Age: age, Score: score,} c.Stulist[id]= stu}//删除学生func(c *Class)deleteStudent(){var id int fmt.Print("请输入删除学生id:") fmt.Scan(&id)_, ok := c.Stulist[id]if!ok { fmt.Println("输入学生id不存在,请重新输入")return}delete(c.Stulist, id) fmt.Println("删除成功")}//3、写主执行函数funcmain(){ c :=&Class{ Stulist:make(map[int]*Student),}for{var input int fmt.Print(` 欢迎访问学生管理系统! 1、查看所有学生列表 2、添加学生 3、编辑学生信息 4、删除学生 5、退出 宝子们,请选择你要操作编号:`) fmt.Scan(&input)switch input {case1: c.showList()case2: c.addStudent()case3: c.editStudent()case4: c.deleteStudent()case5: os.Exit(0)}}}

本文转载自: https://blog.csdn.net/weixin_38753143/article/details/125544824
版权归原作者 爱写代码的小男孩 所有, 如有侵权,请联系我们删除。

“10-Go语言结构体”的评论:

还没有评论