【问题标题】:Is there problems with nesting many while loops?嵌套许多while循环有问题吗?
【发布时间】:2012-05-06 16:22:27
【问题描述】:

我正在做一些作业,想知道是否有太多嵌套的 while 循环之类的东西。嵌套几个while循环有缺点吗?如果是这样,如何重构我下面的代码 sn-p?

下面是一次读取一行文件,解析由一些定义的分隔符分隔的字段,并在打印到控制台之前删除前导空格的代码。

// Read the file one line at a time
while (fgets(lineStr, MAXLINELENGTH, fp) != NULL)
{
    charPtr = strtok(lineStr, DELIMITERS);

    // Loop until line is parsed
    while (charPtr != NULL)
    {
        // Skip past leading whitespace
        while (isspace(*charPtr))
            charPtr++;

        puts(charPtr);
        charPtr = strtok(NULL, DELIMITERS);
    }
}

【问题讨论】:

  • 不,嵌套循环没有问题。
  • 虽然,您可能希望将其中一些封装到反映它们正在做什么的方法中——最后一个变成skipSpaces(),或类似的。
  • 如果输入可以包含负字符(值大于 127),您可能需要将参数转换为 isspace 以避免未定义的行为:isspace((unsigned char)*charPtr)跨度>
  • 深度嵌套可能表明您应该重构以将事物拆分为不同的功能。例如,你可以有一个函数来解析一行,然后一个函数去除空白,一个标记化,等等。是的,这可能意味着多次通过缓冲区,但如果它不是性能关键用例,更简洁的模块化代码更好。

标签: c while-loop nested-loops


【解决方案1】:

这确实是一个相当主观的话题。在我看来,三个嵌套的 while 循环根本没有问题,但你已经达到了可接受的极限。在我看来,如果你再添加一层或两层嵌套,那么你就会越过期望读者理解的合理范围。人脑在任何一个时间点都只能处理这么多复杂性。

与我的观点相反,有些人会争辩说,一个函数中的嵌套层级不应超过一层,并且函数不应包含超过 10 行代码。相反的论点是,这样的策略会导致代码更加碎片化、脱节。我的经验法则是,如果您无法为一段代码想出一个好的函数名,那么也许那段代码并不真正意味着作为一个函数独立存在。

看看你可以分解这个函数的方法,有几个明显的选择。

  1. 将最外层while 的主体提取到一个单独的函数中。提取的函数将处理一行。它易于命名且易于阅读。
  2. 将跳过空格的while 循环提取到单独的函数中。这又很容易命名,并使您的代码更易于阅读。您将删除空白注释,因为提取函数的名称会使它变得不必要。这可能是值得的。

如果您应用了这些想法,那么您的代码可能看起来像这样:

char* skipWhitespace(char* str)
{
    while (isspace(*str))
        str++;
    return str;
}

void parseLine(char *lineStr)
{
    charPtr = strtok(lineStr, DELIMITERS);
    while (charPtr != NULL)
    {
        charPtr = skipWhitespace(charPtr);
        puts(charPtr);
        charPtr = strtok(NULL, DELIMITERS);
    }
}
......
while (fgets(lineStr, MAXLINELENGTH, fp) != NULL)
    parseLine(lineStr);

请注意,提取方法的重构和命名使 cmets 有点多余,因此我将其删除。另一个好的经验法则是,如果您需要过多地注释代码,那么它可能还没有被很好地考虑。

归根结底,确实没有硬性规定,这取决于判断和个人喜好。在我看来,问题中的代码非常清晰易读,但在我看来,重构后的版本更清晰一些。

免责声明:我不对代码的正确性或其他方面发表评论。我只是忽略了这方面。

【讨论】:

  • 我喜欢你的“没有名字,没有功能”的经验法则。显然只是一个指导方针,但它带来了一个很好的观点——如果你不能描述这个函数会做什么,你可能不需要它成为一个函数!
  • 感谢重构代码。将不得不询问教授他是否更喜欢代码保持原样,或者更喜欢将单个嵌套循环重构为它们自己的函数。我确实喜欢没有 cmets 的想法,因为函数名称足够清晰,可以传达这个想法。
  • 你更喜欢哪个?当你去找你的教授时,你至少应该有一个意见,并准备好以一种或另一种方式争论。
  • 我更喜欢功能。到目前为止,我们编写的所有代码都包含在测试代码调用的一个函数中,它可以解释嵌套的 while。我想转向更复杂的编码方式应该没有问题。
  • 将一些代码分解为专用函数将允许您重用这些函数。但请注意,功能需要非常完善,才能有效地重复使用。所以skipWhitespace 将是重复使用的候选对象,但parseLine 必须仅针对当前问题(我猜)。
【解决方案2】:

如前所述,这是相对主观的。但是,嵌套循环的方式可能会对代码的性能产生直接影响。考虑缓存感知 编程。也就是说,您希望以处理器可以在需要之前将下一个数据块预取(即预测)到高速缓存内存中的方式来安排您的代码。这将允许更多的缓存命中和更快的内存访问时间。

请注意,这对于您的示例来说并不是特别重要,但是,如果您要进行多次内存访问,这可能会显着提高或降低性能。如果您在行优先架构上以列方式遍历多维数组,则可能会出现许多缓存未命中(请注意,缓存未命中在实时方面的成本非常高)。

所以嵌套循环不一定不好,但它肯定会对性能产生显着影响,尤其是在一些任意数量的 n 循环之后。

【讨论】:

    【解决方案3】:

    唯一真正的缺点是可读性,对此并没有任何硬性规定……尽管超过 3 个嵌套通常会激怒与您一起工作的任何其他人。正如另一位海报所说,有时最好通过将循环移动到另一个函数来打破嵌套,但是你在这里所拥有的对我来说是完全可读的——这是唯一真正的指标;纯粹的主观意见:)

    【讨论】:

      猜你喜欢
      • 2016-06-06
      • 2015-05-23
      • 2021-07-31
      • 2015-07-04
      • 2015-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多