24.1 运算符宏
<iso646.h> 定义了一组宏,用单词拼写某些运算符。ISO 646 字符集的某些国家变体中没有 |、^、~ 等字符,这些宏为在这类环境中编写程序提供了替代写法。
1. 对应关系
| 运算符 | 宏 |
|---|---|
&& | and |
&= | and_eq |
& | bitand |
| | bitor |
~ | compl |
! | not |
!= | not_eq |
|| | or |
|= | or_eq |
^ | xor |
^= | xor_eq |
这些宏在预处理阶段被替换为对应的运算符,因此不改变运算符的含义和优先级。
2. 示例
#include <iso646.h>
#include <stdio.h>
int main(void) {
int a = 5;
int b = 10;
if (a not_eq b and a < b) {
puts("a < b");
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
输出:
a < b
3. 命名冲突
这些名字是宏,包含 <iso646.h> 之后,and、or、not 等名字作为标识符出现时就会被替换,不限表达式内部。如果程序把这些名字用作标识符(例如函数名 xor 或结构成员 compl),包含该头文件后就无法通过编译。因此,只在需要 <iso646.h> 的源文件里包含它,不要写进被广泛包含的公共头文件。
4. 与代用记号的区别
<iso646.h> 中的名字是普通的宏,只有包含该头文件后才可用;24.2 介绍的代用记号(如 <%、<:)则是语言本身的记号,无需包含任何头文件,编译器识别记号时就把它们当作对应的标点。
5. 习题
5.1 巩固
下面的程序有六条输出语句。先不编译,写出每一行的值,再用 gcc -std=c17 -Wall -Wextra 编译运行核对。
#include <iso646.h>
#include <stdio.h>
int main(void) {
int a = 5;
int b = 10;
printf("%d\n", a not_eq b);
printf("%d\n", a < b and b < 20);
printf("%d\n", a bitand b);
printf("%d\n", a xor b);
printf("%d\n", compl 0);
printf("%d\n", a > b or b > 20);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后把六行改写成对应的符号形式,两个版本各用 gcc -E -P 预处理一次,用 diff 比较结果。
验收:两个版本运行输出相同,预处理结果也相同;并说明 compl 0 一行为什么得到负数。
<iso646.h> 里的名字是宏,这一点用预处理命令能直接看出来。写一个程序,依次做四件事,把结果记下来:
- 在
#include <iso646.h>之前用#ifdef and判断,把结果定义为BEFORE(1 或 0),在包含头文件之后再判断一次,定义为AFTER,最后打印这两个值。 - 定义
#define STR(x) #x和#define XSTR(x) STR(x),分别输出STR(and)、XSTR(and)、STR(not_eq)、XSTR(not_eq)。 - 写
#if 1 and 1,在包含头文件之后编译一次。 - 把这段条件编译移到
#include <iso646.h>之前,再编译一次。
验收:第 1 步打印 0 1;第 2 步依次输出 and、&&、not_eq、!=;第 3 步编过;第 4 步报错 missing binary operator before token "and"。最后用一句话说明 STR 与 XSTR 结果不同的原因。
5.2 编程
写一个按位标志模块,实现文件里只用运算符宏,公共头文件里一个宏名都不出现。
flags.h 只放声明和 include guard,不包含 <iso646.h>:
unsigned bits_union(unsigned a, unsigned b);
unsigned bits_diff(unsigned a, unsigned b); /* a 中为 1、b 中为 0 的位 */
unsigned bits_sym_diff(unsigned a, unsigned b); /* 两值不同的位 */
unsigned bits_add(unsigned set, unsigned mask);
unsigned bits_remove(unsigned set, unsigned mask);
unsigned bits_toggle(unsigned set, unsigned mask);
int bits_has_all(unsigned set, unsigned mask); /* mask 为 0 时返回 0 */
int bits_has_any(unsigned set, unsigned mask);
int bits_equal(unsigned a, unsigned b);2
3
4
5
6
7
8
9
要求:
flags.c包含<iso646.h>,九个函数全部用宏拼写实现,不出现裸的&、|、^、~、!运算符,比较一律写not_eq,不用!=。- 测试程序用下面的
main.c,它自己也包含<iso646.h>:
#include <iso646.h>
#include <stdio.h>
#include "flags.h"
int main(void) {
unsigned flags = 0u;
flags = bits_add(flags, 0x3u);
printf("%#x\n", flags);
flags = bits_add(flags, 0x8u);
printf("%#x\n", flags);
printf("%d %d\n", bits_has_all(flags, 0x9u), bits_has_any(flags, 0x4u));
flags = bits_toggle(flags, 0x2u);
printf("%#x\n", flags);
flags = bits_remove(flags, 0x3u);
printf("%#x\n", flags);
printf("%#x %#x %#x\n", bits_union(0x1u, 0x2u), bits_diff(0x7u, 0x3u),
bits_sym_diff(0x5u, 0x3u));
printf("%d %d\n", bits_equal(flags, 0x8u), bits_has_all(flags, 0u));
if (bits_has_any(flags, 0x8u) or bits_equal(flags, 0x4u)) {
puts("bit 3 is set");
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 另写一个只包含
flags.h、再定义struct legacy { unsigned compl; int xor; };的源文件,用gcc -fsyntax-only检查它能编过。
验收:
0x3
0xb
1 0
0x9
0x8
0x3 0x4 0x6
1 0
bit 3 is set2
3
4
5
6
7
8
gcc -E -P flags.c | grep -wE 'and|or|not|bitand|bitor|compl|xor' 没有输出;三个文件都编过,-Wall -Wextra 无警告。
5.3 综合与挑战
下面这个项目编不过。legacy.h 是旧代码,名字不能改。把五个文件放进同一目录后编译:
/* legacy.h */
#ifndef LEGACY_H
#define LEGACY_H
int xor(int a, int b);
struct legacy_flags {
unsigned compl;
};
#endif2
3
4
5
6
7
8
9
10
11
/* legacy.c */
#include "legacy.h"
int xor(int a, int b) {
return a ^ b;
}2
3
4
5
6
/* common.h */
#ifndef COMMON_H
#define COMMON_H
#include <iso646.h>
#include <stdint.h>
#include "legacy.h"
int is_nonzero(int x);
#endif2
3
4
5
6
7
8
9
10
11
12
/* common.c */
#include "common.h"
int is_nonzero(int x) {
return x not_eq 0;
}2
3
4
5
6
/* main.c */
#include <stdio.h>
#include "common.h"
int main(void) {
struct legacy_flags f = {5u};
printf("%d\n", xor(6, 10));
printf("%u\n", f.compl);
printf("%d\n", is_nonzero(3));
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
命令:gcc -std=c17 -Wall -Wextra main.c common.c legacy.c -o app。
要求:
- 先编译,抄下前两条错误,说明它们为什么指向
legacy.h,而不指向包含头文件的main.c。 - 不改
legacy.h、legacy.c、main.c,只改common.h和common.c,让项目通过并输出三行。给出做法并说明原理。 - 用
#undef也能修好第 2 问。写出这种做法,并说明它会给后来的维护者带来什么问题。 - 在
common.h里加一条检查:若<iso646.h>已经被包含,就用#error中止并打印提示。用先包含<iso646.h>再包含common.h的源文件验证它生效。 - 对修改后的
common.c运行gcc -E -P,指出x not_eq 0变成了什么。
验收:第 2 问输出 12、5、1;第 4 问的报错里有你写的提示文字;第 5 问中 not_eq 变成 !=。