3.1 基础复习:表达式、语句和代码块
表达式
表达式和语句是 C 语言中的基础概念。什么是表达式呢?表达式就是由一系列操作符和操作数构成的式子。操作符可以是 C 语言标准规定的各种算术运算符、逻辑运算符、赋值运算符、比较运算符等。操作数可以是一个常量,也可以是一个变量。表达式也可以没有操作符,单独的一个常量甚至是一个字符串,也是一个表达式。下面的字符序列都是表达式:
2 + 3 2 i = 2 + 3 i = i++ + 3 "wit"
表达式一般用来数据计算或实现某种功能的算法。表达式有2个基本属性:值和类型。如上面的表达式2+3,它的值为5。根据操作符的不同,表达式可以分为多种类型,如:
- 关系表达式
- 逻辑表达式
- 条件表达式
- 赋值表达式
- 算术表达式
- ……
语句
语句是构成程序的基本单元,一般形式如下:
表达式 ; i = 2 + 3 ;
表达式的后面加一个; 就构成了一条基本的语句。编译器在编译程序、解析程序时,不是根据物理行,而是根据分号 ; 来判断一条语句的结束标记的。如 i = 2 + 3; 这条语句,你写成下面的形式也是可以编译通过的:
i = 2 + 3 ;
代码块
不同的语句,使用大括号{}括起来,就构成了一个代码块。C 语言允许在代码块里定义一个变量,这个变量的作用域也仅限于这个代码块内,因为编译器就是根据{}来做入栈出栈操作来管理变量的作用域的。如下面的程序:
int main(void) { int i = 3; printf("i=%d\n",i); { int i = 4; printf("i=%d\n",i); } printf("i=%d\n",i); return 0; }
运行结果为:
i=3
i=4
i=3
3.2 语句表达式
语句表达式的定义
GNU C 对 C 标准作了扩展,允许在一个表达式里内嵌语句,允许在表达式内部使用局部变量、for 循环和 goto 跳转语句。这样的表达式,我们称之为语句表达式。语句表达式的格式如下:
({ 表达式1; 表达式2; 表达式3; })
语句表达式最外面使用小括号()括起来,里面一对大括号{}包起来的是代码块,代码块里允许内嵌各种语句。语句的格式可以是 “表达式;”这种一般格式的语句,也可以是循环、跳转等语句。
跟一般表达式一样,语句表达式也有自己的值。语句表达式的值为内嵌语句中最后一个表达式的值。我们举个例子,使用语句表达式求值。
int main(void) { int sum = 0; sum = ({ int s = 0; for( int i = 0; i < 10; i++) s = s + i; s; }); printf("sum = %d\n",sum); return 0; }
在上面的程序中,通过语句表达式实现了从1到10的累加求和,因为语句表达式的值等于最后一个表达式的值,所以在 for 循环的后面,我们要添加一个 s; 语句表示整个语句表达式的值。如果不加这一句,你会发现 sum=0。或者你将这一行语句改为100; 你会发现最后 sum 的值就变成了100,这是因为语句表达式的值总等于最后一个表达式的值。
语句表达式内使用 goto 跳转
在上面的程序中,我们在语句表达式内定义了局部变量,使用了 for 循环语句。在语句表达式内,我们同样也可以使用 goto 进行跳转。
int main(void) { int sum = 0; sum = ({ int s = 0; for( int i = 0; i < 10; i++) s = s + i; goto here; s; }); printf("sum = %d\n",sum); here: printf("here:\n"); printf("sum = %d\n",sum); return 0; }
3.3 在宏定义中使用语句表达式
语句表达式的亮点在于定义复杂功能的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞。下面就以一个宏定义例子,让我们来见识见识语句表达式在宏定义中的强悍杀伤力!
假如你此刻正在面试,面试职位是:Linux C 语言开发工程师。面试官给你出了一道题:
请定义一个宏,求两个数的最大值。
别看这么简单的一个考题,面试官就能根据你写出的宏,来判断你的 C 语言功底,来决定给不给你 Offer。
合格
对于学过 C 语言的同学,写出这个宏基本上不是什么难事,使用条件运算符就能完成:
#define MAX(x,y) x > y ? x : y
这是最基本的 C 语言语法,如果连这个也写不出来,估计场面会比较尴尬。面试官为了缓解尴尬,一般会对你说:小伙子,你很棒,回去等消息吧,有消息,我们会通知你!这时候,你应该明白:不用再等了,赶紧把这篇文章看完,接着面下家。这个宏能写出来,也不要觉得你很牛 X,因为这只能说明你有了 C 语言的基础,但还有很大的进步空间。比如,我们写一个程序,验证一下我们定义的宏是否正确:
#define MAX(x,y) x > y ? x : y int main(void) { printf("max=%d",MAX(1,2)); printf("max=%d",MAX(2,1)); printf("max=%d",MAX(2,2)); printf("max=%d",MAX(1!=1,1!=2)); return 0; }
测试程序吗,我们肯定要把各种可能出现的情况都测一遍。这不,测试第4行语句,当宏的参数是一个表达式,发现实际运行结果为 max=0,跟我们预期结果 max=1 不一样。这是因为,宏展开后,就变成了这个样子:
printf("max=%d",1!=1>1!=2?1!=1:1!=2);
因为比较运算符 > 的优先级为6,大于 !=(优先级为7),所以展开的表达式,运算顺序发生了改变,结果就跟我们的预期不一样了。为了避免这种展开错误,我们可以给宏的参数加一个小括号()来防止展开后,表达式的运算顺序发生变化。这样的宏才能算一个合格的宏:
#define MAX(x,y) (x) > (y) ? (x) : (y)
中等
上面的宏,只能算合格,但还是存在漏洞。比如,我们使用下面的代码测试:
#define MAX(x,y) (x) > (y) ? (x) : (y) int main(void) { printf("max=%d",3 + MAX(1,2)); return 0; }
在程序中,我们打印表达式 3 + MAX(1, 2) 的值,预期结果应该是5,但实际运行结果却是1。我们展开后,发现同样有问题:
3 + (1) > (2) ? (1) : (2);
因为运算符 + 的优先级大于比较运算符 >,所以这个表达式就变为4>2?1:2,最后结果为1也就见怪不怪了。此时我们应该继续修改这个宏:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
使用小括