关于 C 语言软件项目的文件结构组织

作者 Marlous 日期 2019-02-21
关于 C 语言软件项目的文件结构组织

参考:
1、 C程序怎样组织更有结构性
2、 C语言综合项目实战
3、 C语言头文件&& 实现文件 && 工程文件组织
4、 C语言项目中头文件/代码文件的组织问题(使用VC6.0)
5、 C语言多文件项目的例子
6、 Nginx 项目实例
7、 软件工程(C语言实践篇)学习心得总结
8、 软件工程(C编码实践篇)学习总结
9、 中国科学技术大学的《软件工程(C 编码实战)》
10、 参考博文《软件工程 C 实践二 课程总结笔记》
11、 C语言头文件组织与包含原则
12、 Nginx代码风格图示

一 接口定义内容

补充:不同部分隔两行区分。参考 Nginx 头文件

  • 版权等信息
  • #ifndef、#define
  • 包含的头文件
  • 宏定义
  • 结构体等声明
  • 函数声明

二 几种头文件写法

1 为用户提供调用接口

  1. 概念:
    有一些头文件是为用户提供调用接口,这种头文件中声明了模块中需要给其他模块使用的函数和数据。

  2. 规则:

  • 一个模块一个接口,不能几个模块用一个接口。
  • 文件名为和实现模块的 c 文件相同。
  • 尽量不要使用 extern 来声明一些共享的数据。因为这种做法是不安全的,外部其他模块的用户可能不能完全理解这些变量的含义,最好提供函数访问这些变量。
  • 尽量避免包含其他的头文件,除非这些头文件是独立存在的。这一点的意思是,在作为接口的头文件中,尽量不要包含其他模块的那些暴露 .c 文件中内容的头文件,但是可以包好一些不是用来暴露接口的头文件。
  • 不要包含那些只有在可执行文件中才使用的头文件,这些头文件应该在 .c 文件中包含。这一点如同上一点,为了提高接口的独立性和透明度。
  • 接口文件要有面向用户的充足的注释。从应用角度描述每个暴露的内容。
  • 接口文件在发布后尽量避免修改,即使修改也要保证不影响用户程序。

2 多个代码文件使用一个接口文件

  1. 概念:
    多个代码文件使用一个接口文件:这种头文件用于那些认为一个模块使用一个文件太大的情况。对于这种情况对于这种情况在参考上述建议后,也要参考以下建议。

  2. 规则:

  • 多个代码文件组成的一个模块只有一个接口文件。因为这些文件完成的是一个模块。
  • 使用模块下文件命名 <系统名><模块名命名>。
  • 不要滥用这种文件。
  • 有时候也会出现几个 .c 文件用于共享数据的 .h 文件,这种文件的特点是在一个 .c 文件里定义全局变量,而在其他 .c 文件里使用,要将这种文件和用于暴露模块接口的文件区别。
  • 一个模块如果有几个子模块,可以用一个 .h 文件暴露接口,在这个文件里用 include 包含每个子模块的接口文件。

3 说明性头文件

  1. 概念:
    还有一种头文件,说明性头文件,这种头文件不需要有一个对应的代码文件,在这种文件里大多包含了大量的宏定义,没有暴露的数据变量和函数。

  2. 规则:

  • 包含一些需要的概念性的东西。
  • 命名方式,定义的功能 .h。
  • 不包含任何其他的头文件。
  • 不定义任何类型。
  • 不包含任何数据和函数声明。

三 建议

  • 命名方式:模块名.c。
  • 用 static 修饰本地的数据和函数。
  • 不要使用 external,这是在 .h 中使用的,可以被包含进来。
  • 无论什么时候定义内部的对象,确保独立与其他执行文件。
  • 这个文件里必须包含相应功能函数。

四 接口写法示例

1 一般模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifndef _LINK_TABLE_H_
#define _LINK_TABLE_H_


#include <pthread.h>


#define SUCCESS 0
#define FAILURE (-1)


typedef struct LinkTableNode
{
struct LinkTableNode pNext;
}tLinkTableNode;


typedef struct LinkTable tLinkTable;


tLinkTable * CreateLinkTable();

int DeleteLinkTable(tLinkTable *pLinkTable);

int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args),void *args);

tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);

tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);


#endif /* _LINK_TABLE_H_ */

2 子系统

  • 定义:

    1
    2
    3
    4
    .h 文件:

    int MenuConfig(char *cmd, char *desc, int (*handler)());
    int ExcuteMenu();
  • 调用子系统:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .c 文件:

    int main(int argc, char **argv)
    {
    MenuConfig("version", "xxx v1.0(Menu program v3.0.0 inside)", NULL);
    MenuConfig("argtest", "test arg option", argtest);
    MenuConfig("quit", "quit from xxx", Quit);
    ExcuteMenu();
    return 0;
    }

五 实践实例

  1. 头文件包含问题:
  • 头文件分散在不同地方:
    选中工程再右键点击,选择右键菜单的 Propertise:C/C++ Build -> Settings -> Tool Settings -> Includes 点击添加的符号,会出现选择头文件的对话框,此时可以选择头文件或头文件所在目录。需要将涉及到的文件夹都添加。
    头文件分散在不同地方

  • 头文件集中在 includes 文件夹中:
    只要如上,添加 includes 文件夹就行了。

  1. 项目文件结构:
    项目文件结构
  • 如图(未完成),只有一个线性表模块;主程序由线性表模块等构成;每个模块由子模块(modules 文件夹中)构成。
  • 与文件名、文件夹名(包括加上上级文件名字的)相同的 .h 文件是该模块的接口。
  • 每个 c 文件先包含 common.h(主程序内容相关重要的定义),然后包含自己的或自己层级(多个子模块集中接口).h 文件,最后包含本 c 程序文件自己要用到的模块(本项目自己的模块)的接口文件。
  • core 文件夹为主程序文件夹。/ common.h 是为主程序提供的接口(所有子模块的聚合接口文件)。
  • 补充:每个 .h 接口文件,需要向外暴露的声明在 .h 里,不需要暴露的声明在 .c 里。

六 小结

  1. 概述:
  • 每个 .c 都有一个对应的 .h 文件。/ 或多个 .c 对应一个 .h 文件。
  • 每个 .c 都包含自己的头文件和需要用到其它 .c 中函数的头文件。/ 改进:包含公共的头文件(通常多处都用到的如 stdio、stdlib 等)、自己的头文件、要用到的头文件。
  • .h 文件里只写声明,不写定义。
  • 自己 .c 的声明函数原型写在自己的 .h 文件中。用 static 限制只在自己的 .c 中使用。
  1. 头文件内容:
  • 版权等信息
  • #ifndef、#define
  • 包含的头文件
  • 宏定义
  • 结构体等声明
  • 函数声明