C语言操作符

C语言操作符

前言

本人的技术笔记会优先发布在个人站点longyxblg.cn

操作符分类

原码、反码、补码

整数的2进制表示方法有三种,即原码、反码、补码。

有符号的三种表示方法均有符号位和数值位两部分组成。

首位被当作符号位,剩下的均为数值位。

符号位用 0 表示正,用 1 表示负。

正整数的原码、反码、补码都相同。

负整数的三种表示方法各不相同。

  • 原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。

  • 反码:原码的数值位取反,符号位不变。( 0 变 1 ,1 变 0)

  • 补码:反码 + 1。

例如:
对于整型来说,其实存放在内存中的就是他的补码。

inta=-10;// 原码就是 a 的二进制表示// 一个整型数在内存中占 4 个字节(32 位),一个字节由8个 bit 位组成,所以 a 的二进制表示为:// 10000000 00000000 00000000 00001010 就是原码// 11111111 11111111 11111111 11110101 就是 a 的反码// 11111111 11111111 11111111 11110110 就是 a 的补码

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

移位操作符

全程操作的都是数据的补码。

左移位操作符

移位规则:左边抛弃,右边补0

inta=10;// 00000000 00000000 00000000 00001010intnum=a<<1;// 左移一位,相当于乘以 2// 00000000 00000000 00000000 00010100printf("%d\n",num);

inta=-10;// 10000000 00000000 00000000 00001010 就是 a 的原码// 11111111 11111111 11111111 11110101 就是 a 的反码// 11111111 11111111 11111111 11110110 就是 a 的补码intnum=a<<1;// 11111111 11111111 11111111 11101100 就是 a 左移一位后的结果,这是补码// 11111111 11111111 11111111 11101011 就是 a 左移一位后的反码// 10000000 00000000 00000000 00010100 就是 a 左移一位后的原码

右移位操作符

右移位操作符分两种情况:逻辑右移和算术右移

逻辑移位规则:右边抛弃,左边补0

inta=10;// 00000000 00000000 00000000 00001010intnum=a>>1;// 右移一位,相当于除以 2// 00000000 00000000 00000000 00000101printf("%d\n",num);

算术移位规则:右边抛弃,左边用概该数的符号位填充

inta=-10;// 10000000 00000000 00000000 00001010 就是 a 的原码// 11111111 11111111 11111111 11110101 就是 a 的反码// 11111111 11111111 11111111 11110110 就是 a 的补码intnum=a>>1;// 11111111 11111111 11111111 11111011 就是 a 右移一位后的结果// 11111111 11111111 11111111 11111010 就是 a 右移一位后的反码// 10000000 00000000 00000000 00000101 就是 a 右移一位后的补码printf("%d\n",num);


对于移位操作符,不能移动负数位,这个标准是未定义的。

位操作符

&// 按位与|// 按位或^// 按位异或!// 按位取反

注:他们操作的必须是整数。

& 是双目操作符,对两个数的补码进行操作,都为1是1,有0就是0

| 是双目操作符,对两个数的补码进行操作,有1就是1,都是0位0

^ 是双目操作符,对两个数的补码进行操作,位相同为0,不同为1

int num1 = 10; // 00000000 00000000 00000000 00001010 就是 num1 的二进制表示 int num2 = 20; // 00000000 00000000 00000000 00010100 就是 num2 的二进制表示 printf("%d\n", num1 & num2); // 都是1才是1,否则为0 // 00000000 00000000 00000000 00001010 // 00000000 00000000 00000000 00010100 // 00000000 00000000 00000000 00000000 printf("%d\n", num1 | num2); // 有1为1,否则为0 // 00000000 00000000 00000000 00001010 // 00000000 00000000 00000000 00010100 // 00000000 00000000 00000000 00010110 printf("%d\n", num1 ^ num2); // 相同为0,不同为1 // 00000000 00000000 00000000 00001010 // 00000000 00000000 00000000 00010100 // 00000000 00000000 00000000 00010110 printf("%d\n", ~ num1); // 按位取反 // 1取0,0取1 // 00000000 00000000 00000000 00001010 // 11111111 11111111 11111111 11110101 就是 num1 的按位取反结果,这是补码 // 11111111 11111111 11111111 11110100 这是反码 // 10000000 00000000 00000000 00001011 这是原码

使用位操作符实现交换两变量的值

假设现在有一变量 x ,那么 x 一定满足 x ^ x = 0, 0 ^ x = x , 对于变量 a,b ,满足 (a ^ b) ^ c = a ^ (b ^ c),即可推导出

inta=0;intb=0;scanf("%d %d",&a,&b);a=a^b;b=a^b;a=a^b;printf("a = %d b = %d",a,b);

求一个整数存储在内存中的二进制中的 1 的个数

对于 1 来说,他与任意一个整数进行 & 运算只会出现 0 和 1 两种结果。

00000000000000000000000000000001// 1在内存中的存储

那么就可以这么设计

// 计算一个整数的二进制表示中有多少个 1intnum=0;intcount=0;scanf("%d",&num);for(inti=0;i<32;i++){// 1与任意一个整数进行按位与运算,如果该整数的二进制表示中对应位是 1,那么结果就是 1,否则就是 0if(1&num>>i){count++;// 说明 num 的二进制表示中第 i 位是 1,那么就把 count 加 1}}printf("有 %d 个 1\n",count);

还有一种效率更高的算法,不需要循环32次

intnum=-1;intcount=0;while(num){count++;num=num&(num-1);// 这个操作会把 num 的二进制表示中最后一个 1 变成 0}printf("有 %d 个 1\n",count);

这个算法利用的就是每次 num & num - 1 会将这个数放在末尾的1消掉。

直到这个数变成0为止,统计这个过程执行了多少次,就知道 num 的二进制表示中有多少个 1 了。


注:只有在补码存储下该公式成立

逗号表达式

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

inta=3;intb=4;intc=(a+b,a*b,a-b);printf("%d\n",c);

这儿有一串代码

a=test1();test2();while(a>0){// 业务处理test2();}

那么根据逗号表达式的计算,还可以这么写

while(a=test1(),test2(),a>0){// 业务处理test2();}

标访问[]和函数调用()

这俩没啥好说的

intarr[10];//创建数组 arr[9]=10;//使用下标引用操作符。[]的两个操作数是arr和9
voidtest1(){printf("hehe\n");}voidtest2(constchar*str){printf("%s\n",str);}intmain(){test1();//这里的()就是作为函数调用操作符。test2("hello world.");//这里的()就是函数调用操作符。return0;}

结构体访问操作符

这一块儿放到结构体。

表达式求值

整型提升

C语言中整型算术运算总是至少以缺省整型类型的精度来进行的。

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

chara=3;charb=127;charc=a+b;printf("%d ",c);

这里 a 和 b 在进行相加之前,就会先提升为普通整型,再执行加法计算。计算完成后,结果在被截断后存放进 c 中。

整型提升的规则是

  • 有符号整数提升是按照变量的数据类型的符号位来提升的

  • 无符号整数提升,高位补0

charc=a+b;// a: 00000000 00000000 00000000 00000011// b: 00000000 00000000 00000000 01111111// a + b: 00000000 00000000 00000000 10000010// 存到 c 中时,c 只有1个字节,所以只保留最后8位// c: 10000010 这是补码// 而 %d 输出的是有符号整数// 所以C又会发生整型提升,c会被提升成int类型// 适用有符号整数提升是按照变量的数据类型的符号位来提升的// c: 11111111 11111111 11111111 10000010 这是补码// 11111111 11111111 11111111 10000001 这是反码// 10000000 00000000 00000000 01111110 这是原码

所以最后输出的是 -126

算术转换

如果某个操作符的各个操作数属于不同的类型,那么需将其中一个操作数的类型转换为另一个操作数的类型。

如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外一个操作数的类型后执行运算。