1

这是我遇到的一个普遍问题,我还没有找到一个感觉不是很“hack-y”的解决方案。假设我有一些元素数组xs = {a_1, a_2, ..., a_n},我知道其中一些元素x在数组中。我希望遍历数组,并对每个元素做一些事情,直到并包括元素x。这是一个几乎可以做到这一点的版本,只是它省略了最后一个元素。请注意,在此示例中,数组恰好是整数的排序列表,但在一般情况下,这可能不一定是真的。

int xs[] = {1,2,3,4,5};
for (int i = 0; xs[i] != 4; i++) {
    foo(xs[i]);
}

到目前为止,我看到的唯一解决方案是:

  1. foo(xs[i]);只需在 for 循环之后添加一个 final语句。这首先是丑陋和重复的,尤其是在foo不仅仅是函数调用而是语句列表的情况下。其次,它需要i在 for 循环的范围之外定义。

  2. 手动中断循环,在无限循环中使用 if 语句。这在我看来又是丑陋的,因为我们并没有真正充分地使用 for 和 while 结构。这个问题几乎是你使用 for 循环的原型,唯一的区别是我们只是希望它再通过一次循环。

有谁知道解决这个问题的好方法?

4

3 回答 3

6

在 C 中,for循环是一个“在正文前检查”操作,您需要“在正文后检查”变体,一个do while循环,类似于:

int xs[] = {1,2,3,4,5};
{
    int i = 0;
    do {
        foo(xs[i]);
    } while (xs[i++] != 4);
}

你会注意到我已经将整个块包含在它自己的范围内(最外面的{}大括号)。这只是为了限制 的存在,i使其更符合for循环行为。

就显示这一点的完整程序而言,以下代码:

#include <stdio.h>

void foo(int x) {
    printf("%d\n", x);
}

int main(void) {
    int xs[] = {1,2,3,4,5};
    {
        int i = 0;
        do {
            foo(xs[i]);
        } while (xs[i++] != 4);
    }
    return 0;
}

输出:

1
2
3
4

顺便说一句,像您一样,我不太喜欢您看到的其他两种解决方案。

对于第一个解决方案,在这种情况下实际上不起作用,因为生命周期i仅限于for循环本身(intfor语句初始化部分中这样做)。

这意味着i在循环之后将没有您期望的值。要么没有i(编译时错误),要么有一个隐藏i在循环中,因此不太可能for具有您期望的值,从而导致潜在的错误。

其次,我有时会在正文中中断循环,但通常只在正文的开头,以便控制逻辑在单个区域中仍然可见。for如果条件会很长,我倾向于这样做,但还有其他方法可以做到这一点。

于 2021-05-15T02:18:20.657 回答
0

只要前一个元素(如果可用)不是,就尝试处理循环4

int xs[] = {1,2,3,4,5};
for (int i = 0; i == 0 || xs[i - 1] != 4; i++) {
    foo(xs[i]);
}
于 2021-05-15T05:41:43.440 回答
0

这可能不是对原始问题的直接回答,但我强烈建议不要养成这样解析数组的习惯(这就像一个定时炸弹等待在随机时间点爆炸)。

我知道您说过您已经知道 x 是 xs 的成员,但是如果不是(并且由于各种原因可能会意外发生),那么如果幸运的话,您的循环将使您的程序崩溃,否则它将破坏不相关的数据你不走运。

在我看来,通过额外的检查进行防御既不丑也不“hacky”。

如果障碍是看似未知的 xs 长度,则不是。静态数组通过声明或初始化(如您的示例)具有已知长度。在后一种情况下,可以在声明的数组范围内按需计算长度,sizeof(arr) / sizeof(*arr)您甚至可以将其设为可重用宏。

#define ARRLEN(a) (sizeof(a)/sizeof(*(a)))
    ...
    int xs[] = {1,2,3,4,5};
    /* way later... */
    size_t xslen = ARRLEN(xs);
    for (size_t i=0; i < xslen; i++) {
        if (xs[i] == 4) {
            foo( xs[i] );
            break;
        };
    }

即使数组中不存在 4,这也不会超出 xs。


编辑:

在声明的数组的范围内”意味着宏(或其直接代码)将不适用于作为函数参数传递的数组。

// This does NOT work, because sizeof(arr) returns the size of an int-pointer
size_t foo( int arr[] ) {
    return( sizeof(arr)/sizeof(*arr) );
}

如果您需要函数内部数组的长度,您也可以将它作为参数与数组一起传递(实际上只是指向第一个元素的指针)。

或者,如果性能不是问题,您可以使用sentinel下面解释的方法。

[编辑结束]


另一种方法是用一个sentinel值(您故意认为无效的值)手动标记数组的末尾。例如,对于整数,它可以是 INT_MAX:

#include <limits.h>
    ...
    int xs[] = {1,2,3,4,5, INT_MAX};
    for (size_t i=0; xs[i] != INT_MAX; i++) {
        if (xs[i] == 4) {
            foo( xs[i] );
            break;
        };
    }

哨兵在解析未知长度的动态分配的指针数组时非常常见,哨兵为 NULL。

无论如何,我的主要观点是,与代码美观相比,防止意外缓冲区溢出可能具有更高的优先级:)

于 2021-05-15T08:43:25.183 回答