你可以使用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);
UPDATE:
美好的不太熟悉宏,但它们看起来像函数.这行末尾的反斜杠是什么?还有,为什么要加下划线?习俗偏爱第二行和第四行非常清晰
宏是"生成"代码的一种方式.它们[通常]在编译器的一个单独的第一阶段进行处理: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);
下划线是my个人习惯(40多年后形成;-).
大多数人将宏定义为:
#define RNGE(num,lo,hi) \
(((num) >= (lo)) && ((num) <= (hi)))
#define RNGEM1(num,lo,hi) \
(((num) >= (lo)) && ((num) < (hi)))
我用它们来区分宏参数和C符号.注意这里我把_val
改成了num
.我故意这么做是为了使用与你的例子相同的符号(即num
).现在,生成的输出中有num
个.但是,这是来自[function]作用域C变量directly的num
(例如,我忘了在宏中定义参数)还是来自参数?
这有点争议,因为在C语言中,有一些库标准(例如POSIX)为标准库使用保留了所有以"x"开头的符号.
然而,这只是一个惯例.我用"u"定义的符号很少与任何内部库符号冲突.如果有人这样做,我完全愿意承担更改代码的责任.事实上,40年来,我有过never次这样的冲突,而且从来没有重新编写过代码.
对于宏参数,它们位于不同的命名空间中,到目前为止可能与[POSIX]库中的全局变量或函数名冲突.
此外,我认为POSIX为自己 Select 这样一个广泛的机制是极端傲慢的.在python
中,"u"前缀是一种约定,即对象函数对对象是私有的.
如果你想在不"违反"公约的情况下享受"uu"的好处(例如):
#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++)那样具有重载函数.因此,我们必须为每种参数类型显式地生成单独的函数.
在本例中,宏适用于any种可比较的类型.
UPDATE #2:
有关使用do { ... } while (0)
代替if/else
梯形图逻辑或switch/case
的更多信息,请参阅我的答案:How to properly make a counting algorithm to count from file?
谢谢你的额外解释.我记得在Swift中,这些范围运算符非常适合在switch 声明中使用.然而,在这里,如果我在switch 中使用这个宏,编译器仍然会告诉我"表达式不是整型常量表达式".我错过了什么吗?——
简单的回答是...
有了switch (switch_expression)
,那么switch_expression
可以是任何东西.
但是,C和case case_expression
的限制是case_expression
可以是在compile次时计算为constant的值.
所以,处理这个问题的方法是使用我上面第一个例子中的代码.
Warning:这是你应该停止阅读的地方…:-)
答案很长...
那么,why C有这个限制吗?主要是历史.但是,C希望生成极快的代码.
许多switch/case
个块在case
表达式中使用consecutive个数字:
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;
}
简单/明显的代码是(使用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
条语句中有lot条.但是,由于case
个表达式是consecutive,优化编译器可以检测到这一点,并生成进行范围判断的[asm]代码(即if num
在范围1-100
内),并且可以使用num
值索引到直接跳到相应代码段的指针表中.
下面是一些伪代码,显示了这一点:
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
Side note:我称之为"伪"代码,但实际上这是valid C代码,使用了C中"计算"goto
的非常高级用法.不要实际使用它,因为它实际上从来都不需要.编译器通常会使用上述更简单的 struct 生成更好的代码.记住,我警告你:-)
见:
- Can you make a computed goto in C++?
- https://eli.thegreenplace.net/2012/07/12/computed-goto-for-efficient-dispatch-tables