zoukankan      html  css  js  c++  java
  • C语言的本质(7)——C语言运算符大全

    C语言的本质(7)——C语言运算符大全

      

    C语言的结合方向

       C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左)。例如算术运算符的结合性是自左至右,即先左后右。如有表达式 x-y+z则 y 应先与“-”号结合,执行 x-y 运算,然后再执行+z 的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向称为“右结合性”。  最典型的右结合性运算符是赋值运算符。如 x=y=z,由于“=”的右结合性,应先执行 y=z 再执行 x=(y=z)运算。C语言运算符中有不少为右结合性,应注意区别,以避免理解错误。

    C语言的运算符:

    1、复合赋值运算符

    复合赋值运算符包括*= ,/=, %=, +=, -=, <<=, >>=, &=, ^=, |=,在赋值的同时做一个运算。例如a += 1相当于a = a + 1。但有一点细微的区别,前者对表达式a只求值一次,而后者求值两次,如果a是一个复杂的表达式,求值一次和求值两次的效率是不同的,例如a[i+j] += 1和a[i+j] = a[i+j] + 1。仅仅是效率上的差别吗?对于没有Side Effect的表达式,求值一次和求值两次的结果是一样的,但对于有Side Effect的表达式则不一定,例如a[foo()] += 1和a[foo()] = a[foo()] + 1,如果foo()函数调用有SideEffect,比如会打印一条消息,那么前者只打印一次,而后者打印两次。

    ++i相当于i = i + 1,其实更准确地说应该是等价于i += 1,而--i等价于i -= 1。

    2、条件运算符

    条件运算符是C语言中唯一一个三目运算符(Ternary Operator),带三个操作数,它的形式是

    表达式1 ? 表达式2 : 表达式3


    这个运算符所组成的整个表达式的值等于表达式2或表达式3的值,取决于表达式1的值是否为真,可以把它想像成这样的函数:

    if (表达式1)
             return表达式2;
    else
             return表达式3;


    表达式1相当于if语句的控制表达式,因此它的值必须是标量类型,而表达式2和3相当于同一个函数在不同情况下的返回值,因此它们的类型要求一致,也要做Usual Arithmetic Conversion。

    下面举个例子,定义一个函数求两个参数中较大的一个。

    int max(int a, int b)
    {
             return(a > b) ? a : b;
    }

     

    3、逗号运算符

    逗号运算符也是一种双目运算符,它的形式是表达式1, 表达式2,两个表达式不要求类型一致,左边的表达式1先求值,求完了直接把值丢掉,再求右边表达式2的值作为整个表达式的值。逗号运算符是左结合的,类似于+-*/运算符,根据组合规则可以写出表达式1, 表达式2, 表达式3, ..., 表达式n这种形式,表达式1, 表达式2可以看作一个子表达式,先求表达式1的值,然后求表达式2的值作为这个子表达式的值,然后这个值再和表达式3组成一个更大的表达式,求表达式3的值作为这个更大的表达式的值,依此类推,整个计算过程就是从左到右依次求值,最后一个表达式的值成为整个表达式的值。

    注意,函数调用时各实参也是用逗号隔开,那个逗号不能看作逗号运算符。但可以这样:

     f(a, (t=3, t+2), c)传给函数f的有三个参数,第二个参数的值是表达式t+2的值。

    4、sizeof运算符与typedef类型声明

    sizeof是一个很特殊的运算符,它有两种形式:sizeof表达式和sizeof(类型名)。它的特殊之处在于,sizeof 表达式中的表达式并不求值,只是根据类型转换规则求得该表达式的类型,然后把这种类型所占的字节数作为sizeof 表达式这整个表达式的值。有些人喜欢写成sizeof(表达式)的形式也可以,这里的括号和return(1);的括号一样,没有任何作用。但另外一种形式sizeof(类型名)的括号则是必须写的,整个表达式的值也是这种类型所占的字节数。

    比如用sizeof运算符求一个数组的长度:

    int a[12];
    printf("%d
    ", sizeof a/sizeof a[0]);


    在上面这个例子中,由于sizeof表达式中的表达式不需要求值,所以不需要到运行时才计算,事实上,在编译时就知道sizeof a的值是48,sizeof a[0]的值是4,所以在编译时就已经把sizeof a/sizeof a[0]替换成常量12了,这是一个常量表达式。

    准确地说,sizeof表达式的值是size_t类型的,这个类型定义在stddef.h头文件中,不过你的代码中只要不出现size_t这个类型名就不用包含这个头文件,比如像上面的例子就不用包含这个头文件。size_t这个类型是我们讲过的整型中的某一种,编译器可能会用typedef做一个类型声明:


    typedef unsigned long size_t;那么size_t类型就是unsignedlong类型。之所以不直接规定sizeof的值是unsigned long型的,而要规定一个size_t类型,是为了允许不同的编译器根据自己平台的情况定义size_t为不同的类型,这样使用size_t类型的代码就具有很好的可移植性,但不管编译器怎么实现,C标准明确规定sizeof的值是无符号整型的。

    typedef这个关键字用于给一个类型起个新的名字,上面的声明可以这么看:去掉typedef就成了一个变量声明unsigned long size_t;,size_t是一个变量名,类型是unsigned long,那么加上typedef之后,size_t就是一个类型名,就代表unsigned long类型。再举个例子:

    typedef char array_t[10];

    array_t a;就相当于定义char a[10];类型名也遵循标识符的命名规则,并且通常加个_t后缀,表示Type。

    C语言运算符的优先级

    下面按优先级从高到低的顺序总结一下各种运算符,每一条所列的各运算符具有相同的优先级,对于同一优先级的多个运算符按什么顺序计算也有说明,双目运算符就简单地用“左结合”或“右结合”来说明了。

    1、标识符、常量、字符串和用()括号套起来的表达式是组成表达式的最基本单元,在运算中做操作数,优先级最高。

    2、后缀运算符,包括数组取下标[]、函数调用()、结构体取成员.、指向结构体的指针取成员->、后缀自增++、后缀自减--。如果一个操作数后面有多个后缀,按照离操作数从近到远的顺序(也就是从左到右)依次运算,比如a.name++,先算a.name,再++,这里的.name应该看成a的一个后缀,而不是把.看成双目运算符。

    3、单目运算符,包括前缀自增++、前缀自减--、sizeof、类型转换()、取地址运算&、指针间接寻址*、正号+、负号-、按位取反~、逻辑非! 。如果一个操作数前面有多个前缀,按照离操作数从近到远的顺序(也就是从右到左)依次运算,比如!~a,先算~a,再求!。

    4、乘*、除/、模%运算符。这三个运算符是右结合的。

    5、加+、减-运算符。右结合。

    6、移位运算符<<和>>。右结合。

    7、关系运算符< > <=>=。右结合。

    8、相等性运算符==和!=。右结合。

    9、按位与&。右结合。

    10、按位异或^。右结合。

    11、按位或|。右结合。

    12、逻辑与&&。右结合。

    13、逻辑或||。右结合。

    14、条件运算符:?。在第 2 节 “if/else语句”讲过Dangling-else问题,条件运算符也有类似的问题。例如a ? b : c ? d : e是看成(a ? b : c) ? d : e还是a ? b : (c ? d : e)?C语言规定是后者。

    15、赋值=和各种复合赋值(*= /=%= += -= <<= >>= &= ^= |=)。左结合。

    16、逗号运算符。右结合。

    下面的表格详细说明了运算符的结合方向和优先级:

    优先级

    运算符

    名称或含义

    使用形式

    结合方向

    说明

    1

    []

    数组下标

    数组名[常量表达式]

    左到右

    ()

    圆括号

    (表达式)/函数名(形参表)

    .

    成员选择(对象)

    对象.成员名

    ->

    成员选择(指针)

    对象指针->成员名

    2

    -

    负号运算符

    -表达式

    右到左

    单目运算符

    (类型)

    强制类型转换

    (数据类型)表达式

    ++

    自增运算符

    ++变量名/变量名++

    单目运算符

    --

    自减运算符

    --变量名/变量名--

    单目运算符

    *

    取值运算符

    *指针变量

    单目运算符

    &

    取地址运算符

    &变量名

    单目运算符

    !

    逻辑非运算符

    !表达式

    单目运算符

    ~

    按位取反运算符

    ~表达式

    单目运算符

    sizeof

    长度运算符

    sizeof(表达式)

    3

    /

    表达式/表达式

    左到右

    双目运算符

    *

    表达式*表达式

    双目运算符

    %

    余数(取模)

    整型表达式/整型表达式

    双目运算符

    4

    +

    表达式+表达式

    左到右

    双目运算符

    -

    表达式-表达式

    双目运算符

    5

    << 

    左移

    变量<<表达式

    左到右

    双目运算符

    >> 

    右移

    变量>>表达式

    双目运算符

    6

    大于

    表达式>表达式

    左到右

    双目运算符

    >=

    大于等于

    表达式>=表达式

    双目运算符

    小于

    表达式<表达式

    双目运算符

    <=

    小于等于

    表达式<=表达式

    双目运算符

    7

    ==

    等于

    表达式==表达式

    左到右

    双目运算符

    !=

    不等于

    表达式!= 表达式

    双目运算符

    8

    &

    按位与

    表达式&表达式

    左到右

    双目运算符

    9

    ^

    按位异或

    表达式^表达式

    左到右

    双目运算符

    10

    |

    按位或

    表达式|表达式

    左到右

    双目运算符

    11

    &&

    逻辑与

    表达式&&表达式

    左到右

    双目运算符

    12

    ||

    逻辑或

    表达式||表达式

    左到右

    双目运算符

    13

    ?:

    条件运算符

    表达式1? 表达式2: 表达式3

    右到左

    三目运算符

    14

    =

    赋值运算符

    变量=表达式

    右到左

    /=

    除后赋值

    变量/=表达式

    *=

    乘后赋值

    变量*=表达式

    %=

    取模后赋值

    变量%=表达式

    +=

    加后赋值

    变量+=表达式

    -=

    减后赋值

    变量-=表达式

    <<=

    左移后赋值

    变量<<=表达式

    >>=

    右移后赋值

    变量>>=表达式

    &=

    按位与后赋值

    变量&=表达式

    ^=

    按位异或后赋值

    变量^=表达式

    |=

    按位或后赋值

    变量|=表达式

    15

    ,

    逗号运算符

    表达式,表达式,…

    左到右

    从左向右顺序运算

  • 相关阅读:
    git 专题
    Android yyyymmdd转成yyyy-MM-dd格式
    Android LayoutInflater.from(context).inflate
    Android TextView内容过长加省略号,点击显示全部内容
    Android 标题栏封装
    Android 自定义Android带图片和文字的ImageButton
    Android 防止按钮连续点击的方法(Button,ImageButton等)
    Android 动态改变布局属性RelativeLayout.LayoutParams.addRule()
    Android ActionBar的Overlay模式如何不遮盖顶部内容的问题
    Android 时间轴TimeLine
  • 原文地址:https://www.cnblogs.com/new0801/p/6177118.html
Copyright © 2011-2022 走看看