如何在C语言中将文本文件复制到字符串?

4
我需要将文本文件的内容复制到动态分配的字符数组中。
我的问题是如何获取文件内容的大小;谷歌告诉我需要使用fseek和ftell,但是需要以二进制模式打开该文件,这只会得到垃圾数据。
编辑:我尝试在文本模式下打开,但是得到了奇怪的数字。以下是代码(为了清晰起见,我省略了简单的错误检查):
long f_size;
char* code;
size_t code_s, result;
FILE* fp = fopen(argv[0], "r");
fseek(fp, 0, SEEK_END);
f_size = ftell(fp); /* This returns 29696, but file is 85 bytes */
fseek(fp, 0, SEEK_SET);
code_s = sizeof(char) * f_size;
code = malloc(code_s);
result = fread(code, 1, f_size, fp); /* This returns 1045, it should be the same as f_size */

以二进制模式打开文件不会改变其内容。请发布您的代码,以便我们发现可能存在的问题。 - anon
请查看此问题:https://dev59.com/YXRC5IYBdhLWcg3wFdJx - Nick Dandoulakis
这不就是我正在做的吗? - Javier
你是否真正打开了文件?你必须检查 fp 是否为 NULL。 - Nick Dandoulakis
是的,我有。问题在于这些函数返回的数字似乎没有意义。 - Javier
10个回答

14
问题的根源在这里:
FILE* fp = fopen(argv[0], "r");

argv[0]是你的可执行程序,不是参数。它肯定不会是文本文件。尝试使用argv[1],看看会发生什么。


5
除非使用固定宽度编码,否则无法在读取数据之前确定文件的字符大小。例如,UTF-8 格式的 8 字节文件长度可能是 2 到 8 个字符。这不是文件 API 的限制,而是因为没有直接从“二进制数据大小”到“字符数”的映射。如果您有一个固定宽度编码,则可以将文件大小(以字节为单位)除以每个字符的字节数来计算。ASCII 是最明显的例子,但如果您的文件以 UTF-16 编码,并且您恰好在一个将 UTF-16 码点视为“本地”内部字符类型的系统上(包括 Java、.NET 和 Windows),那么您可以像处理固定宽度一样预测要分配的“字符”数量。(由于 Unicode 字符 U+FFFF 以上的字符由多个代码点编码,UTF-16 是可变宽度的,但很多时候开发人员会忽略这一点。)

我之前没有意识到...所以我应该读整个文件,递增一个计数器吗?那不会很慢吗? - Javier
或者使用fstat(2)。请参见http://www.gnu.org/s/libc/manual/html_node/Reading-Attributes.html。 - scvalex
@reyjavikvi: 你是想要快速还是准确?如果你正在使用变宽编码,那么没有办法在不读取文件数据的情况下完成 - 除非其他东西已经先行完成了(例如操作系统)并缓存了数据。 - Jon Skeet
顺便说一下,我一直假设您对字符数而不是字节数感兴趣,并且您使用的是可变宽度编码。如果您只想知道文件大小(以字节为单位),那就是另外一个简单得多的问题了。 - Jon Skeet
@jbcreix:我的观点是,许多平台 - 包括Java和.NET - 使用UTF-16代码点作为“字符”。例如,如果您想读取一个包含120个UTF-16代码点的文件,则需要分配大小为120的字符数组,并且如果文件以UTF-16编码,则可以根据文件大小预测该大小。你可以争论这是否是一个好主意(顺便说一句,我并不是在给出建议),但这是主要系统实现的方式。不过,我会编辑答案,使其更加清晰明了... - Jon Skeet

2

如果你正在为Linux(或其他类Unix操作系统)开发,你可以在打开文件之前使用stat来获取文件大小:

#include <stdio.h>
#include <sys/stat.h>

int main() {
   struct stat file_stat;

   if(stat("main.c", &file_stat) != 0) {
      perror("could not stat");
      return (1);
   }
   printf("%d\n", (int) file_stat.st_size);

   return (0);
}

编辑:看到代码后,我需要和其他帖子的人一样进入这一行:

从程序调用中获取参数的数组是这样构建的:

[0] 程序本身的名称
[1] 给出的第一个参数
[2] 给出的第二个参数
[n] 给出的第n个参数

在尝试使用argv数组的'0'之外的字段之前,您还应该检查argc:

if (argc < 2) {
   printf ("Usage: %s arg1", argv[0]);
   return (1);
}

2

可以尝试一下这个(我没有编译过,但我已经做了无数次,所以我相信至少接近):

char* readFile(char* filename)
{
    FILE* file = fopen(filename,"r");
    if(file == NULL)
    {
        return NULL;
    }

    fseek(file, 0, SEEK_END);
    long int size = ftell(file);
    rewind(file);

    char* content = calloc(size + 1, 1);

    fread(content,1,size,file);

    return content;
}

2
我相信argv [0]不会是一个文本文件。

1

argv[0] 是可执行文件的路径,因此 argv[1] 将是第一个用户提交的输入。尝试修改并添加一些简单的错误检查,例如检查 fp == 0,我们可能可以帮助您进一步。


0
另一种方法是分次读取文件,并根据需要扩展动态缓冲区:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGESIZE 128

int main(int argc, char **argv)
{
  char *buf = NULL, *tmp = NULL;
  size_t bufSiz = 0;
  char inputBuf[PAGESIZE];
  FILE *in;

  if (argc < 2)
  {
    printf("Usage: %s filename\n", argv[0]);
    return 0;
  }

  in = fopen(argv[1], "r");
  if (in)
  {
    /**
     * Read a page at a time until reaching the end of the file
     */
    while (fgets(inputBuf, sizeof inputBuf, in) != NULL)
    {
      /**
       * Extend the dynamic buffer by the length of the string
       * in the input buffer
       */
      tmp = realloc(buf, bufSiz + strlen(inputBuf) + 1);
      if (tmp)
      {
        /**
         * Add to the contents of the dynamic buffer
         */
        buf = tmp;
        buf[bufSiz] = 0;
        strcat(buf, inputBuf);
        bufSiz += strlen(inputBuf) + 1;
      }
      else
      {
        printf("Unable to extend dynamic buffer: releasing allocated memory\n");
        free(buf);
        buf = NULL;
        break;
      }
    }

    if (feof(in))
      printf("Reached the end of input file %s\n", argv[1]);
    else if (ferror(in))
      printf("Error while reading input file %s\n", argv[1]);

    if (buf)
    {
      printf("File contents:\n%s\n", buf);
      printf("Read %lu characters from %s\n", 
       (unsigned long) strlen(buf), argv[1]);
    }

    free(buf);
    fclose(in);   
  }
  else
  {
    printf("Unable to open input file %s\n", argv[1]);
  }

  return 0;
}

这种方法存在缺点;首先,如果没有足够的内存来容纳文件的内容,你不会立即知道。此外,realloc() 调用相对昂贵,因此你不希望将页面大小设置得太小。

然而,这避免了使用 fstat() 或 fseek()/ftell() 来事先确定文件的大小。


0

您可以打开文件,将光标放在文件末尾,存储偏移量,然后返回文件顶部并计算差异。


0

文本文件也可以使用 fseek

  • fseek 到文件末尾
  • ftell 获取偏移量
  • fseek 回到文件开头

这样你就可以得到文件的大小了。


0

没有示例代码有点难,但是fstat(或stat)会告诉您文件的大小。您需要分配所需的内存,并将文件读入其中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接