c语言-运算符与表达式

1 篇文章 0 订阅

1. 运算符

什么是运算符?

运算符是用来表示某种运算(操作)的符号。 如: + - * /

几目运算符? 表示这个运算符需要带几个操作数(运算数)

单目运算符: 该运算符只需要一个操作数,如: ++ -- & 双目运算符: 该运算符需要两个操作数, 如: + - * / 三目运算符: 该运算符需要三个操作数, 如: ? :

结合性: 决定先算哪个操作数的问题。

从左至右结合,还是 从右至左结合 举个例子: 已知 + 结合性 “从左至右”结合, C语言中: a + b 和 b + a 含义是不一样的!!!

运算符的优先级

在含有多个运算符的表达式中,决定先哪个运算符,后算哪个运算符的问题 如: a + b * c

单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符

2. 表达式

什么是表达式?

表达某个意思的式子。 C语言中的表达式,一般来说是用运算符连接操作数的式子,如: 3 + 5

表达式的值

是一个表达式,就一定会有一个值,这个值就是表达式的值。 表达式最终需要表达某个意思,某个意思就是表达式的值!!!

3. 运算符的分类

3.1 算术运算符

进行算术运算的运算符。

++ -- 单目运算符

  • / % 双目运算符

    • 双目运算符

+-*/ 只需要操作数是一个数(整数可以,小数也可以) % 求余数, 要求两个操作数都必须为整数

如: 3 + 4 3.5 + 4.7 3.5*7 3.5/2 3.5/8.9

5 % 3 
3 % 2.3 ERROR
4 / 2 => 2
5 /3 => 1
5 / 2 => 2
5 /2.0 => 2.5
-3 % 2   可以。 (负数求余数,请参考《数论》)
​
3 / 2 => 1
(double)(3/2) => 1.0
(double)3 / 2 => 1.5
1.0* 3 / 2 => 1.5
​
用C语言的表达式描述数学表达式 a / b
    1.0*a/b

++ (自增运算符)

自增(+1)运算符, 单目运算符,要求操作数必须为一个左值(lvalue)

因为:在C语言中,只有变量才能具备左值

前置++: ++在前面,如: ++x 后置++: ++在后面,如: x++

    5++   //ERROR, 5不是变量,
    (a+b)++ //ERROR (a+b)是一个表达式,不是变量。
    int x;
    x++  可以的
    ++x  可以的
​
    char y;
    y++  可以的
​

-- (自减运算符)

自减(-1)运算符,单目运算符,要求操作数必须为一个左值(lvalue) 前置--: --在前面,如: --x 后置--: --在后面,如: x--

Increment/decrement operators are unary operators that increment/decrement the value of a variable by 1.
​
They can have postfix form:
expr ++         
expr --         
​
As well as the prefix form:
++ expr         
-- expr         
​
The operand expr of both prefix and postfix increment or decrement must be a modifiable lvalue of integer type (including _Bool and enums), real floating type, or a pointer type.
>> 自增/自减的操作数必须为一个可修改的左值(“变量”),而且类型必须为: int(包括 bool, enums),real floating type (浮点数), or a pointer type(指针)
​
++/--操作数类型为:
    整型
    浮点型
    指针类型
​
​
                表达式的值          做完表达式后,i的值
    i++             i                   i = i  + 1
    ++i            i+1                  i = i  + 1
    i--             i                   i = i  - 1
    --i            i-1                  i = i  - 1

练习:

  1. 在不同的C编译器下,运行如下代码,看看他们的结果有什么不一样?

#include <stdio.h>
int main()
{
    int i = 5;
    int a;
​
    a = (i++) + (i++) + (i++);
    
    printf("%d\n", a);
    return 0;
}
​
  1. 在不同的C编译器下,运行如下代码,看看他们的结果有什么不一样?

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

结论:

上面那两处代码不是一个优秀的程序员写出来的。(我只是说明这个问题,才写的,so,我其实是一个优秀的程序员!!!)

3.2 关系运算符

用来判断两个 东西的关系的运算符。 “关系”:数值大小关系。 “比 数值的大小” 双目运算符,结合性: 从左至右。

​
    >  >=   <  <=
    ==  != 
​

关系表达式: 用关系运算符连接起来的式子。 如: a > b 关系表达式的值: 关系成立 1 关系不成立 0

如:

    5 > 3  表达式的值是 1
    5 > 7  表达式的值是 0
​
    5 > 4 > 3 表达式的值是 0
     结合性是  从左至右
     =>   (5 > 4) > 3
     =>     1 > 3
     =>  0
    5 > 4 > 3 是一个合法的关系表达式,是拿  表达式5>4 的值 和 3进行PK.
    在C语言中, 5 > 4 > 3 这个表达式和数学上的“5>4>3”的含义是不一样的。
    (1) C语言上的, 5 > 4 > 3 <=> (5>4) > 3
    (2) 数学上的, 5 > 4 > 3 是 "5 > 4 并且  4 > 3"
​

3.3 逻辑运算符

! 逻辑非 单目运算符 “取反” && 逻辑与 双目运算符 "并且", 从左至右结合 || 逻辑或 双目运算符 “或者”, 从左至右结合

逻辑表达式: 用逻辑运算符(! && ||)连接起来的式子,称之为逻辑表达式 逻辑表达式的值 逻辑真 true 1(非0) 逻辑假 false 0

    a       b       a && b     a || b
    真      真        真         真
    假      真        假         真
    真      假        假         真
    假      假        假         假

例子:

    int a = 4, b = 5;

    a && b  => true 1
    a || b  => true 1
    !a && b  =>  0 && b =>  false 0

    5 > 3 && 8 < 4 - !0
        5 > 3 && 8 < 4 - 1
        5 > 3 && 8 < 3
         1  &&  8 < 3
         1  &&  0 => 0

练习:

  1. 分析如下程序的输出结果

int main()
{
    int a,b,c,d,m,n;
    a = 1;
    b = 2;
    c = 3;
    d = 4;
    m = 1;
    n = 1;

    (m =  a > b) && (n = c > d);

    printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,m,n);
    return 0;
}

C语言运算符是 “惰性运算”: (a) a && b && c

只有a为真时,才需要 判断b的值 只有a和b都为真时,才需要判断 c的值

(b) a || b || c

只要a为真,就不必判断b和c的值啦 只有a和b都为假时,才需要判断c的值

... In a word, 如果事先知道表达式的值啦,那么后面的运算符或表达式就不需要执行啦, 因为没那个必要啦,这个就是C语言运算符的“惰性”。

  1. 用逻辑表达式来判断y(年份)是否为闰年 (1) 能被4整除,但又不能被100整除 (2) 能被 400整除 满足上述条件之一,则为闰年

    (1) || (2)   => 这个表达式的值(true/false) 就代表 y 是否为闰年 


    (1)
        (y%4 == 0) &&  (y%100 != 0)

    (2)
        y % 400 == 0

=> ((y%4 == 0) &&  (y%100 != 0)) || ( y % 400 == 0)

  1. 用逻辑表达式来判断y(年份)是否 不为闰年 (1) 能被4整除,但又不能被100整除 (2) 能被 400整除 满足上述条件之一,则为闰年

    !((1) || (2) )

=> !(((y%4 == 0) &&  (y%100 != 0)) || ( y % 400 == 0))

3.4 位运算符

位运算 是按 bit位展开来进行的运算。意思是说,操作数会展开成bit位,然后再进行运算。 位运算符要求操作数必须为整数(兼容的整数类型)

    &  按位与
    |  按位或
    ^  按位异或 
    ~  按位取反
    << 按位左移
    >> 按位右移

除了 ~(按位取反)是单目运算符外,其他的都是双目运算符,结合性 从左至右

1. ~按位取反

1 -> 0 0 -> 1

    !3  逻辑非
        => 0

    ~3
        00000000 00000000 00000000 00000011
        11111111 11111111 11111111 11111100  <-   ~3

NOTE:

按位取反 ~ 和 逻辑非 !的区别

!x ~x 两个都是单目运算符 ~x 要求操作数x是一个整数 !x 对x无要求

练习:

  1. 分析如下程序的输出结果

int main()
{
    int a = ~(-3);
    printf("%d\n", a); //2
    printf("%u\n", a); //2
}


int main()
{
    int a = !(-3);
    printf("%d\n", a); //0
    printf("%u\n", a); //0
}

2. & 按位与

a & b, 双目运算符,结合性 从左至右

    a       b            a & b
    1       1              1
    1       0              0
    0       1              0
    0       0              0
注意:
>    按位与& 没有进位操作!!!
>    按位与& 如果两个bit位都为1,结果才为1,否则为0

例子:

    3 & 5  = ?   1
    00000000 00000000 00000000 00000011
&   00000000 00000000 00000000 00000101
    00000000 00000000 00000000 00000001

    3 & 7 = ?  3
    00000000 00000000 00000000 00000011
    00000000 00000000 00000000 00000111
    00000000 00000000 00000000 00000011

NOTE:

按位与& 和 逻辑与 && 的区别

练习:

  1. 假设有一个整型变量a, 要把a的第5bit,变为0,其他bit位不变,该如何操作?

  a =   a &  ~(1 << 5);

结论:

  • 一个bit位与 0 进行“按位与 &”操作,结果为0

    x & 0 == 0
    x   x & 0
    1   1 & 0   0
    0   0 & 0   0
  • 一个bit位与 1 进行“按位与 &”操作,结果为x (保留原值)

    x & 1 == x
    x    x & 1
    1    1 & 1  1
    0    0 & 1  0

3. | 按位或

a | b , 双目运算符,结合性从左至右

    a   b   a | b
    1   1     1
    1   0     1
    0   1     1
    0   0     0

按位或,只要有一个bit操作数为1,其结果就为1 例子:

    3 | 7 = ?   7
     00000000 00000000 00000000 00000011
     00000000 00000000 00000000 00000111
     00000000 00000000 00000000 00000111

    3 || 7 = ? 1

练习:

  1. 假设有一个整型变量a, 要使a的第5bit置1,其他bit位不变,该如何操作?

a = a | ( 1 << 5);

结论:

  • 一个bit位与 1进行“按位或 |”操作,结果为1

    x | 1 = 1
    x   x | 1
    0   0 | 1  = 1
    1   1 | 1  = 1
  • 一个bit位与 0进行"按位或 |"操作,结果为 保留原值

    x | 0 = x
    x   x | 0
    0   0 | 0 = 0
    1   1 | 0 = 1

4. 按位异或 ^

"异或": 求异,不同为1,相同为0. a ^ b, 双目运算符,结合性从左至右

    a    b   a ^ b
    1    1     0
    1    0     1
    0    1     1
    0    0     0 

例子:

    2 ^ 32 = ?  34
        00000000 00000000 00000000 00000010
    ^   00000000 00000000 00000000 00100000
        00000000 00000000 00000000 00100010

    3 ^ 7 = ?  4
        00000000 00000000 00000000 00000011
    ^   00000000 00000000 00000000 00000111
        00000000 00000000 00000000 00000100

练习:

  1. 假设有一个整型变量a, 要使a的第5bit保留,其他位取反,该如何操作?

a  = a ^ ~(1<<5);

2. 有两个整型变量a, b, 要交换a和b的值,但是要求不能用其他变量.

     a = a ^ b;
     b = a ^ b;
     a = a ^ b;
证明方法: (周龙提供的)

    a     b     a = a ^ b   b = a ^ b    a = a ^ b      a    b
    1     1     a = 0,b=1   a = 0,b= 1   a = 1, b = 1   1    1
    1     0     a = 1, b=0  b = 1, a= 1  a = 0, b = 1   0    1
    0     1   ...
    0     0   ...

结论:

  • 一个bit位与0进行"按位异或 ^" 结果为,保留原值

    x ^ 0 = x

    x   x ^ 0
    1   1 ^ 0 = 1
    0   0 ^ 0 = 0
  • 一个bit位与1进行“按位异或 ^”, 结果 取反

    x ^ 1 = ~x

    x   x ^ 1 
    1   1 ^ 1 = 0 
    0   0 ^ 1 = 1

5. 按位左移 <<

a << n 双目运算符,结合性从左至右结合。“把a按bit位整体左移n位”

高位左移后,舍弃; 低位空出n位,直接补0. 如果左移舍弃的高位都为0, 那么左移n位,就表示在原值乘以 2的n次方

例子:

    int a = 8 << 2;
         00000000 00000000 00000000 00001000 << 2
         000000 00000000 00000000 0000100000

    int a = (unsigned char)-1 << 2;
                       -1:  11111111 11111111 11111111 11111111
        (unsigned char)-1 : 11111111
          int   : (unsigned char)-1 => int 短->长
                   00000000 00000000 00000000  11111111 << 2
                   000000 00000000 00000000  1111111100
                    255*4 = 1020


    printf("%d\n", a);
    printf("%u\n", a);


    unsigned char a = (unsigned char)-1 << 2;
    printf("%d\n", a); //252
    printf("%u\n", a); //252
                -1:  11111111 11111111 11111111 11111111
        (unsigned char)-1 : 11111111
          int   : (unsigned char)-1 => int 短->长
                   00000000 00000000 00000000  11111111 << 2
                   000000 00000000 00000000  1111111100
            a:  11111100 
        %d : 000...0000   11111100

    char a = (unsigned char)-1 << 2;
    printf("%d\n", a); // -4
    printf("%u\n", a); // 2^32 - 4

          (unsigned char)-1 : 11111111
          int   : (unsigned char)-1 => int 短->长
                   00000000 00000000 00000000  11111111 << 2
                   000000 00000000 00000000  1111111100
            a:  11111100 
        %d : a -> int
            11111111 11111111 11111111 11111100
            11111111 11111111 11111111 11111011
            00000000 00000000 00000000 00000100

练习:

  1. 构造一个整数,它的第7bit和第28bit为1,其他bit为0,该如何构造?

(1 << 7) | (1 << 28)

6. 按位右移 >>

x >> n 双目运算符,从左至右结合。 “把x按bit位,整体右移n位”

低位右移后,直接舍弃。高位补什么呢?

对于无符号数(x),高位全部补0; 对于有符号数(x),高位全部补原符号位。

练习:

  1. 分析如下程序的输出结果。假设 int 占32bits

int main()
{
    int a = -1;
    a = a >> 31;

        11111111 11111111 11111111 11111111 >> 31
        1111111111111111.....1
    printf("%d\n", a); // -1
    printf("%u\n", a); // 2^32 - 1
    return 0;
}
int main()
{
    unsigned int a = -1;
    a = a >> 31;
        11111111 11111111 11111111 11111111 >> 31
        0000..000001
    printf("%d\n", a); //1
    printf("%u\n", a); //1
    return 0;
}
  1. 分析如下程序的输出结果。假设 int 占32bits

int main()
{
    char a = -1;
    int b = a >> 31;


    a: 11111111
    a >> 31
        111....1111111111 >> 31
        111111111111111....1  b

    printf("%d\n", b); // -1
    printf("%u\n", b); // 2^32 - 1
    return 0;
}
int main()
{
    unsigned char a = -1;
    int b = a >> 31;

    a: 11111111
    a >> 31
        0000000000000000...00011111111 >> 31
        0000...000000
    printf("%d\n", b); // 0
    printf("%u\n", b); // 0
    return 0;
}

7. 掩码(mask)

掩码(mask) 是一个位模式,表示从一个字(word,32bits, ...)中选出的位集合。 掩码中为1的那些bit位,就是我们想要选取的 bit位集合。 用我(龙哥)的话说: "掩码表示你对哪些bit位感兴趣"

如:

    如果我要选取的是第0bit和第1bit,则掩码为  0x3
    00000000 00000000 00000000 00000011

    如果 mask = 0xff, 表示我要选取低8位
    00000000 00000000 00000000 11111111

例子:

  1. 假设有一个整型变量 x = 0x345678AB, 我想要选取x的低8位,该如何操作呢?

    低8位的掩码 mask = 0xff
    x & mask  => x & 0xff => 0xAB
  1. 假设有一个整型变量 x, 我想要选取x的第5bit开始的4个bit位,该如何操作呢?

    mask:  000000...0000111100000    0xf << 5
    mask = 0xf << 5

    ( x & (0xf << 5) ) >> 5

3.5 赋值运算符

a = b 双目运算符,结合性从右至左, "把表达式b的值赋值给 a"

赋值运算符的优先级 排倒数第二,倒数第一是逗号运算符

a = b

把表达式b的值 赋值给a(把这个值写入到a的地址中去,a需要具备一个可写的地址(左值),“变量”) 一般来说,a为一个可变的数据对象,“变量” 赋值运算符的左边操作数 必须为一个 “可写的地址”,左值,变量

例子:

    5 = 5  ; //ERROR 

    5 == 5  //ok
    2 + 3 = 5; //ERROR

    int x = 2, y = 3;
    x + y = 5; //ERROR

    x = 5; //ok

    x++ = 6; //ERROR

赋值表达式:由赋值运算符连接操作数的式子,称之为赋值表达式。 赋值表达式的值,就是赋完值后左边那个操作数的值。

    x = 3  这个是一个赋值表达式,这个表达式的值就是最后x的值, 3
    y = x = 3
        这个是合法的赋值表达式
     =>
       y =  (x = 3)
       把表达式"x = 3"的值,赋值给y

    x = 2 + 3*y
        合法的.

复合的赋值运算符: 赋值运算符可以和算术运算符、位运算符组成复合的赋值运算符.

    +=   -=   *=   /=   %=
    <<=  >>=  |=   &=  ^=

    a += b
        => a = a + b

    x <<= 2
        => x = x << 2
    ...

3.6 条件运算符

? : 三目运算符 expressions ? a : b

先判断 expressions 的值, 如果它的值为真(非0), 则整个表达式的值就是a 如果它的值为假(0), 则整个表达式的值就是b

如:

    if (a > b)
    {
        x = 250;
    }
    else 
    {
        x = 360;
    }

    x = a > b ? 250 : 360;

    or 

    a > b ? (x = 250) : (x = 360) ;

3.7 逗号运算符

a , b 双目运算符,结合性从左至右,优先级是最低的

先算表达式a的值,然后再算表达式b的值,整个逗号表达式的值就是最右边表达式b的值。

表达式1,表达式2 例子:

a = 3 , 2
请问上面这个是什么东西呢?
> 上面这个是一个 逗号表达式


a = (3, 2)
这个是一个赋值表达式,把“表达式(3,2)”的值,赋值给a

a = 3, a = 2
> 这个是一个逗号表达式

a = 3, a = 2, a = 4
> 这个也是一个逗号表达式, => (a = 3, a = 2), a = 4

逗号表达式的形式可以是下面这样: 扩展的逗号表达式 表达式1, 表达式2, 表达式3, .... , 表达式n

求值顺序: 先求表达式1的值,然后再求表达式2的值, ..., 最后求表达式n的值 整个逗号表达式的值 就是最右边的那个表达式的值!!!!

3.8 指针运算符

  • 指向运算符 & 取地址符 先 wait wait, 后面有专题讲

3.9 求字节运算符

sizeof

3.10 分量运算符

. ->

3.11 下标运算符

[] 取数组的元素 int a[10]; a[0] a[1] ....

3.12 强制类型转换运算符

(类型)表达式 如: (int) 3.5 (int)(3.2 + 4.9) => 8 (int)3.2 + 4.9 => 7.9

3.13 其他

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值