由Menu小项目所引发的对软件工程的思考
学习了孟老师的这几节课程,我学习了如何搭建一个简单的命令行menu小程序,从最简单的程序开始,一步步的根据软件工程的一般规律,进行逐步开发、完善,最终实现了一个比较通用的menu程序,可以让别的开发者进行作为内嵌子程序调用执行。经过这段时间的学习,收益匪浅,于是将一些学到的东西总结如下。
为了menu小程序的开发,首先需要配置VSCOde 的C++环境,因为VScode不带C++的编译器。
1 编译和环境配置
首先从官网下载MinGW-w64,下载Install安装程序,安装过程十分简单,一路next即可。安装完毕之后通过cmd执行gcc -v查看安装是否成功:
安装完成之后我们打开VScode,在扩展部分添加C/C++插件:
之后打开文件夹,在项目根目录下调用终端程序,输入code.命令,即可生成三个配置文件task.json,launch.json,c_cpp_propertities.json,我们修改task.json中的command项和option,将之前的mingw64的目录复制到如下部分即可:
"command": "D:\\2Software\\mingw64\\bin\\g++.exe",
"options": {
"cwd": "D:\\2Software\\mingw64\\bin"
}
至此,环境配置成功,可以运行menu小程序。
2 软件工程一般原理分析
2.1 模块化设计
模块化设计是软件工程开发中的指导思想,是指把整个项目分成相对独立的模块,使每一个模块可以被独立的进行设计和开发。它的本质是软件开发中的关注点分离,把大问题分解成小问题,再逐个击破,实质就是分而治之的思想。
模块化设计可以降低系统中的耦合度,可以进行更好的扩展和可重用。体现在menu项目中,就是数据结构和菜单业务进行分离处理,在逻辑上进行了切分,同时将接口的声明和实现放在不同的文件中,具体的分析详见代码头部的注释:
linklist.h文件进行接口的声明,.c文件进行接口的具体实现,具体使用的时候只需要调用接口即可:
//linklist.h接口的声明,同时把底层数据结构放在这里
typedef struct DataNode
{
char* cmd;
char* desc;
int (*handler)();
struct DataNode *next;
} tDataNode; /* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tDataNode * head, char * cmd);
/* show all cmd in listlist */
int ShowAllCmd(tDataNode * head);
//linklist.c接口的具体实现过程,可以不关注,直接调用即可 tDataNode* FindCmd(tDataNode * head, char * cmd)
{
if(head == NULL || cmd == NULL)
{
return NULL;
}
tDataNode *p = head;
while(p != NULL)
{
if(!strcmp(p->cmd, cmd))
{
return p;
}
p = p->next;
}
return NULL;
} int ShowAllCmd(tDataNode * head)
{
printf("Menu List:\n");
tDataNode *p = head;
while(p != NULL)
{
printf("%s - %s\n", p->cmd, p->desc);
p = p->next;
}
return 0;
}
//main函数,易于扩展,只需要在head数组添加新功能即可,同时在业务逻辑不涉及具体的数据结构,只传入指针,那么下次不使用链表的时候也可以重用。p->handler也有多态的思想在里面: static tDataNode head[] =
{
{"help", "this is help cmd!", Help,&head[1]},
{"version", "menu program v1.0", NULL, NULL}
}; main()
{
/* cmd line begins */
while(1)
{
char cmd[CMD_MAX_LEN];
printf("Input a cmd number > ");
scanf("%s", cmd);
tDataNode *p = FindCmd(head, cmd);
if( p == NULL)
{
printf("This is a wrong cmd!\n ");
continue;
}
printf("%s - %s\n", p->cmd, p->desc);
if(p->handler != NULL)
{
p->handler();
} }
}
2.2 可重用接口
可重用接口这部分是使接口进行通用化,把linklist接口改为更为通用的listable接口,增加了代码的可重用性。分析见代码的顶层注释:
//linktable.h文件
//LinktableNode结构体只保留了最基本的遍历功能,具体的data数据并没有包含,这是因为用户可以自己添加自己所需要的数据,而linktable.h这个通用接口只需要实现最基本的遍历功能即可,无需关心数据,只需关心遍历这一个逻辑,这样就使接口更通用,可重用性更高。 typedef struct LinkTableNode
{
struct LinkTableNode * pNext;
}tLinkTableNode; /*
* LinkTable Type
*/
typedef struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
}tLinkTable; /*
* Create a LinkTable
*/
tLinkTable * CreateLinkTable();
/*
* Delete a LinkTable
*/
int DeleteLinkTable(tLinkTable *pLinkTable);
/*
* Add a LinkTableNode to LinkTable
*/
int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
/*
* Delete a LinkTableNode from LinkTable
*/
int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
/*
* get LinkTableHead
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
/*
* get next LinkTableNode
*/
tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
//主程序调用更加一般的接口来实现 main()
{
InitMenuData(&head);
/* cmd line begins */
while(1)
{
char cmd[CMD_MAX_LEN];
printf("Input a cmd number > ");
scanf("%s", cmd);
tDataNode *p = FindCmd(head, cmd);
if( p == NULL)
{
printf("This is a wrong cmd!\n ");
continue;
}
printf("%s - %s\n", p->cmd, p->desc);
if(p->handler != NULL)
{
p->handler();
} }
}
//使用callback方式进行回调,有点像lamb表达式 int SearchCondition(tLinkTableNode * pLinkTableNode)
{
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if(strcmp(pNode->cmd, cmd) == 0)
{
return SUCCESS;
}
return FAILURE;
} /* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
return (tDataNode*)SearchLinkTableNode(head,SearchCondition);
}
//为了更加通用,可以修改cmd数组,使其变为局部变量,同时增加一个args参数 tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args); int SearchCondition(tLinkTableNode * pLinkTableNode, void * args)
{
char * cmd = (char*) args;
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if(strcmp(pNode->cmd, cmd) == 0)
{
return SUCCESS;
}
return FAILURE;
}
2.3 线程安全
线程安全主要是使用锁机制,在多个进程同时写的时候或者一个读一个写的时候容易发生。可重入的函数不一定是线程安全的,可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重用函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题。不可重用的函数一定不是线程安全的。详细结合代码分析,见代码首行注释
//这个方法一般不会引发线程安全问题,因为创造table会开辟不同的空间
tLinkTable * CreateLinkTable()
{
tLinkTable * pLinkTable = (tLinkTable *)malloc(sizeof(tLinkTable));
if(pLinkTable == NULL)
{
return NULL;
}
pLinkTable->pHead = NULL;
pLinkTable->pTail = NULL;
pLinkTable->SumOfNode = 0;
pthread_mutex_init(&(pLinkTable->mutex), NULL);
return pLinkTable;
} /*
* 删除可能会引发线程安全问题,主要是在free操作上会引发安全问题
*/
int DeleteLinkTable(tLinkTable *pLinkTable)
{
if(pLinkTable == NULL)
{
return FAILURE;
}
while(pLinkTable->pHead != NULL)
{
tLinkTableNode * p = pLinkTable->pHead;
pthread_mutex_lock(&(pLinkTable->mutex));
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1 ;
pthread_mutex_unlock(&(pLinkTable->mutex));
free(p);
}
pLinkTable->pHead = NULL;
pLinkTable->pTail = NULL;
pLinkTable->SumOfNode = 0;
pthread_mutex_destroy(&(pLinkTable->mutex));
free(pLinkTable);
return SUCCESS;
} /*
* 不会引发线程安全问题
*/
int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode)
{
if(pLinkTable == NULL || pNode == NULL)
{
return FAILURE;
}
pNode->pNext = NULL;
pthread_mutex_lock(&(pLinkTable->mutex));
if(pLinkTable->pHead == NULL)
{
pLinkTable->pHead = pNode;
}
if(pLinkTable->pTail == NULL)
{
pLinkTable->pTail = pNode;
}
else
{
pLinkTable->pTail->pNext = pNode;
pLinkTable->pTail = pNode;
}
pLinkTable->SumOfNode += 1 ;
pthread_mutex_unlock(&(pLinkTable->mutex));
return SUCCESS;
} /*
* 一般不会有线程安全问题,但是如果一个进程创建,一个删除,可能会引发线程安全问题
*/
int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode)
{
if(pLinkTable == NULL || pNode == NULL)
{
return FAILURE;
}
pthread_mutex_lock(&(pLinkTable->mutex));
if(pLinkTable->pHead == pNode)
{
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1 ;
if(pLinkTable->SumOfNode == 0)
{
pLinkTable->pTail = NULL;
}
pthread_mutex_unlock(&(pLinkTable->mutex));
return SUCCESS;
}
tLinkTableNode * pTempNode = pLinkTable->pHead;
while(pTempNode != NULL)
{
if(pTempNode->pNext == pNode)
{
pTempNode->pNext = pTempNode->pNext->pNext;
pLinkTable->SumOfNode -= 1 ;
if(pLinkTable->SumOfNode == 0)
{
pLinkTable->pTail = NULL;
}
pthread_mutex_unlock(&(pLinkTable->mutex));
return SUCCESS;
}
pTempNode = pTempNode->pNext;
}
pthread_mutex_unlock(&(pLinkTable->mutex));
return FAILURE;
} /*
读操作,不会引发线程安全问题
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args)
{
if(pLinkTable == NULL || Conditon == NULL)
{
return NULL;
}
tLinkTableNode * pNode = pLinkTable->pHead;
while(pNode != NULL)
{
if(Conditon(pNode,args) == SUCCESS)
{
return pNode;
}
pNode = pNode->pNext;
}
return NULL;
} /*
* 读操作,不会引发线程安全问题
*/
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable)
{
if(pLinkTable == NULL)
{
return NULL;
}
return pLinkTable->pHead;
} /*
* 读操作,不会引发线程安全问题
*/
tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode)
{
if(pLinkTable == NULL || pNode == NULL)
{
return NULL;
}
tLinkTableNode * pTempNode = pLinkTable->pHead;
while(pTempNode != NULL)
{
if(pTempNode == pNode)
{
return pTempNode->pNext;
}
pTempNode = pTempNode->pNext;
}
return NULL;
}
总结:
我们开发软件的时候应该按照软件工程的一般规律,在模块化、可重用接口的设计、以及线程安全的问题上多下文章,尽可能的提高软件开发的效率和软件的质量。
由Menu小项目所引发的对软件工程的思考的更多相关文章
- 小项目特供 贪吃蛇游戏(基于C语言)
C语言写贪吃蛇本来是打算去年暑假写的,结果因为ACM集训给耽搁了,因此借寒假的两天功夫写了这个贪吃蛇小项目,顺带把C语言重温了一次. 是发表博客的前一天开始写的,一共写了三个版本,第一天写了第一版,第 ...
- java小项目之:扫雷,这游戏没有你想的那么简单!
扫雷 我之前分享的小项目和小游戏,电影购票.坦克大战.捕鱼达人.贪吃蛇等,虽然已经是耳熟能详人尽皆知的项目和游戏,但是保不齐真的有人没接触过. 今天分享的这个项目,我不相信没人接触过(仅限80后-00 ...
- 用struts2标签如何从数据库获取数据并在查询页面显示。最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变量。
最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变 ...
- IOS-小项目(饿了么 网络部分 简单实现)
在介绍小项目之前,在此说明一下此代码并非本人所写,我只是随笔的整理者. 在介绍之前先展现一下效果图. 看过效果图大家应该很熟悉了,就是饿了么的一个界面而已,值得注意的是,实现时并没有采用本地连接,而是 ...
- Andriod小项目——在线音乐播放器
转载自: http://blog.csdn.net/sunkes/article/details/51189189 Andriod小项目——在线音乐播放器 Android在线音乐播放器 从大一开始就已 ...
- 模拟XShell的小项目
不知道大家有没有用过XShell这款工具,这款工具通过windows可以远程操作处于开机状态的linux操作系统,也就是说把你的电脑和一台服务器连入网络,你通过输入服务器所在的IP地址建立一个会话就可 ...
- 【PHP小项目使用MVC架构】
小项目名称是雇员管理系统. mvc是一种项目的开发模式,中文名称为模式视图控制器,是强制程序员将数据的输入.处理.输出分开的一种开发模式. 在这个小项目中,控制器使用service作为后缀名. 项目u ...
- MOGRE学习笔记(3)--MOGRE小项目练习
学习OGRE有一段时间了,领导为了检测学习效果,根据已有C++项目,弄一个类似的用c#语言编写的小项目. 配置:win7,DirectX2009,vs2010. 项目要求: 1.有Ogre窗口(尺寸1 ...
- Web前端开发:SQL Jsp小项目(一)
Jsp的学习算是告一段落,针对这段时间的学习,写了一个Jsp小项目来巩固学到的知识. 框架示意图 User list process UserAdd process 需要的界面效果: 需要工具:Ecl ...
随机推荐
- zookeeper watch笔记
ZK其核心原理满足CP, 实现的是最终一致性, 它只保证顺序一致性. zookeeper 基于 zxid 以及阻塞队列的方式来实现请求的顺序一致性.如果一个client连接到一个最新的 followe ...
- Redis小记(一)
1.redis的数据结构 (1)动态字符串(SDS) redis自身构建了一个简单动态字符串的抽象类型,SDS,在redis里,包含字符串的键值对在底层都是由SDS来实现的. 除了用来保存数据库的字符 ...
- 怎样禁用UEFI boot option中的USB启动,防止U盘WIN PE系统黑入电脑?
Title: Disable all UEFI boot options tblatt 问: Hello, system: Latitude E6530 I want to disable all U ...
- MyEclpse 2015在线安装Gradle插件图解
MyEclpse 2015 安装Gradle插件 安装方式:在线安装 一.如何获得Gradle插件在线安装地址 Gradle插件最新在线安装地址可在如下网址中查找: https://github.co ...
- 067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数
067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数 本文知识点:数组作为方法参数 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...
- 【题解】[USACO13FEB]Tractor S
题目戳我 \(\text{Solution:}\) 好久没写啥\(dfs\)了,借这个题整理下细节. 观察到答案具有二分性,所以先求出其差的最大最小值,\(\log val\)的复杂度不成问题. 考虑 ...
- Tomcat 第六篇:类加载机制
1. 引言 Tomcat 在部署 Web 应用的时候,是将应用放在 webapps 文件夹目录下,而 webapps 对应到 Tomcat 中是容器 Host ,里面的文件夹则是对应到 Context ...
- Win10安装MongoDB
1. 下载安装包:mongodb-win32-x86_64-2012plus-4.2.7-signed.msi 2. 安装,注意选择安装目录 3. 新建配置文件mongo.conf: ``` #数据 ...
- 学习Jmeter,这一篇就够了
Jmeter在软件测试中的常用功能,看完你应该就会了 1.Jmeter的下载安装 1.1 Jmeter的官方网址是:http://jmeter.apache.org/:下载最新的包到本地进行解压,路 ...
- 学习WebDav
目录 前言 初识WebDav 有哪些支持webdav的网盘? WebDAV的特性和优势 服务端的搭建 调用WebDav接口 PROPFIND方法 PROPPATCH方法 MKCOL方法 PUT方法 G ...