首先感谢孟宁老师的教学指导。这篇文章主要基于孟宁老师上课的内容完成。
仔细阅读分析源代码,结合代码分析其中的软件工程方法、规范或软件工程思想。具体要求如下:
对模块化设计、可重用接口、线程安全等议题结合代码进行理解和分析;
为了在不同环境下保持一致,我们选择Mingw-w64/GCC
brew install gcc gdb # for macOS
可能需要运行xcode-select —install 和brew update
sudo apt install build-essential gdb # for Ubuntu Linux
可能需要运行sudo apt update
https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe for windows
command+shift+p
,再输入edit
回车生成如下文件{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"macFrameworkPath": [
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**",
"/Library/Developer/CommandLineTools/usr/include/c++/v1",
"/usr/local/include",
// "/Library/Developer/CommandLineTools/usr/lib/clang/9.0.0/include",
"/Library/Developer/CommandLineTools/usr/include"
// "/usr/include"
],
"defines": [],
"macFrameworkPath": ["/System/Library/Frameworks", "/Library/Frameworks"],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
command+shift+p
打开命令行工具窗口,输入或者选择Tasks: Configure Task
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "msbuild",
"args": [
// Ask msbuild to generate full paths for file names.
"/property:GenerateFullPaths=true",
"/t:build",
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"presentation": {
// Reveal the output only if unrecognized errors occur.
"reveal": "silent"
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile"
}
]
}
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "c++",
"command": "clang++",
"type": "shell",
"args": ["./c++/hello.cpp", "-std=c++11", "-g"],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
}
]
}
launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) 启动",
"type": "cppdbg",
"request": "launch",
"program": "输入程序名称,例如 ${workspaceFolder}/a.out",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb"
}
]
}
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "c/c++ Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/a.out",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"MIMode": "lldb",
"preLaunchTask": "c++"
}
]
}
更具体配置可参考:https://code.visualstudio.com/docs/cpp/config-clang-mac
c
源码文件是主要起作用的文件,后面的test
开头的c文件都是具体使用对应模块进行自定义menu的案例。linktable
文件主要操作对应数据指针make
命令编译文件,然后执行一个test案例即可./test
这里执行的是这个。执行之后界面如下:quit
和version
命令测试函数声明和定义
声明:函数功能的描述
定义:函数的具体实现
函数声明和定义分开放置的好处
编译速度:将所有包含的文件连接在一起然后进行解析时,减少包含文件中代码的数量和复杂性将缩短编译时间。
避免代码重复/内联:如果您在头文件中完全定义了一个函数,则包含该头并引用该函数的每个目标文件都将包含该函数的自身版本。附带说明一下,如果要进行内联,则需要将完整的定义放入头文件中(在大多数编译器中)。
封装/清晰度:一个定义良好的类/函数集以及一些文档应足以供其他开发人员使用您的代码。 (理想情况下)不需要他们了解代码的工作原理-那么为什么要求他们筛选代码呢? (当然,相反的说法是,当需求仍然存在时,对于他们访问实现可能会很有用)。
在此模块中的具体体现就是:在.h
文件中进行函数声明,在.c
文件中进行具体的函数实现。如下图:
menu.h
menu.c
注释和版权信息:注释也要使用英文,不要使用中文或特殊字符,要保持源代码是ASCII字符格式文件;
不要解释程序是如何工作的,要解释程序做什么,为什么这么做,以及特别需要注意的地方;
每个源文件头部应该有版权、作者、版本、描述等相关信息。
- 在代码中的具体体现:
不同语言都有其命名规范,C语言变量命名是驼峰式的,在老师的代码中体现的淋漓尽致。
typedef struct DataNode { tLinkTableNode * pNext; char* cmd; char* desc; int (*handler)(int argc, char *argv[]); } tDataNode;
struct LinkTable
{
tLinkTableNode *pHead;
tLinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
};
```
这里还增加了一个锁,增加了操作的安全性。
2.1 内聚度与耦合度
内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。
在这个模块设计中如何体现:
- 体现1:
![](https://img2020.cnblogs.com/blog/2164312/202010/2164312-20201028191500636-1183978996.png)
`linktable`这两个文件中主要是对操作节点链的处理。每增加或者删除一个操作,只要调用这两个文件里面的函数即可,而不用再自己写了。
增加了复用性,降低了耦合性。
- 体现2:
```
/* show all cmd in listlist */
int ShowAllCmd(tLinkTable * head)
{
tDataNode * pNode = (tDataNode*)GetLinkTableHead(head);
while(pNode != NULL)
{
printf(" * %s - %s\n", pNode->cmd, pNode->desc);
pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode);
}
return 0;
}
int Help(int argc, char *argv[])
{
ShowAllCmd(head);
return 0;
}
int SetPrompt(char * p)
{
if (p == NULL)
{
return 0;
}
strcpy(prompt,p);
return 0;
}
/* add cmd to menu */
int MenuConfig(char * cmd, char * desc, int (*handler)())
{
tDataNode* pNode = NULL;
if ( head == NULL)
{
head = CreateLinkTable();
pNode = (tDataNode*)malloc(sizeof(tDataNode));
pNode->cmd = "help";
pNode->desc = "Menu List";
pNode->handler = Help;
AddLinkTableNode(head,(tLinkTableNode *)pNode);
}
pNode = (tDataNode*)malloc(sizeof(tDataNode));
pNode->cmd = cmd;
pNode->desc = desc;
pNode->handler = handler;
AddLinkTableNode(head,(tLinkTableNode *)pNode);
return 0;
}
/* Menu Engine Execute */
int ExecuteMenu()
{
/* cmd line begins */
while(1)
{
int argc = 0;
char *argv[CMD_MAX_ARGV_NUM];
char cmd[CMD_MAX_LEN];
char *pcmd = NULL;
printf("%s",prompt);
/* scanf("%s", cmd); */
pcmd = fgets(cmd, CMD_MAX_LEN, stdin);
if(pcmd == NULL)
{
continue;
}
/* convert cmd to argc/argv */
pcmd = strtok(pcmd," ");
while(pcmd != NULL && argc < CMD_MAX_ARGV_NUM)
{
argv[argc] = pcmd;
argc++;
pcmd = strtok(NULL," ");
}
if(argc == 1)
{
int len = strlen(argv[0]);
*(argv[0] + len - 1) = ‘\0‘;
}
tDataNode *p = (tDataNode*)SearchLinkTableNode(head,SearchConditon,(void*)argv[0]);
if( p == NULL)
{
continue;
}
printf("%s - %s\n", p->cmd, p->desc);
if(p->handler != NULL)
{
p->handler(argc, argv);
}
}
}
```
这里主要是把添加的菜单项放入menu中。功能集中,内聚度低,里面的其他功能直接复用上面已经定义的节点操作,耦合度低。
2.2 接口模块
软件模块接口在面向过程的语言中一般是定义一些数据结构和函数接口API,在面向对象的编程语言中一般在类或接口类中定义一些公有的(public)属性和方法。两类编程语言中接口形式上有很大不同,但是不管是函数接口API还是公有的方法本质上都是函数定义。我们将重点介绍两种函数接口方式,即Call-in方式的函数接口和Callback方式的函数接口。这里我们先来理解函数接口规格。
具体体现:
`tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);`
该接口的目标是从链表中取出链表的头节点,函数名GetLinkTableHead清晰明确地表明了接口的目标;
该接口的目标是从链表中取出链表的头节点,函数名GetLinkTableHead清晰明确地表明了接口的目标;
该接口的前置条件是链表必须存在使用该接口才有意义,也就是链表pLinkTable != NULL;
使用该接口的双方遵守的协议规范是通过数据结构tLinkTableNode和tLinkTable定义的;
使用该接口之后的效果是找到了链表的头节点,这里是通过tLinkTableNode类型的指针作为返回值来作为后置条件,C语言中也可以使用指针类型的参数作为后置条件;
该接口没有特别要求接口的质量属性,如果搜索一个节点可能需要在可以接受的延时时间范围内完成搜索;
原文:https://www.cnblogs.com/shizi-4/p/13890512.html