3、语句表达式
3.1 表达式、语句、代码块
- 复习:什么是表达式、语句和代码块?
-
表达式:是由一系列操作符和操作数构成的式子
- 操作符:可以是C语言标准规定的各种算术运算符、逻辑运算符、赋值运算符、比较运算符。
- 操作数:可以是一个常量,也可以是一个变量
- 表达式也可以没有操作符,单独的一个常量甚至一个字符串,都是一个表达式。
一个例子
-
语句:表达式后面加一个
;
,就是语句一个例子
-
代码块:不同的语句,使用大括号
{}
括起来,就构成了一个代码块。C语言允许在代码块内定义一个变量,这个变量的作用域也仅限于这个代码块内。一个例子
程序运行结果如下:
-
3.2、语句表达式
-
GNU C
对C
语言标准作了扩展,允许在表达式内部使用局部变量、for循环和goto跳转语句。这种类型的表达式,我们称为语句表达式。语句表达式的格式如下。
语句表达式最外面使用小括号
()
括起来,里面一对大括号{}
包起来的是代码块,代码块里允许内嵌各种语句。语句的格式可以是一般表达式,也可以是循环、跳转语句。 -
和一般表达式一样,语句表达式也有自己的值。语句表达式的值为内嵌语句中最后一个表达式的值。
- 实例:使用语句表达式求值,该例中语句表达式的值为
s
。
该例中在语句表达式内定义了局部变量,使用了for
循环语句,下面展示使用goto进行跳转。 - 实例:在语句表达式中使用
goto
语句
- 实例:使用语句表达式求值,该例中语句表达式的值为
3.3、在宏定义中使用语句表达式
- 语句表达式常用于何处?
- 语句表达式的主要用途在于定义功能复杂的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,还能避免宏定义带来的歧义和漏洞。
- 实例:在GCC编译环境中定义一个宏,求两个相同数据类型数的最大值。
- 解法1
验证一下该解法:
测试第4行语句,当宏的参数是一个表达式,发现实际运行结果为max=0
,和我们预期结果max=1
不一样。宏展开后为如下结果。
因为比较运算符>
的优先级为6
,大于!=
(优先级为7
),所以在展开的表达式中,运算顺序发生了改变,结果就和预期不一样了。 - 解法2
这种解法规避了解法1中的漏洞,但依旧不合格。对该解法进行测试:
在程序中,我们打印表达式3+MAX(1,2)
的值,预期结果应该是5
,但实际运行结果却是1
。宏展开后,我们发现同样有问题。
因为运算符+
的优先级大于比较运算符>
,所以这个表达式就变为4>2?1:2
,最终导致结果错误。 - 解法3
这种解法规避了解法2中的漏洞,但还是不完美。对该解法进行测试:
实际运行结果发现max=7
,而不是预期结果max=6
。这是因为变量i
和j
在宏展开后,做了两次自增运算,导致打印出的i
值为7
。 - 解法4
这次发现效果非常好,不妨测试一下:
在语句表达式中,我们定义了2
个局部变量_x
、_y
来存储宏参数x和y的值,然后使用_x
和_y
来比较大小,这样就避免了i
和j
带来的2
次自增运算问题。但是这依旧不完美,因为当前只能比较两个整形数据。 - 解法5
在这个宏中,我们添加一个参数type
,用来指定临时变量_x
和_y
的类型。这样,我们在比较两个数的大小时,只要将2
个数据的类型作为参数传给宏,就可以比较任意类型的数据了。当然在这里依旧可以进行优化,因为这里多了一个参数,可以想办法避免增加此参数 - 解法6
在这个宏定义中,我们使用了typeof
关键字来自动获取宏的两个参数类型。需要理解的是(void)(&x==&y);
,它的作用有两个- 用来给用户提示一个警告,对于不同类型的指针比较,编译器会发出一个警告,提示两种数据的类型不同。
- 两个数进行比较运算,运算的结果却没有用到,有些编译器可能会给出一个
warning
,加一个(void
)后,就可以消除这个警告。
- 用来给用户提示一个警告,对于不同类型的指针比较,编译器会发出一个警告,提示两种数据的类型不同。
- 解法1
3.4、内核中的语句表达式
- 在内核中,尤其在内核的宏定义中被大量使用。使用语句表达式定义宏,不仅可以实现复杂的功能,还可以避免宏定义带来的一些歧义和漏洞。如在
Linux
内核中,max_t
和min_t
的宏定义,就使用了语句表达式。