您好,登录后才能下订单哦!
这篇文章主要讲解了“怎么组织构建多文件C语言程序”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么组织构建多文件C语言程序”吧!
位于第二部分中的 /* 01 项目包含文件 */
的源代码如下:
/* main.c - 喵呜喵呜流式编解码器 */.../* 01 项目包含文件 */#include "main.h"#include "mmecode.h"#include "mmdecode.h"
#include
是 C 语言的预处理命令,它会将该文件名的文件内容拷贝到当前文件中。如果程序员在头文件名称周围使用双引号(""
),编译器将会在当前目录寻找该文件。如果文件被尖括号包围(<>
),编译器将在一组预定义的目录中查找该文件。
main.h 文件中包含了 main.c 文件中用到的定义和类型定义。我喜欢尽可能多将声明放在头文件里,以便我在我的程序的其他位置使用这些定义。
头文件 mmencode.h 和 mmdecode.h 几乎相同,因此我以 mmencode.h
为例来分析。
/* mmencode.h - 喵呜喵呜流编解码器 */ #ifndef _MMENCODE_H#define _MMENCODE_H #include <stdio.h> int mm_encode(FILE *src, FILE *dst); #endif /* _MMENCODE_H */
#ifdef
、#define
、#endif
指令统称为 “防护” 指令。其可以防止 C 编译器在一个文件中多次包含同一文件。如果编译器在一个文件中发现多个定义/原型/声明,它将会产生警告。因此这些防护措施是必要的。
在这些防护内部,只有两个东西:#include
指令和函数原型声明。我在这里包含了 stdio.h
头文件,以便于能在函数原型中使用 FILE
定义。函数原型也可以被包含在其他 C 文件中,以便于在文件的命名空间中创建它。你可以将每个文件视为一个独立的命名空间,其中的变量和函数不能被另一个文件中的函数或者变量使用。
编写头文件很复杂,并且在大型项目中很难管理它。不要忘记使用防护。
该程序的功能是按照字节进行 MeowMeow
字符串的编解码,事实上这是该项目中最简单的部分。截止目前我所做的工作便是支持允许在适当的位置调用此函数:解析命令行,确定要使用的操作,并打开将要操作的文件。下面的循环是编码的过程:
/* mmencode.c - 喵呜喵呜流式编解码器 */... while (!feof(src)) { if (!fgets(buf, sizeof(buf), src)) break; for(i=0; i<strlen(buf); i++) { lo = (buf[i] & 0x000f); hi = (buf[i] & 0x00f0) >> 4; fputs(tbl[hi], dst); fputs(tbl[lo], dst); } }
简单的说,当文件中还有数据块时( feof(3)
),该循环读取(feof(3)
)文件中的一个数据块。然后将读入的内容的每个字节分成两个 hi
和 lo
的半字节。半字节是半个字节,即 4 个位。这里的奥妙之处在于可以用 4 个位来编码 16 个值。我将 hi
和 lo
用作 16 个字符串查找表 tbl
的索引,表中包含了用半字节编码的 MeowMeow
字符串。这些字符串使用 fputs(3)
函数写入目标 FILE
流,然后我们继续处理缓存区的下一个字节。
该表使用 table.h 中的宏定义进行初始化,在没有特殊原因(比如:要展示包含了另一个项目的本地头文件)时,我喜欢使用宏来进行初始化。我将在未来的文章中进一步探讨原因。
我承认在开始工作前花了一些时间。解码的循环与编码类似:读取 MeowMeow
字符串到缓冲区,将编码从字符串转换为字节
/* mmdecode.c - 喵呜喵呜流式编解码器 */ ... int mm_decode(FILE *src, FILE *dst) { if (!src || !dst) { errno = EINVAL; return -1; } return stupid_decode(src, dst); }
这不符合你的期望吗?
在这里,我通过外部公开的 mm_decode()
函数公开了 stupid_decode()
函数细节。我上面所说的“外部”是指在这个文件之外。因为 stupid_decode()
函数不在该头文件中,因此无法在其他文件中调用它。
当我们想发布一个可靠的公共接口时,有时候会这样做,但是我们还没有完全使用函数解决问题。在本例中,我编写了一个 I/O 密集型函数,该函数每次从源中读取 8 个字节,然后解码获得 1 个字节写入目标流中。较好的实现是一次处理多于 8 个字节的缓冲区。更好的实现还可以通过缓冲区输出字节,进而减少目标流中单字节的写入次数。
/* mmdecode.c - 喵呜喵呜流式编解码器 */...int stupid_decode(FILE *src, FILE *dst){ char buf[9]; decoded_byte_t byte; int i; while (!feof(src)) { if (!fgets(buf, sizeof(buf), src)) break; byte.field.f0 = isupper(buf[0]); byte.field.f1 = isupper(buf[1]); byte.field.f2 = isupper(buf[2]); byte.field.f3 = isupper(buf[3]); byte.field.f4 = isupper(buf[4]); byte.field.f5 = isupper(buf[5]); byte.field.f6 = isupper(buf[6]); byte.field.f7 = isupper(buf[7]); fputc(byte.value, dst); } return 0;}
我并没有使用编码器中使用的位移方法,而是创建了一个名为 decoded_byte_t
的自定义数据结构。
/* mmdecode.c - 喵呜喵呜流式编解码器 */... typedef struct { unsigned char f7:1; unsigned char f6:1; unsigned char f5:1; unsigned char f4:1; unsigned char f3:1; unsigned char f2:1; unsigned char f1:1; unsigned char f0:1;} fields_t; typedef union { fields_t field; unsigned char value;} decoded_byte_t;
初次看到代码时可能会感到有点儿复杂,但不要放弃。decoded_byte_t
被定义为 fields_t
和 unsigned char
的 联合。可以将联合中的命名成员看作同一内存区域的别名。在这种情况下,value
和 field
指向相同的 8 位内存区域。将 field.f0
设置为 1
也将会设置 value
中的最低有效位。
虽然 unsigned char
并不神秘,但是对 fields_t
的类型定义(typedef
)也许看起来有些陌生。现代 C 编译器允许程序员在结构体中指定单个位字段的值。字段所在的类型是一个无符号整数类型,并在成员标识符后紧跟一个冒号和一个整数,该整数指定了位字段的长度。
这种数据结构使得按字段名称访问字节中的每个位变得简单,并可以通过联合中的 value
字段访问组合后的值。我们依赖编译器生成正确的移位指令来访问字段,这可以在调试时为你节省不少时间。
最后,因为 stupid_decode()
函数一次仅从源 FILE
流中读取 8 个字节,所以它效率并不高。通常我们尝试最小化读写次数,以提高性能和降低调用系统调用的开销。请记住:少量的读取/写入大的块比大量的读取/写入小的块好得多。
感谢各位的阅读,以上就是“怎么组织构建多文件C语言程序”的内容了,经过本文的学习后,相信大家对怎么组织构建多文件C语言程序这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。