五、操作符详解

发布时间 2023-07-29 13:54:27作者: Arongsec

五、操作符详解

分类

  • 算数操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构成员

算数操作符

		+  -  *  /  %
int a = 5/2;
//a=2
double a = 5.0/2
//a=2.500000 默认打印六位小数
int a = 5%2;
//a=1 (注意:取模%只能用于整数!!)

移位操作符

右移操作符

除2

  1. 算术右移(主要)

​ 右边丢弃,左边补原符号位,原来是正数,补0;原来是负数,补1

  1. 逻辑右移

    右边丢弃,左边补0

int a = 16;
a >> 1;  // >> 右移操作符,移动的是二进制位
//16的二进制:    00000000000000000000000000010000
//右移1位:     (0)0000000000000000000000000001000

//如果是负数
int a = -1;
int b = a >> 1;
//此时移动的是补码  a的补码: 原码:10000000000000000000000000000001
						  反码:11111111111111111111111111111110//符号位不变,其他位按位取反
						  补码:11111111111111111111111111111111//反码+1
				 将
                              a的补码右移:右边丢一个1,左边补符号位,为1,补码还是全1

左移操作符

乘2

左移操作符:左边丢弃,右边补0

int a = 5;
int b = a << 1;
//       a:00000000000000000000000000000101
//	     b:00000000000000000000000000001010
// 		 b = 10;
//		 左移:左边丢弃,右边补0

位操作符

  • & 按位 —— 全都要1

  • | 按位 —— 至少有1个1

  • ^ 按位 异或 —— 相异为1,相同为0

    注意:它们的操作数必须是整数!!

例题

1. 不创建临时变量,交换两个数的值(此方法相比加减法来说不会溢出)

#include<stdio.h>
int main()
{
    int a=3,b=5;
	a=a^b;
	b=a^b;
	a=a^b;
	printf("a=%d,b=%d",a,b);
    return 0;
}

2. 统计数字补码中1的个数

// #include<stdio.h>
// int main()
// {
//     int num = 0;
//     int count = 0;
//     scanf("%d",&num);
//     while(num)
//     {
//         if(num%2 == 1)
//         count++;
//         num = num/2; 
//     }
//     printf("count=%d",count);
//     return 0;
// }  
                        //无法计算负数
#include<stdio.h>
int main()
{
    int num = 0;
    int count = 0;
    scanf("%d",&num);

    int i = 0;
    for(i=0;i<32;i++)
    {
        if(1==((num >> i) & 1)) //此处num右移发生在if的判断条件内,不改变num的实际值,所以每次位移i;
                                //如果将num的右移写在if外,则会改变num的值,则每次只能右移1位;但对于一开始末尾为1的数字来说,一开始就右移1,会导致结果少算了1个
            					//num & 1的意思即为num和00000000000000000000000000000001做与操作
        {
            count++;
        }
    }
    printf("count=%d",count);
    return 0;
}

赋值操作符

变量创建时给值叫初始化;

变量已经有值了再给值叫赋值;

赋值操作符可以 连续使用,即连续赋值

复合赋值符

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

单目操作符

!		 逻辑反
-   	 负值
+		 正值
&		 取地址
sizeof   操作数的类型长度(以字节为单位)
~		 二进制按位取反
--		 前置、后置--
++		 前置、后置++
* 		 解引用
(类型) 	强制类型转换

注:

  • sizeof内部的表达式并不实际参与运算

关系操作符

逻辑操作符

&& 逻辑与
|| 逻辑或
关注的是数字本身为真/假,而不是关注二进制

&&左边如果为假,则右边的都不执行

int i=0,a=0,b=1,c=2;
i = a++ && ++b && ++c;
//结果: a=1,b=1,c=2
//原因:a++时先使用a,a此时为0;&&前为0,则后面所有的都不执行;但a++执行时还是要对a+1,则a = 1;

||左边为真,则右边也不执行

int i=0,a=1,b=2,c=3;
i = a++||++b||c++;
//结果:a=2,b=2,c=3

条件操作符(三目操作符)

exp1 ? exp2 : exp3

逗号表达式

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果

下标引用、函数调用和结构成员

下标引用操作符

[ ]

操作数:一个数组名+一个索引值

函数调用操作符

调用函数的时候使用的()

操作数:函数名(+参数)

结构成员

符号: 结构体变量.成员名

结构体指针 -> 成员名

使用:

方法一

方法二

方法三

表达式求值

表达式求值的顺序一部分是由操作符的优先级结合性决定的,同样,有些表达式的操作数在求值的过程中需要转换为其他类型

隐式类型转换

C的整型算术运算总是以缺省整型类型的精度来进行的

整型提升

表达式中的字符短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升是按照变量的数据类型的符号位来提升的(若是无符号类型,直接补0)

#include <stdio.h>
int main()
{
char a = 3;
// a本来的值: 00000000000000000000000000000011
//char类型只有1个字节8位,发生截断,实际a的值则为截断后最末尾的8位: 00000011
char b = 127;
//b本来的值:000000000000000000000000011111111
//截断后b的实际值为: 011111111
char c = a+b;
//a+b的过程发生了整型提升
// char类型一般都是有符号位的,则对a来说,00000011的符号位为0,则将a整型化的时候前面都补0,即补为00000000000000000000000000000011
//对b来说,011111111,符号位为0,则将b整型化前面全部补0,即为000000000000000000000000011111111
//再执行c=a+b, 00000000000000000000000000000011+000000000000000000000000011111111=00000000000000000000000010000010
//c再进行截断,转为char类型 c=10000010
printf ( "%d" ,c);
//打印c,先对c进行整型提升,c的符号位为1,则前面全部补1,为11111111111111111111111110000010
//打印的是变量的原码,存储的是变量的补码
//11111111111111111111111110000010 - 补码
//11111111111111111111111111111101 - 反码
//10000000000000000000000000000010 - 原码
//原码 = -126
return 0;
}

例子2

#include <stdio.h>
int main()
{
    char a = 0xb6;
    if(a == 0xb6)
        printf("a");
    else
        printf("no!");
    return 0;
}
/*  1.定义变量 char a = 0xb6;
    在这一步,我们将一个十六进制常量 0xb6 赋值给 char 类型的变量 a。0xb6 是一个八位的十六进制数,它的二进制表示为:10110110。

    2.表达式 a == 0xb6 中的整型提升
    在进行比较运算时,a 会被提升为 int 类型。整型提升将 char 类型的 a 扩展为 int 类型,并使用符号扩展。
    扩展后的 a 变为:11111111 11111111 11111111 10110110

    3.0xb6 的二进制表示不变
    在这里,0xb6 本就是一个常量,因此其二进制表示不会发生改变,仍然是:00000000 00000000 00000000 10110110

    4.进行比较运算 a == 0xb6
    现在,我们有两个相同大小的整数,一个是 a 经过整型提升后的结果,另一个是常量 0xb6 的二进制表示。
    此时进行比较运算 a == 0xb6,实际上比较的是两个整数的值。因为 a 的值与 0xb6 不相等,所以条件不成立,printf("a") 不会被执行。*/

只要参与表达运算,就会发生整型提升,如加减乘除等

例子3

#include<stdio.h>
int main()
{
    char c = 1;
    printf("%u ",sizeof(c));
    printf("%u ",sizeof(+c));
    return 0;
}
//结果为1 4
//c没有进行运算,不用整型提升,char类型占1个字节
//+c进行了运算,需要整型提升,即变为了整型,占4的字节
算术转换

如果某个操作符的操作数属于不同的类型,那么除非其中一个操作数能转换为另一个操作数的类型,否则操作就无法进行。

寻常算术转换

层次体系:

long double
double
float
unsigned long int
long int
unsigned int
int

默认下转上

操作符的属性

优先级

1,优先级1级
结合方向 左结合(自左至右) :()圆括号 , []下标运算符 , ->指向结构成员运算符 , .结构体成员运算符 。

2,优先级2级
结合方向 右结合(自右向左) : !逻辑非运算符 , ~按位取反运算符 , ++自增运算符 , --自减运算符 ,-负号运算符 ,类型转换运算符 , *指针运算符 , &地址与运算符 , sizeof长度运算符。

3,优先级3级
结合方向 左结合 双目运算符 : *乘法运算符 ,/除法运算符 , %取余运算符 。

4,优先级4级
结合方向 左结合 双目运算符 :+加法运算符 , -减法运算符。

5,优先级5级
结合方向 左结合 双目运算符:<<左移运算符 ,>>右移运算符 。

6,优先级6级
结合方向 左结合 双目运算符:< , <=,> , >=关系运算符。

7,优先级7级
结合方向 左结合 双目运算符:==等于运算符(判断) , !=不等于运算符(判断)。

8,优先级8级
结合方向 左结合 双目运算符:&按位与运算符。

9,优先级9级
结合方向 左结合 双面运算符:^按位异或运算符。

10,优先级10级
结合方向 左结合 双目运算符:|按位或运算符。

11,优先级11级
结合方向 左结合 双目运算符:&&逻辑与运算符。

12,优先级12级
结合方向 左结合 双目运算符:||逻辑或运算符。

13,优先级13级
结合方向 右结合 三目运算符:?:条件运算符。

14,优先级14级
结合方向 右结合 双目运算符:=赋值运算符 , +=加后复值运算符 ,-=减后赋值运算符 ,
*=乘后赋值运算符 , /=除后赋值运算符 ,%=取模后赋值运算符 , <<=左移后赋值运算符 ,
>>=右移后赋值运算符 ,&=按位与后赋值运算符 , ^=按位异或后赋值运算符 ,
|=按位或后赋值运算符。

15,优先级15级
结合方向 左结合 :,逗号运算符。

结合性

是否控制求值顺序

有问题的表达式:

a*b+c*d+e*f
只能保证*比各自相邻的+号先执行,但无法保证第一个+和第三个*谁先执行
c + --c;
只能保证先--再+ 但无法确定左边的c的取值是在--c之前取得还是--c之后取得

表达式必须要有唯一的计算路径