类型别名和自定义类型
在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
类型,这个别名只是让你写代码是更清晰明了,比如rune
和byte
就是类型别名,他们底层是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等。 - 接收者类型:
接收者类型和参数类似,可以是指针类型和非指针类型。 - 方法名、参数列表、返回参数:
具体格式与函数定义相同
值接受者
方法也是一个函数,他是一个只允许特定类型调用的函数,如下代码xiaotian
和diting
都是Dog
类型结构体,可以调用方法函数kan()
,而nazha
是ren
类型结构体,不能调用方法函数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
在结构体ren
和dog
都存在时,默认优先选择本结构体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)
}
结构体和方法补充知识点
因为slice
和map
这两种数据类型都包含了指向底层数据的指针,因此我们在需要复制它们时要特别注意。我们来看下面的例子:
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)
}
同样的问题也存在于返回值slice
和map
的情况,在实际编码过程中一定要注意这个问题。
实例-学员管理系统
实例学员管理系统,支持添加,修改,删除,查找学员。
// 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("输入序号后回车,请勿输入其它~~")
}
}
}