4

下面的程序应该找到 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: ;
}
4

7 回答 7

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;
    }
}
于 2012-12-02T07:51:56.987 回答
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;
    }
}
于 2012-12-02T09:01:13.683 回答
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;
}
于 2012-12-02T07:57:55.500 回答
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. */
于 2012-12-02T09:45:47.960 回答
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您的代码中删除它可能需要您调整某些代码行的位置。

于 2012-12-02T07:45:40.287 回答
0

自从我使用我的第一个 goto 来打破......嗯......永远的嵌套循环,今天,我将对此进行权衡。我对此感到难过,所以我读了一些关于为什么 goto 被认为是邪恶的。

Dijkstra(和其他人)认为 goto 是邪恶的原因是代码是在块中构建的,其中每个块都有前置条件和后置条件。goto(或中断)意味着您不能保证代码块将为下一个块生成有效的后置条件作为前提条件(即您不能“证明您的代码正确”,因为中断/转到可能会跳过代码块的重要部分)

但是,我认为 goto 在您的示例中很好的原因是:您实际上是在前面设置了一个完全有效的后置条件 (first_zero_row = -1 ...ie: "no all-zero row found")。因此,您的块不可能进入无效的后置条件状态。

不过,可读性问题是另一回事。Goto 使代码变得相当不可读。因此,对于您的特定示例,可能会有更好的选择。

于 2013-07-29T14:22:29.787 回答
-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
}
....

这比原来的更加灵活和可读。至少我们现在有一个函数可以用于具有不同行数和列数的任何数组,而不是假设它们是相同的。

于 2013-04-22T11:59:52.017 回答