1

我已经完成了一个程序,其中给定的输入应该是字符流,并且程序会计算非空白字符和单词。word 被定义为由空格字符分隔的字符流。所以这是程序..

#include <stdio.h> 
#include<ctype.h>
#include <stdbool.h>
#include<iso646.h>

int main(void)  
{   unsigned long int wordcount = 0,charcount = 0, count=1;
    int ch;
    bool flag, prev;



    while ((ch = getchar()) != EOF) 
      {   if(isgraph(ch))
              flag=true;
          else
              flag=false;

          if(flag)
             charcount++;

          if(count ==1)
             prev = flag;

        if(count != 1)
           {   if(prev and (not flag))
                  wordcount++;
               prev = flag;
           }

          count++;
      }

    if((ch == EOF) and flag)
          wordcount++;

   printf("\nnumber of words counted are %lu \n", wordcount);
   printf("\nnumber of characters counted are %lu \n", charcount);



   return 0;

}

现在我已经用简单的句子检查了这个程序。但只是为了练习,我想对此进行详细的软件测试。那么我该怎么做呢?我只是给出更多的句子吗?我试图从我在古腾堡项目中找到的一些小说中给出几段。我还能在这里做什么?我也可以提高这个程序的效率吗?

4

2 回答 2

3

有各种基本测试要做:

  1. 空的文件
  2. 文件只有一个空格
  3. 只有一个非空白的文件
  4. 一个空白和一个非空白的文件
  5. 一个非空白和一个空白的文件
  6. 仅包含多个空格的文件
  7. 仅包含多个非空白的文件
  8. 包含多个空格后跟非空格的文件

如此继续……这是边界测试;确保代码在边界条件下正常工作。

您将值 from 分配getchar()给 an unsigned long int(现在已在问题中解决)是不寻常的。由于常规字符的返回值为正,文件结尾或错误的返回值为负 (EOF),因此将其分配给带符号的 plain 是正常的int

ch == EOF循环后的测试是多余的;退出循环的唯一方法是条件为真时。

使用<iso646.h>and (宏)关键字andnot很不寻常。

最常见的是,人们不会将代码与块的左大括号放在同一行。

您可以在您设置charcountif块中增加flag = true;. 您可以使用else块代替if (count != 1). 事实上,AFAICT,你的代码:

if(count ==1)
    prev = flag;

if(count != 1)
    {   if(prev and (not flag))
            wordcount++;
        prev = flag;
    }

可以写成:

if (count > 1 and prev and (not flag))
    wordcount++;
prev = flag;

“计算的字符数”的描述并不完全准确;它是您报告的图形(非空白、非控制)字符的数量。不过,这可能是在挑剔程度的过度挑剔端(以及观察到“单词数量”是一个奇异的数量,它应该是“是”而不是“是”)。

count从1 而不是 0开始有点不寻常。它似乎记录了“比读入程序的原始字符数多一个”,这是一个不寻常的记录量。更通常的情况是,您也将其初始化为 0,并修改我重写的测试以读取:

if (count != 0 and prev and (not flag))

(您可以使用count != 0or count > 0; 对于无符号值,这些术语是等效的。)

您可以通过prev适当地初始化(可能是false)来简化条件。

于 2012-12-16T06:21:27.100 回答
0

养成将要测试的常数放在左侧的习惯,例如

而 (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[] 中的实际内容。

于 2012-12-16T08:55:42.077 回答