下面的程序应该找到n×n矩阵的第一个全零行(如果有的话)。这是解决问题的正确方法吗?在C语言中,或者在任何语言中,是否存在另一种良好的结构化方法?我正在尝试更多地了解goto语句及其适当的用途/替代方案。我感谢任何帮助!
int first_zero_row = -1; /* none */
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (A[i][j]) goto next;
}
first_zero_row = i;
break;
next: ;
}
答案 0 :(得分:4)
我不反对在某些情况下使用goto
,但我认为这可以像这样重写:
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (A[i][j]) break;
}
if (j==n) { /* the row was all zeros */
first_zero_row = i;
break;
}
}
答案 1 :(得分:4)
如何使用小辅助函数来简化循环中的代码:
bool is_zero_row(int* Row, int size)
{
int j;
for (j = 0; j < size; j++)
if (Row[j] != 0)
return false;
return true;
}
然后
int first_zero_row = -1; /* none */
int i;
for (i = 0; i < n; i++) {
if (is_zero_row(A[i], n))
{
first_zero_row = i;
break;
}
}
答案 2 :(得分:1)
首先:是的,你的方法对我来说是正确的。你可以像这样重写它:
int first_zero_row = -1; /* none */
int i, j;
for (i = 0; i < n; i++) {
int all_zeroes = 1;
for (j = 0; j < n; j++) {
if (A[i][j]) {
all_zeroes = 0;
break;
}
}
if (all_zeroes) {
first_zero_row = i;
break;
}
}
但我认为这实际上不如goto
版本明确。在像Perl这样提供循环标签的语言中,您可以这样做:
my $first_zero_row = -1; # none
ROW:
for my $i (0 .. $n-1) {
for my $j (0 .. $n-1) {
next ROW if $A[$i][$j];
}
$first_zero_row = $i;
last ROW;
}
答案 3 :(得分:1)
您的代码对我来说非常易读。不分青红皂白地追捕goto
并不好。去阅读Donald E. Knuth的Structured Programming with go to Statements,了解有关这个主题的有趣想法。
我的 goto
版本,以获得最高效率且非常易读:
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++)
if (A[i][j]) goto not_this_row;
goto row_found;
not_this_row:
}
/* Not found case here */
row_found:
/* Found case, i is the first zero row. */
答案 4 :(得分:0)
您可以使用一个可用作flag
的变量。对于if (A[i][j]) goto next;
条件修改标志值而不是goto next
。
然后再检查该标记值以执行其他操作,即在您的案例中next
标签之后。
int flag = 0;
for ( i = 0; i < n; i++ ) {
for ( j = 0; j < n; j++ ) {
if ( A[i][j] ){
flag = 1;
break;
}
}
if( flag == 1 ){
// do your stuff
continue;
}
if( flag == 0 ){
first_zero_row = i;
break;
}
注意:从代码中删除goto
可能需要您调整某些代码行的位置。
答案 5 :(得分:0)
因为我已经使用了我的第一个goto来打破嵌套循环......好吧......今天,我将对此进行权衡。我感觉不好,所以我读了一下为什么goto被认为是邪恶的。
Dijkstra(和其他人)认为goto是邪恶的原因是代码是在块中构建的,其中每个块都有前置条件和后置条件。 goto(或break)意味着你不能保证代码块会为下一个块生成一个有效的后置条件作为前提条件(即你不能'证明你的代码是正确',因为break / goto可能会跳过代码块的基本部分)
但是,我认为goto在你的例子中很好的原因是:你实际上是在事先设置一个完全有效的后置条件(first_zero_row = -1 ......,即:“没有找到全零行”)。因此,您的块无法进入无效的后置条件状态。
可读性问题是另一回事。 Goto使代码变得难以理解。因此,对于您的特定示例,可能有更好的替代方案。
答案 6 :(得分:-2)
在我看来,所有GOTO
都是邪恶的。如果在任何情况下你都需要它们,你最好考虑一下
“提取方法”重构。
需要使用“GOTO”是一个非常好的指标,表明你的方法(或功能)过于复杂,你应该把它分成不同的部分。
例如,问题的给定示例可以(并且应该)重构为:
bool is_zero_row(int *row, int size){
for(i = 0; i<size; ++i){
if(row[i]) return false;
}
return true;
}
bool find_zero_row_in_array(int **A, int rows, int row_size){
int i,j;
for(i = 0; i < rows; ++i) {
if(is_zero_row(A[i], row_size)) return true;
}
return false;
}
...
if(find_zero_row_in_array(A, n, n)){
//Found
} else {
//Not found
}
....
这比原始版本更灵活,更易读。至少我们现在有一个函数可以用于任何具有不同行数和列数的数组,而不是假设它们是相同的。