养成将要测试的常数放在左侧的习惯,例如
而 (EOF != (ch = getchar()))
...因为这将使您免于花费无数小时,就在您最能承受挫折的时候,当您不小心输入 = 时,您的意思是 ==。由于您不能将变量分配给常量,因此编译器将标记您的错误并保存您的问题。
根据我的经验,一旦你习惯阅读这种代码,你会发现在 if(, while( 旁边寻找要测试的内容比在如果您有很长的测试列表,例如打开文件、套接字等,然后通过 malloc() 分配内存来保存文件数据,则尤其如此。
PS:经过一番考证,CS 101有几个基本的东西值得一提……
第一,这里有一个经典案例——在这种情况下,因为你需要查看一个字符的后面,即使是在第一次通过 while() 循环时——用于播种 while() 循环。解决方案是使用一个简单的 if() 块设置 while() 循环,该块执行与 while() 循环相同的逻辑的单次传递。(仅供参考,while() 是具有终止条件的 if() 的无限集合)
执行此操作的正确方法如图所示。回报是能够抛弃所有的 if() 测试,并检查这是否是每次通过该循环时第一次通过 while() 循环。这里的第一遍由 while() 循环之前的 if() 测试处理。
第二,我发现你的变量名没有提供信息。这并不意味着他们是“错误的”,但很可能有人试图维护您的代码也会遇到困难。以我的经验,当您更好地理解一段代码时,变量名称会越来越好。用它作为你是否理解问题、是否有好的解决方案以及知道原因的试金石。
第三,当您发现自己将 main() 中的变量初始化为 1 时,它应该在您的脑海中引发关于正确流量控制的标志,就像现在的 PassKnt 一样,被设置为 1。此外,通常,您希望增加 a循环/if/while 结束处的循环计数器,而不是它的开头。同样,这应该让你质疑你的逻辑。
注意:默认情况下,记事本以 unicode 格式保存。如果使用记事本为此程序创建测试文件,请务必以ANSI格式保存。
我把它留在了,因为它使程序更容易理解,但这里不需要 IsGraphFlg。不是在循环底部将 IsGraphFlg 分配给 WasGraphFlg,而是可以在 if-else 块的上半部和下半部的底部完成,因为上下文提供与 IsGraphFlag 相同的信息。
while (EOF != (ch = fgetc(pFile))) {
if(isgraph(ch)) {
IsGraphFlg=true;
charcount++;
} else { // this char is whitespace, last char was part of a word
IsGraphFlg=false;
if(WasGraphFlg) {
wordcount++;
}
}
WasGraphFlg = IsGraphFlg;
PassKnt++;
}
您可能还会注意到 PassKnt 现在也没有任何用处,也不再需要。
有人建议 isgraph() 是最佳的,但是当我创建一个 bool 数组并使用 isgraph() 对其进行初始化时,代码运行(超出内存缓冲区,这比戴尔 XPS 8500 上的文件快 10 倍) ) 不到 2/3 的时间 - 每个字符 9.25 个时钟而不是 14.75 个时钟。这是一个完全可选的优化——尽管很重要。
bool IsGph[256];
for(i=0; i<sizeof(IsGph); i++) {
IsGph[i] = isgraph((unsigned char)i);
}
在使用中,if(isgraph(i)) 在主要字符和单词计数循环中被替换为 if(IsGph[i])。
代码更新于 2012 年 12 月 30 日
// Word_Counter.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdafx.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <memory.h>
#include <locale>
#define UCHAR unsigned char
#define dbl double
#define LLONG __int64
#define PROCESSOR_HZ ((LLONG) 3400000000)
#pragma warning(disable : 4996)
//
// function prototypes
FILE *OpenFiles (int *FileSz, char *FileName);
// -----------------------------------------------------------------------
FILE *OpenFiles (int *FileSz, char *FileName) {
FILE *pFile=NULL;
if (NULL == (pFile = fopen ((char *)FileName, "r+t" ))) {
printf ( "Can't open %s\n", FileName );
return NULL;
} else {
fseek(pFile,0,SEEK_END);
*FileSz = ftell(pFile);
rewind(pFile);
printf("\nFile size is %i", *FileSz);
return pFile;
}
}
// -----------------------------------------------------------------------
int _tmain(int argc, char *argv[]) {
bool IsGph[256];
UCHAR *p, *pBuff=NULL;
int WrdKnt=0,CharKnt=0;
int i, j, FileSz, LoopKnt=3500;
time_t Etime=0,start=0, Eclocks=0;
FILE *pFile=NULL;
bool WasGraphFlg=false;
// Initialize boolean array to detect printable characters
for(i=0; i<sizeof(IsGph); i++) {
IsGph[i] = isgraph((unsigned char)i);
}
if(NULL == (pFile = OpenFiles(&FileSz, (char *)argv[1]))) {
return 0;
}
// --- Process out of buffer, not stdin -------------------------------
pBuff = (unsigned char *)calloc(FileSz, sizeof(char));
fread(pBuff, sizeof(char), FileSz, pFile);
start = clock();
for(i=LoopKnt; i; i--) {
p= pBuff;
CharKnt=0;
WrdKnt=0;
for(j=FileSz; j; j--) {
if(IsGph[*p++]) {
CharKnt++;
WasGraphFlg = true;
} else { // this char is whitespace, and
if(WasGraphFlg) { // last char was part of word ?
WrdKnt++;
}
WasGraphFlg = false;
}
}
}
Etime = clock() - start;
Eclocks= Etime * PROCESSOR_HZ/(LLONG) CLOCKS_PER_SEC;
printf("\nElapsed time for %10i loops was %10i milliseconds",
LoopKnt, Etime);
printf("\nCPU cycles consumed per char were %2f\n",
(dbl)Eclocks/(dbl)((LLONG)FileSz*(LLONG)LoopKnt));
printf("\n%i words counted per loop", WrdKnt);
printf("\n%i chars counted per loop\n", CharKnt);
getchar();
return _fcloseall();
free(pBuff);
}
如果您在命令行参数指定文件名时遇到问题,请在 Visual Studio 的“项目->属性->配置属性->常规”下,将“Unicode”更改为严重错误标记的“多字节”字符集。您总是可以在调试器中查看 argv[1] 的内存,以了解 argv[] 中的实际内容。