常被忽视的C语言操作符优先级

由操作符优先级左右的美观和高级声明

最开始,在学习C语言的时候,都是为了快速上手,很少有人真正关心C语言操作符的优先级;
除非你是科班出身,有老师监督并有考试来约束;

那为什么我们都会有这个忽略它的想法呢?

  1. 首先我们都感觉自己很聪明,这部分用的的时候查查就可以了
  2. 只要使用括号,没有解决不了的事情,如果有,那就再加一个括号…
  3. 为了快速投入到工作中,如果你不经常写代码,更不会关心它

可能的原因也许还有很多,但不论都是什么,反正操作符的优先级看起来并没有那么重要。

本文就要把你的观念正一正,看看学好操作符的优先级,到底能有什么用。

语句的简洁与美观

我们先来看一个例子:

#include <stdio.h>

typedef struct PERSON {
    char * name;
    int age;
    int score[3];
    int * (* action) (int *, int);
} Person;

int* myaction(int* a, int b){
    return &a[b];
}

typedef struct OPTS {
    Person * (* func) (Person * p, int m);
} Opts;

Person * my_func(Person *p, int m){
    p->score[2] += m;
    return p;
}

int main(void){
    Person p = {
        .name = "Tom",
        .age  = 18,
        .score = {89, 67, 99},
        .action = myaction,
    };

    Person * pp = &p;
    Person ** ppp = &pp;

    Opts op = {
        .func = my_func
    };

    Opts * opp = &op;

    int result = (int)++opp->func(*ppp, (int)(char)1)->action(pp->score,1)[1];
    printf("result: %d\n", result);

    return 0;
}

如果你能在不运行的情况下,得到答案,那真是很棒,说明你的功底比较扎实。
如果你不知道答案,那就在自己的机器上运行下吧,真正的答案是101

如果你用括号来分割你不清楚的表达式组合,看看你要添加多少括号?

其实,在你的潜意识中已经记住很多操作符的优先级了;
如:先算数运算符(乘除后加减),紧接着是移位操作,然后关系运算在逻辑运算之前,最后基本是赋值操作

而容易弄混的,往往是这几个:
&,*,(函数调用),[],->,.,(聚组),++,--

这几个中,最特殊且优先级最高的是(聚组),因为你知道,你可以用括号来改变任何求值顺序

取址指针解引用&*是这几个中优先级最低的,所以他们是最后来运算的,所以,如果你写成这样:*a->b.c,你要真的知道你在访问什么。

接下来,他们这几个都是同一个级别:->,.,[],(函数),后缀++和--
他们的优先级,仅次于(聚组),其实他们是同一个优先级,只不过括号操作更霸道一些。
而且,这几个操作付都是从左到右结合的,他们的结合顺序是自然的结果,与我们读代码的直觉一致。

好了,是不是很简单?我们做个简单的总结,引用《C和指针》中的一段话:

复杂表达式的求值顺序是由3个元素决定的:操作符的优先级操作符的结合性以及操作符是否控制执行顺序。两个响铃的操作符那个先执行取决于他们的优先级,如果两者的优先级相同,那马他们的执行顺序由他们的结合性决定。简单地说,结核性就是遗传操作符是从左向右依次执行还是从右向左逐个执行。最后,有4个操作符,他们可以对整个表达式的求值顺序施加控制,他们或者保证某个子表达式能够在另一个子表达式的所有求值过程完成之前进行求值,或者可能使某个表达式被完全跳过而不再求值。(这几个控制执行顺序的操作符就是逻辑操作符:|| && ?? ,没错,最后一个是逗号)

操作符的优先级

操作符的表格会徒占篇幅,所以我们给出两个可以索引的位置:

高级声明中的使用技巧

复杂的声明往往是新手的苦恼源泉,他们不扎实的基本功没有办法很好很快的完成识别。

那该怎么克服呢?其实也很简单,对于C语言来说,声明表达式的阅读顺序和表达式求值顺序一样。

换句话说,如果你知道表达式如何求值(前面说过,有三个决定性元素),那多么复杂的声明也不在话下。

这里先提前说几句:

  1. C语言中,函数只能返回标量值,没有办法返回数组,绕过去的办法只有指针
  2. 任何语言,都没有一个方法判断函数的相等性(指针不相等并不代表函数不相同,他们可以完成同样的工作),这也是业界的共识
  3. 函数没有办法存入数组中,因为函数的大小是编译器决定的,而且往往都是不同大小的,但函数的指针可以放在数组中,他们都是指针长度

最后的例子是这样:

int (* a[])(int, int (*)(float, char));

基本技巧是:

  1. 找到优先级最高的部分,并发现声明的符号(你也可以说是变量)
  2. 按照表达式的求值顺序开始阅读,注意操作符的优先级等影响因素

那么我们读一下例子中的声明表达式:

声明的符号变量是a,他是一个数组,内部元素是函数指针,函数指针指向接收一个整数和另一个函数指针,并返回整形值的函数;形参中需要被传递的函数接收单浮点整形和字符整形并返回整形值

还有一个好用的工具是cdecl,它和一个过程调用的标准同名,也是帮助我们理解复杂声明表达式的好帮手,笔者的操作系统是ubuntu20.04,所以有包管理器中会包含默认的工具资源,其他系统请自行百度解决。

安装指令:sudo apt install cdecl
运行测试:

cdecl> explain int (* a[])(int, int (* )(float, char));
## output as below:
declare a as array of pointer to function (int, pointer to function (float, char) returning int) returning int

cdecl> declare a as array of pointer to function (int, pointer to function (float, char) returning int) returning int
## output as below:
int (*a[])(int , int (*)(float , char ))

explaindeclare是cdecl的常用指令,至于他们还有什么功能自己摸索吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值