使用 C 创建、读取和打印到标准输出

Create, read and print to stdout using C

这是我第一次在 Stack Overflow 上提问,我会尽力提出一个好问题。 如果我遗漏了相关信息或类似内容,请随时纠正我。

我正在编写一个创建简单选项菜单的小程序。 我的计划包括几个步骤:

  1. 从该宏读取文件名#define file_dir "/home/me/dir"
  2. 将该文件名存储到 names.txt。
  3. 我必须在我的简单菜单中显示 names.txt 的内容作为选项。

目前我能够完成三个步骤中的两个,但我猜不是很好。 我创建了 2 个函数来完成这 2 个工作。 create_file(), read_file(), 分别.

现在是我的问题真正开始的地方:

当我单独执行时,每个函数都工作正常。如果我按原计划打电话 第二个函数read_file()改为将文件内容打印到stdout 它重写 names.txt 并在文件末尾放置一个“方块”字符。

我的计划是将 read_file() 的 return 重定向到一个数组。 所以我可以在这个简单的菜单中显示为选项。

请帮我理解。 为什么我不能像那样使用这两个功能? 我知道我是 C 的新手,这个程序还远未完成。

这是我的代码:

#include <stdio.h>
#include <dirent.h>
#include <unistd.h>

#define my_dir "/home/me/dir"

  int roms_list;

  int create_list()
    {
    /* redirect stdout to a file */
    freopen("names.txt", "a+", stdout);

      /* open dir and print their content */
      DIR *dir;
      struct dirent *ent;
        if ((dir = opendir (nes_dir)) != NULL)
      {
            while ((ent = readdir (dir)) != NULL)
        {
                printf ("%s\n", ent->d_name);
          }
        }
      closedir(dir);
      close(names.txt);
    }

  int read_list()
  {
    FILE * list;
    char  ch;

    list = fopen("names.txt", "r+");

    if(NULL == list)
      {
        printf("file cant' be opened \n");
        return 1;
      }

    do
      {
        ch = fgetc(list);
        printf("%c", ch);
      }
    while (ch != EOF);

    fclose(list);
  }

int main()
{
  create_list();
  read_list();

  return 0;
}

您似乎正在打印 EOF。你应该检查 ch 是否是 EOF 打印之前。

Also fgetc() returns int 并将 return 值转换为 char 将阻止它将 EOF 与有效字节之一区分开来,因此对于 ch.

,您应该使用 int 而不是 char

而不是这个:

    char  ch;

    /* ... */

    do
      {
        ch = fgetc(list);
        printf("%c", ch);
      }
    while (ch != EOF);

你应该使用:

    int  ch;

    /* ... */

    while ((ch = fgetc(list)) != EOF)
      {
        printf("%c", ch);
      }

或者:

    int  ch;

    /* ... */

    ch = fgetc(list);
    while (ch != EOF)
      {
        printf("%c", ch);
        ch = fgetc(list);
      }

正如 MikeCAT 指出的那样,您在检查 ch != EOF 之前尝试 printf("%c", ch); 导致尝试使用 %c 转换说明符打印 int EOF 值,导致 未定义的行为 由于参数类型和转换说明符不匹配。 ch 必须是 int 类型才能匹配 fgetc() 的 return 类型并与 EOF.

进行有效比较

If a conversion specification is invalid, the behavior is undefined. If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

C11 Standard - 7.21.6.1(p9)

您的代码需要改进的其他领域

  • 您的 create_list() 函数是类型 int,但无法 return 任何值。由于 create_list() 可以成功或失败,因此 return 类型必须能够传达成功或失败。输入 int 就可以了,例如,您可以在读取失败或成功时输入 return 0;,return 写入文件的条目数;
  • 你的read_list()函数只是一个输出函数,输出写入文件的内容。虽然它可能成功或失败,但它对您的程序的持续运行并不重要。为输出函数选择类型 void 就可以了。
  • 不要在函数中对文件名或目录名进行硬编码。你不应该为了从不同的目录读取或写入不同的文件名而重新编译你的程序。将要读取的目录和要写入的文件名作为参数传递给您的程序。这就是 main() 的参数的用途,例如int main (int argc, char **argv)。 (或提示用户输入两个字符串值)
  • main() 中打开文件一次,并在成功打开后,将打开文件流的 FILE* 指针作为参数传递给每个函数。您验证 main() 中的打开,因为如果 fopen() 失败,则无需调用任何一个函数。
  • 将要读取的目录名称作为 const char * 参数传递给 create_list()
  • 将您对 read_list() 的呼叫设置为 create_list() 成功 return。如果 create_list() 失败,则无需调用 read_list().

将这些改进放在一起,您可以执行类似于以下操作的操作:

#include <stdio.h>
#include <dirent.h>

/* returns 0 on failure, no. of files written on success */
int create_list (FILE *fp, const char *dname)
{
  /* open dir and print their content */
  DIR *dir;
  struct dirent *ent;
  int n = 0;              /* simple counter for no. of entries read */
  
  if ((dir = opendir (dname)) == NULL) {  /* return 0 on failure to open */
    return 0;
  }
  
  while ((ent = readdir (dir)) != NULL) {
    /* skip dot files */
    if ((ent->d_name[0] == '.' && !ent->d_name[1]) ||
        (ent->d_name[0] == '.' && ent->d_name[1] == '.')) {
      continue;
    }
    fprintf (fp, "%s\n", ent->d_name);
    n++;                                  /* increment counter */
  }
  
  closedir(dir);
  
  return n;     /* return the number of enteries written */
}

/* read list can be type void - it simply outputs contents of file */
void read_list (FILE *fp)
{
  int  ch;    /* must be int  */

  while ((ch = fgetc (fp)) != EOF) {  /* read char, validate not EOF */
      putchar (ch);                   /* write to stdout */
  }
}


int main (int argc, char **argv) {
  
  char *dname, *fname;    /* dirname and filename pointers */
  int nfiles = 0;         /* no. of files written */
  FILE *fp = NULL;        /* file pointer */
  
  if (argc != 3) {  /* validate 2 arguments given (dirname filename) */
    fputs ("error: dirname and filename required\n"
           "usage: ./program \"/path/to/files\" \"filename\"\n", stderr);
    return 1;
  }
  dname = argv[1];        /* assign arguments to give descriptive names */
  fname = argv[2];        /* (you could just use argv[x], a name helps) */
  
  fp = fopen (fname, "w+");   /* open file for reading/writing */
  
  if (!fp) {  /* validate file open for reading/writing */
    perror ("file open failed");
    return 1;
  }
  
  /* validate create_list succeeds */
  if ((nfiles = create_list (fp, dname))) {
    printf ("%d files:\n\n", nfiles);     /* number of entries in file */
    rewind (fp);                          /* rewind file pointer */
    read_list (fp);                       /* read list */
  }
  
  if (fclose (fp) != 0) {             /* always validate close-after-write */
    perror ("fclose fp");
  }
}

例子Use/Output

您提供要读取的目录作为第一个参数,并提供要写入的文件名作为第二个参数。 ./progname /path/to/read /file/to/write

一个简短的例子:

$ ./bin/dirlist_names ./km dat/dnames.txt
47 files:

startstop.o
kernelmod_hello1.c
.chardev.o.cmd
hello-4.o
.hello-2.mod.cmd
hello-2.mod

<snip>

hello-5.mod
.startstop.o.cmd
.hello-4.mod.cmd
chardev.mod
Makefile
hello-2.c