2

这是我难以解决的家庭作业的一部分。

我有一个简单的结构:

typedef struct Client {
    char* lname;
    unsigned int id;
    unsigned int car_id;
} Client;

练习是:

  1. 创建一个名为公司名称的文本文件,然后创建一个带有 txt 扩展名的分支号。该文件包含所有客户的详细信息。

  2. 您在练习 1 中创建的文件将被压缩。因此,将创建一个扩展名为 .cmpr 的二进制文件。

我真的不知道如何实现2。

我记得在讲座上教授说我们必须使用“所有”变量,以及二元运算符(<<、>>、|、&、~),但我不知道如何使用它。

我在 GCC 和 Eclipse 下使用 Ubuntu。我正在使用 C。

我很乐意得到帮助。谢谢!

4

1 回答 1

6

假设步骤 1 中的文件如下所示:

user1798362
2324
462345

其中三个字段简单地打印在三行上。请注意,以上是该文件的文本/可读(即 ASCII)表示。

以十六进制(十进制)表示形式查看该文件的内容,我们得到(在每个字节值下方打印 ASCII 字符):

75 73 65 72 31 37 39 38 33 36 32 0a 32 33 32 34 0a 34 36 32 33 34 35 0a
 u  s  e  r  1  7  9  8  3  6  2 nl  2  3  2  4 nl  4  6  2  3  4  5 nl                                

这里nl当然是换行符。您可以算出有 24 个字节。

在第 2 步中,您必须发明另一种格式,以尽可能多地保存位。最简单的方法是分别压缩这三个字段中的每一个。

与文本格式使用 anl标记字段结尾的位置类似,您还需要一种方法来定义二进制字段的开始和结束位置。一种常见的方法是在二进制字段数据前面加上一个长度。作为第一步,我们可以用nl长度替换 's 并得到:

58 75 73 65 72 31 37 39 38 33 36 32 20 32 33 32 34 30 34 36 32 33 34 35
--  u  s  e  r  1  7  9  8  3  6  2 --  2  3  2  4 --  4  6  2  3  4  5                                

现在我们只需将整个字节作为位长度。注意58是 77 的十六进制表示(即 11 个字符 * 8 位),位长lname',20 hex equals 4 * 8 = 32, and30 is 6 * 8 = 48. This does not compress anything, as it's still 24 bytes in total. But we already got a binary format because58 ,20 and30` 有特殊含义。

下一步是压缩每个字段。这就是棘手的地方。该lname字段由ASCII字符组成。在 ASCII 中,仅需要/使用 8 位中的 7 位;这是一个很好的表例如u二进制中的字母是01110101. 我们可以安全地切掉最左​​边的位,它总是0. 这产生1110101. 对所有角色都可以这样做。所以你最终会得到 11 个 7 位值 -> 77 位。

这 77 位现在必须适合 8 位字节。这是二进制表示的前 4 个字节user,在砍掉最左边的位之前:

01110101 01110011 01100101 01110010

unsigned char通过将字节(即)向左移动来完成 C 中的切掉一点:

 unsigned char byte = lname[0];
 byte = byte << 1;

当您对所有字符执行此操作时,您会得到:

1110101- 1110011- 1100101- 1110010-

这里我-用来表示这些字节中现在可以填充的位;通过将所有位向左移动一位,它们变得可用。您现在使用下一个字节右侧的一个或多个位来填补这些-空白。对这四个字节执行此操作时,您将获得:

11101011 11001111 00101111 0010----

所以现在有一个 4 位的间隙应该用字符1等的位来填充。

通过使用您提到的 C 中的二元运算符来填补这些空白。我们已经使用了左移<<。结合1110101-1110011-例如,我们这样做:

unsigned char* name;  // name MUST be unsigned to avoid problems with binary operators.
<allocated memory for name and read it from text file>

unsigned char  bytes[10];   // 10 is just a random size that gives us enough space. 

name[0] = name[0] << 1;  // We shift to the left in-place here, so `name` is overwritten.
name[1] = name[1] << 1;  // idem.

bytes[0] = name[0] | (name[1] >> 7);
bytes[1] = name[1] << 1;

name[1] >> 7我们有which1110011- >> 7给出00000001:最右边的位。然后我们使用按位或运算符|将此位“添加”到1110101-,从而得到111010111

您必须在循环中执行这样的操作才能获取正确字节中的所有位。

此名称字段的新长度为 11 * 7 = 77,因此我们丢失了大量的 11 位 :-) 请注意,对于字节长度,我们假设该lname字段的长度永远不会超过 255 / 7 = 36 个字符.

与上面的字节一样,您可以将第二个长度与lname字段的最终位合并。

要压缩您首先使用 ( fscanf(file, %d, ...))读取的数字unsigned int0在这个 4 字节的 unsigned int 中,左侧会有很多s。例如,第一个字段是(以 4 位的块显示仅是为了便于阅读):

0000 0000 0000 0000 0000 1001 0001 0100

左侧有 20 个未使用的位。

你需要摆脱这些。做 32 减去左边零的个数,你就得到这个数字的位长。bytes通过将其位与前一个字段的位合并,将此长度添加到数组中。然后只将数字的有效位添加到bytes. 这将是:

1001 0001 0100

在 C 中,当使用 'int' 的位(还有 'short'、'long'、...任何大于 1 字节的变量/数字)时,您必须考虑字节顺序或字节序

当您对两个数字执行上述步骤两次时,您就完成了。然后你有一个bytes可以写入文件的数组。当然,您必须保留bytes在上述步骤中写入的位置;所以你知道字节数。请注意,在大多数情况下,最后一个字节中会有一些未填充数据的位。但这并没有什么坏处,它只是不可避免地浪费了文件存储在 8 位 = 1 个字节的块中这一事实。

读取二进制文件时,您会得到一个相反的过程。您将读取一个unsigned char bytes数组。然后您知道第一个字节(即bytes[0])包含名称字段的位长。然后通过移位和屏蔽逐字节填充“lname”的字节。ETC....

祝你好运!

于 2013-06-21T10:53:30.163 回答