栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Go语言学习笔记(3)——复杂数据类型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Go语言学习笔记(3)——复杂数据类型

【声明】

非完全原创,部分内容来自于学习其他人的理论和B站视频。如果有侵权,请联系我,可以立即删除掉。

0、Go数据类型分类

引用类型包括:指针,slice,map,channel,接口,函数类型等,其本质是一个指向对象的指针
值类型有:数组、结构体、基本数据类型

一、指针

指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置

1、特点

(1)默认值 nil,没有 NULL 和null常量
(2)操作符 & 取变量地址,* 通过指针访问目标对象
(3)不支持指针运算,不支持 “->” 运算符,直接⽤ . 访问目标成员

2、基本使用
n := 66
var p *int = nil //声明一个变量p, 类型为 *int
p = &n
*p = 888 
fmt.Printf("n = %d, &n = 0x%x, *p = %d, p = 0x%xn", n, &n, *p, p)

var p1 *int //声明方式1
p1 = new(int) //p1指向匿名的int变量
p2 := new(int) //声明方式2
*p2 = 111

output:
n = 888, &n = 0xc0000aa0c0, *p = 888, p = 0xc0000aa0c0
3、指针作为参数传递
func swap(x, y *int) {
    *x, *y = *y, *x
}

func main() {
    a := 6666
    b := 8888
    swap02(&a, &b) //变量值交换
    fmt.Printf("a = %d, b = %dn", a, b)
二、数组 1、概念

数组是存储同一种数据类型多个元素的集合。它既可以存储基本数据类型,也可以存储引用数据类型。数组每个数据被称为数组元素,其包含的元素个数被称为数组的长度。

2、数组定义、遍历

声明明语法如下:
var 数组变量名 [数组长度]Type
注意:数组长度不能是变量

var n int = 6
var a [n]int  //err, non-constant array bound n
var b [10]int //方式1
c := [10]int{} //方式2:10个元素均为0
d := [...]int{1, 2, 3} //方式3:数组长度为3
e := [...]int{2: 6, 4: 8} //方式4:数组长度为5

//二位数组
f := [...][2]int{{0, 1}, {2, 3}, {4, 5}}  //方式1:第2维不能用...
g := [7][2]int{1: {0: 0}, 5: {1: 1}}  //方式2
h := [2][2]int{{0, 0}}


for i := 0; i < len[d]; i++ {
    fmt.Printf("d[%d] = %dn", i, d[i])
}

//range具有两个返回值,第一个返回值是元素的数组下标,第二个返回值是元素的值
for i, v := range e {
    fmt.Println("e[", i, "]=", v)
}

数组的特点是:
(1)长度是固定的,因此定义时长度不能为变量,且数组定义后其长度不可修改
(2)数组是随机访问的,因为其地址是连续的,可以直接通过索引来计算访问的地址,时间为O(1)

func main(){
	a := [3][3]int8{}
	for i, val := range a {
		for j := 0; j < len(val); j++ {
			fmt.Printf("0x%xt", &a[i][j])
		}
		fmt.Println()
	}
}

output:
0xc0000140b0	0xc0000140b1	0xc0000140b2	
0xc0000140b3	0xc0000140b4	0xc0000140b5	
0xc0000140b6	0xc0000140b7	0xc0000140b8
3、数组作为参数

go语言中,数组作为函数的参数时,默认以值的方式传递的,因此开销会比较大。
数组指针作为参数时,则是地址传递

func val_func(array [3]int) {
    array[0] = 6 // 值传递,此处修改不影响原数组
}

func addr_func(array *[3]int) {
    (*array)[0] = 6 //引用传递,此处修改会影响原数组
}

func main() {
    arr := [3]int{1, 2, 3}
    val_func(arr)
    fmt.Println(arr)
    addr_func(&arr)
    fmt.Println(arr)
}

output:
[1 2 3]
[6 2 3]
三、切片slice

由于数组的长度是确定的,且数组在定义后其长度无法再次修改,因此在遇到不确定长度的场景时,需要用到切片slice

切片并不是数组或数组指针,它通过内部指针和相关属性引⽤数组⽚段,以实现变⻓⽅案。它是一个引用类型

1、切片的声明
var s1 []int //和数组声明差不多,只是不需要指定长度
//make([]T, length, capacity) //capacity省略,则和length的值相同
var s2 []int = make([]int, 1, 2)
s3 := make([]int, 0, 0)
s4 := []int{1, 2} //创建切片并初始化
2、切片的操作
操作含义
s[n]切片s中索引位置为n的项
s[:]从切片s的索引位置0到len(s)-1处所获得的切片
s[low:]从切片s的索引位置low到len(s)-1处所获得的切片
s[:high]从切片s的索引位置0到high处所获得的切片,len=high
s[low:high]从切片s的索引位置low到high处所获得的切片,len=high-low
s[low:high:max]从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low
len(s)切片s的长度,总是<=cap(s)
cap(s)切片s的容量,总是>=len(s)
var s1 []int
//追加元素
s1 = append(s1, 1, 2)    //追加2个元素
fmt.Printf("s2 len = %d, cap = %dn", len(s2), cap(s2))

//复制元素:部分覆盖
data := [...]int{0, 1, 2, 3, 4, 5}
s2 := data[3:]
copy(data, s2)
fmt.Println(data) //[3, 4, 5, 3, 4, 5]
3、切片作为函数参数
func slice_test(s []int){
	s[0] = 6
	s[1] = 8
}

func main() {
    s:= []int{0, 1, 2, 3, 4, 5}
    slice_test(s)
    fmt.Println(s) //[6 8 2 3 4 5]
}
四、map

Go语言中的map(映射、字典)是一种内置的数据结构,它是一个无序的key—value对的集合
map里所有的键都是唯一的,而且必须是支持==和!= 操作符的类型;map值可以是任意类型,没有限制,包括nil

map的格式:
map[keyType]valueType

var map1 map[int]string  //声明一个map但没有初始化, map1 = nil

map2 := map[int]string{1: "jack", 2: "tom"}
map[1] = 'x' //将'jack'修改为`x`
map[3] = 'y' //追加一个元素,3:"y"
map3 := make(map[int]string)

map4 := make(map[int]map[int]string, 66) //指定容量

for k, v := range map2 {
    fmt.Printf("%d: %sn", k, v)
}
for k := range map2 {
    fmt.Printf("%d: %sn", k, map2[k])
}

//第一个返回value,第二个返回是否存在
val, ok := map[3] //ok = true
val2, ok2 := map[5] //val2 = "", ok2 = false

delete(map2, 1) //删除key = 1的数据
五、结构体struct

在开发中,有时需要将不同类型的数据组合成一个整体。比如人含有姓名、年龄、性别等属性,单独定义不太方便,因此可以使用结构体struct

1、结构体的基本操作

由于结构体是值类型,因此两个结构体用==比较时,实际上是逐个属性进行比较;同时,作为参数是值传递,函数中的操作不会影响原结构体

type Student struct {
    name string
    sex  byte
    age  int
}

func main() {
    //1、顺序初始化,必须每个成员都初始化
    var s1 Student = Student{"jack", 'm', 18}

    //2、指定初始化某个成员,没有初始化的成员为零值
    s2 := Student{name: "tom"}
	s2.sex= "f"
    s2.age= 20

	p := &s2
	p.name = 'hello' //p.name 和 (*p).name等价,go底层做了优化
	(*p).age = 29	
}
func test1(s Student){
	s.name = "go"
}
func test2(s *Student){
	s.name = "hello"
}
func main(){
	s := Student{"jack", 'm', 18}
	test1(s)
	fmt.Println(s)
	test2(&s)
	fmt.Println(s)
}

output:
{jack 109 18}
{hello 109 18}
2、结构体的继承
type Study struct {
	subjects []string
}

type Person struct {
	name string
	sex  byte
	age  int
}
type Student struct {
	Person //此处表示继承
	Study  //多重继承
	id     int
	name   string
	course [9]int
}

func main() {
	subjects := []string{"english", "math"}
	st := Study{subjects}
	p := Person{"jack", 'm', 20}
	s := Student{p, st, 2018, "tom", [9]int{0}}
	fmt.Printf("s.name = %s, s.Person.name = %s", s.name, s.Person.name)
}
output:
s.name = tom, s.Person.name = jack
六、接口

接口(interface)是一个抽象的数据类型,接口类型具体描述了一系列方法的集合。接口定义的是某种规范,不涉及实现,因此接口类型不能实例化

接口只有方法声明,没有实现,没有数据字段

格式:

type Person interface {
    sleep()
    eat()
}

接口是用来定义行为的类型,但行为的实现需要结构体的类型。
注意:只有实现了接口中的所有方法才算实现了接口,因此它接耦合了接口和类之间的联系

1、接口的实现
type Person interface{
	sleep()
	eat()
}

//Student实现了Person的所有接口,因此它实现了Person
type Student struct{
	id int
	name string
}
//Student实现sleep()方法
func  (s *Student) sleep() {
    fmt.Printf("Student[%d, %s] sleep!!n", s.id, s.name)
}
func  (s *Student) eat() {
    fmt.Printf("Student[%d, %s] eat!!n", s.id, s.name)
}

//Teacher 实现了Person的所有接口,因此它实现了Person
type Teacher struct { //老师
    name, addr  string
}
//Teacher实现sleep()方法
func (t *Teacher) sleep() {
    fmt.Printf("Teacher[%s, %s] sleep!!n", t.name, t.addr)
}
func (t *Teacher) eat() {
    fmt.Printf("Teacher[%s, %s] eat!!n", t.name, t.addr)
}

//由于两个结构体都实现了接口,因此参数为接口但实际可传结构体
//父类的引用去调用方法,实际是根据子类的类型去执行子类中的方法,即相同的调用方式调出不同的结果,即多态
func test(p Person){
	p.sleep()
	p.eat()
}

func main(){
	s := Student{1020, "jack"}
	t := Teacher{"mary", "Beijing"}
	test(&s)
	test(&t)
}

output:
Student[1020, jack] sleep!!
Student[1020, jack] eat!!
Teacher[mary, Beijing] sleep!!
Teacher[mary, Beijing] eat!!
七、json标签及序列化 1、json简介

json 是一种数据交换格式,它以key-value 键值对的形式来保存数据,键和值(int类型不需要双引号)都用双引号""包裹,使用冒号:分隔。广泛用于网络传输中
查看json和结构体对应关系的网址:https://www.json.cn/

2、结构体序列化和反序列化

注意点:
(1)结构体的标签tag对应的字段必须是大写,否则序列化出来的结果为空字符串。原因:json的方法在json包中实现的,获取结构体字段时必然要跨包访问,如果小写则获取不到字段
(2)Marshal和Unmarshal方法中形参是interface{},因此实参必须是结构体指针。另一个原因在于,反序列化时,需要给实参结构体变量赋值,因此需要传地址
(3)序列化后字符串的顺序和结构体字段的顺序可能会不一样,json序列化后默认按照键的字母排序

type Student struct {
	Id     int    `json:"id"`
	Name   string `json:"name"`
	Sex    byte   `json:"sex"`
	Age    int    `json:"age"`
	Course []int  `json:"course"`
}
//此处重写String()方法,直接打印结构体时会自动调用,注意此处用的是(s Student),而不是指针
func (s Student) String() string {
	return fmt.Sprintf("[%T] Id = %d, Name = %s, Sex = %c, Age = %d, Course = (%T)%v",
		s, s.Id, s.Name, s.Sex, s.Age, s.Course, s.Course)
}

func main() {
	s := Student{20220730, "tom", 'f', 30, []int{90, 95, 80}}
	//序列化
	data, err := json.Marshal(&s)
	if err != nil {
		fmt.Println("序列化失败, err = ", err)
	}
	fmt.Println("序列化后的结果:", string(data))

	//反序列化
	var s2 Student
	str := "{"id":19970606,"name":"json","sex":78,"age":32,"course":[60, 100]}"
	err = json.Unmarshal([]byte(str), &s2) //传地址才能改变原结构体
	if err != nil {
		fmt.Println("反序列化失败, err = ", err)
	}
	fmt.Println("反序列化后的结果:", s2)
}

output:
序列化后的结果: {"id":20220730,"name":"tom","sex":102,"age":30,"course":[90,95,80]}
反序列化后的结果: [main.Student] Id = 19970606, Name = json, Sex = N, Age = 32, Course = ([]int)[60 100]
3、Map序列化和反序列化
func main(){
	m := make(map[string]interface{})
	m["name"] = "Go"
	m["age"] = 13
	m["creator"] = "google"
	m["similar"] = []string{"C", "Java", "Python"}

	//序列化
	data, err := json.Marshal(&m)
	if err != nil {
		fmt.Println("序列化失败, err = ", err)
	}
	fmt.Println("序列化后的结果:", string(data))
	//反序列化
	var m2 map[string]interface{}
	mapstr := "{"name":"Go","age":13,"creator":"google","similar":["C","Java","Python"]}"
	err = json.Unmarshal([]byte(mapstr), &m2) //传地址才能改变原结构体
	if err != nil {
		fmt.Println("反序列化失败, err = ", err)
	}
	fmt.Printf("反序列化后的结果:(%T)  %v", m2, m2)
}

output:
序列化后的结果: {"age":13,"creator":"google","name":"Go","similar":["C","Java","Python"]}
反序列化后的结果:(map[string]interface {})  map[age:13 creator:google name:Go similar:[C Java Python]]
八、面向对象和面向过程 1、比较 1. 1、面向过程编程(POP)

它是直接将解决问题的步骤分析出来,然后用函数把步骤一步一步实现,然后再依次调用就可以了;它一种以过程为中心的编程思想,面向过程编程是基于“冯·诺依曼”模型

  • 优点:流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,便于节点分析
  • 缺点:需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大
1.2、面向对象编程(OOP)

它是将构成问题的事物,分解成若干个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在解决问题过程中的行为。

  • 优点:易维护、易扩展、易复用,有封装、继承、多态的特性、更容易设计出耦合的系统
  • 缺点:性能比面向过程低。
1.3、面向对象的三个特性
  • 封装:封装是将过程和数据包围起来,数据只能通过定义的接口访问。面向对象计算从一个基本概念开始,即现实世界可以表示为一系列完全自治的、封装的对象,这些对象通过受保护的接口访问其他对象。
  • 继承:继承是一种层次模型,它连接类,允许并鼓励类的重用,提供了一种明确表达共性的方法。对象的新类可以从现有类派生,这个过程称为类继承。新类继承原类的属性。新类被称为原类的派生类(子类),原类被称为新类的基类(父类)。
  • 多态:多态允许不同类的对象响应相同的消息。例如,同样的加法,两次相加和两个整数相加,一定是完全不同的。多态语言具有灵活性、抽象性、行为共享性和代码共享性等优点,较好地解决了应用程序功能的同名问题。
1.4、go语言面向对象编程 1.4.1、抽象

在定义一个结构体或者类时,实际上就是把一类事物共有的属性(字段)和行为(方法)提取出来形成一个物理模型。go语言通过结构体来实现抽象,其他语言都是通过类

1.4.2、封装

把抽象出的字段和对字段操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。其他语言将字段和方法封装在类里面,而go语言的结构体中只定义字段,在结构体外定义方法

1.4.3、多态

指相同的消息给予不同的对象会引发不同的动作

变量/函数多态

  • 变量多态是指:基类型的变量(对于C++是引用或指针)可以被赋值基类型对象,也可以被赋值派生类型的对象。
  • 函数多态是指,相同的函数调用界面(函数名与实参表),传送给一个对象变量,可以有不同的行为,这视该对象变量所指向的对象类型而定

静态/动态多态

  • 动态多态(dynamic polymorphism):通过类继承机制和虚函数机制生效于运行期。可以优雅地处理异质对象集合,只要其共同的基类定义了虚函数的接口。也被称为子类型多态(Subtype polymorphism)或包含多态(inclusion polymorphism)。在面向对象程序设计中,这被直接称为多态。
  • 静态多态(static polymorphism):模板也允许将不同的特殊行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为“静态”。可以用来实现类型安全、运行高效的同质对象集合操作。C++ STL不采用动态多态来实现就是个例子。C++中实现的方式有:函数重载、运算符重载、带变量的宏多态、非参数化多态或译作特设多态、参数化多态(把类型作为参数的多态。在面向对象程序设计中,这被称作泛型编程)。Go语言没有这种多态
1.4.4、继承

为防止代码冗余/复用,更利于代码的维护和功能拓展,需要继承方式。其他语言中继承需要用关键字extends来实现,go语言在结构体中第一行加上父结构体名即可

//C++实现继承
class Person{
public:
    void show();
protected:
    char *m_name;
    int m_age;
}
void People::show() {
    cout << m_name << "的年龄是" << m_age << endl;
}

class Student : public Person{
	int id;
}
//Java实现继承
public class Person{
	String name;
    int age;
    public show() {
    	System.out.prinf("name = %s, age = %dn", name, age);
    }
}

public class Student extends Person{
	int id;
	void sleep(){
        System.out.println(name+"sleep");
    }
}
2、实现家庭收支记账软件 2.1、需求

软件显示的界面如下:

-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
收支    收支金额        剩余金额        说明
收入    10000.00        10000.00        工资
支出    100.00          9900.00         买菜

要求:
(1)软件刚进入时,需要显示主界面,并等到用户输入数字进行功能选择
(2)支持4个功能:查看明细、登记收入、等级支出、退出

2.2、分析

因为程序的退出需要用户输入数字选择,因此主页面及这4个功能都需要放在一个循环中,同时添加一个变量来控制循环是否退出
实现的思路:
(1)接收用户输入,需要用到多个变量:功能选择的数字、记录收入/支出、收支的金额、收支的说明
(2)用于统计的变量:统计剩余的金额、统计每一次收支的log

2.3、面向过程的实现
package main
import "fmt"
func main() {
	var num int
	var total, money float32
	var explain, log string
	flag := false
	//记录当前是否有交易
	for loop := true; loop; {
		fmt.Println("-------------家庭收支记账软件-------------")
		fmt.Println(" 1 查看收支明细")
		fmt.Println(" 2 登记收入记录")
		fmt.Println(" 3 登记支出记录")
		fmt.Println(" 4 退出")
		fmt.Printf(" 请选择(1~4):")
		fmt.Scan(&num)
		switch num {
		case 1:
			fmt.Println("-------------当前收支明细记录-------------")
			if flag {
				fmt.Println("收支t收支金额t剩余金额t说明")
				fmt.Println(log)
			} else {
				fmt.Println("当前没有收支记录,请添加一笔收入n")
			}
		case 2:
			fmt.Printf("请输入收入金额:")
			fmt.Scan(&money)
			fmt.Printf("请输入收入说明:")
			fmt.Scan(&explain)
			total += money
			log += fmt.Sprintf("收入t%-10.2ft%-10.2ft%sn", money, total, explain)
			flag = true
		case 3:
			fmt.Printf("请输入支出金额:")
			fmt.Scan(&money)
			fmt.Printf("请输入支出说明:")
			fmt.Scan(&explain)
			if total < money {
				fmt.Println("支出失败,当前余额不足!!!")
			} else {
				total -= money
				log += fmt.Sprintf("支出t%-10.2ft%-10.2ft%sn", money, total, explain)
			}
		case 4:
			loop = false
		default:
			fmt.Println("输入序号有误,请重新输入")
		}
		fmt.Println()
	}
}
PS D:WorkSpaceGolangsrcTest01pop> go run .main.go
-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
当前没有收支记录,请添加一笔收入


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):2
请输入收入金额:10000
请输入收入说明:工资

-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
收支    收支金额        剩余金额        说明
收入    10000.00        10000.00        工资


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):3
请输入支出金额:100
请输入支出说明:买菜

-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
收支    收支金额        剩余金额        说明
收入    10000.00        10000.00        工资
支出    100.00          9900.00         买菜


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):4
2.4、面向对象的实现
package main

import "fmt"

type Account struct {
	total, money float32
	explain, log string
	flag         bool
}

func (a *Account) ShowDealInfo() {
	fmt.Println("-------------当前收支明细记录-------------")
	if a.flag {
		fmt.Println("收支t收支金额t剩余金额t说明")
		fmt.Println(a.log)
	} else {
		fmt.Println("当前没有收支记录,请添加一笔收入n")
	}
}

func (a *Account) ManageIncoming() {
	fmt.Printf("请输入收入金额:")
	fmt.Scan(&a.money)
	fmt.Printf("请输入收入说明:")
	fmt.Scan(&a.explain)
	a.total += a.money
	a.log += fmt.Sprintf("收入t%-10.2ft%-10.2ft%sn", a.money, a.total, a.explain)
	a.flag = true
}

func (a *Account) ManageSpend() {
	fmt.Printf("请输入支出金额:")
	fmt.Scan(&a.money)
	fmt.Printf("请输入支出说明:")
	fmt.Scan(&a.explain)
	if a.total < a.money {
		fmt.Println("支出失败,当前余额不足!!!")
	} else {
		a.total -= a.money
		a.log += fmt.Sprintf("支出t%-10.2ft%-10.2ft%sn", a.money, a.total, a.explain)
	}
}

func (a *Account) ShowMainUI() {
	fmt.Println("-------------家庭收支记账软件-------------")
	fmt.Println(" 1 查看收支明细")
	fmt.Println(" 2 登记收入记录")
	fmt.Println(" 3 登记支出记录")
	fmt.Println(" 4 退出")
	fmt.Printf(" 请选择(1~4):")
}
func GetAccount() *Account {
	return &Account{0, 0, "", "", false}
}

func main() {
	acc := GetAccount()
	num := 0
	ans := "Y"
	for loop := true; loop; {
		acc.ShowMainUI()
		fmt.Scan(&num)
		switch num {
		case 1:
			acc.ShowDealInfo()
		case 2:
			acc.ManageIncoming()
		case 3:
			acc.ManageSpend()
		case 4:
			for {
				fmt.Printf("你确定要退出吗?Y/N:")
				fmt.Scan(&ans)
				if ans == "Y" || ans == "y" {
					loop = false
					break
				} else if ans == "N" || ans == "n" {
					break
				} else {
					fmt.Println("输入错误,请重新输入!")
				}
			}
		default:
			fmt.Println("输入序号有误,请重新输入")
		}
		fmt.Println()
	}
}
PS D:WorkSpaceGolangsrcTest01oop> go run .main.go
-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出        
 请选择(1~4):1
-------------当前收支明细记录-------------
当前没有收支记录,请添加一笔收入


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):2
请输入收入金额:1300
请输入收入说明:补贴

-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
收支    收支金额        剩余金额        说明
收入    1300.00         1300.00         补贴


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):3
请输入支出金额:20
请输入支出说明:买饮料

-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
收支    收支金额        剩余金额        说明
收入    1300.00         1300.00         补贴
支出    20.00           1280.00         买饮料


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):3
请输入支出金额:10000
请输入支出说明:付款
支出失败,当前余额不足!!!

-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):1
-------------当前收支明细记录-------------
收支    收支金额        剩余金额        说明
收入    1300.00         1300.00         补贴
支出    20.00           1280.00         买饮料


-------------家庭收支记账软件-------------
 1 查看收支明细
 2 登记收入记录
 3 登记支出记录
 4 退出
 请选择(1~4):4
你确定要退出吗?Y/N:Y
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1031737.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号