【问题标题】:character by character reading from a file in C从C中的文件中逐个字符地读取
【发布时间】:2020-07-08 00:04:53
【问题描述】:

如何将文件中的文本读入动态字符数组? 我找到了一种方法来计算文件中的字符数并创建一个动态数组,但我不知道如何将字符分配给数组的元素?

FILE *text;
char* Str;
int count = 0;
char c;
text = fopen("text.txt", "r");
while(c = (fgetc(text))!= EOF)
{
  count ++;
}
Str = (char*)malloc(count * sizeof(char));

fclose(text);

【问题讨论】:

  • fgetc() 返回int,而不是char。将从fgetc() 返回的值截断为char 可能会导致您的代码无法正确识别EOF,因为EOF 是一个专门选择的值,因此它适合@ 987654329@ 值。

标签: c arrays file dynamic filereader


【解决方案1】:

在 C 中没有可移植的、符合标准的方法来提前知道如何从FILE 流中读取字节。

首先,流甚至可能是不可搜索的——它可以是管道或终端,甚至是套接字连接。在这样的流上,一旦你读取了输入,它就消失了,再也不会被读取了。您可以推回一个char 值,但这还不足以知道还有多少数据需要读取,或者重新读取整个流。

即使流是您可以查找的文件,您也不能在可移植、严格符合 C 代码中使用 fseek()/ftell() 来了解文件的大小。

如果是二进制流,则不能使用 fseek() 查找文件末尾 - 这是明确未定义的行为 per the C standard

...二进制流不需要有意义地支持 fseek 调用的 wherece 值为SEEK_END

Footnote 268 even says:

将文件位置指示器设置为文件结尾,与fseek(file, 0, SEEK_END) 一样,对于二进制流具有未定义的行为...

所以你不能在二进制流中便携地使用fseek()

您不能使用ftell() 来获取文本流的字节数。每the C standard again

对于文本流,其文件位置指示符包含未指定的信息,fseek 函数可使用该信息将流的文件位置指示符返回到 ftell 调用时的位置;两个这样的返回值之间的差异不一定是衡量写入或读取字符数的有意义的量度。

确实存在从ftell() 返回的值与字节数完全不同的系统。

了解您可以从流中读取多少字节的唯一可移植且一致的方法是实际读取它们,并且您不能依赖于能够再次读取它们。

如果要将整个流读入内存,则必须不断地重新分配内存,或者使用其他一些动态方案。

这是一种将流的全部内容读入内存的非常低效但可移植且严格符合的方式(为了算法清晰和防止出现垂直滚动条,所有错误检查和头文件都被省略 - 它确实需要错误检查并需要正确的头文件):

// get input stream with `fopen()` or some other manner
FILE *input = ...

size_t count = 0;
char *data = NULL;

for ( ;; )
{
    int c = fgetc( input );
    if ( c == EOF )
    {
        break;
    }

    data = realloc( data, count + 1 );

    data[ count ] = c;

    count++;
}

// optional - terminate the data with a '\0'
// to treat the data as a C-style string
data = realloc( data, count + 1 );
data[ count ] = '\0';
count++;

无论流是什么,这都会起作用。

在 Linux 等 POSIX 风格的系统上,您可以使用 fileno()fstat() 来获取文件的大小(同样,所有错误检查和头文件都被省略了):

char *data = NULL;
FILE *input = ...

int fd = fileno( input );

struct stat sb;

fstat( fd, &sb );

if ( S_ISREG( sb.st_mode ) )
{
    // sb.st_size + 1 for C-style string
    char *data = malloc( sb.st_size + 1 );
    data[ sb.st_size ] = '\0';
}

// now if data is not NULL you can read into the buffer data points to
// if data is NULL, see above code to read char-by-char

// this tries to read the entire stream in one call to fread()
// there are a lot of other ways to do this
size_t totalRead = 0;
while ( totalRead < sb.st_size )
{
    size_t bytesRead = fread( data + totalRead, 1, sb.st_size - totalRead, input );

    totalRead += bytesRead;
}

以上内容也可以在 Windows 上运行。您可能会得到some compiler warnings,或者也必须使用_fileno()_fstat() and struct _stat。*

您可能还需要define the S_ISREG() macro on Windows:

#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)

* 那是 _fileno()_fstat()struct _stat,没有超链接 underline-munge。

【讨论】:

    【解决方案2】:

    对于二进制文件,可以使用fseekftell不用读取文件就知道大小,分配内存然后读取所有内容:

    ...
    text = fopen("text.txt", "r");
    fseek(txt, 0, SEEK_END);
    char *ix = Str = malloc(ftell(txt);
    while(c = (fgetc(text))!= EOF)
    {
      ix++ = c;
    }
    count = ix - Str;       // get the exact count...
    ...
    

    对于文本文件,在具有多字节行尾的系统上(例如使用 \r\n 的 Windows),这将分配比所需更多的字节。你当然可以扫描文件两次,第一次是大小,第二次是实际读取字符,但你也可以忽略额外的字节,或者你可以realloc

    ...
    count = ix - Str;
    Str = realloc(Str, count);
    ...
    

    当然,对于现实世界的程序,您应该控制所有 io 和分配函数的返回值:fopenfseekfteelmallocrealloc...

    【讨论】:

    • 对于二进制文件,您可以使用 fseek 和 ftell 来知道大小而无需读取文件 只有当您的操作系统提供超出标准 C 所做的保证时,这才是正确的。在严格符合 C 的情况下,二进制文件末尾的 fseek() 是明确未定义的行为。每footnote 268 of the C11 standard:“将文件位置指示器设置为文件结尾,就像fseek(file, 0, SEEK_END)一样,对于二进制流具有未定义的行为......”这也假设文件是​​可搜索的 - 它不必是.
    • 对于文本文件,value from ftell() in strictly-confomant C also has no relation to a byte count:“对于文本流,其文件位置指示符包含未指定的信息,fseek 函数可使用该信息将流的文件位置指示符返回到其位置ftell 调用的时间;两个这样的返回值之间的差异不一定是衡量写入或读取字符数的有意义的量度。”以z/OS 为例。
    【解决方案3】:

    要按照您的要求进行操作,您必须再次阅读整个文件:

    ...
    // go back to the beginning
    fseek(text, 0L, SEEK_SET);
    // read
    ssize_t readsize = fread(Str, sizeof(char), count, text);
    if(readsize != count) {
      printf("woops - something bad happened\n");
    }
    
    // do stuff with it
    // ...
    
    fclose(text);
    

    但是您的字符串不是以这种方式终止的。如果你尝试使用一些常见的字符串函数,比如strlen,那会给你带来一些麻烦。

    要正确地终止您的字符串,您必须为另外一个字符分配空间并将最后一个字符设置为 '\0':

    ...
    // allocate count + 1 (for the null terminator) 
    Str = (char*)malloc((count + 1) * sizeof(char));    
    
    // go back to the beginning
    fseek(text, 0L, SEEK_SET);
    // read
    ssize_t readsize = fread(Str, sizeof(char), count, text);
    if(readsize != count) {
      printf("woops - something bad happened\n");
    }
    // add null terminator
    Str[count] = '\0';
    
    // do stuff with it
    // ...
    
    fclose(text);
    

    现在,如果您想知道文件中的字符数而不逐个计数,您可以通过更有效的方式获得该数字:

    ...
    text = fopen("text.txt", "r");
    
    // seek to the end of the file
    fseek(text, 0L, SEEK_END);
    // get your current position in that file
    count = ftell(text)
    
    // allocate count + 1 (for the null terminator) 
    Str = (char*)malloc((count + 1) * sizeof(char));    
    ...
    

    现在以更结构化的形式呈现:

    // open file
    FILE *text = fopen("text.txt", "r");
    
    // seek to the end of the file
    fseek(text, 0L, SEEK_END);
    // get your current position in that file
    ssize_t count = ftell(text)
    
    // allocate count + 1 (for the null terminator) 
    char* Str = (char*)malloc((count + 1) * sizeof(char));    
    
    // go back to the beginning
    fseek(text, 0L, SEEK_SET);
    // read
    ssize_t readsize = fread(Str, sizeof(char), count, text);
    if(readsize != count) {
      printf("woops - something bad happened\n");
    }
    
    fclose(text);
    
    // add null terminator
    Str[count] = '\0';
    
    // do stuff with it
    // ...
    

    编辑:

    正如 Andrew Henle 指出的那样,并非每个 FILE 流都是可查找的,您甚至不能依赖能够再次读取文件(或者再次读取文件时文件具有相同的长度/内容)。尽管这是公认的答案,但如果您事先不知道您正在处理什么样的文件流,那么他的解决方案绝对是要走的路。

    【讨论】:

      猜你喜欢
      • 2012-07-22
      • 1970-01-01
      • 2012-08-27
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      • 2020-10-20
      • 2011-06-16
      • 1970-01-01
      相关资源
      最近更新 更多