嵌套多个while循环是否存在问题?

8

我正在做一些作业,想知道是否存在嵌套太多while循环的情况。 嵌套多个while循环是否有缺点? 如果是这样,如何重构下面的代码片段?

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

// 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);
    }
}

7
没有问题,嵌套循环不会有影响。 - pmg
1
虽然你可能想将其中一些封装成反映它们正在执行的方法 - 最后一个变成 skipSpaces() 或类似的东西。 - Clockwork-Muse
如果输入可能包含负值字符(值大于127),您可能需要将参数转换为isspace,以避免未定义的行为:isspace((unsigned char)*charPtr) - pmg
1
深层嵌套可能表明您应该重构以将事物拆分为不同的函数。例如,您可以有一个解析行的函数,然后是一个去除空格的函数,一个标记化函数等等。是的,这可能意味着对缓冲区进行多次传递,但如果它不是性能关键的用例,则更清晰的模块化代码更好。 - TJD
3个回答

8
这是一个非常主观的话题。在我看来,嵌套三个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);

请注意,提取方法的重构和命名使得注释有些多余,我已经将它们删除。另一个好的经验法则是,如果你需要过多地注释代码,那么可能它还没有被很好地重构。最终,真的没有硬性规定,这取决于判断和个人喜好。在我看来,问题中的代码非常清晰易读,但是我认为重构后的版本只是稍微更加清晰一些。
免责声明:关于代码的正确性与否,我不做任何评论。我只是忽略了这方面的内容。

2
我喜欢你的“无名,无功能”法则。显然只是一个指导方针,但它提出了一个很好的观点 - 如果你不能描述函数会做什么,那么你可能不需要它成为一个函数! - corsiKa
感谢您重构的代码。我需要问一下教授是否喜欢代码保持原样,还是希望将各个嵌套循环重构为自己的函数。我确实喜欢不使用注释的想法,因为函数名称已经足够清晰地传达了思路。 - Peter
你更喜欢哪个?当你去见教授时,你至少应该有一个观点并准备好为其辩护。 - David Heffernan
我更喜欢使用函数。到目前为止,我们编写的所有代码都包含在一个名为“test code”的函数中,这就解释了嵌套的while循环。我想应该没有问题尝试一些更复杂的编码方式。 - Peter
将一些代码分解为专用函数将允许您重复使用这些函数。但是要注意,函数需要非常完善才能有效地重复使用。因此,“skipWhitespace”将成为可重复使用的候选项,但“parseLine”必须仅针对当前问题具体化(我猜)。 - David Heffernan
我想补充一点,你很有可能比你的教授更快地掌握更好的编程技巧和风格。这就是我在SO上要说的全部内容 :-) - Jonathon Reinhart

3
唯一的真正缺点是可读性,这方面没有什么硬性规定...尽管超过3个嵌套通常会让你与其他人一起工作时感到烦躁。正如另一位发帖者所说,有时通过将循环移动到另一个函数中来打破嵌套可能更好,但在我看来,你在这里所写的代码是完全可读的——这是唯一真正的度量标准;纯主观意见 :)

0

正如先前提到的,这是相对主观的。然而,你嵌套循环的方式可能会直接影响代码的性能。考虑“缓存感知”编程。也就是说,你希望以这样的方式安排你的代码,让处理器能够在需要数据之前预取(即预测)下一个数据块到缓存中。这将允许更多的缓存命中和更快的内存访问时间。

请注意,这对于你的示例来说并不特别重要,然而,如果你进行了许多内存访问,这可能会显著地增加或减少性能。如果你按列的方式遍历多维数组,而 CPU 架构是行主架构,则可能会有许多缓存未命中(请注意,缓存未命中在实时方面非常昂贵)。

因此,嵌套循环并不一定是不好的,但它肯定会对性能产生明显的影响,尤其是在某个任意数量n的循环之后。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接