~每一步中很多代码的注释都是跟着上一句写的,可以把注释连起来看~
一、实验原理
1. Huffman编码步骤
(1)统计各个符号(把文件的一个字节看成一个符号)在文件中出现的概率,按照出现概率从小到大排序。
(2)每一次选出概率最小的两个符号作为二叉树的叶节点,合并两个叶节点的概率,合并后的节点作为它们的父节点,直至合并到根节点。
(3)二叉树的左节点为0,右节点为1,从上到下由根节点到叶节点得到每个叶节点的编码。
因此,将节点和码字的数据类型定义如下
typedef struct huffman_node_tag //节点数据类型
{
unsigned char isLeaf; // 1 表示为叶节点,0 表示不是叶节点
unsigned long count; //这个字节在文件中出现的次数
struct huffman_node_tag *parent; //父节点指针
union{
struct{ //如果不是叶节点,这里为左右子节点指针
struct huffman_node_tag *zero, *one;
};
unsigned char symbol; //如果是叶节点,这里为一个字节的8位二进制数值
};
} huffman_node;
//————————————————————————————————————————————————————————
typedef struct huffman_code_tag //码字数据类型
{
unsigned long numbits; //码字长度
/* 码字,一个unsigned char可以保存8个比特位,
因此码字的第1到第8比特由低到高保存在bits[0]中,第9比特到第16比特保存在bits[1]中,以此类推 */
unsigned char *bits;
} huffman_code;
2. 静态链接库的使用
本实验由两个项目组成,第一个项目为 Huffman 编码的具体实现,名为 huff_code,创建项目时选择的是静态库,生成一个 .lib 文件。第二个项目 huff_run 只需要包含这个库即可调用其中的编码函数,后面的其它实验也要用到这个库。项目属性需要配置库目录属性,也就是第一个项目生成文件的路径,和附加依赖性属性,也就是库的名称,如图 1 所示。由于代码中用到了字节序转换的函数 htonl、ntohl,附加依赖项还需包含 ws2_32.lib。
二、实验流程及代码分析
1. Huffman编码流程
(1)读取文件
//--------huffcode.c--------
...
static void usage(FILE* out){ //命令行参数格式
fputs("Usage: huffcode [-i<input file>] [-o<output file>] [-d|-c]\n" "..." , out);
}
//————————————————————————————————————————————————————————
int main(int argc, char** argv)
{
char memory = 0; //memory表示是否对内存数据进行操作
char compress = 1; //compress为1表示编码,0表示解码
const char *file_in = NULL, *file_out = NULL;
FILE *in = stdin, *out = stdout;
while((opt = getopt(argc, argv, "i:o:cdhvm")) != -1){ //读取命令行参数的选项
switch(opt){
case 'i': file_in = optarg; break; // i 为输入文件
case 'o': file_out = optarg; break; // o 为输出文件
case 'c': compress = 1; break; // c 为压缩操作
case 'd': compress = 0; break; // d 为解压缩操作
case 'h': usage(stdout); system("pause"); return 0; // h 为输出参数用法说明
case 'v': version(stdout); system("pause"); return 0; // v 为输出版本号信息
case 'm': memory = 1; break; // m 为对内存数据进行编码
default: usage(stderr); system(