代码中的软件工程--menu项目分析

前言

首先感谢孟宁老师对本次课题的教学指导。
这次我们要通过一个命令行的菜单小程序,去分析其中的软件工程方法、规范以及软件工程思想。

参考资料:https://gitee.com/mengning997/se/blob/master/README.md#代码中的软件工程

1.使用VS code完成编译和调试环境配置

先安装如下图所示的C/C++扩展

下载编译器

Mac上可能已经安装了Clang。要验证它是否正确,先打开macOS终端窗口,然后输入以下命令:
clang --version
如果未安装Clang,就输入以下命令以安装
xcode-select --install

在macOS终端上,创建一个名为的空文件夹project,存储所有VS Code项目,然后创建一个名为的子文件夹test,导航至该子文件夹,然后在该文件夹中打开VS Code:

code .命令在当前工作文件夹中打开VS Code,该文件夹成为“工作区”。.vscode在工作区的文件夹中创建三个文件:

  • tasks.json (编译器构建设置)
  • launch.json (调试器设置)
  • c_cpp_properties.json (编译器路径和IntelliSense设置)

在test工作区下新建文件并命名该文件test.cpp

编译test.cpp

接下来,创建一个tasks.json文件来告诉VS Code如何构建(编译)程序。该任务将调用Clang C ++编译器从源代码创建可执行文件。
test.cpp在编辑器中打开很重要,因为下一步将使用编辑器中的活动文件作为上下文,以在下一步中创建构建任务。
从主菜单中,选择终端 > 配置默认生成任务。将出现一个下拉列表,列出VS Code在计算机上找到的编译器的各种预定义构建任务。选择“ C / C ++ clang ++构建活动文件”以构建当前在编辑器中显示(活动)的文件。

这将tasks.json在.vscode文件夹中创建一个文件,然后在编辑器中将其打开。

现在将tasks.json中代码更换为

{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "type": "shell",
      "label": "clang++ build active file",
      "command": "/usr/bin/clang++",
      "args": [
        "-std=c++17",
        "-stdlib=libc++",
        "-g",
        "${file}",
        "-o",
        "${fileDirname}/${fileBasenameNoExtension}"
      ],
      "options": {
        "cwd": "${workspaceFolder}"
      },
      "problemMatcher": ["$gcc"],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ]
}

做出了如下改变

  • "args"已更新为可使用C ++ 17进行编译,因为我们test.cpp使用的是C ++ 17语言功能。
  • 将当前工作目录指令("cwd")更改为其中的文件夹test.cpp。

运行构建

回到test.cpp。因为我们要构建test.cpp文件,所以该文件必须在编辑器中处于活动状态,以便进行下一步。
要运行您在task.json中定义的构建任务,按⇧ ⌘ B或从终端主菜单中选择运行构建任务。
任务开始时,可以看到集成终端窗口出现在代码编辑器下方。任务完成后,终端将显示编译器的输出,指示构建成功还是失败。对于成功的Clang构建,输出看起来像这样:

输入ls,现在看到可执行文件test以及调试文件(test.dSYM)。

现在可以输入./test执行了,test写的是一个模拟栈的程序。

修改task.json

可以tasks.json使用"${workspaceFolder}/*.cpp"代替的参数来修改以构建多个C ++文件${file}。这将生成.cpp当前文件夹中的所有文件。也可以通过替换"${fileDirname}/${fileBasenameNoExtension}"为硬编码的文件名(例如"${workspaceFolder}/myProgram.out")来修改输出文件名。

现在已经完成了配置。欲了解更多配置详情,可以参见
https://code.visualstudio.com/docs/cpp/config-clang-mac

打开menu项目,先make编译所有文件,执行./test文件。






2、编写高质量代码

2.1 代码风格与规范

代码风格分原则是简明、易读、无二义性。
要写出stupid的代码,也就是让人一眼就能看懂的代码,而不要在写代码时各种炫技,虽然看起来很帅,但是增加了阅读代码的难度,是不可取的。
要在代码中写注释吗?
最精简的是无注释,理想的状态是即便没有注释,也能通过函数、变量等的命名直接理解代码。其次是简短的注释。
最后是将函数功能、各参数的含义和输入/输出用途等一一列举,这往往是模块的对外接口,以方便自动生成开发者文档。
下面是menu程序中优秀的程序块头部注释范例

  • 内容完整、布局整齐的程序块头部注释
/* Copyright (C) SSE-USTC, 2012-2013                                */
/*                                                                  */
/*  FILE NAME             :  linktabe.c                             */
/*  PRINCIPAL AUTHOR      :  Mengning                               */
/*  SUBSYSTEM NAME        :  LinkTable                              */
/*  MODULE NAME           :  LinkTable                              */
/*  LANGUAGE              :  C                                      */
/*  TARGET ENVIRONMENT    :  ANY                                    */
/*  DATE OF FIRST RELEASE :  2012/12/30                             */
/*  DESCRIPTION           :  interface of Link Table                */
/********************************************************************/

/*
 * Revision log:
 *
 * Created by Mengning,2012/12/30
 * Provide right Callback interface by Mengning,2012/09/17
 *
 */
  • 模块前简短的功能介绍
/*
 * Create a LinkTable
 */
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;
}

代码风格规范总结:

  • 缩进:4个空格;
  • 行宽:< 100个字符;
  • 代码行内要适当多留空格,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for (i=0; i<10; i++)和if ((a<=b) && (c<=d));
  • 在一个函数体内,逻揖上密切相关的语句之间不加空行,逻辑上不相关的代码块之间要适当留有空行以示区隔;
  • 在复杂的表达式中要用括号来清楚的表示逻辑优先级;
  • 花括号:所有 ‘{’ 和 ‘}’ 应独占一行且成对对齐;
  • 不要把多条语句和多个变量的定义放在同一行;
  • 命名:合适的命名会大大增加代码的可读性;
  • 类名、函数名、变量名等的命名一定要与程序里的含义保持一致,以便于阅读理解;
  • 类型的成员变量通常用m_或者_来做前缀以示区别;
  • 一般变量名、对象名等使用LowerCamel风格,即第一个单词首字母小写,之后的单词都首字母大写,第一个单词一般都表示变量类型,比如int型变量iCounter;
  • 类型、类、函数名等一般都用Pascal风格,即所有单词首字母大写;
  • 类型、类、变量一般用名词或者组合名词,如Member
  • 函数名一般使用动词或者动宾短语,如get/set,RenderPage;
  • 注释和版权信息:注释也要使用英文,不要使用中文或特殊字符,要保持源代码是ASCII字符格式文件;
  • 不要解释程序是如何工作的,要解释程序做什么,为什么这么做,以及特别需要注意的地方;
  • 每个源文件头部应该有版权、作者、版本、描述等相关信息。

2.2 编写高质量代码的基本方法

  • 通过控制结构简化代码
  • 通过数据结构简化代码(后面实现代码中将数组改成链表,简洁了代码)
  • 添加错误处理






3、模块化设计

模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这个做法背后的基本原理是关注点的分离 (SoC, Separation of Concerns)。
模块化设计是软件工程领域最重要的原则,其实就是分而治之的意思。
软件设计好坏的重要指标是内聚度和耦合度。
一般在软件设计中我们追求松散耦合,理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。

软件设计的一些基本方法

  • KISS(Keep It Simple & Stupid)原则

一行代码只做一件事
一个块代码只做一件事
一个函数只做一件事
一个软件模块只做一件事

  • 使用本地化外部接口来提高代码的适应能力
  • 不要和陌生人说话原则
  • 先写伪代码的代码结构更好一些
  • using design to frame the code(matching design with implementation)

最初的代码


/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015                                                  */
/*                                                                                                */
/*  FILE NAME             :  menu.c                                                               */
/*  PRINCIPAL AUTHOR      :  Mengning                                                             */
/*  SUBSYSTEM NAME        :  menu                                                                 */
/*  MODULE NAME           :  menu                                                                 */
/*  LANGUAGE              :  C                                                                    */
/*  TARGET ENVIRONMENT    :  ANY                                                                  */
/*  DATE OF FIRST RELEASE :  2014/08/31                                                           */
/*  DESCRIPTION           :  This is a menu program                                               */
/**************************************************************************************************/

/*
 * Revision log:
 *
 * Created by Mengning, 2014/08/31
 *
 */


#include <stdio.h>
#include <stdlib.h>

int Help();
int Quit();

#define CMD_MAX_LEN 128
#define DESC_LEN    1024
#define CMD_NUM     10

typedef struct DataNode
{
    char*   cmd;
    char*   desc;
    int     (*handler)();
    struct  DataNode *next;
} tDataNode;

static tDataNode head[] = 
{
    {"help", "this is help cmd!", Help,&head[1]},
    {"version", "menu program v1.0", NULL, &head[2]},
    {"quit", "Quit from menu", Quit, NULL}
};

int main()
{
    /* cmd line begins */
    while(1)
    {
        char cmd[CMD_MAX_LEN];
        printf("Input a cmd number > ");
        scanf("%s", cmd);
        tDataNode *p = head;
        while(p != NULL)
        {
            if(strcmp(p->cmd, cmd) == 0)
            {
                printf("%s - %s\n", p->cmd, p->desc);
                if(p->handler != NULL)
                {
                    p->handler();
                }
                break;
            }
            p = p->next;
        }
        if(p == NULL) 
        {
            printf("This is a wrong cmd!\n ");
        }
    }
}

int Help()
{
    printf("Menu List:\n");
    tDataNode *p = head;
    while(p != NULL)
    {
        printf("%s - %s\n", p->cmd, p->desc);
        p = p->next;
    }
    return 0; 
}

int Quit()
{
    exit(0);
}

上面基本把所有功能都放在引擎中了,包括一些可能发生变更的部分,这是不符合模块化设计思想的。我们将引擎中的链表遍历抽取出来封装,把可能发生变更的部分放到一起。

//使用链表存储可能不是经过深思熟虑的,菜单业务本身需求没有变,可能其中性能的需求会变化。
//将链表的遍历抽取出来,下次更换数据结构时仍然不用改变引擎中的代码。
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; 
}

/* menu program */

static tDataNode head[] = 
{
    {"help", "this is help cmd!", Help,&head[1]},
    {"version", "menu program v1.0", NULL, NULL}
};

int 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();
        }
   
    }
}

上面代码已经初步体现了模块化的思想,代码的可重用行变高。
然而还不是最理想的状态,因为都在一个文件下,想要修改功能还是得改这个文件(包含很多功能)。
所以我们可以把不同的模块放到不同的源文件中


/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015                                                  */
/*                                                                                                */
/*  FILE NAME             :  linklist.c                                                           */
/*  PRINCIPAL AUTHOR      :  Mengning                                                             */
/*  SUBSYSTEM NAME        :  menu                                                                 */
/*  MODULE NAME           :  linklist                                                             */
/*  LANGUAGE              :  C                                                                    */
/*  TARGET ENVIRONMENT    :  ANY                                                                  */
/*  DATE OF FIRST RELEASE :  2014/09/10                                                           */
/*  DESCRIPTION           :  linklist for menu program                                            */
/**************************************************************************************************/

/*
 * Revision log:
 *
 * Created by Mengning, 2014/09/10
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include "linklist.h"


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; 
}

/**************************************************************************************************/
/* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015                                                  */
/*                                                                                                */
/*  FILE NAME             :  linklist.h                                                           */
/*  PRINCIPAL AUTHOR      :  Mengning                                                             */
/*  SUBSYSTEM NAME        :  menu                                                                 */
/*  MODULE NAME           :  linklist                                                             */
/*  LANGUAGE              :  C                                                                    */
/*  TARGET ENVIRONMENT    :  ANY                                                                  */
/*  DATE OF FIRST RELEASE :  2014/09/10                                                           */
/*  DESCRIPTION           :  linklist for menu program                                            */
/**************************************************************************************************/

/*
 * Revision log:
 *
 * Created by Mengning, 2014/09/10
 *
 */


/* data struct and its operations */

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);

以上将一个文件下的不同功能模块放到不同的文件,充分体现了可重用设计思想,但只实现了面向接口编程,并不是可重用的,想要实现可重用,需要通用的接口设计。






4.可重用接口

上面lab3.3中linklist.c和linklist.h并没有实现可重用,分离出来的数据结构和它的操作还有很多菜单业务上的痕迹。因此这里linktable模块,这一模块是可重用的,只对外暴露其接口。linktable模块只做与数据处理有关的操作,不涉及对menu业务的操作,进一步实现了内聚,降低了耦合度。
linktabe.c

/*
 * Create a LinkTable
 */
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;
}
/*
 * Delete a LinkTable
 */
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;		
}
/*
 * Add a LinkTableNode to LinkTable
 */
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;		
}
/*
 * Delete a LinkTableNode from LinkTable
 */
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;		
}

/*
 * get LinkTableHead
 */
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable)
{
    if(pLinkTable == NULL)
    {
        return NULL;
    }    
    return pLinkTable->pHead;
}

/*
 * get next LinkTableNode
 */
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;
}




linktabe.h

/*
 * LinkTable Node Type
 */
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);

#endif /* _LINK_TABLE_H_ */

从以上代码可以看出,linktable.c可以根据需要定义不同的数据结构,关键点如下:

typedef struct DataNode
{
    tLinkTableNode * pNext;
    char*   cmd;
    char*   desc;
    int     (*handler)();
} tDataNode;

然而这还不是完美的,现在利用callback函数参数使Linktable的查询接口更加通用

/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode));

Linktable增加Callback方式的接口,需要两个函数接口,一个是call-in方式函数,如SearchLinkTableNode函数,其中有一个函数作为参数,这个作为参数的函数就是callback函数,如代码中Conditon函数。
callback接口方式中,SearchLinkTableNode函数使用Conditon参数接收pNode,一旦发现接收到,就返回pNode
call-in方式的函数接口SearchLinkTableNode增加了一个参数argscallback函数Conditon也增加了一个参数args

/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode,void * args);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);

前面的方式中用户程序定义了的SearchCondition,使用了全局变量cmd进行消息传递。修改cmd数组,使其变为局部变量,利用callback回调函数参数使Linktable的查询接口更加通用,有效地提高了接口的通用性。
通过将linktable.h中在接口调用时不是必须内容转移到linktable.c中,这样可以有效地隐藏软件模块内部的实现细节,为外部调用接口的开发者提供更加简洁的接口信息,同时也减少外部调用接口的开发者有意或无意的破坏软件模块的内部数据。通过接口进行信息隐藏已经成为面向对象编程语言的标准做法,使用public和private来声明属性和方法对于外部调用接口的开发者是否可见。

通用接口总结

通用接口定义的基本方法

  • 参数化上下文
  • 移除前置条件
  • 简化后置条件






5、线程安全

如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。


//线程安全,一般创建都是开辟新的空间,不会出现资源共享的情况,所以与其他线程不会产生冲突
/*
 * Create a LinkTable
 */
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操作过后,另一个线程又对这一块空间free,就会出现异常。
/*
 * Delete a LinkTable
 */
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;		
}


//线程安全,可重入的,加锁解锁合理
/*
 * Add a LinkTableNode to LinkTable
 */
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;		
}


//删除一个结点,通过锁的机制,也是可重入的,线程安全
/*
 * Delete a LinkTableNode from LinkTable
 */
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;		
}

//查找是可重入的,线程安全
/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode);
 */
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;
}






总结

我们在进行开发时,要深入理解模块化设计、可重用接口、线程安全等方面,灵活运用,这样我们写的代码才合理,才符合软件工程的思想,才能在开发过程中效益最大化。

posted @ 2020-11-03 23:08  程鹏777  阅读(175)  评论(0编辑  收藏  举报