基本上,我必须编写一个程序来计算.c文件中各种不同的符号。我让它与除垂直线"|
"之外的所有需要的符号一起使用。出于某种原因,它只是不会计算它们。
这是我使用的方法:
int countGreaterLesserEquals(char filename[])
{
FILE *fp = fopen(filename,"r");
FILE *f;
int temp = 0; // ASCII code of the character
int capital = 0;
int lesser = 0;
int numbers = 0;
int comments = 0;
int lines = 0;
int spc = 0;
if (fp == NULL) {
printf("File is invalid\empty.n");
return 0;
}
while ((temp = fgetc(fp)) != EOF) {
if (temp >= 'a' && temp <= 'z') {
capital++;
}
else if (temp >= 'A' && temp <= 'Z') {
lesser++;
}
else if( temp == '/') temp = fgetc(fp); {
if(temp == '/')
comments++;
}
if (temp >= '0' && temp <= '9') {
numbers++;
}
if (temp == '|') {
spc++;
}
if (temp == 'n') {
lines++;
}
}
}
在这一行:
else if( temp == '/') temp = fgetc(fp); {
我相信你有一个放错地方的{
.据我了解,它应该在temp = fgetc(fp);
之前.
如果遵循编码样式指南,将每个表达式放在其自己的行上并正确缩进代码,则可以轻松避免此类错误。
更新:这个fgetc
是一个极端情况。如果你在这里阅读过去的EOF
怎么办?您没有检查此错误。
首先,一些编译器警告:
- 'f' : 未引用的局部变量
- 并非所有控制路径都返回值
因此,可以删除f
,并且该函数也应该在成功时返回一个值。最好在最高级别设置编译器警告。
然后,存在以下问题:
else if( temp == '/') temp = fgetc(fp); {
if(temp == '/')
comments++;
}
检查else
末尾的;
。这意味着它后面的块总是被执行。此外,对于此fgetc()
,不会检查EOF
或错误。
另外,如果temp
是 /
,但以下字符不是,它将被跳过,因此我们需要将字符放回流中(在这种情况下最简单的解决方案)。
下面是一个完整的示例:
int countGreaterLesserEquals(char filename[])
{
FILE *fp = fopen(filename, "r");
int temp = 0; // ASCII code of the character
int capital = 0;
int lesser = 0;
int numbers = 0;
int comments = 0;
int lines = 0;
int spc = 0;
if (fp == NULL) {
printf("File is invalid\empty.n");
return 0;
}
while ((temp = fgetc(fp)) != EOF) {
// check characters - check most common first
if (temp >= 'a' && temp <= 'z') lesser++;
else if (temp >= 'A' && temp <= 'Z') capital++;
else if (temp >= '0' && temp <= '9') numbers++;
else if (temp == '|') spc++;
else if (temp == 'n') lines++;
else if( temp == '/')
if ((temp = fgetc(fp)) == EOF)
break; // handle error/eof
else
if(temp == '/') comments++;
else ungetc(temp, fp); // put character back into the stream
}
fclose (fp); // close as soon as possible
printf("capital: %dnlesser: %dncomments: %dn"
"numbers: %dnspc: %dnlines: %dn",
capital, lesser, comments, numbers, spc, lines
);
return 1;
}
虽然通常建议将if
语句放在大括号内,但我认为在这种情况下,为了清楚起见,我们可以将它们放在同一行上。
在这种情况下,每个if
前面都可以有一个else
。这样,程序就不必在已经找到剩余的情况下检查剩余的情况。出于同样的原因,对最常见字符的检查最好放在第一位(但事实确实如此)。
作为替代方案,您可以使用 islower(temp)
、 isupper(temp)
和 isdigit(temp)
来表示前三种情况。
性能:
为了完整起见:虽然这可能是对小文件的练习,但对于较大的文件,数据应该在缓冲区中读取以获得更好的性能(甚至在文件上使用内存映射)。
更新,@SteveSummit对fgetc
性能的评论:
很好的答案,但我不同意你关于在 结束。
fgetc
已经缓冲了!所以表现直截了当 这样的代码即使对于大输入也应该没问题;通常有 无需由于担心"效率"而使代码复杂化。
虽然这个评论起初似乎是有效的,但我真的很想知道性能的真正差异是什么(因为我从来没有使用过fgetc
因为我以前没有测试过这个),所以我写了一个小测试程序:
打开一个大文件并将每个字节汇总成一个uint32_t
,这与扫描上述某些字符相当。数据已由操作系统磁盘缓存缓存(因为我们正在测试功能/扫描的性能,而不是硬盘的读取速度)。虽然上面的示例代码最有可能用于小文件,但我想我也可以将较大文件的测试结果放在这里。
这些是平均结果:
- using fgetc : 8770
- using a buffer and scan the chars using a pointer : 188
- use memory mapping and scan chars using a pointer : 118
现在,我很确定使用缓冲区和内存映射会更快(我一直将它们用于更大的数据),速度的差异甚至比预期的还要大。好的,可能会有一些可能的优化 fgetc
,但即使这些优化会使速度翻倍,差异仍然很大。
底线:是的,值得为较大的文件优化这一点。例如,如果使用buffers/mmap处理文件的数据需要1秒,那么使用fgetc
处理文件数据需要一分钟多的时间!