十一、操作符详解

1 二进制

• 2进制的数字每一位都是由0~1的数字组成

• 2进制中满2进1

1.1.1 2进制转10进制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVHIZ1Bb-1691935886397)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805090710096.png)]

1.1.2 10进制转2进制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6UOelWp-1691935886397)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805090952998.png)]

1.2.1 2进制转8进制

8进制的每一位数字都是0~7,而数字0~7各自写成2进制最多有3个2进制位就足够了,所以2进制转8进制时,从2进制序列中右边低位开始向左每3个2进制位就换算成一个8进制位,剩余不够3个2进制位的直接换算。

如:2进制数01101011,换算成8进制就为0153。

8进制在C语言中表示的时候,数字前会加0。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnQesNZ9-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805091652436.png)]

1.2.2 2进制转16进制

16进制的每一位数字都是0~9、A~F,各自写成2进制,最多有4个2进制位就足够了,所以2进制转16进制时,从2进制序列中右边低位开始向左每4个2进制位就换算成一个16进制位,剩余不够4个2进制位的直接换算。

如:2进制数01101011,换算成16进制就为0x6b。

16进制在C语言中表示的时候,数字前会加0x。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PVAHAsPV-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805092347735.png)]

2 原码、反码、补码

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

三种表示方法均有符号位和数字位两部分,符号位用“0”表示“正”,用“1”表示“负”。

C语言中,一个整形数的最高位是符号位,其余都是数字位。

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

负整数的原码:直接将数值按照正负数的形式转换成2进制得到即为原码。

负整数的反码:原码的符号位不变,数字位按位取反即得反码。

负整数的补码:反码加1即得补码。

对于整形来说,数据存放在内存中其实存放的是补码。

因为使用补码,可以将符号位和数值位统一处理,同时,加法和减法也可以统一处理(CPU中只有加法器)。

此外,补码与原码相互转换,其运算过程相同,不需要额外的硬件电路。

3 移位操作符

<< 左移操作符

>> 右移操作符

注:移位操作符的操作数只能是整数,不能是小数。

3.1 左移操作符

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

#include <stdio.h>
int main()
{
	int a = 10;
	int b = a << 1;
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-goTWGLgw-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805102120696.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-33GgUIjM-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805093711655.png)]

3.2 右移操作符

1.逻辑右移:左边补0,右边抛弃。

2.算术右移:左边用原该值的符号位填充,右边抛弃。

到底采用哪种右移,是不确定的,取决于编译器,大部分编译器采用算术右移。

#include <stdio.h>
int main()
{
	int a = -1;
	int b = a >> 1;
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-alpEmuzc-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805103304288.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5R7LsD1v-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805094200695.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ajfG1vL-1691935886398)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805094217716.png)]

注意:对于移位操作符,不能移动负数位,否则会报错。

例:

int num = 10;
num>>-1;//error

4 位操作符

& //按位与
| //按位或
^ //按位异或
~ //按位取反
注:它们的操作数必须是整数,不能是小数。

4.1 按位与

#include <stdio.h>
int main()
{
	int a = 5;
	int b = -6;
	int c = a & b;
	//00000000000000000000000000000101   5的补码
	//11111111111111111111111111111010	-6的补码
	//按位与:对应的二进制位同时为1则为1,有0则为0
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	printf("c=%d\n", c);
	return 0;
}

输出结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A89yuBNO-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805104336099.png)]

应用:

1.如果想看某个二进制数的最低位是几,可以采用 a&1 的操作。

2.如果想看某个二进制数从右往左数的第n位是几,可以先 a>>n-1 ,再 a&1。

例:

#include <stdio.h>
int main()
{
	int a = 0;
	int n = 0;
	printf("请输入一个数以及你想观察的位数:");
    scanf("%d %d", &a, &n);
	printf("%d二进制形式下从右往左数的第%d位是%d\n", a, n, a >> n - 1 & 1);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5fei8NSA-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805175251517.png)]

4.2 按位或

#include <stdio.h>
int main()
{
	int a = 5;
	int b = -6;
	int c = a | b;
	//00000000000000000000000000000101   5的补码
	//11111111111111111111111111111010	-6的补码
	//按位或:对应的二进制位同时为0则为0,有1则为1
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	printf("c=%d\n", c);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FZ8KZnJj-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805104521028.png)]

应用:将指定位变为1。

例:

#include <stdio.h>
int main()
{
	int a = 0;
	int n = 0;
	printf("输入一个数,以及你想让它的第几位变为1?\n");
	scanf("%d %d", &a, &n);
	a = a | (1 << n-1);//将a的二进制中第n位改成1
	printf("这个数的第%d位变为1后产生的数为:%d", n, a);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jA7UcU2E-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805175135760.png)]

4.3 按位异或

#include <stdio.h>
int main()
{
	int a = 5;
	int b = -6;
	int c = a ^ b;
	//00000000000000000000000000000101   5的补码
	//11111111111111111111111111111010	-6的补码
	//按位异或:对应的二进制位相同则为0,相异则为1
	printf("a=%d\n", a);
	printf("b=%d\n", b);
	printf("c=%d\n", c);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CReuveba-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805104641093.png)]

注意:异或是支持交换律的!

例:

3^3^5=5
3^5^3=5

面试题:不创建临时变量(第三个变量),实现两个数的交换。
方法一:

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d,b=%d\n", a, b);
	a = a + b;//a加b的和暂时放在a里面去
	b = a - b;//实际上为a加b减b
	a = a - b;//实际上是a加b减a
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

缺陷:a和b两个数字过大的时候容易导致越界。

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNZLZDS3-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805164541035.png)]

方法二:

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d,b=%d\n", a, b);
	a = a ^ b;
	b = a ^ b;//相当于a^b^b,而b^b=0,所以a^b^b=a^0=a
	a = a ^ b;//相当于a^b^a,而由异或运算支持交换律得到a^b^a=a^a^b=b
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

这种异或操作具有局限性:
1.只能作用于整数交换;
2.代码的可读性差;
3.执行的效率低于采用临时变量的方法。

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QCw6F3F3-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805112108315.png)]

4.4 按位取反

#include <stdio.h>
int main()
{
	int n = 0;
	int a = ~n;
	//00000000000000000000000000000000
	//按位取反:对应的二进制位是0的则为1,是1的则为0
	printf("%d\n", a);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-674T8vaJ-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805173943966.png)]

应用:将指定位变0。

例:

#include <stdio.h>
int main()
{
	int a = 0;
	int n = 0;
	printf("输入一个数,以及你想让它的第几位变为0?\n");
	scanf("%d %d", &a, &n);
	a = a & ~(1 << n - 1);//将a的二进制中第n位改成0
	printf("这个数的第%d位变为0后产生的数为:%d", n, a);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qztQPXYI-1691935886399)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805175953426.png)]

4.5 练习

谷歌面试题:求一个整数以2进制形式存储在内存中时1的个数。

方法一:

分析:在10进制中,我们采用模1010的办法来得到10进制数中的每一位数。那么推而广之,对于2进制数,可以采用模22的办法得到2进制数中的每一位数。
#include <stdio.h>
int main()
{
	int a = 0;
	int count = 0;//计数
	scanf("%d", &a);
	while (a)
	{
		if (a % 2 == 1)//判断当前最低位是否为1
			count++;
		a = a / 2;//将a去掉一位,再赋给a
	}
	printf("这个数在二进制形式中1的个数 = %d\n", count);
	return 0;
}
存在缺陷,因为当输入的数为负数时,计算会出错

方法二:

#include <stdio.h>
int main()
{
	int a = 0;
	int count = 0;//计数
	scanf("%d", &a);
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if (((a >> i) & 1) == 1)//判断右移i位后当前最低位是否为1
		{
			count++;
		}
	}
	printf("这个数在二进制形式中1的个数 = %d\n", count);
	return 0;
}

方法三:

#include <stdio.h>
int main()
{
	int a = 0;
	int count = 0;//计数
	scanf("%d", &a);
	while (a)
	{
		count++;
		a = a & (a - 1);//只要执行1次,a的二进制序列中,最右边的1就消失了,能执行几次就说明有几个1
	}
	printf("这个数在二进制形式中1的个数 = %d\n", count);
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRDEBlqu-1691935886400)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805172439662.png)]

练习:写一个代码,判断一个数是否是2的n次方

分析:2^n这个数的二进制形式中只有一个1,如果去掉一个1后二进制中1的个数为0,则说明这个数是2的n次方
#include <stdio.h>
int main()
{
	int n = 0;
	while (scanf("%d", &n) != EOF)
	{
		if ((n & (n - 1)) == 0)//去掉一个1后判断二进制中1的个数是否为0
		{
			printf("yes\n");
		}
		else
			printf("no\n");
	}
	return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pBHBvcCx-1691935886400)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230805173521572.png)]

5 逗号表达式

exp1, exp2, exp3, … ,expN

逗号表达式,就是用逗号隔开的多个表达式,是优先级最低的操作符。

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

//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?

//代码2
if (a =b + 1, c=a / 2, d > 0)

//代码3
a = get_val();
count_val(a);
while (a > 0)
{
 //业务处理
 a = get_val();
 count_val(a);
}
如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
 //业务处理
}

6 下标引用[]和函数调用()操作符

6.1 []下标引用操作符

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

int arr[10];//创建数组。
arr[9] = 10;//[ ]的两个操作数是arr和9。

6.2 ()函数调用操作符

函数调用操作符接受一个或者多个操作数,第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

函数调用操作符至少有一个操作数,即操作数中至少要有函数名。

#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int ret = Add(2, 3);//函数调用操作符
    int n = sizeof(ret);//注意:sizeof是一个操作符而不是函数,因为它后面的括号其实可以省略。
	printf("%d\n", ret);
	return 0;
}

7 操作符的属性

7.1 优先级

优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级有所不同。

例:

3 + 4 * 5;

上面示例中,表达式 3 + 4 * 5 里面既有加法运算符(+),又有乘法运算符(*)。由于乘法的优先级高于加法,所以会先计算 4 * 5 ,而不是先计算 3 + 4。

7.2 结合性

如果两个运算符的优先级相同,优先级没办法确定计算顺序时,这时候就要靠结合性来进行确定。

结合性指的是,根据运算符是左结合,还是右结合来决定执行顺序。大部分运算符是左结合(从左向右执行),少数操作符是右结合(从右向左执行),比如赋值运算符(=)。

常见操作符的优先级:

• 圆括号( () )
• 自增运算符( ++ ),自减运算符( -- )
• 一元运算符( +- )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( <> 等)
• 赋值运算符( =

由于圆括号的优先级最高,所以可以使用它来改变其他运算符的优先级。

部分运算符的优先级:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cd0Ldiii-1691935886400)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230808093152738.png)]

8 表达式求值

表达式求值之前首先要进行类型转换,当表达式中的值转换到适当的类型时,才开始计算。

两种类型转换:

1.整型提升

2.算术转换

8.1 整形提升

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

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

整型提升的原因:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通⽤寄存器的⻓度。
因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准长度。
通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)的。所以,表达式中各种长度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执行运算。

整型提升的方式:

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

2.无符号整数提升,高位补0。

例1:

//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111

//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001

//无符号整形提升,⾼位补0

例2:

#include <stdio.h>
int main()
{
	char a = 5;
	//5 —— int  —— 4个字节 —— 32bit
	//a —— char —— 1个字节 —— 8bit
	//00000000000000000000000000000101 —— 5的补码
	//5要存储到a中,需要截断,所以存在a中的实际上为:00000101
	char b = 127;
	//127 —— int  —— 4个字节 —— 32bit
	//b   —— char —— 1个字节 —— 8bit
	//00000000000000000000000001111111 —— 127的补码
	//127要存储到b中,需要截断,所以存在a中的实际上为:01111111
	char c = a + b;//a和b都为有符号的char类型,如要相加就会发生整型提升,且两个符号位为0,所以高位补0
	//a   —— 00000000000000000000000000000101
	//b   —— 00000000000000000000000001111111
	//a+b —— 00000000000000000000000010000100
	//a+b要存储到c中,需要截断,所以存在c中的实际上为:10000100
	printf("%d\n", c);//%d是按照十进制的形式打印有符号的整数,
					  //而c为有符号的char类型,如要打印也会发生整型提升,且c的符号位为1,所以高位补1
	//c   —— 11111111111111111111111110000100 —— 补码
	//负数取反加一得原码 —— 10000000000000000000000001111100 —— -124的原码
	return 0;
}

8.2 算术转换

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

下面的层次体系称为寻常算术转换。

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

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

8.3 问题表达式解析

8.3.1 表达式1

//表达式的求值部分由操作符的优先级决定。
a*b + c*d + e*f

表达式1在计算的时候,由于 * 比 + 的优先级高,只能保证 * 的计算比 + 要早,但是优先级并不能决定第三个 * 比第一个 + 早执行。

所以表达式的计算顺序就可以能是:

a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f

或者

a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f

所以仅通过操作符的属性,无法确定该表达式的唯一计算路径。

8.3.2 表达式2

c + --c;

同上,操作符的优先级只能决定 – 运算在 + 运算的前面,但是我们没办法确定左边的c是 – 之前的值还是 --之后的值,所以结果是不可预测的,会有歧义。

8.3.3 表达式3

int main()
{
 	int i = 10;
 	i = i-- - --i * ( i = -3 ) * i++ + ++i;
 	printf("i = %d\n", i);
 	return 0;
}

表达式3在不同编译器中测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZK2by461-1691935886400)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230808103439831.png)]

8.3.4 表达式4

#include <sdtio.h>
int fun()
{
	static int count = 1;
	return ++count;
}
int main()
{
	int answer;
	answer = fun() - fun() * fun();
	printf("%d\n", answer);
	return 0;
}

上述代码 answer = fun() - fun() * fun() 中,从操作符的优先级得知:先算乘法,再算减法。

但是函数调用的先后顺序无法通过操作符的优先级来确定,所以这个代码也存在问题。

8.3.5 表达式5

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

gcc编译器执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FS2LtKNo-1691935886400)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230808104245069.png)]

VS2019执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gtyx6ZIA-1691935886400)(C:\Users\HackerKevin\AppData\Roaming\Typora\typora-user-images\image-20230808105630282.png)]

同样的代码产生了不同的结果,这是因为依靠操作符的优先级和结合性无法决定代码中的第一个 + 和第三个前置 ++ 的先后顺序,所以第一个 + 在执行的时候,第三个 ++ 是否执行是不确定的。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HackerKevn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值