转到和代码重复 - 在这种情况下它们可以避免吗?
Goto and Code Repetition - Are they avoidable in this case?
我最近遇到了一个编程问题,在我看来,最优化的解决方法是使用 goto,尽管这不是一个好习惯。问题是:告诉用户输入一个正自然数(> 0)并读取输入。如果此数字有效,请告诉用户该数字的平方。在输入正确时执行此操作。我想出了一些解决方案,但它们似乎都有问题。这是其中两个:
解决方案 1 - 问题:使用 goto
#include <stdio.h>
int main()
{
int num;
_LOOP:
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0) {
printf("Square: %i\n", num * num);
goto _LOOP;
}
printf("Invalid number\n");
return 0;
}
解决方案 2 - 问题:仔细检查 num > 0(代码重复)
#include <stdio.h>
int main()
{
int num;
do {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0)
printf("Square: %i\n", num * num);
} while (num > 0);
printf("Invalid number\n");
return 0;
}
显然,有更多的方法可以解决这个问题,但我想出的所有其他方法都没有使用 goto 来解决相同的代码重复问题。那么,是否有一种解决方案可以避免 goto 和代码重复?如果没有,我应该去哪一个?
这是答案的一半;尝试填补缺失的内容。请记住,有时最好将循环构造为“做某事直到...”而不是“做某事而...”
for (;;) {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num <= 0)
break;
printf("Square: %i\n", num * num);
}
printf("Invalid number\n");
[更新为@rdbo 的回答]
跳出循环呢?这基本上是循环结束的 goto
语句,没有显式使用 goto
.
#include <stdio.h>
int main()
{
int num;
while(1) {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0) {
printf("Square: %i\n", num * num);
} else {
printf("Invalid number\n");
break;
}
}
return 0;
}
对于初学者来说,如果您希望得到一个非负数,那么变量 num
应该是无符号整数类型,例如 unsigned int
.
正如您的问题中所写,用户可以输入无效数据或中断输入。你必须处理这样的情况。
乘法 num * num
也会导致溢出。
并且使用 goto
而不是循环确实是一个坏主意。
注意在使用变量的最小范围内声明变量。
为这样的任务使用 for 循环也是一个坏主意。使用 while 循环非常有表现力。
程序可以这样看
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
while ( true )
{
printf( "Enter a positive natural number: " );
unsigned int num;
if ( scanf( "%u", &num ) != 1 || num == 0 ) break;
printf( "Square: %llu\n", ( unsigned long long )num * num );
}
puts( "Invalid number" );
return 0;
}
程序输出可能看起来像
Enter a positive natural number: 100000000
Square: 10000000000000000
Enter a positive natural number: 0
Invalid number
或者将最后一个输出语句移到while语句中会更好。例如
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
while ( true )
{
printf( "Enter a positive natural number: " );
unsigned int num;
if ( scanf( "%u", &num ) != 1 || num == 0 )
{
puts( "Invalid number" );
break;
}
printf( "Square: %llu\n", ( unsigned long long )num * num );
}
return 0;
}
另一个选项:如果满足继续条件,则检查存储的布尔值。它比无限 loop/break 方法(对我来说)更容易阅读,并且没有代码重复。
#include <stdio.h>
#include <stdbool.h>
int main()
{
int num;
bool bContinue;
do {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0){
printf("Square: %i\n", num * num);
bContinue = true;
}
else{
printf("Invalid number\n");
bContinue = false;
}
} while (bContinue);
return 0;
}
我有点惊讶还没有人建议功能分解。
与其编写一大堆原始语句,不如将 main
分割成更小的函数。
除了 readability/maintainability 好处外,它还有助于以非常自然的方式消除代码重复。
在 OP 的情况下,从最终用户那里获得输入是一项单独的责任,并且是一个单独功能的不错选择。
static bool user_enters_number(int *ptr_to_num)
{
printf("Enter a positive natural number: ");
return scanf("%i", ptr_to_num) == 1;
}
注意 user_enters_number
显式测试 scanf
的 return 值。
这改进了文件结尾处理。
同样,您可以为数字验证赋予它自己的功能。
这看起来有点矫枉过正(只是 num > 0
,对吧?),但它让我们有机会将验证与生成的错误消息结合起来。
在 main
末尾打印“无效数字”感觉不对。无效数字不是唯一的退出条件;文件结尾是另一个。
因此,我会让验证函数确定消息。
作为奖励,这使得支持多种错误类型成为可能(例如,负数和零的单独消息)。
static bool is_valid_number(int num)
{
bool ok = (num > 0);
if (!ok) printf("Invalid number\n");
return ok;
}
我们现在有两个布尔类型的函数,它们可以与 &&
整齐地链接在一起,并放入循环的条件部分,这是一种惯用的说法:如果这些函数中的任何一个失败(即 returns false),立即退出循环。
剩下的是一个非常干净的 main
函数。
int main(void)
{
int num;
while (user_enters_number(&num) && is_valid_number(num))
{
printf("Square: %i\n", num * num);
}
}
要了解可维护性方面的好处,请尝试重写此代码,使其接受两个数字并打印它们的乘积。
int main(void)
{
int num1, num2;
while (user_enters_number(&num1) && is_valid_number(num1) &&
user_enters_number(&num2) && is_valid_number(num2))
{
printf("Product: %i\n", num1 * num2);
}
}
变化微不足道,仅限于单一功能
(尽管您可能会考虑将参数 input_prompt
添加到 user_enters_number
)。
这种 'divide-and-conquer' 方法没有性能损失:智能编译器会做任何必要的事情来优化代码,例如内联函数。
我最近遇到了一个编程问题,在我看来,最优化的解决方法是使用 goto,尽管这不是一个好习惯。问题是:告诉用户输入一个正自然数(> 0)并读取输入。如果此数字有效,请告诉用户该数字的平方。在输入正确时执行此操作。我想出了一些解决方案,但它们似乎都有问题。这是其中两个:
解决方案 1 - 问题:使用 goto
#include <stdio.h>
int main()
{
int num;
_LOOP:
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0) {
printf("Square: %i\n", num * num);
goto _LOOP;
}
printf("Invalid number\n");
return 0;
}
解决方案 2 - 问题:仔细检查 num > 0(代码重复)
#include <stdio.h>
int main()
{
int num;
do {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0)
printf("Square: %i\n", num * num);
} while (num > 0);
printf("Invalid number\n");
return 0;
}
显然,有更多的方法可以解决这个问题,但我想出的所有其他方法都没有使用 goto 来解决相同的代码重复问题。那么,是否有一种解决方案可以避免 goto 和代码重复?如果没有,我应该去哪一个?
这是答案的一半;尝试填补缺失的内容。请记住,有时最好将循环构造为“做某事直到...”而不是“做某事而...”
for (;;) {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num <= 0)
break;
printf("Square: %i\n", num * num);
}
printf("Invalid number\n");
[更新为@rdbo 的回答]
跳出循环呢?这基本上是循环结束的 goto
语句,没有显式使用 goto
.
#include <stdio.h>
int main()
{
int num;
while(1) {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0) {
printf("Square: %i\n", num * num);
} else {
printf("Invalid number\n");
break;
}
}
return 0;
}
对于初学者来说,如果您希望得到一个非负数,那么变量 num
应该是无符号整数类型,例如 unsigned int
.
正如您的问题中所写,用户可以输入无效数据或中断输入。你必须处理这样的情况。
乘法 num * num
也会导致溢出。
并且使用 goto
而不是循环确实是一个坏主意。
注意在使用变量的最小范围内声明变量。
为这样的任务使用 for 循环也是一个坏主意。使用 while 循环非常有表现力。
程序可以这样看
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
while ( true )
{
printf( "Enter a positive natural number: " );
unsigned int num;
if ( scanf( "%u", &num ) != 1 || num == 0 ) break;
printf( "Square: %llu\n", ( unsigned long long )num * num );
}
puts( "Invalid number" );
return 0;
}
程序输出可能看起来像
Enter a positive natural number: 100000000
Square: 10000000000000000
Enter a positive natural number: 0
Invalid number
或者将最后一个输出语句移到while语句中会更好。例如
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
while ( true )
{
printf( "Enter a positive natural number: " );
unsigned int num;
if ( scanf( "%u", &num ) != 1 || num == 0 )
{
puts( "Invalid number" );
break;
}
printf( "Square: %llu\n", ( unsigned long long )num * num );
}
return 0;
}
另一个选项:如果满足继续条件,则检查存储的布尔值。它比无限 loop/break 方法(对我来说)更容易阅读,并且没有代码重复。
#include <stdio.h>
#include <stdbool.h>
int main()
{
int num;
bool bContinue;
do {
printf("Enter a positive natural number: ");
scanf("%i", &num);
if (num > 0){
printf("Square: %i\n", num * num);
bContinue = true;
}
else{
printf("Invalid number\n");
bContinue = false;
}
} while (bContinue);
return 0;
}
我有点惊讶还没有人建议功能分解。
与其编写一大堆原始语句,不如将 main
分割成更小的函数。
除了 readability/maintainability 好处外,它还有助于以非常自然的方式消除代码重复。
在 OP 的情况下,从最终用户那里获得输入是一项单独的责任,并且是一个单独功能的不错选择。
static bool user_enters_number(int *ptr_to_num)
{
printf("Enter a positive natural number: ");
return scanf("%i", ptr_to_num) == 1;
}
注意 user_enters_number
显式测试 scanf
的 return 值。
这改进了文件结尾处理。
同样,您可以为数字验证赋予它自己的功能。
这看起来有点矫枉过正(只是 num > 0
,对吧?),但它让我们有机会将验证与生成的错误消息结合起来。
在 main
末尾打印“无效数字”感觉不对。无效数字不是唯一的退出条件;文件结尾是另一个。
因此,我会让验证函数确定消息。
作为奖励,这使得支持多种错误类型成为可能(例如,负数和零的单独消息)。
static bool is_valid_number(int num)
{
bool ok = (num > 0);
if (!ok) printf("Invalid number\n");
return ok;
}
我们现在有两个布尔类型的函数,它们可以与 &&
整齐地链接在一起,并放入循环的条件部分,这是一种惯用的说法:如果这些函数中的任何一个失败(即 returns false),立即退出循环。
剩下的是一个非常干净的 main
函数。
int main(void)
{
int num;
while (user_enters_number(&num) && is_valid_number(num))
{
printf("Square: %i\n", num * num);
}
}
要了解可维护性方面的好处,请尝试重写此代码,使其接受两个数字并打印它们的乘积。
int main(void)
{
int num1, num2;
while (user_enters_number(&num1) && is_valid_number(num1) &&
user_enters_number(&num2) && is_valid_number(num2))
{
printf("Product: %i\n", num1 * num2);
}
}
变化微不足道,仅限于单一功能
(尽管您可能会考虑将参数 input_prompt
添加到 user_enters_number
)。
这种 'divide-and-conquer' 方法没有性能损失:智能编译器会做任何必要的事情来优化代码,例如内联函数。