问题描述
我编写了以下函数,用于检查字符串中是否存在任何非数字字符。该函数必须在找到任何非数字字符后立即停止并退出循环并返回 true。否则,它将返回false。这是我正在使用的代码:(包含 ctype.h 库)。
bool isnotdigit(string argv)
{
bool y = false;
for (int i = 0,n = strlen(argv); i < n; i++)
{
char c = argv[i];
if (! isdigit(c))
{
y = true;
break;
}
}
return y;
}
也可以这样做:
bool isnotdigit(string argv)
{
bool y = false;
for (int i = 0,n = strlen(argv); i < n; i++)
{
char c = argv[i];
if (! isdigit(c))
{
y = true;
goto next;
}
}
next:
return y;
}
如果我没记错的话,两个代码的工作方式是一样的。对?那么,两者的优缺点是什么?特别是在上面的 bool 函数的情况下。
解决方法
这是一个编程风格的问题,因此,您可能不会得到明确的答案。
有人说break
只是伪装的goto
,所以一个和另一个一样糟糕。有人说你永远不应该使用 break
。
但 break
的全部意义在于它是一个保证不会混淆的 goto
。它总是从循环内部,到循环外部。 (当然,除非它是 switch
语句的一部分。)
在这种情况下,goto
给您带来的好处很少或没有。所以我认为没有人会说你的第二个例子比你的第一个好得多。 (当存在嵌套循环时,事情会变得更复杂。稍后会详细介绍。)
最后,另一种编写函数的方式是这样的:
bool isnotdigit(string argv)
{
for (int i = 0,n = strlen(argv); i < n; i++)
{
char c = argv[i];
if (! isdigit(c))
{
return false;
}
}
return true;
}
但是有些人说this 是不好的风格,因为(他们说)一个函数应该只有一个return
语句。然而,其他人说这是好的风格,因为它摆脱了额外的布尔变量 y
,并摆脱了额外的变量(特别是额外的小布尔变量,只是跟踪事物)也是一个很好的规则。
以下是我的看法:
-
goto
可能会令人困惑,您几乎不需要它。我几乎从不使用goto
。 -
break
没问题。它几乎从不令人困惑。我用它所有的时间。所以我更喜欢你的第一个片段而不是你的第二个片段。 - 额外的布尔变量,只是跟踪一些小事情,可能会令人困惑,尤其是当它们存在的唯一原因是为了绕过一些其他“规则”时。
- 我对多个
return
语句没有任何问题,因此我总是会使用更类似于第三种选择的方法,正如我在本答案中所介绍的那样。
现在,公平地说,反对多个 return
语句的论点可能是一个很好的论点。 如果在退出之前你有清理工作要做,或者如果你以后不得不添加一些清理代码,很容易忘记在两个地方(或三个地方)添加它或更多地方)如果有多个 return
语句。对我来说,当函数小而简单时(就像在这种情况下一样),我认为清洁度(以及额外布尔变量的损失)大于风险。但是如果你的元风格是你不喜欢判断调用,如果你喜欢严格的规则,你可以在任何地方应用以避免表面风险,那么“没有多个return
语句”规则是有道理的。
最后,还有嵌套循环的问题。对于第二个示例中 goto next;
的用法,我能想象的唯一理由是 如果 后来的程序员出现并添加了一个嵌套循环,则带有 break
的代码可能不会不再工作,并且必须以某种方式返工。通过使用 goto
,合理化可能会发生,代码对这种可能性更加健壮。 (就我个人而言,我认为这不是对 goto
的一个很好的合理化,但正如我所说,这是我唯一能想到的。)
一旦您有了嵌套循环,优缺点(即 goto
与 break
与多个 return
语句)肯定会发生变化,如对 {{3 }}。
附言一些语言通过使用类似 break(2)
的东西来“解决”嵌套循环中断问题,它可以一次中断多个循环。 C 没有这个,原因是它被认为太有可能令人困惑,而且如果后来的维护程序员添加或删除了一层嵌套,它很可能会破坏。
或者换句话说,尽管单级-break
是保证不会混淆的goto
,但多级-break
可能与{{ 1}}。而且,当然,您可以在这里对我的描述提出异议:毕竟,谁说 goto
保证不会混淆?当然,不能严格保证,但这是因为任何语言功能如果使用不当都会令人困惑。 (举个例子:额外的小布尔变量,只是跟踪各种事情。)
它们在功能上是等效的,但我更愿意只使用 goto
来跳出嵌套循环,而不是单个循环。
goto
经常被人反对,因为它可以导致“spaghetti code”,但它在 C 中有它的位置:
-
彻底摆脱嵌套循环
-
资源释放,如下:
char *buffer1 = NULL,*buffer2 = NULL,*buffer3 = NULL; buffer1 = malloc(1000); if(NULL == buffer1) { goto cleanup; } buffer2 = malloc(1000); if(NULL == buffer2) { goto cleanup; } buffer3 = malloc(1000); if(NULL == buffer3) { goto cleanup; } use(buffer1,buffer2,buffer3); cleanup: if(buffer1 != NULL) { free(buffer1); buffer1 = NULL; } if(buffer2 != NULL) { free(buffer2); buffer2 = NULL; } if(buffer3 != NULL) { free(buffer3); buffer3 = NULL; }
如果有其他选择,请不要使用 goto
。
但是在这个函数中你不需要,只要结果明确时return
:
bool isnotdigit(string argv)
{
for (int i = 0,n = strlen(argv); i < n; i++)
{
char c = argv[i];
if (! isdigit(c))
{
return true;
}
}
return false;
}
,
至少在您示例中的简单情况下,我建议避免同时使用 goto
和 break
,而是将条件添加到 for
语句中。 (这可能是个人偏好或可能适用于您的项目的编码指南的问题。)
bool isnotdigit(string argv)
{
bool y = false;
for (int i = 0,n = strlen(argv); (i < n) && !y; i++)
{
char c = argv[i];
/* or alternatively: y = ! isdigit(c); */
if (! isdigit(c))
{
y = true;
}
}
return y;
}
顺便说一句:我建议重命名函数参数,因为 argv
可能会与 main
函数的标准参数混淆,后者通常称为 argc
和 argv
。而不是 y
我会建议一个有意义的名字,例如wrong_char_found