【问题标题】:C Program to count comment lines (// and /* */)计算注释行数的 C 程序(// 和 /* */)
【发布时间】:2013-05-26 13:18:05
【问题描述】:

我需要一个程序来计算 .txt 或 .c 文件的行数并返回以下内容:

文件:
简单注释:N 行
多行注释:N 行
总行数:N 行

我有这个:

if (pFile != NULL) 
{
    do { 
    c = fgetc(pFile);

    if (c == '\n') n++;

    } while (c != EOF);

而且我不知道如何实现它的其余部分。
我也尝试了 strstr() 函数,也没有得到它。

【问题讨论】:

  • 这是 perl 的工作,而不是 c。
  • 最好的办法是写一个解析器,仅仅通过字符检查是不可能完全正确的。
  • 你需要编写一个基于 c 标准的解析器(只是最初的几个翻译阶段。否则你将花费无尽的时间来追踪诸如“这些是注释分隔符 /* // */”之类的情况。三元组和续行也不是你的朋友。
  • 这可能是一个 hard 问题,具体取决于您在解析时需要跟踪的一些事项 1) // to EOL cmets, 2) /* to */ cmets 3) @ 987654324@ text to know cmets can't start in them 4) '...' char constants to know cmets can't start in them or 5) 假设 #include 文件表现良好(不要以打开的注释结尾,引用字符串, 等等)。 6) 其他人发布三合字母、二合字母、非 ASCII。除此之外,小菜一碟。

标签: c file count comments line


【解决方案1】:

您可以编写一个能够处理大多数情况的状态机。

当您扫描文件时,您将处于以下状态之一:

  1. TEXT - 常规(非注释)文本;这是您将要开始的状态。在此状态下看到的任何换行都会导致总行数计数器增加。
  2. SAW_SLASH - 您已经看到了一个 /,它可能是单行或多行注释的开头。如果下一个字符是/,您将进入 SINGLE_COMMENT 状态。如果下一个字符是*,您将进入 MULTI_COMMENT 状态。对于任何其他字符,您将返回 TEXT 状态。
  3. SINGLE_COMMENT - 您已经看到了// 令牌;你会一直保持这个状态,直到你看到一个换行符;一旦看到换行符,您将增加单行 cmets 的数量以及总行数,然后返回 TEXT 状态。
  4. MULTI_COMMENT - 你已经看到了/* 令牌;在看到下一个 */ 令牌之前,您将保持此状态。您在此状态下看到的任何换行符都会导致多注释行计数器与总行数一起增加。
  5. SAW_STAR - 在 MULTI_COMMENT 状态下,您看到了一个 *。如果下一个字符是/,您将返回 TEXT 状态。如果下一个字符是*,您将停留在 SAW_STAR 状态。否则,您将返回 MULTI_COMMENT 状态。

有一些我没有处理的极端情况(例如在评论状态下遇到 EOF),但以下应该是一个合理的示例,说明如何执行此类操作。

请注意,嵌套的 cmets 不会被计算在内;即,如果 //-delimited 评论出现在 /* */-delimited 评论中,则只会更新多评论计数器。

您可能希望将计数逻辑分解到它自己的函数中;只是尽量让这个例子简单明了。

#include <stdio.h>
#include <stdlib.h>

/**
 * Count up the number of total lines, single-comment lines,
 * and multi-comment lines in a file.
 */
int main(int argc, char **argv)
{
  FILE *fp;
  int c;
  unsigned int chars  = 0;
  unsigned int total  = 0;
  unsigned int multi  = 0;
  unsigned int single = 0;

  enum states { TEXT, 
                SAW_SLASH, 
                SAW_STAR, 
                SINGLE_COMMENT, 
                MULTI_COMMENT } state = TEXT;

  if ( argc < 2 )
  {
    fprintf(stderr, "USAGE: %s <filename>\n", argv[0]);
    exit(0);
  }

  fp = fopen( argv[1], "r" );
  if ( !fp )
  {
    fprintf(stderr, "Cannot open file %s\n", argv[1] );
    exit(0);
  }

  while ( (c = fgetc( fp )) != EOF )
  {
    chars++;
    switch( state )
    {
      case TEXT :
        switch( c )
        {
          case '/'  : state = SAW_SLASH; break;
          case '\n' : total++; // fall-through
          default   : break;
        }
        break;

      case SAW_SLASH :
        switch( c )
        {
          case '/'  : state = SINGLE_COMMENT; break;
          case '*'  : state = MULTI_COMMENT; break;
          case '\n' : total++; // fall through
          default   : state = TEXT; break;
        }
        break;

      case SAW_STAR :
        switch( c )
        {
          case '/'  : state = TEXT; multi++; break;
          case '*'  : break;
          case '\n' : total++; multi++; // fall through
          default   : state = MULTI_COMMENT; break;
        }
        break;

      case SINGLE_COMMENT :
        switch( c )
        {
          case '\n' : state = TEXT; total++; single++; // fall through
          default   : break;
        }
        break;

      case MULTI_COMMENT :
        switch( c )
        {
          case '*'  : state = SAW_STAR; break;
          case '\n' : total++; multi++; // fall through
          default   : break;
        }
        break;

      default: // NOT REACHABLE
        break;
    }
  }

  fclose(fp);

  printf( "File                 : %s\n", argv[1] );
  printf( "Total lines          : %8u\n", total );
  printf( "Single-comment lines : %8u\n", single );
  printf( "Multi-comment lines  : %8u\n", multi );
  return 0;
}

编辑

这是一个与上述程序等效的表驱动程序。我创建了一个state 表来控制状态转换和一个action 表来控制当我改变状态时会发生什么。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

/**
 * Using preprocessor macros instead of enums, per request; normally
 * I would use enums, since they obey scoping rules and
 * show up in debuggers.
 */
#define TEXT           0
#define SAW_SLASH      1
#define SAW_STAR       2
#define SINGLE_COMMENT 3
#define MULTI_COMMENT  4

#define TOTAL_STATES   5

#define NO_ACTION      0
#define INC_TOTAL      1
#define INC_SINGLE     2
#define INC_MULTI      4

/**
 * This example assumes 7-bit ASCII, for a total of
 * 128 character encodings.  You'll want to change this
 * to handle other encodings.
 */
#define ENCODINGS    128

/**
 * Need a state table to control state transitions and an action
 * table to specify what happens on a transition.  Each table
 * is indexed by the state and the next input character.
 */
static int  state[TOTAL_STATES][ENCODINGS]; // Since these tables are declared at file scope, they will be initialized to
static int action[TOTAL_STATES][ENCODINGS]; // all elements 0, which correspond to the "default" states defined above.

/**
 * Initialize our state table.
 */
void initState( int (*state)[ENCODINGS] )
{
  /**
   * If we're in the TEXT state and see a '/' character, move to the SAW_SLASH
   * state, otherwise stay in the TEXT state
   */
  state[TEXT]['/'] = SAW_SLASH;

  /**
   * If we're in the SAW_SLASH state, we can go one of three ways depending
   * on the next character.
   */
  state[SAW_SLASH]['/'] = SINGLE_COMMENT;
  state[SAW_SLASH]['*'] = MULTI_COMMENT;
  state[SAW_SLASH]['\n'] = TEXT;

  /**
   * For all but a few specific characters, if we're in any one of
   * the SAW_STAR, SINGLE_COMMENT, or MULTI_COMMENT states,
   * we stay in that state.
   */
  for ( size_t i = 0; i < ENCODINGS; i++ )
  {
    state[SAW_STAR][i] = MULTI_COMMENT;
    state[SINGLE_COMMENT][i] = SINGLE_COMMENT;
    state[MULTI_COMMENT][i] = MULTI_COMMENT;
  }

  /**
   * Exceptions to the loop above.
   */
  state[SAW_STAR]['/'] = TEXT;
  state[SAW_STAR]['*'] = SAW_STAR;

  state[SINGLE_COMMENT]['\n'] = TEXT;
  state[MULTI_COMMENT]['*'] = SAW_STAR;
}

/**
 * Initialize our action table
 */
void initAction( int (*action)[ENCODINGS] )
{
  action[TEXT]['\n'] = INC_TOTAL;
  action[SAW_STAR]['/'] = INC_MULTI;
  action[MULTI_COMMENT]['\n'] = INC_MULTI | INC_TOTAL;   // Multiple actions are bitwise-OR'd
  action[SINGLE_COMMENT]['\n'] = INC_SINGLE | INC_TOTAL; // together
  action[SAW_SLASH]['\n'] = INC_TOTAL;
}

/**
 * Scan the input file for comments
 */
void countComments( FILE *stream, size_t *totalLines, size_t *single, size_t *multi )
{
  *totalLines = *single = *multi = 0;

  int c;
  int curState = TEXT, curAction = NO_ACTION;

  while ( ( c = fgetc( stream ) ) != EOF )
  {
    curAction = action[curState][c]; // Read the action before we overwrite the state
    curState = state[curState][c];   // Get the new state (which may be the same as the old state)

    if ( curAction & INC_TOTAL )     // Execute the action.
      (*totalLines)++;

    if ( curAction & INC_SINGLE )
      (*single)++;

    if ( curAction & INC_MULTI )
      (*multi)++;
  }
}

/**
 * Main function.
 */
int main( int argc, char **argv )
{
  /**
   * Input sanity check
   */
  if ( argc < 2 )
  {
    fprintf( stderr, "USAGE: %s <filename>\n", argv[0] );
    exit( EXIT_FAILURE );
  }

  /**
   * Open the input file
   */
  FILE *fp = fopen( argv[1], "r" );
  if ( !fp )
  {
    fprintf( stderr, "Cannot open file %s\n", argv[1] );
    exit( EXIT_FAILURE );
  }

  /**
   * If input file was successfully opened, initialize our
   * state and action tables.
   */
  initState( state );
  initAction( action );

  size_t totalLines, single, multi;

  /**
   * Do the thing.
   */
  countComments( fp, &totalLines, &single, &multi );
  fclose( fp );

  printf( "File                 : %s\n", argv[1] );
  printf( "Total lines          : %zu\n", totalLines );
  printf( "Single-comment lines : %zu\n", single );
  printf( "Multi-comment lines  : %zu\n", multi );

  return EXIT_SUCCESS;
}

运行文件本身给我们

$ ./comment_counter comment_counter.c
File                 : comment_counter.c
Total lines          : 150
Single-comment lines : 7
Multi-comment lines  : 42

我认为是对的。这与第一个版本具有所有相同的弱点,只是形式不同。

【讨论】:

  • 嘿约翰。你能帮帮我吗?我的任务是编写一个 C 程序来计算另一个文件中 cmets 的数量。这是你在这里写的一个很好的例子。我在您的示例中进行了一些更改,现在它解决了我的任务。但是有没有办法不使用枚举?您的解决方案的替代方案?
  • @Alex:你可以使用预处理器宏而不是枚举;我只是选择了枚举,因为它们遵守范围规则并且名称保存在调试器中。您可以放弃嵌套的 switch 语句并使用表驱动的方法。
  • 问题是我们一个月前开始在大学学习C,我对这些东西不是很熟悉。我只是迷失了所有嵌套开关。
  • @Alex:是的,嵌套开关很快就会变得丑陋,但它们的编写速度很快。处理一个表格驱动的示例来附加到这个答案。
  • @Alex:查看我的编辑 - 添加了一个表格驱动版本来做同样的事情。
【解决方案2】:

所以 n == 文件中的行数。

您需要另一个变量,每次看到字符 / 后跟另一个 / 时都会计数(这很简单,基本上与您执行 n++ 的方式相同,但它应该看起来像

 if( c == '/') c = fgetc(pFile); if(c == '/') comments++; else break;

然后对于多行 cmets,你做同样的事情,只计算 /,就像上面一样。您只需要注意 n 何时增加(每行),以及其他的何时增加(每行一次以 // 和一次以 / 开头的每一行,直到你击中一个 */)。

【讨论】:

  • 你可能想考虑这个 //comment // 而不是这个评论 所以,当你在 if() 中执行 cmets++ 时,有效地继续获取字符,直到你得到换行符或 eof。剩下的东西我想单行 cmets 没问题
猜你喜欢
  • 2017-04-10
  • 2018-04-24
  • 2020-12-18
  • 2017-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-21
相关资源
最近更新 更多