谁说#define语句后面不能有分号?

想必学过C的朋友们一定会记得一个基本的define使用原则,那就是#define 语句后面是不需要分号的,但是果真如此吗?

一个容易招打的例子

首先必须明确,实际中写下面这样的代码是容易招打的,对于出题人,可能也会顺便骂一骂。不过骂归骂,该学的语法还是得学。下面的代码是读者在群里的一个提问相关代码:

1
2
3
4
5
6
7
8
9
10
11
//来源:公众号【编程珠玑】
//作者:守望先生
//https://www.yanbinghu.com
#include<stdio.h>
#define A(x) x;x;x;x;
int main(void)
{
int n = 10;
A(A(printf("%d\n",n++)));
return 0;
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

在这里我们先不解释为什么输出上面的结果,如果你已经完全清楚了,也可以一起复习一下。

#define的用法

关于#define的用法,介绍的书籍和文章有很多,也有经常被拿来与typedef(参考《一句话帮你理解typedef用法》)以及const(参考《const关键字到底该怎么用?》)对比的,这里不展开,关于#define一些其他的用法,也不会涉及,只说明最基本的用法。

说起define,最简单明了的用法,就是字符串的简单替换,提高代码的可读性,并且一次修改,多处生效。

1
2
3
4
5
6
7
8
9
//来源:公众号【编程珠玑】
//作者:守望先生
#include<stdio.h>
#define NUM 9
int main(void)
{
printf("%d\n",NUM);
return 0;
}

这个想必不用我多解释了,输出结果是9。实际上,等同于下面这样:

1
2
3
4
5
6
#include<stdio.h>
int main(void)
{
printf("%d\n",9);
return 0;
}

注意,#define语句那里并没有加分号,因为我们知道加上分号之后,是会报错的。

1
2
3
4
5
6
7
#include<stdio.h>
#define NUM 9;
int main(void)
{
printf("%d\n",NUM);
return 0;
}

编译报错:

1
2
3
4
$ gcc -o test test.c
test.c: In function ‘main’:
test.c:2:14: error: expected ‘)’ before ‘;’ token
#define NUM 9;

为什么会报错?你可能会说,这很明显啊,#define语句后面怎么可以有分号呢?说得是,不过真正的原因,并不是因为#define后面有分号,而在于它导致了不该有分号的地方,有了分号:

1
2
3
4
5
6
#include<stdio.h>
int main(void)
{
printf("%d\n",9;);
return 0;
}

看见没有,9后面还有一个分号,你说能不报错吗?什么,这是我猜的?来来来,预编译(参考《hello程序是如何编译出来的》)给你看看:

1
2
3
4
5
6
7
8
9
10
$ gcc -E -o test.i test.c
$ tail -10 test.i
# 2 "test.c" 2
# 2 "test.c"
int main(void)
{
printf("%d\n",9;);
return 0;
}

现在信了吧?

所以,总结一下普通用法的#define只是简单的字符串替换而已,所以对于下面这种题目,你应该容易理解了:

1
2
3
4
5
6
7
8
9
10
/*来源:公众号【编程珠玑】*/
//作者:守望先生
#include<stdio.h>
#define area(x) x*x  //计算数的平方
int main(void)
{
int y = area(2+3);
printf("%d\n",y);
return 0;
}

替换嘛,area(x),其中x是2+2,那么得到:

1
2+3*2+3

因此得到结果11,而不是16。
所以你可能更多看到的会是下面这种:

1
#define area(x) (x)*(x)

这样得到的就会是:

1
(2+3)*(2+3)

就可以得到25了。

为什么一般来说#define后面没有分号?

其中通过前面的例子你也明白了,所谓#define语句后面一般没有分号的原因在于,将要替换的字符串还原之后,导致还原位置的语句出现问题,因此才使得后面不能有分号。
也就是说,如果替换之后,语法正常,其实是可以的(注意空格问题)。比如:

1
2
3
4
5
6
7
8
9
10
//来源:公众号【编程珠玑】
//作者:守望先生
#include<stdio.h>
#define NUM 1024;
int main(void)
{
int a = NUM;
printf("%d\n",a);
return 0;
}

我在#define语句的后面加了个分号,替换到原处之后,语句变成了:

1
int a = 1024;;

显然这并不会有语法问题,只是显得非常累赘;并且这也只是碰巧不会有问题,像前面的例子那样还是会有问题。

真相大白

再回到最初的代码,想必你已经心里有数了。将#define定义的相关内容进行替换之后,得到的结果是下面这样的:

1
printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);;printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);;printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);;printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);printf("%d\n",n++);;;

总结

本文并不鼓励你在实际中写这样的代码。只是通过这样的例子来加深对#define的用法。而#define语句之后是否有分号,视你自己的实际代码情况而定。当然,绝大多数时候是不需要的。而需要的时候,也请尽量保证代码的可读性良好。

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!