【问题标题】:Pattern Recognition for File input in CC中文件输入的模式识别
【发布时间】:2021-02-02 22:28:27
【问题描述】:

我试图使用 scanf 从 C 中的文件获取输入。文件中的数据如下:

223234       <Justin>      <Riverside>  

这是我尝试过的以下正则表达式:

FILE* fid;


    int id;
    char name[100], city[100];
    char dontcare1[40], dontcare3[40];
    char dontcare2,dontcare4[40],dontcare5;


    fid = fopen("test.txt", "r");

    fscanf(fid,"%d%[^<]%c%[^<]%c%[>]%c ",&id,&dontcare1[0],
                                            &dontcare2,&dontcare3[0],&dontcare4[0],
                                            &city[0],&dontcare5);

我想知道是否有更好的方法来做到这一点,我如何在不创建额外变量的情况下考虑文件中的空格,这似乎没有选择括号中的城市名称。

【问题讨论】:

  • 还有很多更好的方法,尽管它们都涉及不使用scanf。

标签: c scanf


【解决方案1】:

在*scanf() 中,您可以期待文字字符,一个空格可以匹配多个分隔符。

为了避免处理文件,我的示例使用sscanf() 进行了简化,但它与fscanf() 的工作方式相同。

这里的技巧是使用%n 来获取到该点的读取字符数;这样,我们确保最后一个 &gt; 文字实际上已被读取 (我们无法知道*scanf()的结果)

/**
  gcc -std=c99 -o prog_c prog_c.c \
      -pedantic -Wall -Wextra -Wconversion \
      -Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <stdio.h>

int
main(void)
{
  const char *line="223234       <Justin>      <Riverside>";
  int id;
  char name[100], city[100];

  int n_read=-1;
  sscanf(line, "%d <%[^>]> <%[^>]>%n",
         &id, name, city, &n_read);
  if(n_read!=-1) // read till the end then updated
  {
    printf("id=%d\n", id);
    printf("name=%s\n", name);
    printf("city=%s\n", city);
  }
  return 0;
}

【讨论】:

    【解决方案2】:

    在尝试打开文件时,确保文件实际打开成功很有用。

    FILE *fid;
    fid = fopen("path/to/file", "r");
    
    if (fid == NULL){
        printf("Unable to open file. \n");
        return -1;
    }
    
    

    实际上解决您的问题,我可能只是使用string.h 的strtok 函数,然后使用空格作为分隔符。

    另外,我不会使用scanf,而是使用fgets...原因可以在其他各种 SO 文章中找到。以下是一个未经测试的解决方案。

            char line[100], line_parse[100]; // Buffer(s) to store lines upto 100
            char *ret; // token used for strtok
    
            // Read an integer and store read status in success.
            while (fgets(line, sizeof(line), fPtrIn) != NULL)
            {
                // Copy the line for parsing, as strtok changes original string
                strcpy(line_parse, line);
    
                // Separate the line into tokens
                ret = strtok(line_parse, " ");
                
                while (ret != NULL)
                {/*do something with current field*/
                    ret = strtok(NULL, " "); // Move onto next field
                }
    

    请注意strtok 不是线程安全的。因此,在多线程代码中,您不应使用此函数。不幸的是,ISO C 标准本身并没有提供该函数的线程安全版本。但是很多平台都提供了这样的功能作为扩展:在POSIX兼容的平台上(比如Linux),你可以使用strtok_r这个功能。在 Microsoft Windows 上,您可以使用函数strtok_s。这两个函数都是线程安全的。

    【讨论】:

    【解决方案3】:

    您实际上可以通过将行读入数组(缓冲区)然后使用sscanf() 从行中解析您需要的内容来非常简单地做到这一点。不要直接使用scanf(),因为这会给您带来一整套与输入流中哪些字符未读相关的陷阱。相反,通过一次读取一行来完成所有输入,然后使用sscanf() 解析缓冲区中的值,就像使用scanf() 一样,但是通过使用fgets() 读取,您会消耗一整行一段时间,输入流中剩余的内容不取决于转换的成功或失败。

    例如,你可以这样做:

    #include <stdio.h>
    
    #define MAXC 1024
    #define NCMAX 100
    
    int main (void) {
        
        char buf[MAXC],
            name[NCMAX],
            city[NCMAX];
        unsigned n;
        
        if (!fgets (buf, MAXC, stdin))
            return 1;
        
        if (sscanf (buf, "%u <%99[^>]> <%99[^>]>", &n, name, city) != 3) {
            fputs ("error: invalid format", stderr);
            return 1;
        }
        
        printf ("no.  : %u\nname : %s\ncity : %s\n", n, name, city);
    }
    

    sscanf() 格式字符串是关键。 "%u &lt;%99[^&gt;]&gt; &lt;%99[^&gt;]&gt;" 将数字读取为无符号值,&lt;%99[^&gt;]&gt; 使用 '&lt;',然后字符类 %99[^&gt;] 使用 99 的 field-width 修饰符来保护您的数组边界和[^&gt;] 类将读取不包括 &gt; 的所有字符(对于下一个 city 也是如此)。通过检查退货验证,以确保发生了三个有效的转换。如果不是,则处理错误。

    使用/输出示例

    您在文件dat/no_name_place.txt 中输入后,该文件会被简单地重定向到stdin 并被程序读取,结果是:

    $ ./bin/no_name_city < dat/no_name_place.txt
    no.  : 223234
    name : Justin
    city : Riverside
    

    【讨论】:

    • sscanf() 的结果是3,即使在城市之后没有读取&gt;。但是,我不知道这对这个特定问题是否重要。
    • 是的,你是对的,因为最后一个'&gt;'不是最终转换的一部分,所以它不会导致匹配或输入失败。不,我怀疑它很重要,它可能会从 format-string 中完全省略。重要的是阅读城市名称,包括所有字符而不是'&gt;'。
    【解决方案4】:

    如果您必须使用scanf(),其他答案似乎涵盖了各个方面。这是一种替代方法,使用 fgetc() 和 strcpy() 逐个字符地获取输入。

    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    
    #define MAX_SIZE 100
    
    int main(void)
    {
        int id = 0, c = 0;
        char buff1[MAX_SIZE], buff2[MAX_SIZE];
        size_t i = 0U;
        FILE *fptr = NULL;
    
         if (!(fptr = fopen("test.txt", "r")))
         {
            perror("error opening file");
            return -1;
         }
    
        while ((c = fgetc(fptr)) != EOF)
        {
            if (isdigit(c)) /* maybe check INT_MAX here if you are planning to scan big numbers */
            {
                id = (id * 10) + (c - '0');
            }
    
            if (i != 0 && c == ' ')
            {
                buff2[i] = '\0';
                strcpy(buff1, buff2);
                i = 0U;
            }
    
            if (isalpha(c))
            {
                if (i < MAX_SIZE - 1)
                {
                    buff2[i++] = c;
                }
                else
                {
                    fputs("Buff full", stderr);
                    return -1;
                }
            }
        }
        buff2[i] = '\0';
    
        return 0;
    }
    

    【讨论】:

    • 请注意perror 在这种情况下可能不起作用,因为不能保证fopen 会设置errno,即使它失败了。这是由 POSIX 保证的,但不是 ISO C 保证的。因此,您可能希望在调用fopen 之前将errno 设置为0,并且仅在errno != 0 时调用perror。如果fopen 失败并且errno == 0 失败,那么您可能需要提供一般错误消息。
    猜你喜欢
    • 1970-01-01
    • 2018-09-22
    • 2012-09-17
    • 2019-06-10
    • 1970-01-01
    • 2019-02-24
    • 2012-09-21
    • 2015-01-19
    • 2013-06-12
    相关资源
    最近更新 更多