【问题标题】:File Handling question on C programming关于 C 编程的文件处理问题
【发布时间】:2009-04-11 05:48:24
【问题描述】:

我想从给定的输入文件中逐行读取,处理每一行(即它的单词),然后移动到另一行...

所以我正在使用 fscanf(fptr,"%s",words) 来读取单词,一旦遇到行尾就应该停止...

但这在 fscanf 中是不可能的,我猜...所以请告诉我该怎么做...

我应该阅读给定行中的所有单词(即应该遇到行尾)以终止然后移动到另一行,并重复相同的过程..

【问题讨论】:

    标签: c filehandle


    【解决方案1】:

    使用fgets()。是的,链接是cplusplus,但它来自c stdio.h

    您也可以使用sscanf() 从字符串中读取单词,或者仅使用strtok() 将它们分开。


    回应评论:fgets() 的这种行为(将\n 留在字符串中)允许您确定是否遇到了实际的行尾。请注意,如果提供的缓冲区不够大,fgets() 也可能只从文件中读取部分行。在您的情况下 - 最后检查 \n 并将其删除,如果您不需要它。像这样的:

    // actually you'll get str contents from fgets()
    char str[MAX_LEN] = "hello there\n";
    size_t len = strlen(str);
    if (len && str[len-1] == '\n') {
        str[len-1] = 0;
    }
    

    就这么简单。

    【讨论】:

    • 但是如果我使用 printf("-%s-",line),,, 打印该行,当我使用 fgets 读取一行时它会打印 '\n' 以及换行符? ?这是什么,以及如何解决这个问题..
    • @Young:在回答正文中回复了您的评论。
    • +1 fgets 比 fscanf 更受欢迎,因为输入可以是任何东西,因此在 fscanf 中很容易导致缓冲区溢出。
    • str 指向一个只读字符串(因为你用文字初始化它),所以你应该把它限定为 const。 strlen() 的返回类型是 size_t,所以我认为 len 也应该是 size_t。这不是问题,但它使代码更通用。我认为调用 strchr() 更明确。
    • @Bastien Léonard:感谢您的发言。我同意 size_t (更新了答案)。 const 不是必需的,因为 string 实际上不是只读的,初始化只是一个示例。这段代码的目的是演示“chomping”'\n'。 “调用 strchr() 更明确”是什么意思?
    【解决方案2】:

    如果您在使用可用的 GNU 扩展的系统上工作,则有一个称为 getline (man 3 getline) 的东西,它允许您逐行读取文件,而 getline 会在需要时为您分配额外的内存。手册页包含一个我修改为使用 strtok (man 3 strtrok) 拆分行的示例。

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        FILE * fp;
        char * line = NULL;
        size_t len = 0;
        ssize_t read;
    
        fp = fopen("/etc/motd", "r");
        if (fp == NULL)
        {
            printf("File open failed\n");
            return 0;
        }
    
        while ((read = getline(&line, &len, fp)) != -1) {
            // At this point we have a line held within 'line'
            printf("Line: %s", line);
            const char * delim = " \n";
            char * ptr; 
            ptr = (char * )strtok(line,delim);
    
            while(ptr != NULL)
            {
                printf("Word: %s\n",ptr);
                ptr = (char *) strtok(NULL,delim);
            }
        }
    
        if (line)
        {
            free(line);
        }
        return 0;
    }
    

    【讨论】:

    • 是的,不在标准中,但比 fgets() 安全很多
    【解决方案3】:

    鉴于所有 stdio 函数中固有的缓冲,我很想用 getc() 逐个字符地读取流。如果需要,一个简单的有限状态机可以识别字边界和行边界。一个优点是完全没有缓冲区溢出,除了在您的进一步处理需要时收集当前单词的任何缓冲区。

    您可能想要做一个快速基准测试,比较使用 getc() 与 fgets() 完全读取大文件所需的时间...

    如果外部约束要求文件确实一次读取一行(例如,如果您需要处理来自 tty 的面向行的输入),那么 fgets() 可能是您的朋友,正如其他答案指出的那样,但即便如此,只要输入流在行缓冲模式下运行,如果 stdin 在 tty 上,这对于 stdin 来说很常见,那么 getc() 方法可能是可以接受的。

    编辑:要控制输入流上的缓冲区,您可能需要调用 setbuf() 或 setvbuf() 将其强制为缓冲模式。如果输入流最终没有缓冲,那么在原始流上使用某种形式的显式缓冲区总是比 getc() 快。

    最佳性能可能会使用与您的磁盘 I/O 相关的缓冲区,大小至少为两个磁盘块,并且可能更多。通常,通过将输入安排为内存映射文件并在处理文件时依赖内核的分页来读取和填充缓冲区,就好像它是一个巨大的字符串一样,甚至可以击败这种性能。

    无论选择如何,如果性能很重要,那么您需要对几种方法进行基准测试,然后选择最适合您的平台的方法。即使这样,如果您的问题被编写、调试和使用,最简单的表达方式可能仍然是最佳的整体答案。

    【讨论】:

    • 缓冲通常是一件好事。使用 getc() 而不是 fgets() 可能会慢得多。
    • 它可能没有你想象的那么慢,因为输入流通常已经被标准库缓冲了。
    【解决方案4】:

    但这在 fscanf 中是不可能的,

    是的,有点邪恶;)

    更新:对邪恶的更多澄清

    但不幸的是有点错误。我假设[^\n]%*[^\n] 应该是[^\n]%*。此外,应该注意这种方法会从行中去除空格。 ——蜻蜓

    注意xstr(MAXLINE) [^\n] 读取MAXLINE 字符,可以是除换行符以外的任何字符(即\n)。说明符的第二部分,即 *[^\n] 拒绝任何内容(这就是 * 字符存在的原因)如果该行有超过 MAXLINE 个字符但 NOT包括换行符。换行符告诉scanf 停止匹配。如果我们按照蜻蜓的建议做呢?唯一的问题是scanf 将不知道在哪里停止,并且会一直抑制分配,直到下一个换行符被击中(这是第一部分的另一个匹配项)。因此,您将在报告时跟踪一行输入。

    如果你想循环阅读怎么办?需要稍作修改。我们需要添加一个getchar() 来使用不匹配的换行符。代码如下:

    #include <stdio.h>
    
    #define MAXLINE 255
    
    /* stringify macros: these work only in pairs, so keep both */
    #define str(x) #x
    #define xstr(x) str(x)
    
    int main() {
        char line[ MAXLINE + 1 ];
        /* 
           Wickedness explained: we read from `stdin` to `line`.
           The format specifier is the only tricky part: We don't
           bite off more than we can chew -- hence the specification 
           of maximum number of chars i.e. MAXLINE. However, this
           width has to go into a string, so we stringify it using  
           macros. The careful reader will observe that once we have
           read MAXLINE characters we discard the rest upto and
           including a newline.
         */
        int n = fscanf(stdin, "%" xstr(MAXLINE) "[^\n]%*[^\n]", line);
        if (!feof(stdin)) {
            getchar();
        }
        while (n == 1) {
            printf("[line:] %s\n", line);
            n = fscanf(stdin, "%" xstr(MAXLINE) "[^\n]%*[^\n]", line);
            if (!feof(stdin)) {
                getchar();
            }
        } 
        return 0;
    }
    

    【讨论】:

    • @Young:我添加了cmets。你能指出哪些部分你不明白吗?然后我可以用适当的信息编辑我的帖子。干杯!
    • +1,非常好的邪恶,但不幸的是有点错误。我假设“[^\n]%*[^\n]”应该是“[^\n]%*”。此外,应该注意的是,这种方法会从行中去除空格。
    • @dragonfly:不。我强烈建议你运行这段代码。我将编辑我的帖子以澄清。
    • @dirkgently:我实际上运行了代码。我将它包装成一个循环来读取文件中的所有行。在这种情况下,您的代码仅输出第一行。如果在cl 下编译,我的工作,但如果使用gcc 编译,则行为相同。所以你的代码真的很邪恶:)
    • @dragonfly:你需要一个 getchar() 换行符。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-10
    • 2011-07-17
    相关资源
    最近更新 更多