在 fwrite() 上循环 fread() 与单独使用它们有什么区别?

What is the difference between looping fread() over fwrite() vs using them separately?

(注意:向半小时前阅读我之前 post 的任何人道歉 - 我意识到我 post 输入了错误的一半代码,实际上是后半部分给我带来了问题。所以,如果这个 post 看起来很熟悉,那可能是!)

我正在尝试制作一个复制 .wav 文件的基本程序,这是我的尝试:

#include <stdio.h>
#include <stdint.h>

int main (void)
{
    FILE* input = fopen("input.wav", "r");
    FILE* output = fopen("output.wav", "w");
    
    uint8_t header [44];
    
    fread(header, sizeof(uint8_t), 44, input);
    fwrite(header, sizeof(uint8_t), 44, output);
    
    int16_t body;
    
    fread(&body, sizeof(int16_t), 1, input);
    fwrite(&body, sizeof(int16_t), 1, output);
    
    fclose(input);
    fclose(output);
}

然而,在未能使其工作之后,我查找了如何做,显然

fread(&body, sizeof(int16_t), 1, input);
fwrite(&body, sizeof(int16_t), 1, output);

应该是

while (fread(&body, sizeof(int16_t), 1, input))
{
    fwrite(&body, sizeof(int16_t), 1, output);
}

为什么第一部分可以毫无问题地复制header,但第二部分必须使用循环才能工作?在我看来,他们似乎在做同样的事情。

看看读写函数。如您所见,这两个函数的签名非常相似并且使用相同的参数。两者都用于对 数据进行操作。

 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
Argument Description
*ptr Points to the buffer where the data is stored
size The size of each block in bytes
nmemb The amount of blocks to read/write
*stream Points to the structure which describes the outgoing stream

因此,您的代码的不同之处在于:

// will read ONE block of data with the size of int16_t
fread(&body, sizeof(int16_t), 1, input);
// will read 44 blocks of data with the size of uint8_t
fread(header, sizeof(uint8_t), 44, input);

// will write ONE block of data with the size of int16_t
fwrite(&body, sizeof(int16_t), 1, output);
// will write 44 blocks of data with the size of int16_t
fwrite(header, sizeof(uint8_t), 44, output);

两种方法return size_t 类型的值,反映读取或写入的字节数。如果他们 return 0,则什么也没有发生。如您所知,不等于 0 的所有内容在 C 中都被认为是真实的。所以回答您的问题:

// reads ONE Block of size int16_t at a time and stores it in body
// => will return 0 and stop the loop if nothing was read anymore
while (fread(&body, sizeof(int16_t), 1, input))
{
    // writes ONE block of size int16_t from body to output
    fwrite(&body, sizeof(int16_t), 1, output);
}

只要还有数据要读取,循环就会继续,然后停止。文件的 header 具有固定大小,因为它是标准化的。文件的其余部分具有可变长度,因此循环。

我认为这里的根本问题是你读写 header 的代码是一次读取 44 字节的 header,而读写 body 只读和写两个字节。但是wav文件的body大概比两个字节大很多!

具体来说,这段代码:

fread(header, sizeof(uint8_t), 44, input);
fwrite(header, sizeof(uint8_t), 44, output);

一次读取和写入一个 44 字节 header。但是这段代码:

fread(&body, sizeof(int16_t), 1, input);
fwrite(&body, sizeof(int16_t), 1, output);

读写两个字节。如果 body 只是两个字节长,那很好。但如果不是,它不会复制整个 body;它只会复制前两个字节。

另一方面,此代码读取和写入多个 two-byte objects,其中有多少:

while (fread(&body, sizeof(int16_t), 1, input) == 1)
    fwrite(&body, sizeof(int16_t), 1, output);

如果您知道 body 有多大,您也可以一次读取和写入所有内容(也就是说,就像您对 header 所做的那样):

uint8_t body[body_size];
fread(body, 1, body_size, input);
fwrite(body, 1, body_size, output);

或者,如果您知道 body 大小为 16 位字,并且您想要那样考虑,您可以

uint16_t body [body_size_in_words];
fread(body, 2, body_size_in_words, input);
fwrite(body, 2, body_size_in_words, output);

freadfwrite 可能会造成混淆,因为它们旨在让您假装您正在读取和写入的不是单个字节。可以通过三种不同的方式使用它们:

  • fread(buf, 1, n, fp) 读取 n 字节
  • fread(buf, size, 1, fp) 读取一条 size 字节的记录
  • fread(buf, size, n, fp) 读取 nsize 字节的记录

在深处,fread 基本上只是乘以 n * size,并尝试读取那么多字节。然后,无论它读取多少字节,它都会将该数字除以 size,然后返回给您——也就是说,它 returns 读取的记录数,不一定是读取的字节数。当然,同样的例子、解释和论点也适用于 fwrite