【发布时间】:2009-04-11 05:48:24
【问题描述】:
我想从给定的输入文件中逐行读取,处理每一行(即它的单词),然后移动到另一行...
所以我正在使用 fscanf(fptr,"%s",words) 来读取单词,一旦遇到行尾就应该停止...
但这在 fscanf 中是不可能的,我猜...所以请告诉我该怎么做...
我应该阅读给定行中的所有单词(即应该遇到行尾)以终止然后移动到另一行,并重复相同的过程..
【问题讨论】:
标签: c filehandle
我想从给定的输入文件中逐行读取,处理每一行(即它的单词),然后移动到另一行...
所以我正在使用 fscanf(fptr,"%s",words) 来读取单词,一旦遇到行尾就应该停止...
但这在 fscanf 中是不可能的,我猜...所以请告诉我该怎么做...
我应该阅读给定行中的所有单词(即应该遇到行尾)以终止然后移动到另一行,并重复相同的过程..
【问题讨论】:
标签: c filehandle
使用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;
}
就这么简单。
【讨论】:
如果您在使用可用的 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;
}
【讨论】:
鉴于所有 stdio 函数中固有的缓冲,我很想用 getc() 逐个字符地读取流。如果需要,一个简单的有限状态机可以识别字边界和行边界。一个优点是完全没有缓冲区溢出,除了在您的进一步处理需要时收集当前单词的任何缓冲区。
您可能想要做一个快速基准测试,比较使用 getc() 与 fgets() 完全读取大文件所需的时间...
如果外部约束要求文件确实一次读取一行(例如,如果您需要处理来自 tty 的面向行的输入),那么 fgets() 可能是您的朋友,正如其他答案指出的那样,但即便如此,只要输入流在行缓冲模式下运行,如果 stdin 在 tty 上,这对于 stdin 来说很常见,那么 getc() 方法可能是可以接受的。
编辑:要控制输入流上的缓冲区,您可能需要调用 setbuf() 或 setvbuf() 将其强制为缓冲模式。如果输入流最终没有缓冲,那么在原始流上使用某种形式的显式缓冲区总是比 getc() 快。
最佳性能可能会使用与您的磁盘 I/O 相关的缓冲区,大小至少为两个磁盘块,并且可能更多。通常,通过将输入安排为内存映射文件并在处理文件时依赖内核的分页来读取和填充缓冲区,就好像它是一个巨大的字符串一样,甚至可以击败这种性能。
无论选择如何,如果性能很重要,那么您需要对几种方法进行基准测试,然后选择最适合您的平台的方法。即使这样,如果您的问题被编写、调试和使用,最简单的表达方式可能仍然是最佳的整体答案。
【讨论】:
但这在 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;
}
【讨论】:
cl 下编译,我的工作,但如果使用gcc 编译,则行为相同。所以你的代码真的很邪恶:)