00. 目录

文章目录

  • 00. 目录
  • 01. C语言的表达式
  • 02. C语言的语句
  • 03. C语言中的代码块
  • 04. C语言中的语句表达式
  • 05. 宏中使用语句表达式
  • 06. Linux内核应用示例
  • 07. 附录

01. C语言的表达式

表达式和语句是 C 语言中的基础概念。什么是表达式呢?表达式就是由一系列操作符和操作数构成的式子。操作符可以是 C 语言标准规定的各种算术运算符、逻辑运算符、赋值运算符、比较运算符等。操作数可以是一个常量,也可以是一个变量。表达式也可以没有操作符,单独的一个常量甚至是一个字符串,也是一个表达式。下面的字符序列都是表达式:

22 + 3322a = a + bi++"hello world"

表达式一般用来数据计算或实现某种功能的算法。表达式有2个基本属性:值和类型。如上面的表达式2+3,它的值为5。根据操作符的不同,表达式可以分为多种类型,如:

  • 关系表达式
  • 逻辑表达式
  • 条件表达式
  • 赋值表达式
  • 算术表达式

等等。

02. C语言的语句

语句是构成程序的基本单元,一般形式如下:表达式;

**表达式的后面加一个; 就构成了一条基本的语句。**编译器在编译程序、解析程序时,不是根据物理行,而是根据分号 ; 来判断一条语句的结束标记的。如 i = 2 + 3; 这条语句,你写成下面的形式也是可以编译通过的:

#include <stdio.h>int main(void)
{int i = 0;//合法的表达式i=1+2;return 0;
}

03. C语言中的代码块

不同的语句,使用大括号{}括起来,就构成了一个代码块。C 语言允许在代码块里定义一个变量,这个变量的作用域也仅限于这个代码块内,因为编译器就是根据{}来做入栈出栈操作来管理变量的作用域的。如下面的程序:

程序示例

#include <stdio.h>int main(void)
{int var = 3;printf("var = %d\n", var);//代码块{int var = 88;printf("代码块 var = %d\n", var);}printf("var = %d\n", var);return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 5hello.c
deng@itcast:~/tmp$ ./a.out
var = 3
代码块 var = 88
var = 3

04. C语言中的语句表达式

GNU C 对 C 标准作了扩展,允许在一个表达式里内嵌语句,允许在表达式内部使用局部变量、for 循环和 goto 跳转语句。这样的表达式,我们称之为语句表达式。语句表达式的格式如下:

({ 表达式1; 表达式2; 表达式3; ... 表达式n;})

语句表达式最外面使用小括号()括起来,里面一对大括号{}包起来的是代码块,代码块里允许内嵌各种语句。语句的格式可以是 “表达式;”这种一般格式的语句,也可以是循环、跳转等语句。

**跟一般表达式一样,语句表达式也有自己的值。语句表达式的值为内嵌语句中最后一个表达式的值。**我们举个例子,使用语句表达式求值。

程序示例

#include <stdio.h>int main(void)
{int sum = 0;sum = ({int sum = 0;for (int i = 0; i < 100; i++){sum = sum + i;}sum;});printf("sum = %d\n", sum);return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
sum = 4950

在上面的程序中,通过语句表达式实现了从0到99的累加求和,因为语句表达式的值等于最后一个表达式的值,所以在 for 循环的后面,我们要添加一个 sum; 语句表示整个语句表达式的值。如果不加这一句,你会发现 sum=0。或者你将这一行语句改为100; 你会发现最后 sum 的值就变成了100,这是因为语句表达式的值总等于最后一个表达式的值。

goto语句和语句表达式结合使用

在语句表达式内,我们同样也可以使用 goto 进行跳转。

程序示例

#include <stdio.h>int main(void)
{int sum = 0;sum = ({int sum = 0;for (int i = 0; i < 100; i++){sum = sum + i;if (i == 10)goto loop;}sum;});printf("sum = %d\n", sum);
loop:printf("goto loop\n");printf("sum = %d\n", sum);return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
goto loop
sum = 0

05. 宏中使用语句表达式

语句表达式的亮点在于定义复杂功能的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞。下面就以一个宏定义例子,让我们来看看语句表达式在宏定义中的应用!

曾经面试的过程中,面试官给我出了一道题:

请定义一个宏,求两个数的最大值

别看这么简单的一个考题,面试官就能根据你写出的宏,来判断你的 C 语言功底,来决定给不给你 Offer。

初级程序员

对于学过 C 语言的同学,写出这个宏基本上不是什么难事,使用条件运算符就能完成:

#define  MAX(x, y)  x > y ? x : y

这是最基本的 C 语言语法,如果连这个也写不出来,估计场面会比较尴尬。面试官为了缓解尴尬,一般会对你说:小伙子,你很棒,回去等消息吧,有消息,我们会通知你!这时候,你应该明白:不用再等了,赶紧把这篇文章看完,接着面下家。这个宏能写出来,也不要觉得你很牛 X,因为这只能说明你有了 C 语言的基础,但还有很大的进步空间。

其实上面的写法在语法上面没有什么问题,但是在实际中有bug。

程序示例

#include <stdio.h>#define MAX(x, y) x > y ? x : yint main(void)
{printf("Max = %d\n", MAX(1, 2));printf("Max = %d\n", MAX(2, 1));printf("Max = %d\n", MAX(2, 2));printf("Max = %d\n", MAX(1 != 1,  1 != 2));return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
Max = 2
Max = 2
Max = 2
Max = 0

测试程序的过程中,我们肯定要把各种可能出现的情况都测一遍。测试第10行语句,当宏的参数是一个表达式,发现实际运行结果为 Max = 0,跟我们预期结果 Max = 1 不一样。这是因为,宏展开后,就变成了这个样子:

printf("Max = %d\n", 1 != 1 > 1 != 2 ? 1 != 1 : 1 != 2);

因为比较运算符 > 的优先级为6,大于 !=(优先级为7),所以展开的表达式,运算顺序发生了改变,结果就跟我们的预期不一样了。为了避免这种展开错误,我们可以给宏的参数加一个小括号()来防止展开后,表达式的运算顺序发生变化。这样的宏才能算一个合格的宏:

#define  MAX(x, y)  (x) > (y) ? (x) : (y)

程序示例

#include <stdio.h>#define MAX(x, y) (x) > (y) ? (x) : (y)int main(void)
{printf("Max = %d\n", MAX(1, 2));printf("Max = %d\n", MAX(2, 1));printf("Max = %d\n", MAX(2, 2));printf("Max = %d\n", MAX(1 != 1,  1 != 2));return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
Max = 2
Max = 2
Max = 2
Max = 1

上面的宏,只能算合格,但还是存在漏洞。比如,我们使用下面的代码测试:

程序示例

#include <stdio.h>#define MAX(x, y) (x) > (y) ? (x) : (y)int main(void)
{printf("max = %d\n", 3 + MAX(1, 2));return 0;
}

测试结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
max = 1

在程序中,我们打印表达式 3 + MAX(1, 2) 的值,预期结果应该是5,但实际运行结果却是1。我们展开后,发现同样有问题:

3 + (1) > (2) ? (1) : (2);

因为运算符 + 的优先级大于比较运算符 >,所以这个表达式就变为4 > 2 ? 1 : 2,最后结果为1也就见怪不怪了。此时我们应该继续修改这个宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

使用小括号将宏定义包起来,这样就避免了当一个表达式同时含有宏定义和其它高优先级运算符时,破坏整个表达式的运算顺序。如果你能写到这一步,说明你比前面那个面试的同学强,前面那个同学已经回去等消息。

中级程序员

上面的宏,虽然解决了运算符优先级带来的问题,但是仍存在一定的漏洞。比如,我们使用下面的测试程序来测试我们定义的宏:

程序示例

#include <stdio.h>#define MAX(x, y) ((x) > (y) ? (x) : (y))int main(void)
{int i = 2;int j = 6;printf("max = %d\n", MAX(i++, j++));return 0;
}

执行结果

deng@itcast:~/tmp$ vim 6.c
deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
max = 7

在程序中,我们定义两个变量 i 和 j,然后比较两个变量的大小,并作自增运算。实际运行结果发现 max = 7,而不是预期结果 max = 6。这是因为变量 i 和 j 在宏展开后,做了两次自增运算,导致打印出 i 的值为7。

遇到这种情况,那该怎么办呢? 这时候,语句表达式就该上场了。我们可以使用语句表达式来定义这个宏,在语句表达式中定义两个临时变量,分别来暂储 i 和 j 的值,然后进行比较,这样就避免了两次自增、自减问题。

程序示例

#include <stdio.h>#define MAX(x, y) ({    \int _x = x;         \int _y = y;         \_x > _y ? _x : _y;  \})int main(void)
{int i = 2;int j = 6;printf("max = %d\n", MAX(i++, j++));return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
max = 6

在语句表达式中,我们定义了2个局部变量 _x、_y 来存储宏参数 x 和 y 的值,然后使用 _x 和 _y 来比较大小,这样就避免了 i 和 j 带来的2次自增运算问题。

高级程序员

在上面这个宏中,我们定义的两个临时变量数据类型是 int 型,只能比较两个整型的数据。那对于其它类型的数据,就需要重新再定义一个宏了,这样太麻烦了!我们可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小:

程序示例

#include <stdio.h>#define MAX(type, x, y) ({    \type _x = x;        \type _y = y;         \_x > _y ? _x : _y;  \})int main(void)
{int i = 2;int j = 6;printf("max = %d\n", MAX(int, i++, j++));printf("max = %lf\n", MAX(float, 3.33, 4.44));return 0;
}

执行结果

deng@itcast:~/tmp$ ./a.out
max = 6
max = 4.440000

在这个宏中,我们添加一个参数:type,用来指定临时变量 _x 和 _y 的类型。这样,我们在比较两个数的大小时,只要将2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。

上面的宏定义中,我们增加了一个type类型参数,来兼容不同的数据类型,其实我们还有更加牛逼的语法,typeof是GNU C新增的一个关键字,用来获取数据类型,我们不用传参进去,让typeof直接获取!

程序示例

#include <stdio.h>#define MAX(x, y) ({    \typeof(x) _x = x;   \typeof(y) _y = y;   \(void)(&_x == &_y);  \_x > _y ? _x : _y;  \})int main(void)
{int i = 2;int j = 6;printf("max = %d\n", MAX(i++, j++));printf("max = %lf\n", MAX(3.33, 4.44));return 0;
}

执行结果

deng@itcast:~/tmp$ gcc 6.c
deng@itcast:~/tmp$ ./a.out
max = 6
max = 4.440000

在这个宏定义中,使用了 typeof 关键字用来获取宏的两个参数类型。干货在**(void) (&x == &y);**这句话,简直是天才般的设计!一是用来给用户提示一个错误,对于不同类型的指针比较,编译器会给一个错误,提示两种数据类型不同;二是,当两个值比较,比较的结果没有用到,有些编译器可能会给出一个warning,加个(void)后,就可以消除这个警告!

06. Linux内核应用示例

Linux内核中使用语句表达式非常多。

#define min(x,y) ({ \typeof(x) _x = (x); \typeof(y) _y = (y); \(void) (&_x == &_y);    \_x < _y ? _x : _y; })#define max(x,y) ({ \typeof(x) _x = (x); \typeof(y) _y = (y); \(void) (&_x == &_y);    \_x > _y ? _x : _y; })#define min_t(type, a, b) min(((type) a), ((type) b))
#define max_t(type, a, b) max(((type) a), ((type) b))

07. 附录

【嵌入式】C语言高级编程-语句表达式(03)相关推荐

  1. c语言高级程序设计第五版PDF,C语言高级编程.pdf

    C语言高级编程 概述 由几个测试程序说开去 预编译与宏 高级预编译介绍 宏的高级用法 变量 变量分类详细解析 我的变量去哪儿了? 大小端对变量的影响 内存与指针 常见内存使用错误大观 指针,又是指针! ...

  2. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  3. 鼠标绘图 c语言,c语言高级编程技术教程 图形显示方式与鼠标输入.doc

    c语言高级编程技术教程 图形显示方式与鼠标输入 c语言高级编程技术教程 图形显示方式和鼠标输入 图形显示方式和鼠标输入 问题的提出编写程序,使用鼠标进行如下操作:按住鼠标器的任意键并移动,十字光 标将 ...

  4. 高级编程中C语言属于,c语言高级编程

    c语言高级编程 C高级编程 责任编辑:admin 更新日期:2005-8-6 深入了解C语言(函数的参数传递和函数使用参数的方法) tangl_99(原作) 关键字 C语言,汇编,代码生成,编译器 C ...

  5. 《go语言圣经》+《Mastering.GO-cn》+《go语言高级编程》PDF下载

    公众号[爱吃橙子的搬砖小徐]开通啦,后续将会同步更新,欢迎订阅 回复[java面试]获得两套面试宝典 回复[golang]获得go语言学习三部曲 <go语言圣经>+<Masterin ...

  6. matlab高级教程教材,MATLAB语言高级编程 PDF_IT教程网

    资源名称:MATLAB语言高级编程 PDF 本书共分8章,主要介绍了matlab的概述.matlab安装与工作桌面:matlab的编程基础,包括matlab的变量.matlab的运算符.矩阵的创建及运 ...

  7. 【嵌入式】C语言高级编程-内建函数(11)

    00. 目录 文章目录 00. 目录 01. 内建函数概述 02. 常用内建函数 03. C 标准库的内建函数 04. 内核中的 likely 和 unlikely 05. 附录 01. 内建函数概述 ...

  8. 【嵌入式】C语言高级编程-内联函数(10)

    00. 目录 文章目录 00. 目录 01. 属性声明 02. 内联函数概述 03. 内联函数与宏 04. 编译器对内联函数的处理 05. static修饰内联函数 06. 附录 01. 属性声明 a ...

  9. 【嵌入式】C语言高级编程-container_of宏(04)

    00. 目录 文章目录 00. 目录 01. typeof 关键字 02. typeof与宏结合 03. typeof在内核源码中应用 04. container_of 宏分析 05. contain ...

最新文章

  1. 纯 Git 实现前端 CI/CD
  2. 如何将eclipse设置为炫丽的全黑背景!
  3. Linux系统挂载操作mount详解
  4. 为什么你的提问没人解答?
  5. 牛客网---Java题库(1~10)
  6. MySQL运行一段时间后自动停止问题的排查
  7. 常用js或jq效果汇总
  8. ORACLE数据库无法执行UPDATE
  9. 正点原子阿波罗STM32F7-红外遥控原理及代码
  10. IOS开发-苹果开发者中心 提示 edit phone number
  11. 【京东】会员激励体系,会员体系结构分析
  12. dfuse 开放其 EOSIO 堆栈的源代码
  13. 软件架构图该怎么画?架构设计如何标准化?
  14. 99道python测试题
  15. Canvas绘制五子棋棋盘
  16. 【渝粤题库】国家开放大学2021春1253C语言程序设计答案
  17. mssql数据库管理的简单介绍
  18. ECharts教程(未完)
  19. Centos7安装教程2022.2
  20. Found duplicate column(s) when inserting into hdfs://nameservice1/origin_data/events_7/data: `dt`;

热门文章

  1. 使用bash判断PATH中是否存在某个路径
  2. 绍兴市一男子醉酒驾车还冲上公交车暴打司机
  3. php的类图怎么生成_PHP设计模式之简单工厂模式
  4. 7-6 jmu-Java-02基本语法-06-枚举 (3 分)
  5. 对的调用没有匹配的函数_前端开发之——函数、事件、js对象
  6. php xml 四种,xml中常见的四种解析方式是什么?
  7. Java黑皮书课后题第6章:*6.5(对三个数排序)使用下面的方法头编写方法,按升序显示三个数
  8. 程序员面试100题之三:不用+、-、×、÷数字运算符做加法
  9. beta 圆桌桌 4
  10. PageRank算法简介及Map-Reduce实现