为什么三元运算符用于在宏中定义1和0?

Vik*_*r S 79 c c++ boolean ternary-operator

我正在为嵌入式项目使用SDK.在这个源代码中,我发现了一些代码,至少我发现这些代码很奇怪.在SDK的许多地方都有这种格式的源代码:

#define ATCI_IS_LOWER( alpha_char )  ( ( (alpha_char >= ATCI_char_a) && (alpha_char <= ATCI_char_z) ) ? 1 : 0 )

#define ATCI_IS_UPPER( alpha_char )  ( ( (alpha_char >= ATCI_CHAR_A) && (alpha_char <= ATCI_CHAR_Z) ) ? 1 : 0 )
Run Code Online (Sandbox Code Playgroud)

这里使用三元运算符有什么不同吗?

是不是

#define FOO (1 > 0)
Run Code Online (Sandbox Code Playgroud)

同样的

#define BAR ( (1 > 0) ? 1 : 0)
Run Code Online (Sandbox Code Playgroud)

我尝试使用它来评估它

printf("%d", FOO == BAR);
Run Code Online (Sandbox Code Playgroud)

得到结果1,所以看起来它们是平等的.是否有理由像他们一样编写代码?

Bat*_*eba 131

你是对的,在C中它是同义的.无论您的特定三元条件 (1 > 0)有型int.

但是在C++中,在一些奇怪的极端情况下(例如作为重载函数的参数),这重要,因为你的三元条件表达式是类型int,而(1 > 0)类型bool.

我的猜测是作者已经考虑到这一点,着眼于保持C++兼容性.

  • 考虑函数`foo`的两个重载,一个采用`const bool&`另一个采用`const int &`.其中一个付钱给你,另一个重新格式化你的硬盘.在这种情况下,您可能希望确保调用正确的重载. (70认同)
  • @Bathsheba虽然是一个合法的角落案例,任何使用整数重载来实现这种不一致行为的程序员都是完全邪恶的. (18认同)
  • @JAB:你不必是邪恶的,你只需要犯一个(常见的)错误,就是根据整体类型编写一段意外做两件事(或者更糟的是调用_undefined behavior_)的代码,并且在一个可以触发截然不同的代码路径的地方这样做的不幸. (7认同)
  • 通过将结果转换为`int`而不是使用三元组来处理这种情况会不会更明显? (3认同)
  • 我认为`bool < - > int`转换是C++中隐含的§4.7/ 4标准(积分转换),所以它有什么关系? (2认同)

unw*_*ind 28

有一些linting工具认为比较结果是布尔值,不能直接用于算术.

不是命名或指向任何手指,但PC-lint是这样的linting工具.

我不是说他们是对的,但这可能解释为什么代码是这样编写的.

  • "不要说出名字或指出任何一根手指,"但你们两个都做了,哈哈. (10认同)

zwo*_*wol 19

你有时会在非常古老的代码中看到这一点,从之前有一个C标准拼写出(x > y)评估为数字1或0; 一些CPU宁愿将其评估为-1或0,而一些非常古老的编译器可能只是跟随,因此一些程序员认为他们需要额外的防御性.

有时你会也看到这一点,因为类似的表述并不一定计算为数字1或0.例如,在

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)
Run Code Online (Sandbox Code Playgroud)

内部&表达式的计算结果为0或数值F_DO_GRENFELZ,可能不是 1,因此使用? 1 : 0它来规范化它.我个人认为将其写为更清楚

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)
Run Code Online (Sandbox Code Playgroud)

但合理的人可以不同意.如果你连续测试了大量这些,测试不同类型的表达式,有人可能已经决定将所有这些表达式放在? 1 : 0最后,而不是担心实际需要它们.


Kon*_*hog 15

SDK代码中存在一个错误,三元可能是修复它的一个障碍.

作为一个宏,参数(alpha_char)可以是任何表达式,并且应该用括号括起来,因为诸如'A'&&'c'之类的表达式将无法通过测试.

#define IS_LOWER( x ) ( ( (x >= 'a') && (x <= 'z') ) ?  1 : 0 )
std::cout << IS_LOWER('A' && 'c');
**1**
std::cout << IS_LOWER('c' && 'A');
**0**
Run Code Online (Sandbox Code Playgroud)

这就是为什么人们应该总是在扩展中括起宏参数.

所以在你的例子中(但有参数),这些都是错误的.

#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)
Run Code Online (Sandbox Code Playgroud)

他们最正确地被替换为

#define BIM(x) ((x) > 0)
Run Code Online (Sandbox Code Playgroud)

@CiaPan在以下评论中提出了一个很好的观点,即多次使用参数会导致无法确定的结果.例如

#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1** 
**BUT ch is now '{'**
Run Code Online (Sandbox Code Playgroud)

  • 另一个错误是该参数被使用了两次,因此带有副作用的参数将导致不可预测的结果:`IS_LOWER(++ var)`可能会增加`var`一次或两次,此外它可能不会注意到并识别小写` 'z'`如果`var`在宏调用之前是''y'.这就是为什么应该避免使用这些宏,或者只是将参数转发给函数. (4认同)

PSk*_*cik 5

在C中没关系.C中的布尔表达式具有类型int和值为0或者1,因此

ConditionalExpr ? 1 : 0
Run Code Online (Sandbox Code Playgroud)

没有效果.

在C++中,它实际上是一个强制转换int,因为C++中的条件表达式具有类型bool.

#include <stdio.h>
#include <stdbool.h>

#ifndef __cplusplus

#define print_type(X) _Generic(X, int: puts("int"), bool: puts("bool") );

#else
template<class T>
int print_type(T const& x);
template<> int print_type<>(int const& x) { return puts("int"); }
template<> int print_type<>(bool const& x) { return puts("bool"); }


#endif

int main()
{
    print_type(1);
    print_type(1 > 0);
    print_type(1 > 0 ? 1 : 0);

/*c++ output:
  int 
  int 
  int

  cc output:
  int
  bool
  int
*/

}
Run Code Online (Sandbox Code Playgroud)

它也可能没有效果,作者只是认为它使代码更清晰.


Han*_*son 5

一个简单的解释是,有些人要么不理解条件会在C中返回相同的值,要么他们认为写入更清晰((a>b)?1:0).

这就解释了为什么有些人也会在具有适当布尔值的语言中使用类似的结构,这在C语法中就是如此(a>b)?true:false).

这也解释了为什么你不应该不必要地改变这个宏.