基于停车管理系统的分析与理解
第一节软件工程综合实践课,王老师让我们开始维护以前同学开发出来的程序,理解程序。找到bug,更正bug,重构小部分代码,满足客户的需求,通过理解需求,设计,开发,回归测试去读程。
因此我在CSDN论坛(https://blog.csdn.net/believe_s/article/details/76576283)上找到了一份关于停车管理系统的代码进行了研读。
这个程序的要求是:停车场是一个能放 n 辆车的狭长通道,只有一个大门,汽车按到达的先后次序停放。若车场满了,车要在门外的便道上等候,一旦有车走,则便道上第一辆车进入。当停车场中的车离开时,由于通道窄,在它后面的车要先退出,待它走后依次进入。汽车离开时按停放时间收费。
基本功能要求:
1)建立三个数据结构分别是:停放队列,让路栈,等候队列
2)输入数据模拟管理过程,数据(入或出,车号)。
项目分析:
此项目需要同时使用栈和队列来实现。由于停车场是个狭长通道,所以无论是汽车进入还是汽车驶出都必须从同一端进行,因此需要借助栈来实现。候车场内为等待进入停车场的车辆,需要按照等待顺序的先后进入停车场。
设计目的:
(1)根据车辆到达停车场到车辆离开停车场时所停留的时间进行计时收费。
(2)当有车辆从停车场离开时,等待的车辆按顺序进入停车场停放。实现停车场的调度功能。
(3)用顺序栈来表示停车场,链队表示停车场外的便道。
(4)显示停车场信息和便道信息。
(5)操作提示界面。
1、所用到的存储定义
车辆定义:
typedef struct
{
int timeIn; // 进入停车场时间
int timeOut; // 离开停车场时间
char plate[10];
// 汽车牌照号码,定义一个字符指针类型
}Car;
算法思想分析
(1)、模拟停车场的车辆进出需要输入车辆的信息,比如车辆的车牌号码、自动匹配本地时间,因此,可以定义一个车辆信息结点类型和一个时间节点类型,在链式栈、链式队列中定义结点类型为车辆信息结点类型。
(2)、车辆离开时,需要打印输出车辆的车位号、到达时间、离开时间以及应缴纳的费用。定义expenses函数实现。
(3)、车辆到达时要输入车辆的信息,并以此存放在停车场内;每进入一辆车,要判断停车场(链式栈)是否已经停满,若已满,则提示该车要在便道上等待;若未满,则进行进栈操作。
(4)、车辆的离开,要另外设计一个辅助栈,当一辆汽车要离开时,在其后的车辆要给其让路,让路的汽车就暂时停放在这个栈中。车辆离开后,要判断便道上是否有车辆在等待,若有则进行入栈操作,即将车辆的信息结点进行入栈操作。
(5)、车辆的查询,既可以查询停车场内的车辆,也可以查询便道上等待的车辆,查询显示车辆的当前情况。
以下代码添加了注释
初始代码:
头文件:
//
// Created by PC-Saw on 2018/12/17.
//
#ifndef __PLOT_H__
#define __PLOT_H__
#define Price 1 // 单价可以自己定义n
#define MAX_STOP 10
#define MAX_PAVE 10
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <time.h> // 包含时间函数的头文件
#include <string.h>
// 汽车信息
typedef struct
{
int timeIn; // 进入停车场时间
int timeOut; // 离开停车场时间
char plate[10];
// 汽车牌照号码,定义一个字符指针类型
}Car;
// 停放栈(用于停放车辆)
typedef struct
{
Car Stop[MAX_STOP]; // 用于停放车辆的栈
int top; // 标记栈顶位置
}Stopping;
// 等候队列
typedef struct
{
int count; // 用来指示队中的数据个数
Car Pave[MAX_PAVE]; // 等候停车的队列
int front, rear; // 标记队头和队尾位置
}Pavement;
// 让路栈
typedef struct
{
Car Help[MAX_STOP]; // 用于让路的队列
int top; // 标记站定位置
}Buffer;
Stopping s;
Pavement p;
Buffer b;
Car c;
char C[10];
void stop_to_pave(); // 车停入便道
void car_come (); // 车停入停车位
void stop_to_buff(); // 车进入让路栈
void car_leave (); // 车离开
void welcome (); // 主界面函数
void Display (); // 显示车辆信息
#endif //__PLOT_H__
源文件:
//
// Created by PC-Saw on 2018/12/17.
//
#include "PLot.h"
void stop_to_pave() // 车停入便道
{
// 判断队满
if (p.count > 0 && (p.front == (p.rear + 1) % MAX_PAVE))
{
printf ("便道已满,请下次再来\n");
}
else
{
strcpy(p.Pave[p.rear].plate, C); // 车进入便道
p.rear = (p.rear + 1) % MAX_PAVE; // 队尾指示器加1
p.count++; // 计数器加1
printf ("牌照为%s的汽车停入便道上的%d的位置\n", C, p.rear);
}
}
void car_come() // 车停入停车位
{
printf ("请输入即将停车的车牌号:"); // 输入车牌号
scanf ("%s", &C);
if (s.top >= MAX_STOP - 1) // 如果停车位已满,停入便道
{
stop_to_pave(); // 停入便道
}
else
{
s.top++; // 停车位栈顶指针加1
time_t t1;
long int t = time(&t1); // 记录进入停车场的时间
char* t2;
t2 = ctime (&t1); // 将当前时间转换为字符串
s.Stop[s.top].timeIn = t;
strcpy(s.Stop[s.top].plate, C); // 将车牌号登记
printf ("牌照为%s的汽车停入停车位的%d车位, 当前时间:%s\n", C, s.top + 1, t2);
}
return ;
}
void stop_to_buff() // 车进入让路栈
{
// 停车位栈压入临时栈,为需要出栈的车辆让出道
while (s.top >= 0)
{
if (0 == strcmp(s.Stop[s.top].plate, C))
{
break;
// 让出的车进入让路栈
strcpy(b.Help[b.top++].plate, s.Stop[s.top].plate);
printf ("牌照为%s的汽车暂时退出停车场\n", s.Stop[s.top--].plate);
}
// 如果停车位中的车都让了道,说明停车位中无车辆需要出行
if (s.top < 0)
{
printf ("停车位上无此车消息\n");
}
else
{
printf ("牌照为%s的汽车从停车场开走\n", s.Stop[s.top].plate);
time_t t1;
long int t = time (&t1);
c.timeOut = t; // 标记离开停车场的时间
char* t2;
t2 = ctime (&t1); // 获取当前时间
printf ("离开时间%s\n需付%ld元\n", t2, Price * (c.timeOut - s.Stop[s.top].timeIn));
s.top--;
}
// 将让路栈中的车辆信息压入停车位栈
while (b.top > 0)
{
strcpy(s.Stop[++s.top].plate, b.Help[--b.top].plate);
printf ("牌照为%s的汽车停回停车位%d车位\n", b.Help[b.top].plate, s.top);
}
// 从便道中 -> 停车位
while (s.top < MAX_STOP-1)
{
if (0 == p.count) // 判断队列是否为空
{
break;
} // 不为空,将便道中优先级高的车停入停车位
else
{
strcpy(s.Stop[++s.top].plate, p.Pave[p.front].plate);
printf ("牌照为%s的汽车从便道中进入停车位的%d车位\n", p.Pave[p.front].plate, s.top);
p.front = (p.front + 1) % MAX_PAVE;
p.count--;
}
}
}
void car_leave() // 车离开
{
printf ("请输入即将离开的车牌号:\n");
scanf ("%s", &C);
if (s.top < 0) // 判断停车位是否有车辆信息
{
printf ("车位已空,无车辆信息!\n");
}
else
{
stop_to_buff();
}
}
void Display()
{
int i = s.top;
if (-1 == i)
{
printf ("停车场为空\n");
}
time_t t1;
long int t = time(&t1); // 标记显示时的时间
printf ("\t车牌号\t\t\t停放时间\t\t当前所需支付金额\n");
while (i != -1)
{
printf ("\t%s\t\t%d秒\t\t\t%d元\n", s.Stop[i].plate, t - s.Stop[i].timeIn, Price * (t - s.Stop[i].timeIn) / 10);
i--;
}
}
void welcome()
{
printf ("\t*******************目前停车场状况***********************\n");
printf ("\t停车场共有%d个车位,当前停车场共有%d辆车,等候区共有%d辆车\n", MAX_STOP, s.top+1, (p.rear + MAX_PAVE - p.front)
% MAX_PAVE);
printf ("\t********************************************************\n");
printf ("\t---------------Welcome to our Car Parking---------------\n");
printf ("\t* 1.Parking *\n");
printf ("\t* 2.leaving *\n");
printf ("\t* 3.situation *\n");
printf ("\t* 4.exit *\n");
printf ("\t--------------------------------------------------------\n");
}
主函数:
#include "PLot.h"
int main()
{
// 初始化
s.top = -1;
b.top = 0;
p.rear = 0;
p.count = 0;
p.front = 0;
while(1)
{
//system("clear");
welcome();
int i, cho;
scanf ("%d", &i);
if (1 == i) car_come();
if (2 == i) car_leave();
if (3 == i) Display();
if (4 == i) break;
printf ("返回请输入1\n");
scanf ("%d", &cho);
if (1 == cho)
{
continue;
}
else
{
printf ("您的输入有误,请重新输入\n");
scanf ("%d", &cho);
continue;
}
}
return 0;
}
运行成功后程序运行结果图:
程序项目需求以及代码理解
此程序主要完成的需求是定义了停车车牌号,停车时间,停放所需支付金额以及停放时间等等基础变量,并且撰写代码完成了可以随时输入即将停车的车牌号,并查询其车牌号停入停车位的车位号,停车当前时间。其次可以查询所有已经在停车场的车辆的车牌号,停放时间以及当前所需支付金额。并且能够随时查询展示出即将离开的车牌号,所有的暂时退出停车位的车辆车牌,正在开走的车辆,停回的车辆,以及开走车辆的离开时间已经已付金额。
参数char _array为车辆是进入还是出去,第二个参数num是车牌号,第三个time是到达/离去时间(假设为int),后面两个参数为栈表和队列表的地址。首先判断_array是 A 入库还是 D 出库。如果为入库,就再接一个IF判断函数用于判出库是否已满(栈是否满)。如果车位没有满,就执行上面入库核心代码else里面的函数直接执行入栈操作。ps:车满是if内的,未满写在了else里面。
可能会产生BUG处:
Eg1:在源代码中一处(strcpy(s.Stop[s.top].plate,c);)为了满足用户将车牌登入的功能中,使用到了strcpy的用法,而strcpy的基本用法是:char * strcpy( char * dst, const char * src )。这个函数把字符串src复制到一分配好的字符串空间dst中,复制的时候包括标志字符串结尾的空字符一起复制。操作成功,返回dst,否则返回NULL。而第二个字符串src的长度+1(+1是因为字符串以空子符结束)不能超过为dst开辟的空间的大小,否则src里的空字符无法复制到dst里面,dst就成了字符数组,不是字符串了,那样如果后面有一条printf或者puts语句什么的,要打印出dst的,那就会由于没有空字符标志字符串的结束而越界了。所以如果是包含数字的,字节数应该+1.
Eg2:在头文件运行过程中,产生的问题是:
解决方案是:①不能打开exe文件,很大可能性是cf.exe仍然在运行,如果界面上有的话,先关掉exe,再调试运行;如果界面上看不到,你到任务管理器里查看进程,有cf.exe的话结束该进程,然后进行调试运行即可。②还有一种可能win7系统在C:/Program Files/Microsoft Visual Studio/myprojiects下是找不到你写的工程文件的,因为在这个目录下写东西是需要管理员权限的,所以系统才会报“cannot open file”的错误,解决的办法是关闭软件,然后以管理员权限运行该软件,这样就可以完美运行调试程序了。
Eg3:在源文件运行过程中,产生的问题是:
解决方案是:在运行时,没有按照顺序先运行头文件成功,顺着这个目录,你要检查一下能不能打开这个头文件。想办法打开了,就解决了。
Eg4 :在头文件运行过程中,产生的问题是:
解决方案是:控制台项目要使用windows子系统, 而不是console, 设置:
[Project] --> [Settings] --> 选择"Link"属性页,
在Project Options中将/subsystem:console改成/subsystem:windows.
解决方法方法恰恰相反的情况:
LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16
解决方法是:将project-settings-link的project options里的 /subsystem:windows
改成 /subsystem:console
可以在原本需求上增加的功能但也有可能产生BUG处:(此处的ulCount是指已停车辆)
Eg1:可以增加停车场系统计算更新停车场目前拥有的车辆,新添代码:
unsigned long FUN(unsigned long ulCount){
unsigned long ulSum=0;
while(0<=ulCount)
{ulSum+=ulCount;
ulCount--;}
return ulSum;}
可是这样会产生的问题是ulCount从0加到它本身的值,但是我们看一个数字之前应该知道他的类型,这个一个无符号长整型的数字,这种类型是没有负数的,如果他到0的时候,在减一,就会变成类型可以表示的最大值4294967295 也就是 (2^32 - 1),很明显这个循环就是一个死循环,因此我们要了解到他的类型。
可以在原本需求上增加的功能但也有可能产生BUG处:
Eg2:如果我们想加入更多查询功能,例如停车,取车,停车状态查询等,我们就需要用到switch-case语句,部分代码如:while(1) {
int sel;
scanf("%d",&sel);
fflush(stdin);
switch(sel)
{
case 1:search();break; //停车状态查询
case 2:taxi();break;//停车
case 3:buy();break;//取车
case 4:put();break;//自助充值
case 5:manage();break; //管理员管理系统
case 0:system("pause");system("cls");break;
default :printf("输入错误,请重新输入\n");
}
if(sel>=0 && sel<=5) break;
}
可能会产生bug处:但是使用switch-case语句需要注意(int a = 2, b = 0; switch(a){ case 1: b = 1; break; case 2: if(2 == a) {break;(这里的break的意图是跳到下行)}b = 2;(意图是跳到这里)break;default:b = 3;}(但事实上是直接跳到这里了)printf("b=%d\n",b);这样会导致整个长话网络的瘫痪。
可以更新增加的一些功能:
1、系统可以增加用户停车卡功能(包括客户名称,停车卡余额,停车卡密码以及充值停车卡的业务),这样也包括了查询停车卡信息,停车卡用户信息,停车卡消费信息等等子功能
2、可以添加帮助用户查询并提示用户是否还存在空位,并具体指出空位号,若没有,提示客户离开等信息。
3、增加等待车辆号,并检验车道、便道是否有车。(大致代码如下)
void list1(SeqStackCar *S)
{
int i;
if(S->top > 0)
{
printf("\n车场:");
printf("\n位置 到达时间 车牌号\n");
for(i = 1; i <= S->top; ++i)
{
printf(" %d ",i);
printf("%d:%d ",S->stack[i]->reach.hour,S->stack[i]->reach.min);
puts(S->stack[i]->num);
}
}
else
{
printf("\n车场里没有车。");
}
}void list2(LinkQueueCar *W)
{
QueueNode *p;
p = W->head->next;
if(W->head != W->rear)
{
printf("\n等待车辆号码为:");
while(p != NULL)
{
puts(p->data->num);
p = p->next;
}
}
else
{
printf("\n便道里没有车。");
测试用例
用例ID |
2019/3/3 |
用例名称 |
停车管理系统 |
|
系统信息验证 |
|
|
|
|
用例描述 |
停车车辆号码输入 |
|
|||||||
|
号码输入正确,并且可以检测到当前停车时间,以及车牌号 |
|
|||||||
|
号码输入错误,重新输入 |
|
|||||||
|
查询目前停车场停车状况,包含停车号,停车位,停车位车辆,等候车辆 |
|
|||||||
|
查询目前停车场停车信息,应付金额以及已经停车时间 |
|
|||||||
|
显示即将离开车辆信息,搜索正在离开车辆信息,停入信息 |
||||||||
用例入口 |
打开IE,在地址栏输入相应地址 |
|
|||||||
|
进入该系统登录页面 |
|
测试用例ID |
场景 |
测试步骤 |
|
预期结果 |
|
|
|
备注 |
|
|
|
TC1 |
|
初始页面显示 |
从用例入口进入 |
页面元素完整,显示与详细设计一致 |
|||||||
TC2 |
|
车辆号码输入 |
输入已存在车辆 |
输入成功 |
|||||||
TC3 |
|
号码-容错性验证 |
输入djidji |
系统显示错误,要求重新输入 |
|||||||
TC4 |
|
停车信息页面显示 |
从用例入口进入 |
页面信息展示完整,与详细设计一致 |
|||||||
TC5 |
|
查询即将离开车辆 |
输入已存在车辆 |
输入成功,显示出离开车辆信息 |
心得体会
对于本次读程,我也有自己的心得体会,理解一个项目,不仅仅是要看懂他的代码,也是要理解用户的需求点,是否能够完成基本的客户需求,在完成之上,是否能让用户的适用舒适度和满意度达到标准也是十分重要的。
因此我寻找了许多网上类似的程序,看到了每个项目制作者针对停车管理系统不同需求的见解。其次,在读程过程中,我遇到了许多不理解的部分,有些知识点需要结合上学期学习的课程例如数据结构等结合着理解,因此需要详细的理解定义并且原先编写这段代码人的编程思想和方法,因此我在网上搜索了很多与之相关的知识点并且添加了一些注释。在运行过程中,也产生了许多问题,通过在网上寻找资料,查询答案,在看到了不同的问题之后,我也找到了不同的解决方案,并且我发现,在编写代码时,每个人的编程风格独特,需要将自己完全理解编写者的思想才能完全的理解编写者是如何满足用户需求和期望的。
我也在研读完这段程序后,通过自己的思考以及网上他人类似的程序,我发现了许多可以在本段代码上增加的功能点,因此为了完善此项目,我添加了一些原先没有的功能,但是,在添加过程中我同样发现了一些可能产生的bug和问题点,为了预先防止,我增加了一些预防这些bug,或者产生这些bug后,如何处理的解决方案。
最后,我根据软件测试与开发课上学习到的如何制作测试用例表格,第一次自己制作了一个初级的测试用例表格,用来测试程序运行是否成功,从不同的角度和层次完成了此次维护他人代码的作业,总共用时四小时左右。我认为这个报告中每个部分都是非常重要的,从设计理解需求是支撑这个项目的根本到去读程序这是过度,能够明白编写者是用什么样的手法完成需求的,而增加功能是添花布置,让程序更加完善,最后的测试用例也是非常重要的,因为只有他,才能让我们看到程序是否还存在漏洞和遗漏。