1 运算符

什么运算符?

​ 运算符是用来表示某种运算(操作)的符号.

如 : + - * /

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

单目运算符 : 该运算符只需要一个操作数, 如 : ++ , -- &

双目运算符 : 该运算符需要两个操作数, 如 : + - * /

三目运算符 : 该运算符需要三个操作数, 如 : ? :

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

从左到右结合, 从右到左

举个例子 :

已知 + 结合性 "从左到右" 结合,在C语言中 :

a + b 和 b + a 含义是不一样的.

运算符的优先级

在含有多个运算符的表达式中,决定先算哪个运算符,后算哪个运算符的问题.

如 : a + b*c

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

2 表达式

什么是表达式?

表达式是表达某种意思的式子.

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

表示式的值

是一个表达式,就一定会有一个值,这个值就是表达式的值.

表达式最终需要表达某个意思,某个意思就是表达式的值.

int a = 3 + 5; 这是一条语句,表达式加上一个 ; 就是一条语句.

3 运算符分类

3.1 算数运算符

进行算数运算的运算符

  • ++ -- 单目运算符

  • + - * /  % 双目运算符
    

    / 只需要操作数是一个数(整数,也可以是小数) 就行

    % 求余数,要求两个操作数都必须为整数

    如 :

    3 + 4 => 7
    3 + 4.0 => 7.0
    4/2  => 2
    4/2.0 => 2.0
    3/2  =>1
    3/2.0 => 1.5
    
    3.5 % 2  error
    3 % 2  => 1
    -3 % 2 (超出知识范围)
    -3 % -2
        
    (double)(3/2) => 1.0
    (double)3/2  => 1.5
    用c语言的表达式 来描述数学表达式 a/b
        1.0*a/b
    

    ++ (自增运算符)

    自增(+1)运算符,单目运算符,要求操作数必须为一个左值而且是一个整数(整型变量)

    5++  //error
    (a + b)++ //error  表示式一定会有一个值
    
    int x;
    x++; //可以的
    ++x; //可以的
    
    char y;
    y++;  //可以的
    
    前置++ : ++ 在前面 如 : ++x
    后置++ : ++ 在后面 如 : x++   
    

    -- (自减运算符)

    自减(-1)运算符,单目运算符,要求操作数必须为一个左值而且是一个整数(整型变量)

    前置 -- : -- 在前面 ,如 : --x
    后置 -- : -- 在后面 ,如 : x--
    
    		     表达式的值       做完表达式后,i的值
    i++			i				i = i + 1						
    ++i		   i+1				i = i + 1	
    i--			i				i = i - 1
    --i		   i-1              i = i - 1
    

    例子 :

    int main()
    {
    int i = 5,j = 6;
    int a;
    a = (i++) + (i + j);
    printf("a = %d\n",a);//17
    
    return 0;
    }
    
    int main()
    {
    int i = 5,j = 6;
    int b;
    b = (++i) + (i + j);
    printf("b = %d\n",b);//18
    
    return 0;
    }
    

3.2 关系运算符

​ 用来判断两个东东的关系的运算符."关系" : 数值的大小关系

双目运算符, 结合性从左到右

< <= > >= 
== !=

关系表达式 : 用关系运算符连接起来的式子. 如 : a < b

关系表达式的值 :

关系成立 1

关系不成立 0

如 :
3 > 5 表达式的值 0
5 > 4 表示式的值 1
    5 > 4 > 3  表达式的值是 0 
    结合性从左到右
  ==> (5 > 4) > 3
  ==>  1 > 3
  ==>  0
    5 > 4 > 3 是一个合法的表达式,先拿表达式 5 > 4 的值 和 3进行比较
    在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 逻辑运算符

!	逻辑非		单目运算符	"取反"	
&&	逻辑与		双目运算符	"并且",从左到右	
||	逻辑或		双目运算符	"或者",从左到右
    逻辑表达式:用逻辑运算符(! && ||)连接起来的式子.
逻辑表达式的值:
	逻辑真 1(非0)
    逻辑假	0

例子 :

int a = 4,b = 5;
a && b ==> 1
a || b ==> 1
!a && b ==> 0 && b ==> 0

    5 > 3 && 8 < 4 - !0
    	5 > 3 && 8 < 4 - 1
    	5 > 3 && 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 语言运算符是 "惰性运算符"

(1) a && b && c

​ 只有a为真时,才需要判断b的值

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

(2) a || b || c

​ 只要a 为真 ,就不必判断b 和 c 的值了

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

总结 : 如果事先知道表达式的值,那么后面的运算符或表达式就不执行.这就是C语言运算符的"惰性''.

3.4 位运算符

​ 位运算符是按bit位展开来进行运算.意思是说,操作数展开成bit位,然后在进行运算.

位运算符要求操作数必须为整数(兼容的整数类型)

&	按位与
|	按位或
^	按位异或
~	按位取反
<<	按位左移
>>	按位右移  
	除了~(按位取反)是单目运算符,其他的都是双目运算符,结合性从左到右

(1) ~ 按位取反
1 -> 0
0 -> 1

~3 :
00000000 00000000 00000000 00000011
11111111 11111111 11111111 11111100  <-  ~3
   printf("%d\n",~3);//-4

==注意:==

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

!x ~x 两个都是单目运算符

~ 要求操作数是整数

! 对x无要求

(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 

#include <stdio.h>

int main()
{
    printf("%d\n",3&7);//3
    printf("%d\n",3&&7);//1
    
    int a = 3.5 & 3.7;//error,位运算符要去 操作数是 整数类型
    int b = 3.5 && 4.7;//可以的
    
    return 0;
}


==注意:==

按位与 & 和 逻辑的区别

练习 :

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

a = a & ~(1<<5)

结论 :

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

    x & 0 => 0

  • 一个bit 位 与 1 进行"按位与 &"操作 ,结果为 x

    x & 1 => x

(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

练习 :

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

a = a | (1 << 5)

结论 :

  • 一个bit位 与 1 "按位或 | "操作 ,结果 1

    x | 1 => 1

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

    x | 0 => x

(4) 按位异或 ^

"异或" : 求异, 不同为1, 相同为 0

a ^ b ,双目运算符 ,结合性从左到右

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

例子 :

3 ^ 6 = ? //5
00000000 00000000 00000000 00000011  3
00000000 00000000 00000000 00000110  6 
00000000 00000000 00000000 00000101  5

练习 :

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

a = a ^ ~(1 << 5)

结论 :

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

    x ^ 0 => x

    证明 :

    ​ when x = 1,1 ^ 0 = 1

    ​ when x = 0,0^0 = 0

  • 一个bit位与1进行"按位异或 ^ "操作 ,结果 ~x(取反)

    x ^ 1 => ~x

    证明 :

    ​ when x = 1,1^1 = 0

    ​ when x = 0, 0^1= 1

(5) 按位左移 <<

a << n 双目运算符,结合性从左到右, "把 a 按bit位整体左移 n 位"

高位左移后, 舍弃, 低位空出 n 位,直接补 0;

如果左移舍弃的高位都为 0, 那么左移 n 位, 就表示 在原值乘以 2 的 n 次方.

例子 :

int a = 8 << 2;
	00000000 000000000 00000000 0000 1000  <<2
 00000000 000000000 00000000 0010 0000

char a = (unsigned char)-1 <<2;
		
printf("%d\n",a);//-4    
printf("%u\n",a);//2^32 -4
 (unsigned char)-1 : 1111 1111
          		 a    : 1111 1100
        %d : a -> int 短的 --> 长的
        11111111 111111111 111111111 1111 1100  -4
        %u : a -> unsigned int  短的 --> 长的
        11111111 111111111 111111111 1111 1100  2^32 -4

练习 :

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

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

(6) 按位右移 >>

x >> n 双目运算符, 从左到右结合, "把x按bit为, 整体右移n位"

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

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

对于有符号数(x),高位全部补符号位.

练习 :

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

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

int main()
{
 unsigned int a = -1;
 	11111111 111111111 111111111 11111111
 a = a >> 31;
 	00000000 00000000 00000000 00000001
 printf("%d\n",a);//1
 printf("%u\n",a);//1
 
 return 0;
}

2.分析如下程序的输出结果, 假设int 32bits

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

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

(7) 掩码(mask)

掩码(mask) 是一个位模式,表示从一个字(word,32bits)中选出的位集合.

掩码中为 1 的那些bit位, 就是我们想要选取的bit位的集合.

如 :

如果我要选取的第0个bit 和 第1个bit, 则掩码为 : 0x3
	00000000 00000000 000000000 00000011
 
如果 mask = 0xff,表示我选取低8bits
 00000000 00000000 00000000 11111111

例子 :

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

​ 低8位的掩码 mask = 0xff

​ x & mask ==> x & 0xff ==> 0xAB

3.5 赋值运算符

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

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

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

如 :

5 = 5;//error
2 + 3 = 5;//error
int x = 1.y = 3;
x + y = 5;//error
x = 5;//可以的
x++ = 6;//error

赋值表达式 : 由赋值运算符连接操作数的式子, 称之为赋值表达式

赋值表达式的值,就是赋完值后左边的那个操作数的值.

如 :

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

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

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

sum <<=5
==> sum = sum <<5
 
sum -=i
==> sum  = sum - i
 

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

这是一个逗号表达式,他的指值为 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 指针运算符

* 指向运算符
& 取地址符
 后面在讲

3.9 求字节运算符

sizeof

3.10 分量运算符

.
->

3.11 下标运算符

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

3.12 强制类型转换运算符

(类型)表达式
如 :
(int)3.5
(int)(3.1 + 4.7)

3.13 其他

函数调用运算.

4 运算符的优先级和结合性

运算符						                                  结合性
() [] -> .                                                  从左向右
! ~ ++ -- +(正号) -(负号)  *(指针) &(取地址)  (type) sizeof    从右向左
* / %                                                       从左到右
+ 加 - 减													   从左到右
<< >>                                                       从左到右
<  <=   >  >=											    从左到右
== !=                                                       从左到右
& 按位与                                                     从左到右
^ 按位异或													 从左到右
| 按位或													  从左到右
&&															从左到右
||														    从左到右
? : 														从右向左
= += -=  *= /= %=  &=									    从右向左
^= |=  <<=  >>=                                             从右向左
,														    从左向右

通用办法 : ==如果不确定优先级,那就用 ( ) 来解决==