【声明】
非完全原创,部分内容来自于学习其他人的理论和B站视频。如果有侵权,请联系我,可以立即删除掉。
一、os
1.1、创建、打开、关闭文件
func Create(name string) (file *File, err error)
//Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)。如果成功,返回的文件对象可用于I/O;对应的文件描述符具有O_RDWR模式。如果出错,错误底层类型是*PathError。
func NewFile(fd uintptr, name string) *File
//根据文件描述符创建相应的文件,返回一个文件对象
func Open(name string) (file *File, err error)
//Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。内部实现其实调用了OpenFile。
func OpenFile(name string, flag int, perm uint32) (file *File, err error)
//OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
//打开名称为name的文件,flag是打开的方式,只读、读写等,perm是权限
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
func (f *File) Close() error
//Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。
1.2、写文件
func (file *File) Write(b []byte) (n int, err error)
//Write向文件中写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误
func (file *File) WriteAt(b []byte, off int64) (n int, err error)
//WriteAt在指定的位置(相对于文件开始位置)写入len(b)字节数据。它返回写入的字节数和可能遇到的任何错误。如果返回值n!=len(b),本方法会返回一个非nil的错误。
func (file *File) WriteString(s string) (ret int, err error)
//WriteString类似Write,但接受一个字符串参数。
1.3、读文件
func (file *File) Read(b []byte) (n int, err error)
//Read方法从f指针中读取最多len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。文件终止标志是读取0个字节且返回值err为io.EOF。
func (file *File) ReadAt(b []byte, off int64) (n int, err error)
//ReadAt从指定的位置(相对于文件开始位置)读取len(b)字节数据并写入b。它返回读取的字节数和可能遇到的任何错误。当n<len(b)时,本方法总是会返回错误;如果是因为到达文件结尾,返回值err会是io.EOF。
1.4、删除文件
func Remove(name string) error
//Remove删除name指定的文件或目录。如果出错,会返回*PathError底层类型的错误。
func RemoveAll(path string) error
//RemoveAll删除path指定的文件,或目录及它包含的任何下级对象。它会尝试删除所有东西,除非遇到错误并返回。如果path指定的对象不存在,RemoveAll会返回nil而不返回错误。
2、其他包的方法
bufio
func NewReader(rd io.Reader) *Reader
//NewReader创建一个具有默认大小缓冲、从r读取的*Reader。
func (b *Reader) ReadString(delim byte) (line string, err error)
//ReadString读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的字符串。
func NewWriter(w io.Writer) *Writer
//NewWriter创建一个具有默认大小缓冲、写入w的*Writer。
func (b *Writer) Write(p []byte) (nn int, err error)
//Write将p的内容写入缓冲。返回写入的字节数。如果返回值nn < len(p),还会返回一个错误说明原因。
func (b *Writer) WriteString(s string) (int, error)
//WriteString写入一个字符串。返回写入的字节数。如果返回值nn < len(s),还会返回一个错误说明原因。
func (b *Writer) Flush() error
//Flush方法将缓冲中的数据写入下层的io.Writer接口。一般写入数据后需要调用该函数将数据刷到文件中
io.ioutil
func ReadAll(r io.Reader) ([]byte, error)
//ReadAll从r读取数据直到EOF或遇到error,返回读取的数据和遇到的错误。
func ReadFile(filename string) ([]byte, error)
//ReadFile 从filename指定的文件中读取数据并返回文件的内容。成功的调用返回的err为nil而非EOF。
func WriteFile(filename string, data []byte, perm os.FileMode) error
//函数向filename指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件。
3、基本使用
3.1、读写文件内容
func main() {
//以读写模式打开文件,文件不存在则创建
path := "D:/data.txt"
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0755)
defer file.Close()
if err != nil {
fmt.Println("文件以读写模式打开失败, err = ", err)
}
str := "Hello world\nHello ketty\nhello golang\n"
bytes, err := file.Write([]byte(str))
if err != nil {
fmt.Println("文件指针写数据失败, err = ", err)
}
fmt.Printf("文件指针写入了[%d]字节\n", bytes)
//file指针写完数据之后,指向了文件末尾
//如果不重新移位,则后面的reader第一次就读到了EOF
offset, err := file.Seek(0, io.SeekStart)
if err != nil {
fmt.Println("文件指针移位到文件头部位置失败, err = ", err)
}
fmt.Printf("文件指针移位到文件头部位置成功, offset = %d\n\n", offset)
//file, _ = os.Open(path)
//根据文件指针创建一个reader,从文件中读数据
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
fmt.Println("bufio.Reader指针读取一行数据失败, err = ", err)
break
}
fmt.Printf("%s", line) //line自带换行符
}
//根据文件路径,直接一次性读取所有内容,该方法封装了Open和Close方法
content, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println("ioutil.ReadFile读数据失败, err = ", err)
}
fmt.Printf("ioutil.ReadFile读取了[%d]字节: %s\n", len(content), string(content))
}
output:
文件指针写入了[37]字节
文件指针移位到文件头部位置成功, offset = 0
Hello world
Hello ketty
hello golang
bufio.Reader指针读取一行数据失败, err = EOF
ioutil.ReadFile读取了[37]字节: Hello world
Hello ketty
hello golang
3.2、写文件的三种方式
func main() {
path := "D:/data2.txt"
str := "Hello, golang\n"
err := ioutil.WriteFile(path, []byte(str), os.ModePerm)
if err != nil {
fmt.Println("ioutil.WriteFile写数据失败, err = ", err)
}
file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0777)
if err != nil {
fmt.Println("文件以读写模式打开失败, err = ", err)
}
str = "Hello, world\n"
for i := 0; i < 3; i++ {
_, err := file.Write([]byte(str))
if err != nil {
fmt.Println("file.Write写数据失败, err = ", err)
}
}
writer := bufio.NewWriter(file)
str = "Hello, keety\n"
for i := 0; i < 3; i++ {
_, err := writer.WriteString(str)
if err != nil {
fmt.Println("bufio.NewWriter(file).WriteString写数据失败, err = ", err)
}
}
writer.Flush() //一定要调用才能写入到文件中
file.Close()
}

3.3、文件读、文件写
func main() {
//从文件中读取内容到内存,然后写入到另一个文件中
src := "D:/data2.txt"
dst := "D:/copyToData2.log"
content, err := ioutil.ReadFile(src)
if err != nil {
fmt.Println("ioutil.ReadFile读数据失败, err = ", err)
}
err = ioutil.WriteFile(dst, content, 0666)
if err != nil {
fmt.Println("ioutil.WriteFile写数据失败, err = ", err)
}
}

3.4、文件拷贝
io
func Copy(dst Writer, src Reader) (written int64, err error)
//将src的数据拷贝到dst,直到在src上到达EOF或发生错误。返回拷贝的字节数和遇到的第一个错误。
func CopyFile(srcPath, dstPath string) (int64, error) {
_, err := os.Stat(srcPath)
if err != nil {
fmt.Println("os.Stat判定源文件不存在, err = ", err)
return 0, errors.New("file is not exist")
}
srcFile, err := os.Open(srcPath)
defer srcFile.Close()
if err != nil {
fmt.Println("os.Open打开文件失败, err = ", err)
}
reader := bufio.NewReader(srcFile)
dstFile, err := os.OpenFile(dstPath, os.O_RDWR|os.O_CREATE, 0666)
defer dstFile.Close()
if err != nil {
fmt.Println("os.OpenFile打开或者创建文件失败, err = ", err)
}
writer := bufio.NewWriter(dstFile)
return io.Copy(writer, reader)
}
func main() {
src := "D:/Rain_fly/Pictures/starry_sky.jpeg"
dst := "D:/Rain_fly/Pictures/copy.jpeg"
len, err := CopyFile(src, dst)
if err != nil {
fmt.Println("文件复制失败, err = ", err)
return
}
fmt.Println("文件复制成功, len = ", len)
}
output:
文件复制成功, len = 173779
3.5、统计文件中字符的个数
type CharCnt struct {
chCnt int //记录字母格式
numCnt int //记录数字的个数
spaceCnt int //记录空格的个数
otherCnt int //记录其他字符个数
}
func main() {
str := "abc 2022\tpkg \r !%\n hello\n"
path := "D:/data"
err := ioutil.WriteFile(path, []byte(str), 0666)
if err != nil {
fmt.Println("ioutil.WriteFile写数据失败, err = ", err)
}
fp, err := os.Open(path)
if err != nil {
fmt.Println("os.Open打开文件失败, err = ", err)
}
defer fp.Close()
reader := bufio.NewReader(fp)
if err != nil {
fmt.Println("bufio.NewReader创建失败, err = ", err)
}
cnt := CharCnt{}
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
//fmt.Println(line)
for _, v := range line {
switch {
case v >= 'a' && v <= 'z' || v >= 'A' && v <= 'Z':
cnt.chCnt++
case v >= '0' && v <= '9':
cnt.numCnt++
case v == ' ' || v == '\t':
cnt.spaceCnt++
default:
cnt.otherCnt++
}
}
}
fmt.Printf("%+v", cnt)
}
output:
{chCnt:11 numCnt:4 spaceCnt:5 otherCnt:5}
二、MVC框架
- M(Model, 模型)是指业务模型,用于数据处理、逻辑处理;
- V(View,视图)是指用户界面;
- C(Controller,控制器):根据客户端的请求控制逻辑走向和画面
使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。
在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据。控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据
2、需求和分析
开发一个用户信息管理系统,要求有增删查改的功能,其显示如下:
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
1 tom man 30 6666 tom@ten.com
2 mary2 woman 17 7777 mary2@qq.com
3 jack man 23 8888 jack@qq.com
----------------客户列表完成-----------------
分析:该功能可以使用MVC框架,其中
- 视图用于显示主页面,并且能够显示增删查改的信息;
- 控制器用于接收用户增删查改输入的信息,并且通过这些信息操控客户列表,显然控制器必须有一个切片来存储客户对象;
- 模型中需要定义客户的结构体、获取/修改字段的方法、返回客户信息的函数,以及一个创建客户对象的方法
3、model的实现
package model
import "fmt"
type Customer struct {
id, age int
name, sex, phone, mail string
}
func (c *Customer) PrintInfo() string {
return fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v",
c.id, c.name, c.sex, c.age, c.phone, c.mail)
}
func (c *Customer) GetName() string {
return c.name
}
func (c *Customer) SetName(name string) {
c.name = name
}
func (c *Customer) GetId() int {
return c.id
}
func (c *Customer) GetAge() int {
return c.age
}
func (c *Customer) SetAge(age int) {
c.age = age
}
func (c *Customer) GetSex() string {
return c.sex
}
func (c *Customer) SetSex(sex string) {
c.sex = sex
}
func (c *Customer) GetPhone() string {
return c.phone
}
func (c *Customer) SetPhone(phone string) {
c.phone = phone
}
func (c *Customer) GetMail() string {
return c.mail
}
func (c *Customer) SetMail(mail string) {
c.mail = mail
}
func NewCustomer(id int, name, sex string, age int,
phone, mail string) Customer {
return Customer{id, age, name, sex, phone, mail}
}
4、controllor的实现
package controllor
import (
"Test0/MVC_customer/model"
"fmt"
"strconv"
)
type Ctrllor struct {
customers []model.Customer
num int //使用num为客户对象自动编号
}
func (c *Ctrllor) AddCustomer(name, sex string, age int,
phone, mail string) {
c.num++
c.customers = append(c.customers, model.NewCustomer(c.num, name, sex, age,
phone, mail))
}
func (c *Ctrllor) ShowAllCustomer() []model.Customer {
return c.customers
}
//工厂模式:获取空的Ctrllor指针
func GetCtrllor() *Ctrllor {
return &Ctrllor{}
}
func (c *Ctrllor) ModifyByName(name string) bool {
flag := true //假设找不到
for i := 0; i < len(c.ShowAllCustomer()) && flag; i++ {
if c.customers[i].GetName() == name {
for {
fmt.Println("请输入客户信息(性别、年龄、电话、邮箱以空格隔开):")
var in [4]string
fmt.Scanf("\n%v %v %v %v", &in[0], &in[1], &in[2], &in[3])
if in[3] == "" {
fmt.Printf("输入信息的格式错误,请重新输入!!!\n\n")
} else {
c.customers[i].SetSex(in[0])
age, _ := strconv.Atoi(in[1])
c.customers[i].SetAge(age)
c.customers[i].SetPhone(in[2])
c.customers[i].SetMail(in[3])
flag = false
break
}
}
}
}
if flag {
fmt.Printf("你输入的姓名不存在,请重新输入!!!\n\n")
}
return !flag
}
func (c *Ctrllor) ModifyById(id int) bool {
flag := true
for i := 0; i < len(c.ShowAllCustomer()) && flag; i++ {
if c.customers[i].GetId() == id {
var in [5]string
for {
fmt.Println("请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):")
fmt.Scanf("\n%v %v %v %v %v", &in[0], &in[1], &in[2], &in[3], &in[4])
if in[4] == "" {
fmt.Println("输入信息的格式错误,请重新输入!!!")
} else {
c.customers[i].SetName(in[0])
c.customers[i].SetSex(in[1])
age, _ := strconv.Atoi(in[2])
c.customers[i].SetAge(age)
c.customers[i].SetPhone(in[3])
c.customers[i].SetMail(in[4])
flag = false
break
}
}
}
}
if flag {
fmt.Printf("你输入的编号不存在,请重新输入!!!\n\n")
}
return !flag
}
func (c *Ctrllor) DeleteById(id int) bool {
flag := true
for i := 0; i < len(c.ShowAllCustomer()) && flag; i++ {
if c.customers[i].GetId() == id {
c.customers = append(c.customers[:i], c.customers[i+1:]...)
flag = false
}
}
if flag {
fmt.Printf("你输入的编号不存在,请重新输入!!!\n\n")
}
return !flag
}
func (c *Ctrllor) DeleteByName(name string) bool {
flag := true
for i := 0; i < len(c.ShowAllCustomer()) && flag; i++ {
if c.customers[i].GetName() == name {
c.customers = append(c.customers[:i], c.customers[i+1:]...)
flag = false
}
}
if flag {
fmt.Printf("你输入的姓名不存在,请重新输入!!!\n\n")
}
return !flag
}
5、view的实现
package main
import (
"Test0/MVC_customer/controllor"
"fmt"
"strconv"
)
type View struct {
selec int //接收用户输入的序号
loop bool //循环控制
ctrl *controllor.Ctrllor
}
func (v *View) AddCustomer() {
fmt.Println("请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):")
var info [5]string
for {
fmt.Scanf("\n%v %v %v %v %v", &info[0], &info[1], &info[2], &info[3], &info[4])
if info[4] == "" {
fmt.Println("输入信息的格式错误,请重新输入!!!")
} else {
break
}
}
age, _ := strconv.Atoi(info[2])
v.ctrl.AddCustomer(info[0], info[1], age, info[3], info[4])
fmt.Printf("客户信息添加完成\n\n")
}
func (v *View) showList() {
fmt.Println("------------------客户列表------------------")
fmt.Println("编号\t姓名\t性别\t年龄\t电话\t邮箱")
for _, c := range v.ctrl.ShowAllCustomer() {
fmt.Println(c.PrintInfo())
}
fmt.Printf("----------------客户列表完成-----------------\n\n")
}
func (v *View) modifyInfo() {
fmt.Printf("请输入待修改客户的编号或者姓名: ")
var info string
fmt.Scan(&info)
id, err := strconv.Atoi(info)
var flag bool
if err != nil { //说明强转失败,输入的是姓名
flag = v.ctrl.ModifyByName(info)
} else {
flag = v.ctrl.ModifyById(id)
}
if flag {
fmt.Printf("客户信息修改完成\n\n")
}
}
func (v *View) deleteInfo() {
fmt.Printf("请输入待删除客户的编号或者姓名: ")
var info string
fmt.Scan(&info)
id, err := strconv.Atoi(info)
var flag bool
if err != nil { //说明强转失败,输入的是姓名
flag = v.ctrl.DeleteByName(info)
} else {
flag = v.ctrl.DeleteById(id)
}
if flag {
fmt.Printf("当前客户已删除\n\n")
}
}
func (v *View) showMenu() {
for v.loop = true; v.loop; {
fmt.Println("-------------客户信息管理软件-------------")
fmt.Println(" 1、添加客户信息")
fmt.Println(" 2、修改客户信息")
fmt.Println(" 3、删除客户信息")
fmt.Println(" 4、显示客户列表")
fmt.Println(" 5、退出管理软件")
fmt.Printf(" 请输入序号选择(1~5):")
fmt.Scan(&v.selec)
switch v.selec {
case 1:
v.AddCustomer()
case 2:
v.modifyInfo()
case 3:
v.deleteInfo()
case 4:
v.showList()
case 5:
v.loop = false
default:
fmt.Printf("序号输入错误,请重新输入!!!\n\n")
}
}
}
func main() {
v := View{0, true, controllor.GetCtrllor()}
v.showMenu()
}
6、测试结果
PS D:\WorkSpace\Golang\src\Test0\MVC_customer\view> go run .\main.go
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
----------------客户列表完成-----------------
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):1
请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):
tom man 20 1234 tom@qq.com
客户信息添加完成
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):1
请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):
mary woman 25 5678 mary@qq.com
客户信息添加完成
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
1 tom man 20 1234 tom@qq.com
2 mary woman 25 5678 mary@qq.com
----------------客户列表完成-----------------
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):2
请输入待修改客户的编号或者姓名: jack
你输入的姓名不存在,请重新输入!!!
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):2
请输入待修改客户的编号或者姓名: tom
请输入客户信息(性别、年龄、电话、邮箱以空格隔开):
man 30 6666 tom@ten.com
客户信息修改完成
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):2
请输入待修改客户的编号或者姓名: 3
你输入的编号不存在,请重新输入!!!
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):2
请输入待修改客户的编号或者姓名: 2
请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):
mary2 woman 17 7777 mary2@qq.com
客户信息修改完成
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
1 tom man 30 6666 tom@ten.com
2 mary2 woman 17 7777 mary2@qq.com
----------------客户列表完成-----------------
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):1
请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):
jack man 23 8888 jack@qq.com
客户信息添加完成
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
1 tom man 30 6666 tom@ten.com
2 mary2 woman 17 7777 mary2@qq.com
3 jack man 23 8888 jack@qq.com
----------------客户列表完成-----------------
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):3
请输入待删除客户的编号或者姓名: 2
当前客户已删除
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
1 tom man 30 6666 tom@ten.com
3 jack man 23 8888 jack@qq.com
----------------客户列表完成-----------------
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):1
请输入客户信息(姓名、性别、年龄、电话、邮箱以空格隔开):
mary woman 25 5678 mary@qq.com
客户信息添加完成
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):4
------------------客户列表------------------
编号 姓名 性别 年龄 电话 邮箱
1 tom man 30 6666 tom@ten.com
3 jack man 23 8888 jack@qq.com
4 mary woman 25 5678 mary@qq.com
----------------客户列表完成-----------------
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):6
序号输入错误,请重新输入!!!
-------------客户信息管理软件-------------
1、添加客户信息
2、修改客户信息
3、删除客户信息
4、显示客户列表
5、退出管理软件
请输入序号选择(1~5):5