学校人员管理系统(go语言纯文件操作)
1.概述
本次学习任务是自建一个学校人员管理系统,其中包含三个类和六个功能。
三个类分别是person、student和teacher类。person类包含编号、姓名和年龄变量,而student和teacher类分别继承person类,且student还包含年级变量,teacher类包含学生数变量。
六个功能分别是:
- 增加人员信息:实现添加人员功能,将信息录入一个文件中。
- 显示人员信息:显示学校人员信息。
- 查找人员信息:根据编号或姓名查找人员。
- 修改人员信息:按照编号修改个人信息。
- 删除人员信息:按照编号删除人员信息。
- 退出管理系统
2.问题分析
三个类的创建没什么好说的,重点在于后面实现的六个功能。(大部分bug异常什么的也都是在六个功能的实现中产生的)
现在我们逐个分析一下:
- 主要是文件的操作,重点在文件的读写,录入信息的同时还要防止编号或姓名重复,不然第三个功能会出现问题(一个编号对应多个人…)。
- 这没什么好说的,就直接文件读取就行了。
- 按照编号查找还行,可以查找到对应信息就直接输出编号下面几行所需要的信息
(大部分人应该都是按顺序读写吧?)。按照姓名查找就有点麻烦了,需要储存名字对应的上一行编号信息,并且输出,多了一步储存操作(当然,如果你不是顺序存储当我没说)。 - 修改有点难受,网上查找并没有找到合适的方法,只能在相应位置开始,进行覆盖,不过覆盖有一些问题,就是覆盖的时候会覆盖到不想覆盖的内容(如果是txt文档的话,其实所有信息都是在同一行的,只是在该换行的地方放了个换行符,从而在显示的时候产生换行效果,所以信息长度过长,会影响下一行信息),所以可以对所有的写入设定一个标准长度,覆盖修改的时候就可以直接按照标椎长度写入。
并不知道如何删除QAQ网上求助无果后,就直接按照标椎长度,用空格进行覆盖来实现删除功能(就是在文档中会产生一些空行,这并不影响实际操作)- 这有手就行吧?
3.解决方法
1.三个类
三个类没什么好说的,直接上代码:
// 定义三个结构体
type person struct {
id int
name string
years int
}
type student struct {
person
grade int
}
type teacher struct {
person
count int
}
2.六个功能
六个功能实现起来就有点多了,一共900+行数,就放比较重要的代码了:
-
三个类,就实现三个添加方法,分别添加到三个文档
(我才不会告诉你我是为了美观才这么做的)- 就举person类为例,输入、判断是否合理就不放代码了,重点放在id和姓名冲突的判断上,这里的思路是循环所有文件,检索“:”之后的信息(因为我的信息储存都是在“:”之后的),判断是否相同,不同才写入。
- 代码如下
-
func (p *person) addPerson() { var per person filePath := "../person.txt" //打开相应类型的文件 file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Println("信息写入失败, error=", err) return } defer file.Close() fmt.Println("请分别输入id(9位数),姓名(长度不可超出9{一个汉字长度3其余长度1}),年龄(介于0到150岁之间)") fmt.Println("格式如下") fmt.Println("(id:)100000000 (姓名:)xxxiix (年龄)100 【期间以一个空格区分】") fmt.Scanf("%d %s %d\n", &per.id, &per.name, &per.years) //获取基本信息 p.SetPerson(per, false, 0) //判断并赋值给p person结构体 if p.id == 0 || p.name == "" { //如果id和姓名为默认值,则出现冲突,直接返回 fmt.Println("输入失败") return } sid := "其他人员编号:" + fmt.Sprintf("%d", p.id) + "\n" //修改成相应的标准字符 sname := "姓名:" + p.name + "\n" syears := "年龄:" + fmt.Sprintf("%d", p.years) + "\n" for len(sid) < 33 { //保证字符长度均为一致 sid += " " } for len(sname) < 33 { sname += " " } for len(syears) < 33 { syears += " " } write := bufio.NewWriter(file) //写入文件 write.WriteString(sid + "\n") write.WriteString(sname + "\n") write.WriteString(syears + "\n") fmt.Println("添加成功") write.Flush() //完成输入 } func (p *person) SetPerson(per person, change bool, oid int) { for n := 1; n < 4; n++ { //循环所有文件 var str string if n == 3 { str = "person" } else if n == 2 { str = "teacher" } else if n == 1 { str = "student" } filePath := "../" + str + ".txt" //寻找相应文件 file, err := os.OpenFile(filePath, os.O_RDONLY, 0666) if err != nil { fmt.Println("文件读取失败, error=", err) return } reader := bufio.NewReader(file) //读取文件内容 for { s, err := reader.ReadString('\n') //一行一行获取文件信息 c := strings.Index(s, ":") st := s[c+1:] //获取文件中:之后的部分,用于对比id和姓名是否冲突 if err == io.EOF { break } //下面判断是否冲突,并保证修改模式情况下原id可以和新id相同 if st == fmt.Sprintf("%d", per.id)+"\n" && (!change || (change && oid != per.id)) { fmt.Println("id冲突") return } //下面保证在非修改模式下,姓名冲突会导致导入失败,修改模式则不处理姓名冲突 if st == per.name+"\n" && !change { fmt.Println("姓名冲突") return } } defer file.Close() } if per.id < 1e8 || per.id >= 1e9 { //判断id是否正确 fmt.Println("输入id有误") return } else { p.id = per.id } if len(per.name) > 9 { //判断姓名是否正确 fmt.Println("输入姓名有误") return } else { p.name = per.name } if per.years < 0 || per.years > 150 { //判断年龄是否正确 fmt.Println("输入年龄有误") return } else { p.years = per.years } }
- 添加功能就这样了。
-
全体人员的信息显示和上面第一个代码类似,就不放了,单个类的信息显示就是没有全体人员没有循环的情况,一样的处理就行了,这里只举person类为例。
- 代码如下:
-
func (p *person) displayPerson() { filePath := "../person.txt" file, err := os.OpenFile(filePath, os.O_RDONLY, 0666) if err != nil { fmt.Println("文件读取失败, error=", err) return } reader := bufio.NewReader(file) for { s, err := reader.ReadString('\n') if len(s) > 2 { if s[0] == ' ' {//由于写入的是标准格式,有两个换行符,所有会产生一行信息和一行空行,这里保证空行不输出(美观) s = " " } } c := strings.TrimLeft(s, " ") if err == io.EOF { break } fmt.Print(c) defer file.Close() } }
- 显示功能就这样了。
-
查找人员信息的话,其实也就是循环所有文件,查找相应的信息,并且输入附近需要的信息(姓名要求输出上两行{一行空行}的信息,所以需要两个变量储存前两行的信息,编号的话就不用这样处理),循环的部分同上,这里就放查找姓名,并输出相应信息的代码。
- 代码如下:
-
func searchName(name string) { sname := name + "\n" for i := 3; i > 0; i-- { var str string var m int if i == 3 { str = "person" m = 0 } else if i == 2 { str = "teacher" m = 2 } else if i == 1 { str = "student" m = 2 } else { return } filePath := "../" + str + ".txt" //打开对应文件 file, err := os.OpenFile(filePath, os.O_RDONLY, 0666) if err != nil { fmt.Println("文件读取失败, error=", err) return } reader := bufio.NewReader(file) //读取对应文件 l := 0 var sh, ch string for { s, err := reader.ReadString('\n') //一行一行读取 if len(s) > 2 { //并忽略空行 if s[0] == ' ' { s = " " } } if l == 5+m { //姓名相同情况下的结束模式 c := strings.TrimLeft(ch, " ") fmt.Print(c) return } c := strings.Index(s, ":") st := s[c+1:] if sname == st { l = 1 } if l > 0 && l < 5+m { //姓名相同情况下的输出模式 c := strings.TrimLeft(ch, " ") fmt.Print(c) l++ ch = sh sh = s continue } if err != nil { break } ch = sh sh = s } } }
- 查找功能就这样了。
-
重头戏来了,感觉最困难的一个功能实现就是修改功能。首先要循环读入文件,通过id查找到相应的位置,然后再输入修改内容,保证修改后的id不与其他id冲突且可以是原本的id,之后再变为标准格式对文件相应位置进行覆盖,最后完成写入。
(就是因为这里的修改可能会因为长度问题覆盖了不该覆盖的东西,所有我在前面的所有写入的方法里都限定了标准格式)- 下面只举person为例
(虽然其他的也就差最后方法不一样了) -
func changeModel() { var id int var per, pers person var stu, stud student var tea, teac teacher var sid, sname, syears string fmt.Println("请输入一个编号") fmt.Scanf("%d\n", &id) //获取id for n := 3; n > 0; n-- { //循环查找,和之前循环类似 var str string var m, l int //m是获取并修改的额外行数,只对学生和老师有效;l是判断人员类型 if n == 3 { str = "person" m = 0 l = 1 } else if n == 2 { str = "teacher" m = 2 l = 2 } else if n == 1 { str = "student" m = 2 l = 3 } else { return } filePath := "../" + str + ".txt" //打开对应文件 file, err := os.OpenFile(filePath, os.O_RDWR, 0666) if err != nil { fmt.Println("文件读取失败, error=", err) return } defer file.Close() lens := 0 long := 0 i := 0 j := 0 reader := bufio.NewReader(file) //读取文件内容 var scount, sgrade string for { s, err := reader.ReadString('\n') //一行一行读取 c := strings.Index(s, ":") st := s[c+1:] //获得:后的有效信息 sid = fmt.Sprintf("%d", id) + "\n" if st == sid { //判断该行是否存在id相等 i = 1 //储存相等的信息 j = 1 //同上,不过只在此次循环存储 } long = lens //获取并保存这一行开始的位置 lens += len(s) //进入下一行开始的位置 if j == 1 { //仅获取一次修改后的信息 if l == 1 { pers.changePerson(per, id) if pers.id == 0 || pers.name == "" { return } sid = "其他人员编号:" + fmt.Sprintf("%d", pers.id) + "\n" //修改成标准模式,下同 for len(sid) < 33 { sid += " " } sname = "姓名:" + pers.name + "\n" for len(sname) < 33 { sname += " " } syears = "年龄:" + fmt.Sprintf("%d", pers.years) + "\n" for len(syears) < 33 { syears += " " } } else if l == 2 { teac.changeTeacher(tea, id) if teac.id == 0 || teac.name == "" || teac.count == 0 { fmt.Println("修改失败") return } sid = "老师编号:" + fmt.Sprintf("%d", teac.id) + "\n" //修改成标准模式,下同 for len(sid) < 33 { sid += " " } sname = "姓名:" + teac.name + "\n" for len(sname) < 33 { sname += " " } syears = "年龄:" + fmt.Sprintf("%d", teac.years) + "\n" for len(syears) < 33 { syears += " " } scount = "所教学生数:" + fmt.Sprintf("%d", teac.count) + "\n" for len(scount) < 33 { scount += " " } } else if l == 3 { stud.changeStudent(stu, id) if stud.id == 0 || stud.name == "" || stud.grade == 0 { fmt.Println("修改失败") return } sid = "学生编号:" + fmt.Sprintf("%d", stud.id) + "\n" //修改成标准模式,下同 for len(sid) < 33 { sid += " " } sname = "姓名:" + stud.name + "\n" for len(sname) < 33 { sname += " " } syears = "年龄:" + fmt.Sprintf("%d", stud.years) + "\n" for len(syears) < 33 { syears += " " } sgrade = "所教学生数:" + fmt.Sprintf("%d", stud.grade) + "\n" for len(scount) < 33 { scount += " " } } j = 0 //结束获取信息,并保证之后循环不会重复获取 } if i > 0 { //进入修改 for i <= 6+m { //由于存储时是一行有效信息,一行为空行存储,所有下面两行写入一次修改信息 if i == 1 { file.WriteAt([]byte(sid+"\n"), int64(long)) i++ break } if i == 3 { file.WriteAt([]byte(sname+"\n"), int64(long)) i++ break } if i == 5 { file.WriteAt([]byte(syears+"\n"), int64(long)) i++ break } if l == 1 && i == 7 { //老师的特殊修改 file.WriteAt([]byte(scount+"\n"), int64(long)) i++ break } if l == 2 && i == 7 { //学生的特殊修改 file.WriteAt([]byte(sgrade+"\n"), int64(long)) i++ break } if i == 6+m { fmt.Println("修改成功") return } i++ break } } if err == io.EOF { break } } } fmt.Println("编号错误!") //全文件没找到输入的id,则输出编号错误 } func (p *person) changePerson(per person, id int) { fmt.Println("请输入修改后的编号,姓名和年龄") fmt.Scanf("%d %s %d\n", &per.id, &per.name, &per.years) //获取 p.SetPerson(per, true, id) }
- 修改功能就这样了。
- 下面只举person为例
-
删除方法其实就是修改方法的简化版,不需要输入修改内容,直接根据id定位信息位置,然后用标准格式的空格覆盖对应位置就行了。
- 代码如下:
-
func deleteModel() { var id int var sid string fmt.Println("请输入一个编号") fmt.Scanf("%d\n", &id) //获取id for n := 3; n > 0; n-- { //全文件循环寻找 var str string var m, l int if n == 3 { str = "person" m = 0 l = 0 } else if n == 2 { str = "teacher" m = 2 l = 1 } else if n == 1 { str = "student" m = 2 l = 2 } else { return } filePath := "../" + str + ".txt" //打开相应文件 file, err := os.OpenFile(filePath, os.O_RDWR, 0666) if err != nil { fmt.Println("文件读取失败, error=", err) return } defer file.Close() lens := 0 long := 0 i := 0 reader := bufio.NewReader(file) //读取相应文件 for { s, err := reader.ReadString('\n') //一行一行获取文件内容 c := strings.Index(s, ":") st := s[c+1:] sid = fmt.Sprintf("%d", id) + "\n" if st == sid { //判断文件中是否存在id与输入id相同 i = 1 //如果相同,储存相同信息 } long = lens lens += len(s) if i > 0 { for i <= 6+m { //循环,用标准空行覆盖对应信息 if i == 1 { //(由于不知道怎么删除,所有只能通过空格来覆盖了{不过这只影响打开文件查看,在此系统查看不受影响}) file.WriteAt([]byte(" \n"), int64(long)) i++ break } if i == 3 { file.WriteAt([]byte(" \n"), int64(long)) i++ break } if i == 5 { file.WriteAt([]byte(" \n"), int64(long)) i++ break } if l == 1 && i == 7 { file.WriteAt([]byte(" \n"), int64(long)) i++ break } if l == 2 && i == 7 { file.WriteAt([]byte(" \n"), int64(long)) i++ break } if i == 6+m { fmt.Println("删除成功") return } i++ break } } if err == io.EOF { break } } } fmt.Println("编号错误!") //若全文件找不到id,则输出编号错误 }
- 删除功能就这样了。
-
退出方法不是有手就行?这里没什么好说了,加个循环处理,对应数字打破循环就行,代码就不放了。
4.总结
(感觉没啥好总结的) 这套代码主要是注重于文件处理操作,如果有谁文件处理操作不太行 (或是有自虐倾向) 可以试试写写这个系统 (保证你写到心累)