自定义类型 类型别名 结构体和json

类型别名和自定义类型

在Go语言中有一些基本的数据类型,如string整型浮点型布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

type myint int
func main() {
    //自定义类型
    var a myint
    a = 100
    fmt.Printf("a:[%d] a类型:%T \n", a, a)
}

类型别名

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

//类型别名
type youint = int
func main() {
    //类型别名
    var b youint
    b = 900
    fmt.Printf("b:[%d] b类型:%T \n", b, b)
}

自定义类型和类型别名的区别:

首先声明时不一样,类型别名要有一个=号。
其次使用占位符输出结果不一样,类型别名本质还是原类型,比如上面的youint本质还是int类型,这个别名只是让你写代码是更清晰明了,比如runebyte就是类型别名,他们底层是int32,你定义rune是一看就知道这里保存的是一个字符,你当然可以使用int32,但是你看到int32是第一感觉是这货保存的是一个数字。类型别名只在代码编写期间有效,代码编译后就不存在你的类型别名youint了。类型别名仅仅只是为了让你更好的识别代码。自定义类型会一只存在,自定义类型用占位符%T输出显示main.myint说明这个类型是在main包中定义的一个myint类型。

结构体

Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。

结构体的定义:

结构体的定义:

type 结构体名字 struct {
字段1 数据类型
字段2 数据类型
....
}

其中:

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

列如定义一本书的结构体:

type book struct {
    name   string
    number int
    author []string
    pub    bool
}

结构体实例化

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

var 结构体实例 结构体类型

如下面代码就是一个结构体的完整演示:

//定义结构体类型
type book struct {
    name   string
    number int
    author []string
    pub    bool
}

func main() {

    //实例化一个结构体
    var aa book

    结构体字段赋值
    aa.name = "天堂路"
    aa.number = 999
    aa.author = []string{"钢蛋", "socold"}
    aa.pub = false

    //访问结构体
    fmt.Printf("aaType[%T] \n %#v \n 访问name字段[%s] \n", aa, aa, aa.name)

    //访问结构体切片元素
    fmt.Println(aa.author[0])

    var bb book
    bb.name = "天堂路"
    fmt.Printf("aaType[%T] \n %#v \n 访问name字段[%s] \n", bb, bb, bb.name)
}

匿名结构体

匿名结构体多用于函数内部,临时使用一次。

//匿名结构体
var a struct {
    x int
    y int
}
a.x = 8
a.y = 9
fmt.Println(a)

结构体是值类型

结构体属于值类型,如下代码修改cc.name并不会改变bb.name

    var bb book
    bb.name = "天堂路"
    fmt.Printf("aaType[%T] \n %#v \n 访问name字段[%s] \n", bb, bb, bb.name)

    fmt.Println(bb.name)

    cc := bb
    cc.name = "地狱门"
    fmt.Println(cc.name)

指针类型结构体

指针类型结构体,因为函数传参数是传的拷贝,所以在函数内修改结构体的字段数据并不会修改结构体本身,如有需要可以指针修改结构体具体字段数据,也可以创建指针类型结构体。

type book struct {
    name, name2 string
    number      int
    author      []string
    pub         bool
}
//要把一个结构体传进函数,要写结构体的名字。
func ff(x book) {
    x.name = "地狱门"
}
//在函数内修改结构体本体需要传指针
func f1(x *book) {
    (*x).name = "地狱门"

    //只要保证函数接收参数是指针,这里就这样写,效果和上面一样
    x.number = 999
}
//如果仅仅传入结构体字段,参数类型应和结构体字段对应
func f2(s *string) {
    *s = "地狱门"
}
func main() {
    //直接用'new()'创建指针类型结构体
    var aa = new(book)
    //指针结构体字段赋值
    (*aa).name = "BookName"
    (*aa).pub = true
    fmt.Printf("%T \n", aa)
    //指针型结构体直接使用,不在取址
    fmt.Println(aa.number)
    f1(aa)
    fmt.Println(aa.number)
    //正常结构体变量bb
    var bb book
    bb.name = "天堂路"
    bb.pub = true
    // //在函数内部修改'book'类型的变量'bb'不会改变本体
    // fmt.Println(bb.name)
    // ff(bb)
    // fmt.Println(bb.name)
    // //传入指针才会修改'book'类型变量'bb'的本体
    // fmt.Println(bb.name)
    // f1(&bb)
    // fmt.Println(bb.name)
    // //函数内部修改结构体字段
    // fmt.Println(bb.name)
    // f2(&(bb.name))
    // fmt.Println(bb.name)
}

结构体初始化

结构体初始化后不对字段复制,该字段的值为对应数据类型的0值。

//简化声明结构体变量
aa := book{
    name:   "BookName",
    name2:  "BOOKNAME",
    pub:    true,
    author: []string{"socold", "好冷"},
}
//没赋值的字段为对应类型的空值
fmt.Printf("aa.name2:[%v] \n", aa.number)

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

//值列表初始化结构体变量'bb'
bb := book{
    "BookName",
    "BOOKNAME",
    688,
    []string{"socold", "好冷"},
    true,
}
fmt.Println(&bb)

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

  • 必须初始化结构体的所有字段。
  • 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  • 该方式不能和键值初始化方式混用。

用构造函数初始化结构体

用构造函数初始化结构体变量,有的时候要初始化多个同一结构体类型的变量,也可以构造一个函数来初始化结构体变量,省时省力,方便快捷。下面这个代码就用一个构造函数来初始化结构体,返回的是一个指针,建议用构造函数初始化结构体是返回指针,提高程序运行效率。因为函数传参数是拷贝副本,如果只返回一个指针比拷贝一个构造体返回更节省程序运行空间。

type book struct {
    name, name2 string
    number      int
    author      []string
    pub         bool
}

//构造函数创建结构体
func newBook(name, name2 string, number int, pub bool, author ...string) *book {
    return &book{
        name:   name,
        name2:  name2,
        number: number,
        pub:    pub,
        author: author,
    }
}
func main() {
    //第一个结构体'book'类型变量
    aa := newBook("BookName", "BOOKNAME", 996, true, "haoleng", "socold", "好冷")
    fmt.Println(aa)

    //第二个结构体'book'类型变量
    bb := newBook("NameTwo", "NAMETWO", 665, false, "作者A", "作战B")
    fmt.Printf("%#v \n", bb)
}

结构体内存布局

结构体内存布局
结构体占用一块连续的内存。

type test struct {
    a int8
    b int8
    c int8
    d int8
}
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 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063

【进阶知识点】关于Go语言中的内存对齐推荐阅读:在 Go 中恰到好处的内存对齐

方法

Go语言中的方法Method是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者Receiver。接收者的概念就类似于其他语言中的this或者self

方法的定义

方法的定义格式如下:

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

其中,

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

值接受者

方法也是一个函数,他是一个只允许特定类型调用的函数,如下代码xiaotianditing都是Dog类型结构体,可以调用方法函数kan(),而nazharen类型结构体,不能调用方法函数kan()

// Dog 这是结构体Dog类型
type Dog struct {
    name   string
    colur  string
    zhuren string
}

// 结构体ren类型
type ren struct {
    name string
    city string
    age  int
}

// Dog类型结构体构造函数
func newDog(name, colur, zhuren string) Dog {
    return Dog{
        name:   name,
        colur:  colur,
        zhuren: zhuren,
    }
}

// ren类型结构体构造函数
func newren(name, city string, age int) *ren {
    return &ren{
        name: name,
        city: city,
        age:  age,
    }
}

//方法和接受者
func (d Dog) kan() {
    fmt.Printf("薅狗毛~%s的%s:汪汪汪~\n", d.colur, d.name)
}
func main() {

    //有构造函数初始化dog类型结构体,返回dog类型本身
    xiaotian := newDog("哮天", "黑色", "哪吒")
    fmt.Println(xiaotian)
    diting := newDog("谛听", "金色", "地藏菩萨")

    //用构造函数初始化ren类型结构体,返回指针
    nezha := newren("三太子", "天庭", 6)
    fmt.Println(nezha)

    //xiaotian的类型是Dog,可以调用方法函数kan()
    xiaotian.kan()

    //diting的类型是Dog,可以调用方法函数kan()
    diting.kan()

    //nezha的类型是ren,不允许调用方法函数kan()
    // nezha.kan()
}

上面代码属于值接受者方法,只能调用Dog中的字段name不能修改,所以可以使用指针接受者修改方法函数。

指针接受者

另外使用指针接受者方法值是传送一个内存地址到方法函数,比值接受者方法拷贝整个Dog结构体到方法函数要节省程序运行效率,推荐使用指针接受者方法。如下jiaoyi()函数就是一个指针接受者方法函数。

// Dog 这是结构体Dog类型
type Dog struct {
    name   string
    colur  string
    zhuren string
}

// Dog类型结构体构造函数
func newDog(name, colur, zhuren string) Dog {
    return Dog{
        name:   name,
        colur:  colur,
        zhuren: zhuren,
    }
}

//方法和值接受者
func (d Dog) kan() {
    fmt.Printf("薅狗毛~%s的%s:汪汪汪~\n", d.colur, d.name)
}

//指针接受者方法函数
func (d *Dog) jiaoyi(zhuren string) {
    d.zhuren = zhuren
}
func main() {

    //有构造函数初始化dog类型结构体,返回dog类型本身
    xiaotian := newDog("哮天", "黑色", "哪吒")

    //指针接受者方法函数
    fmt.Println(xiaotian)
    xiaotian.jiaoyi("地藏菩萨")
    fmt.Println(xiaotian)
}

任意类型添加方法

任意类型添加方法函数,如果要其它类型添加方法怎么办,比如int,正常方法肯定不行,但是我们可以自定义类型。可以把int自定义为myint就可以给他添加一个方法了。
注意事项:

非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。声明自定义变量是要使用myint()强制转换一下,不然编译器默认哦int类型。

type mystring string

func (mystr *mystring) fangfa(s string) {
    *mystr = mystring(s)
}
func main() {

    var bb = mystring("我是自定义类型字符串")
    fmt.Printf("bb Type:%T -----%s \n", bb, bb)
    bb.fangfa("我可以使用方法")
    fmt.Printf("bb Type:%T -----%s \n", bb, bb)
    (&bb).fangfa("(&bb).fangfa()不加()也没问题")
    fmt.Printf("bb Type:%T -----%s \n", bb, bb)
}

结构体匿名字段

结构体匿名字段,结构体也可以不指定字段名字,访问结构体字段是使用字段的类型即可,但是如果多个字段都是同一数据类型会出错,编译不会通过。

type pp struct {
    string
    int
    bool
}

// // 匿名字段结构体多个字段同属一个数据类型会报错
// type dog struct {
//   string
//   string
// }

func main() {
    aa := pp{
        "哪吒",
        3,
        true,
    }
    fmt.Println(aa)

    //访问匿名字段结构体可以使用字段的数据类型
    fmt.Println(aa.string)
}

嵌套结构体

嵌套结构体结构体的字段可以是另外一个结构体。字段名字后面的数据类型写结构体的名字即可。注意看下面代码,除非指定xiaotian.zhuren的类型是指针,否则都是把taizi的数据完整的拷贝到xiaotian.zhuren下面。。

//定义结构体ren类型
type ren struct {
    name string
    age  int
    city string
}

//定义结构体dog类型
type dog struct {
    name   string
    color  string

    //这里zhuren字段是嵌套的结构体ren类型。
    zhuren ren
}

//ren类型实例化构造函数
func newren(name, city string, age int) *ren {
    return &ren{
        name: name,
        age:  age,
        city: city,
    }
}

//dog类型构造函数
//注意这里主人类型是指针
func newdog(name, color string, zhuren *ren) *dog {
    return &dog{
        name:   name,
        color:  color,
        zhuren: *zhuren,
    }
}

func main() {

    //用构造函数实例化ren类型taizi
    taizi := newren("三太子", "天庭", 3)
    fmt.Println(taizi)
    fmt.Printf("%T \n", taizi)

    //用构造函数实例化dog类型xiaotian
    xiaotian := newdog("哮天", "黑色", taizi)
    fmt.Println(xiaotian)
    fmt.Printf("%T \n ", xiaotian)

    fmt.Println("-------------------")

    fmt.Printf("%T \n", xiaotian.zhuren)
    fmt.Println("-------------------")
    //xiaotian.zhuren.name的内存地址和taizi.name的地址不同
    fmt.Printf("哮天主人名字:%s 指针:%v \n", xiaotian.zhuren.name, (&xiaotian.zhuren.name))
    fmt.Printf("三太子名字:%s 指针:%v \n", taizi.name, (&taizi.name))

}

匿名字段嵌套结构体

匿名字段嵌套结构体,如下面代码结构体dog的第三个字段就是一个匿名字段外带嵌套结构体,此时只写被嵌套结构体的名字ren,访问和赋值时使用dog类型变量diting.age即可访问或者赋值该嵌套结构体内字段age。具体原理和变量作用域类似,结构体dog找不到字段age就会去嵌套的结构体内寻找字段age。如果字段名处突,例如字段名name在结构体rendog都存在时,默认优先选择本结构体dog的字段name,此时要访问被嵌套结构体ren内的字段name就需要明确指定xiaotian.ren.name了。

//定义结构体ren类型
type ren struct {
    name string
    age  int
    city string
}

//定义结构体dog类型
type dog struct {
    name  string
    color string

    //dog的匿名字段
    ren
}

//ren类型实例化构造函数
func newren(name, city string, age int) *ren {
    return &ren{
        name: name,
        age:  age,
        city: city,
    }
}

//dog类型构造函数
//注意这里主人类型是指针,且是一个匿名字段
func newdog(name, color string, zhuren *ren) *dog {
    return &dog{

        //这里用列表式初始化
        name,
        color,

        //这个字段是匿名字段
        *zhuren,
    }
}

func main() {

    //用构造函数实例化ren类型taizi
    taizi := newren("三太子", "天庭", 3)

    //用构造函数实例化ren类型taizi
    heiyuan := newren("黑猿王", "厌火国", 800)

    //用构造函数实例化dog类型
    huodou := newdog("祸斗", "火红", heiyuan)
    fmt.Printf("主人名:%s 狗名:%s \n", huodou.ren.name, huodou.name)

    fmt.Println("---------------------")
    //用列表赋值方式实例话结构体dog类型xiaotian
    var xiaotian = dog{
        "哮天",
        "黑色",
        *taizi,
    }
    fmt.Println(xiaotian)
    fmt.Println("---------------------")

    //实例化结构体dog类型diting
    var diting dog
    //dog各字段赋值
    diting.name = "谛听"
    diting.color = "金色"
    //dog匿名字段赋值和访问
    diting.age = 9999
    diting.city = "地狱"
    //dog冲突字段访问和赋值
    diting.ren.name = "地藏王菩萨"
    fmt.Println(diting)
}

结构体中类似继承的概念

结构体的”继承”,Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。至于下面代码中错误写法为啥会出错,懒得解释了,就一句话,类型不符。该方法只能接受ren类型,虽然你传dog类型进去,但是dog类型把ren类型嵌套在自己体内了,所以可以运行。

//定义结构体ren类型
type ren struct {
    name string
    age  int
    city string
}

//定义结构体dog类型
type dog struct {
    name  string
    color string

    //dog的匿名字段
    ren
}

//ren类型实例化构造函数
func newren(name, city string, age int) *ren {
    return &ren{
        name: name,
        age:  age,
        city: city,
    }
}

//dog类型构造函数
func newdog(name, color string, zhuren *ren) *dog {
    return &dog{
        name,
        color,
        *zhuren,
    }
}

//匿名字段嵌套结构体配合方法函数,模拟继承的概念
func (r ren) home(name string) {
    fmt.Printf("%s家住在%s \n", name, r.city)
}

//错误的示范~~~
func (r ren) homeXXX() {
    fmt.Printf("%s家住在%s \n", r.name, r.city)
}

func main() {

    //用构造函数实例化ren类型
    taizi := newren("三太子", "天庭", 3)
    dizang := newren("地藏王菩萨", "地狱", 9000)
    heiyuan := newren("黑猿王", "厌火国", 800)

    //用构造函数实例化dog类型
    huodou := newdog("祸斗", "火红", heiyuan)
    xiaotian := newdog("哮天", "黑色", taizi)
    diting := newdog("谛听", "金色", dizang)

    //正确模拟继承
    taizi.home(taizi.name)
    dizang.home(dizang.name)
    heiyuan.home(heiyuan.name)
    huodou.home(huodou.name)
    xiaotian.home(xiaotian.name)
    diting.home(diting.name)
    fmt.Println("---------------------")

    //错误示范~~~
    taizi.homeXXX()
    dizang.homeXXX()
    heiyuan.homeXXX()
    huodou.homeXXX()
    xiaotian.homeXXX()
    diting.homeXXX()
}

结构体与JSON序列化

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着是值。多个键值之间使用英文,分隔。

//定义结构体ren类型
type ren struct {

    //通过Tag实现JSON序列化该字段的Key
    Name string `json:"name"`

    //字段名首字母大写为公有字段名
    Age int

    //私有字段名不能被JSON获取
    city string
}

//ren类型实例化构造函数
func newren(name, city string, age int) *ren {
    return &ren{
        Name: name,
        Age:  age,
        city: city,
    }
}

func main() {

    //用构造函数实例化ren类型
    heiyuan := newren("黑猿王", "厌火国", 800)

    //JSON序列化:结构体--->JSON式字符串
    data, err := json.Marshal(heiyuan)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("%s \n", data)

    //JSON反序列化:JSON格式字符串--->结构体
    str := `{"Name":"地藏王菩萨","Age":800}`

    //这里dizang拿到的是ren类型指针
    var dizang = &ren{}
    fmt.Printf("%T \n", dizang)

    //这里直接传入dizang即可,他保存的数据就是ren类型指针
    err = json.Unmarshal([]byte(str), dizang)
    if err != nil {
        fmt.Printf("err:[%v] \n", err)
        return
    }
    fmt.Println(dizang)

    //JSON反序列化例2
    sss := `{"Name":"三太子","Age":3}`
    //注意这里taizi拿到的是ren类型本身
    var taizi = ren{}
    fmt.Printf("%T \n", taizi)

    //反序列化是要传入taizi的指针
    err = json.Unmarshal([]byte(sss), &taizi)
    if err != nil {
        fmt.Printf("err:[%v] \n", err)
        return
    }
    fmt.Println(taizi)
}

结构体和方法补充知识点

因为slicemap这两种数据类型都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意。我们来看下面的例子:

type dog struct {
    name   string
    zhuren string
    jineng []string
}

//dog构造函数
func newdog(name, zhuren string, jineng ...string) *dog {
    return &dog{
        name:   name,
        zhuren: zhuren,
        jineng: jineng,
    }
}

//dog构造函数22
func newdog2(name, zhuren string, jineng []string) *dog {
    return &dog{
        name:   name,
        zhuren: zhuren,
        jineng: jineng,
    }
}

//正确无bug构造函数
func newdog3(name, zhuren string, jineng []string) *dog {
    var a dog
    a.name = name
    a.zhuren = zhuren
    a.jineng = make([]string, len(jineng))
    copy(a.jineng, jineng)
    return &a
}

func main() {
    xiaotian := newdog("哮天犬", "哪吒", "狗仗人势", "人模狗样")
    fmt.Println(xiaotian)

    //有Bug的构造函数
    data := []string{"潜龙在渊", "飞龙在天"}
    diting := newdog2("谛听", "地藏王菩萨", data)
    fmt.Println(diting)
    data[0] = "打狗棒法"
    fmt.Println(diting)

    //data和diting.jineng都指向同一内存地址。
    fmt.Printf("data指针:[%v] \n", &data[0])
    fmt.Printf("谛听技能指针:[%v] \n", (&diting.jineng[0]))

    //修改后的构造函数
    data_huo := []string{"九阴白骨爪", "易筋经"}
    huodou := newdog3("祸斗", "黑猿王", data_huo)
    fmt.Println(huodou)
    // data_huo[0] = "打狗棒法"
    // fmt.Println(huodou)

}

同样的问题也存在于返回值slicemap的情况,在实际编码过程中一定要注意这个问题。

实例-学员管理系统

实例学员管理系统,支持添加,修改,删除,查找学员。

// stu 结构体,存储学员信息
type stu struct {
    age, grade int
    name       string
}

var (
    //input 全局变量,获取命令选择
    input_int int
)

// stus 切片,学员列表
var stus = make(map[int]*stu, 10)

//stu构造函数
func newStu(age, grade int, name string) *stu {
    return &stu{
        age:   age,
        grade: grade,
        name:  name,
    }
}

//刷新学员列表
func showStus() {
    for k, _ := range stus {
        fmt.Printf("学号:%d 姓名:%s 年龄:%d 成绩:%d \n", k, stus[k].name, stus[k].age, stus[k].grade)
    }
}

//查询学员信息
func findStus(id int, y bool) bool {
    _, ok := stus[id]
    if ok == true {
        if y == true {
            fmt.Printf("学号:%d 姓名:%s 年龄:%d 成绩:%d \n", id, stus[id].name, stus[id].age, stus[id].grade)
        }
        return true
    }
    return false
}

//添加学员信息
func addStus() {
    var (
        newid, newage, newgrade int
        newname, cmdyes         string
    )
    fmt.Print("请输入学号:")
    fmt.Scanln(&newid)
    if findStus(newid, true) {
        fmt.Println("该学员已存在")
        return
    }
    fmt.Print("请输入姓名:")
    fmt.Scanln(&newname)
    fmt.Print("请输入年龄:")
    fmt.Scanln(&newage)
    fmt.Print("请输入成绩:")
    fmt.Scanln(&newgrade)
    fmt.Println("确认添加一下学员信息:")
    fmt.Printf("学号:%d 姓名:%s 年龄:%d 成绩:%d \n", newid, newname, newage, newgrade)
    fmt.Print("输入'yes'确认添加该学员信息:")
    fmt.Scanln(&cmdyes)
    if cmdyes != "yes" {
        fmt.Println("输入错误,退出添加学员")
        return
    }
    stus[newid] = newStu(newage, newgrade, newname)
    if findStus(newid, true) {
        fmt.Println("已添加该学员信息~~")
    } else {
        fmt.Println("未知错误导致添加失败!!!")
    }
    return
}

func editStus(id int) {
    var (
        cmd, newage, newgrade int
        newname               string
    )
    fmt.Print("[1]修改姓名\t")
    fmt.Print("[2]修改年龄\t")
    fmt.Print("[3]修改成绩\n")
    fmt.Print("你选择修改:")
    fmt.Scanln(&cmd)
    switch cmd {
    case 1:
        //修改姓名
        fmt.Print("输入新的姓名:")
        fmt.Scanln(&newname)
        stus[id].name = newname
        fmt.Printf("学号:%d 姓名:%s 年龄:%d 成绩:%d \n", id, stus[id].name, stus[id].age, stus[id].grade)
    case 2:
        //修改年龄
        fmt.Print("输入新的年龄:")
        fmt.Scanln(&newage)
        stus[id].age = newage
        fmt.Printf("学号:%d 姓名:%s 年龄:%d 成绩:%d \n", id, stus[id].name, stus[id].age, stus[id].grade)
    case 3:
        //修改成绩
        fmt.Print("输入新的成绩:")
        fmt.Scanln(&newgrade)
        stus[id].grade = newgrade
        fmt.Printf("学号:%d 姓名:%s 年龄:%d 成绩:%d \n", id, stus[id].name, stus[id].age, stus[id].grade)
    default:
        fmt.Println("未识别命令,退出修改")
        return
    }
}
func main() {
    //刷新学员列表
    stus[0] = newStu(18, 99, "二蛋")
    stus[1] = newStu(22, 60, "铁锤")

    fmt.Println("学员管理系统")

    for {

        //显示操作菜单
        fmt.Print("[1]刷新\t")
        fmt.Print("[2]添加\t")
        fmt.Print("[3]修改\t")
        fmt.Print("[4]删除\t")
        fmt.Print("[5]查询\t")
        fmt.Print("[6]退出\n")

        //执行对应操作
        fmt.Print("选择操作序号:")
        var cmdNo int
        fmt.Scanln(&cmdNo)
        //菜单解析
        switch cmdNo {
        case 1:
            //刷新学员信息
            showStus()
        case 2:
            //添加学员信息
            addStus()
        case 3:
            //修改学员信息
            var editId int
            fmt.Print("输入要修改的学号:")
            fmt.Scanln(&editId)
            if !findStus(editId, true) {
                fmt.Println("尚未收录该学号学员~~")
                break
            }
            editStus(editId)
        case 4:
            //删除学员信息
            var delId int
            fmt.Print("输入要删除的学号:")
            fmt.Scanln(&delId)
            if !findStus(delId, true) {
                fmt.Println("尚未收录该学号学员~~")
                break
            }
            fmt.Println("##########")
            delete(stus, delId)
        case 5:
            //查询学员信息
            var quId int
            fmt.Print("输入要查询的学号:")
            fmt.Scanln(&quId)
            if !findStus(quId, true) {
                fmt.Println("尚未收录该学号学员~~")
            }
        case 6:
            //退出系统
            os.Exit(1)
        default:
            //未识别菜单选项
            fmt.Println("输入序号后回车,请勿输入其它~~")
        }
    }
}

 上一篇
接口 接口
在Go语言中接口interface是一种类型,一种抽象的类型。interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议规则,只要一台机器有洗衣服和甩干的功能,我就称它
2020-04-22
下一篇 
指针和MAP类型 指针和MAP类型
声明指针指针也是一种数据类型,也可以使用var来声明:var 变量名 *数据类型注意,这里的变量名实际保存的数据是一个十六进制的内存地址,这里的数据类型指的是这个十六进制的内存地址要保存的数据类型。这里就生成了一个int类型的指针。 fun
2020-04-19
  目录