为啥用三元运算符来定义宏中的 1 和 0?

Posted

技术标签:

【中文标题】为啥用三元运算符来定义宏中的 1 和 0?【英文标题】:Why is the ternary operator used to define 1 and 0 in a macro?为什么用三元运算符来定义宏中的 1 和 0? 【发布时间】:2017-08-25 15:14:02 【问题描述】:

我正在为嵌入式项目使用 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 )

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

不是

#define FOO (1 > 0)

#define BAR ( (1 > 0) ? 1 : 0)

?

我尝试通过使用来评估它

printf("%d", FOO == BAR);

并得到结果1,所以看起来它们是相等的。有理由像他们那样编写代码吗?

【问题讨论】:

不,没有理由。你是对的。 部分题外话:使用预处理器的疯狂何时停止?这里涉及的功能可能存在多重评估。只是没必要。 有时,明确一点也很好。这里的三元运算符一目了然,宏的目的是返回一个布尔值。 至少,宏应该使用(alpha_char)而不是alpha_char,以确保它不会破坏if someone tries something crazy like ATCI_IS_LOWER(true || -1) 看起来像我很久以前写的那种C。我是从 Pascal 来到 C 的,它有一个专用的 boolean 类型,所以我浪费了无数时间将诸如 if (n) 之类的恐怖更改为 if (0 != n),可能会添加一个可疑的演员阵容“以确保”。我敢肯定,我也对 if (a &lt; b) ... 这样的不等式进行了防弹。当然它看起来像 Pascal 的 if a &lt; b then ...,但我知道 C 的 &lt; 不是 boolean 而是 int,而 int 可以成为几乎任何东西!恐惧导致镀金,镀金导致偏执,偏执导致......这样的代码。 【参考方案1】:

你是对的,在 C 中它是重复的。您的特定三元条件 (1 &gt; 0) 都是 int 类型。

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

我的猜测是作者对此进行了一些思考,着眼于保持 C++ 兼容性。

【讨论】:

我认为 bool &lt;-&gt; int 转换在标准 §4.7/4 中隐含在 C++ 中(积分转换),那么这有什么关系呢? 考虑函数foo的两个重载,一个采用const bool&amp;,另一个采用const int&amp;。其中一个付钱给你,另一个重新格式化你的硬盘。在这种情况下,您可能需要确保调用正确的重载。 通过将结果转换为int而不是使用三元来处理这种情况不是更明显吗? @Bathsheba 虽然是一个合法的极端情况,但任何使用整数重载来实现这种不一致行为的程序员都是完全邪恶的。 @JAB:你不必作恶,你只需要犯一个(常见的)错误,即编写一段不小心做了两件不同事情的代码(或者更糟的是,调用 未定义的行为)取决于整数类型,并且不幸的是在一个可能触发完全不同的代码路径的地方这样做。【参考方案2】:

有些 linting 工具认为比较的结果是布尔值,不能直接在算术中使用。

不要点名或指指点点,而是PC-lint is such a linting tool。

我并不是说他们是对的,但它可以解释为什么代码会这样写。

【讨论】:

Not to name names or point any fingers, 但你两者都做了,哈哈。【参考方案3】:

在 C 中没关系。 C 中的布尔表达式具有 int 类型和一个值 01,所以

ConditionalExpr ? 1 : 0

没有效果。

在 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
*/


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

【讨论】:

顺便说一句,我认为 C 应该遵循套件并制作布尔表达式 _Bool,现在 C 有 _Bool_Generic。无论如何,在大多数情况下,所有较小的类型都会自动升级为int,因此它不应该破坏太多代码。【参考方案4】:

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**

这就是为什么在展开式中总是应该用括号括起来的宏参数。

所以在您的示例中(但带有参数),这些都是错误的。

#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)

它们最正确的应该被替换为

#define BIM(x) ((x) > 0)

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

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

【讨论】:

另一个错误是参数被使用了两次,所以带有副作用的参数会导致不可预知的结果:IS_LOWER(++ var) 可能会增加一次或两次var,另外它可能不会注意到和识别更低-case 'z' 如果var 在宏调用之前是'y'。这就是为什么应该避免使用此类宏,或者只是将参数转发给函数。【参考方案5】:

一个简单的解释是,有些人要么不理解条件会在 C 中返回相同的值,要么认为写成 ((a&gt;b)?1:0) 更简洁。

这解释了为什么有些人也在具有适当布尔值的语言中使用类似的结构,在 C 语法中为 (a&gt;b)?true:false)

这也解释了为什么您不应不必要地更改此宏。

【讨论】:

【参考方案6】:

您有时会在非常古老的代码中看到这一点,从那时起就有一个 C 标准来说明 (x &gt; y) 的计算结果为数字 1 或 0;一些 CPU 宁愿将其评估为 -1 或 0,而一些非常老的编译器可能只是紧随其后,所以一些程序员认为他们需要额外的防御。

您有时也会看到这种情况,因为 相似 表达式不一定 必须计算为数字 1 或 0。例如,在

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)

内部&amp;-表达式计算结果为0 或F_DO_GRENFELZ 的数值,这可能不是 1,因此? 1 : 0 用于规范化它。我个人认为写成这样更清楚

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)

但理性的人可能会不同意。如果你连续有一大堆这些,测试不同类型的表达式,有人可能会认为将? 1 : 0放在它们的末尾所有比担心更容易维护关于哪些人真正需要它。

【讨论】:

总的来说,我更喜欢使用!!( expr ) 来规范化布尔值,但我承认如果你不熟悉它会让人感到困惑。 @PJTraill 每次你在括号里面加上空格,上帝就会杀死一只小猫。请。想想小猫。 这是我听说在 C 程序中不要在括号内放置空格的最佳理由。【参考方案7】:

也许,作为一个嵌入式软件,会提供一些线索。也许有很多宏都是用这种风格写的,很容易暗示 ACTI 行使用直接逻辑而不是反转逻辑。

【讨论】:

以上是关于为啥用三元运算符来定义宏中的 1 和 0?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在三元运算符中使用“0”会返回第一个值?

三元(三目)运算符

为啥不能重载三元运算符?

三元运算符中为啥不能使用braced-init-list?

为啥我的三元运算符会出现这些错误?

为啥 std::istringstream 在三元 (?:) 运算符中的解析方式似乎与 std::ifstream 不同?