C 编程语言中有范围运算符吗?

Are there range operators in the C programming language?

C 中是否有范围运算符,例如 Swift(封闭范围,开放范围)?
例如,在 switch 语句中,在 Swift 中我会这样写:

switch num {
    case 144...168:
    print("Something between 144 and 168")

    case 120..<144:
    print("Something between 120 and 143")
    
    default: break
}

如果我在 C 中尝试类似的东西,使用 printf 而不是 print 当然,我不能使用相同的范围运算符。有什么东西可以代替吗?
谢谢

标准 C(截至 2022 年...)不提供此类范围运算符。

但是 GCC 提供 case ranges 作为扩展:

6.30 Case Ranges

You can specify a range of consecutive values in a single case label, like this:

case low ... high:

This has the same effect as the proper number of individual case labels, one for each integer value from low to high, inclusive.

This feature is especially useful for ranges of ASCII character codes:

case 'A' ... 'Z':

Be careful: Write spaces around the ..., for otherwise it may be parsed wrong when you use it with integer values. For example, write this:

case 1 ... 5:

rather than this:

case 1...5:

注意有关空格和整数值的警告。

还有这个

case 120..<144:

在你假设的代码中会写成

case 120 ... 143:

使用 GCC 扩展。

您可以使用一系列 if 语句。

当我进行范围匹配时,我会创建宏来简化代码。

而且,我发现 do { } while (0); 块可以简化事情,而不是大型 if/else 阶梯。

这是一些代码:

#define RNGE(_val,_lo,_hi) \
    (((_val) >= (_lo)) && ((_val) <= (_hi)))
#define RNGEM1(_val,_lo,_hi) \
    (((_val) >= (_lo)) && ((_val) < (_hi)))

do {
    if (RNGE(num,144,168)) {
        printf("Something between 144 and 168\n");
        break;
    }

    if (RNGEM1(num,120,144)) {
        printf("Something between 120 and 143\n")
        break;
    }

    // default ...
} while (0);

更新:

Nice! Not too familiar with macros, but they look like functions. What is the backslash at the end of the line? Also, why the underscores? Convention? Preference? Second and fourth line perfectly clear! – NotationMaster

宏是一种“生成”代码的方法。它们 [通常] 在编译器的单独第一阶段进行处理:C 预处理器(又名 cpp)。它们的操作就像您使用编辑器“插入”代码一样。

反斜杠是必需的如果你想在多行上定义你的宏[就像我一样]。

我们可以查看宏生成的源代码(例如)

cc -E -P -o foo.i foo.c

这是该阶段的输出:

do {
 if ((((num) >= (144)) && ((num) <= (168)))) {
  printf("Something between 144 and 168\n");
  break;
 }
 if ((((num) >= (120)) && ((num) < (144)))) {
  printf("Something between 120 and 143\n")
  break;
 }
} while (0);

下划线是我的个人惯例(40多年后发展;-)。

大多数人会将宏定义为:

#define RNGE(num,lo,hi) \
    (((num) >= (lo)) && ((num) <= (hi)))
#define RNGEM1(num,lo,hi) \
    (((num) >= (lo)) && ((num) < (hi)))

我用它们来区分宏参数和 C 符号。注意这里我把 _val 改成了 num。我故意这样做是为了使用您的示例使用的相同符号(即 num)。现在,生成的输出中包含 num。但是,num 是来自 [function] 范围的 C 变量 直接 (例如,我忘记在宏中定义参数)还是通过参数来的?

这有点争议,因为在 C 语言中,有一些库标准(例如 POSIX)保留所有以“_”开头的符号供标准库使用。

然而,这只是一个惯例。我用“_”定义的符号很少会与任何内部库符号发生冲突。如果有人这样做,我完全愿意接受更改我的代码的责任。事实上,40 年来,我从未遇到过这样的冲突,也从未需要重新编写代码。

对于宏参数,它们在不同的命名空间中,到目前为止 较少 可能与 [POSIX] 库中的全局变量或函数名称冲突。

此外,我认为 POSIX 到 co-opt 为自己建立如此广泛的机制是极端的狂妄自大。在 python 中,“_”前缀是对象函数对对象私有的约定。

如果您想要“_”的好处,又不想“违反”约定的麻烦(例如):

#define RNGE(val_,lo_,hi_) \
    (((val_) >= (lo_)) && ((val_) <= (hi_)))
#define RNGEM1(val_,lo_,hi_) \
    (((val_) >= (lo_)) && ((val_) < (hi_)))

宏在某些函数无法运行的情况下很有用。考虑以下我们用 inline 函数替换宏的地方:

static inline int
RNGE(int val,int lo,int hi)
{
    return (val >= lo) && (val <= hi);
}

// this works ...
int num;
do {
    if (RNGE(num,144,168)) {
        printf("Something between 144 and 168\n");
        break;
    }

    // default ...
} while (0)

// this blows up at compile time ...
int arr[1000];
int *ptr = &arr[37];
do {
    if (RNGE(ptr,&arr[144],&arr[168])) {
        printf("Something between array elements 144 and 168\n");
        break;
    }

    // default ...
} while (0)

C 没有像某些语言(例如 C++)那样的重载函数。因此,我们必须为每个参数类型显式生成单独的函数。

在这种情况下,宏适用于任何可以比较的类型。


更新#2:

有关使用 do { ... } while (0) 代替 if/else 梯形逻辑或 switch/case 的更多信息,请参阅我的回答:

thank you for the extra explanation. I recall in Swift these range operators would be perfect for usage in a switch statement. Yet, here, if I use this macro in the switch, the compiler still tells "Expression is not an integer constant expression". Am I missing something here? – NotationMaster

简短的回答是...

  1. with switch (switch_expression) 然后 switch_expression 可以是任何东西。

  2. 但是,具有 case case_expression 的 C 的一个限制是 case_expression 只能 是计算为 [=126 的东西=]constant 在 compile time.

  3. switch/case 是一个 [非常有用] 的细节。但是,它所做的任何事情都可以用 if/else.

    来完成
  4. 因此,处理此问题的方法是使用上面第一个示例中的代码。

警告:这是你应该停止阅读的地方......:-)

长答案...

那么,为什么C有这个限制呢?主要是历史性的。但是,C 想要生成极快的代码。

许多switch/case块在case表达式中使用连续个数字:

switch (num) {
case 1:
    x = 23;
    break;

case 2:
    y = 37;
    break;

case 3:
    z = 19;
    break;

case ...:
    ...;
    break;

case 100:
    q = 28;
    break;

default:
    abort();
    break;
}

simple/obvious 代码将是(使用 if/else 梯形逻辑,我在这里转换为使用我的 do/while/0 “技巧”将是:

do {
    if (num == 1) {
        x = 23;
        break;
    }

    if (num ==  2) {
        y = 37;
        break;
    }

    if (num == 3) {
        z = 19;
        break;
    }

    // cases for 4-99 ...

    if (num == 100) {
        q = 28;
        break;
    }

    // default:
    abort();
} while (0);

但是...这里有 很多if 语句。但是,因为 case 表达式是 连续的 ,优化编译器可以检测到这一点并生成执行范围检查的 [asm] 代码(即 ifnum1-100)范围内,可以用num值索引成一个指针table,直接跳转到对应的代码段。

下面是一些伪代码,显示了这一点:

static void *switch_vector[] = {
    &&L_1,
    &&L_2,
    &&L_3,
    ...,
    &&L_100
};

if (RNGE(num,1,100))
    goto switch_vector[num - 1];
else
    goto L_DEFAULT;

L_1:
    x = 23;
    goto L_ENDSWITCH;

L_2:
    y = 37;
    goto L_ENDSWITCH;

L_3:
    z = 19;
    goto L_ENDSWITCH;

// more labels ...

L_100:
    q = 28;
    goto L_ENDSWITCH;

L_DEFAULT:
    abort();

L_ENDSWITCH:
    // code after switch

旁注: 我称此为“伪”代码,但这实际上是 有效 C 代码,使用了非常高级的用法C 中的“计算”goto。不要实际使用它,因为它实际上从不需要。编译器通常会使用上面更简单的结构生成更好的代码。记住,我确实警告过你:-)

参见:

  1. https://eli.thegreenplace.net/2012/07/12/computed-goto-for-efficient-dispatch-tables