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

00. 目录

文章目录

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

01. C语言的表达式

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

22 + 33

22
    
a = a + b

i++
    
"hello world"

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

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

等等。

02. C语言的语句

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

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

#include 

int main(void)
{
    int i = 0;

    //合法的表达式
    i
    =
    1
    +
    2
    ;

    return 0;
}

03. C语言中的代码块

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

程序示例

#include 

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 

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 

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 

#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 = 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 

#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 

#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 

#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 

#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 

#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 

#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和C++,C语句,C表达式,语句表达式,C高级用法,Linux内核)