硅基计划 学习总结 玖 操作符


一、分类

算数、移位(<<,>>)、位(&,|,^)、赋值、单目、关系逻辑、条件、逗号表达式、下标引用、函数调用、结构成员(. , →,~,^)←为指针做准备

二、二进制与进制转化

1.二进制的每一位都有位权,比如 1 1 1 1,从右到左分别对应2的0次方,2的一次方,2的平方,2的立方,我们把计算结果加起来就是我们得到的十进制数,结果是15

2.这跟我们十进制下的123同理,从右到左对应的是10的0次方,10的一次方,10的平方

3.八进制也类似,但是十六进制超过9,我们用字母代替,为什么?因为加入你用数字,会产生一个两位数,就有歧义,究竟是10还是1和0,因此我们在十六进制下超过10的数字,我们用A~F(也可以写成小写)代替

4.十进制转二进制:一个数每次除上2,取余数,最后除到0,我们再倒着写出它的余数,就是这个数的二进制数

比如35→100011,当然如果你熟悉二进制各个位权,你可以把35拆分成32,2,1分别对于2的四次方,2的一次方,2的零次方,其他我们就写作0

5.对于二进制转八进制,我么有个方法:把二进制数列出来,从最右边开始,每次往左边取三个数,途中结果就是0153,加上0是为了让编译器认为这是八进制数。我们十六进制转二进制可以先把各个部分拿出来,再转化拼凑即可。

6.二进制转十六进制:类似的我们从最右边开始取,向左每次取4位数

 还有一些其他的进制转化,都类似上面这些原理,感兴趣的可以自行探索

三、原码,反码,补码(针对二进制整数)

##对于有符号整数的表示方法有符号位(左边第一个数,0代表正,1代表负)和数值位,而无符号整数其数字全为符号位

##正整数和无符号整数的原码,反码,补码相同,而负整数就都不同

1.原码:数值按正负数形式写成的二进制编码

2.反码:原码符号位(左边第一个)不变,其他位取反

3.补码:犯法二进制序列加上1(加到最右边,即最小位

****对整型数据来说****

数据存储在内存中的为补码,因为这样可以将符号位与数值统一处理

因为CPU默认计算加法(假设你用原码算,算出来的减法都是错的),这样对加减法比较友好,补码和反码相互转化就不需要额外硬件了

当然,打印在你屏幕上的是按照原码打印的

四、移位

1.原理:因为数据存在内存中的形式是按照补码的形式存储的,移动的是你的二进制位,而且操作数只能是整数,不能是负数,不能移动超过自身的二进制位数

2.左移(<<):左边丢弃(最左边开始算),右边补0(最右边开始算)

例如假设一个变量a它等于6,假设左移1位,我们写出其二进制编码,根据原理

我们在根据移动后的补码推出其原码是:0000 0000 0000 0000 0000 0000 0000 0110 打印出12

####我们可以总结出一个规律,如果类似这样左边移去1个0,有类似于乘2效果,两个则乘4.以此类推;如果你左边移去的是1,正负数可能发生变化,这个我们就另当别论了

//1.左移:左边丢弃,右边补上0
int main()
{
	int a = 6;
	//a的二进制数是00000000000000000000000000000110(正整数原码反码补码相同),而左移移位,左边第一个0被丢弃,而最右边再补上一个0
	//00000000000000000000000000001100(正整数原反补相同),你会发现‘1’的权重发生了变化,b的值是a的值的两倍,我们再把它打印出来,发现是12
	int b = (a << 1);//倘若把这里改成移动2位,你会发现变成了6的三倍,也就是24,那我们来总结下,左移过程中,如果左边移动的是0,有类似乘2效果,如果是1就不一定
	printf("%d", b);
 return 0;
}

3.右移(>>):这个比较复杂,因为你最左边代表的是符号位,你移去之后究竟要放什么,这个取决于编译器,因此我们有两种,而我们的VS2022编译器采用的是算术右移

逻辑右移(左边0填充,右边丢弃),算数右移(左边用原数符号位填充,右边丢弃)

//2.右移分为逻辑右移(左边用0填充,右边丢弃)和算数右移(左边用该数的符号位填充,右边丢弃)
int main()
{
	int a = -2;
	int b = (a >> 1);//通过打印结果知道我们VS编译器采用的是算数去反,这样负数才不会可能变成正数
	printf("%d", b);
	return 0;
}


五、位操作符(操作数为整数)

##分类:按位与“&”,按位或“|”,按位异或“^”,按位取反“~”,这些操作符依然面向二进制编码

1.按位与(&):我们先来举个如代码中的例子,我们接着来看原理

//1.按位“与”,原理与逻辑“与”类似,两个二进制补码同时为1才为1,其它情况为0,而且两个补码位数上下一一对应
int main()
{
	int a = 3;//我们先写出a的补码:00000000000000000000000000000011
	int b = -5;//再把b的补码写出来11111111111111111111111011
	int c = a & b;
 printf("%d", c);
	return 0;//通过原理我们得到,00000000000000000000000000000011,正数原反补相同,我们打印结构就是3
}

####原理:两个二进制数对应的二进制位进行比较,两个同时为1才为1,其他情况都为0,下面一个二进制数是-5的补码(原码先求出来再求补码)

因此我们按位与之后的结果就是0000 0000 0000 0000 0000 0000 0000 0011,正数的原码反码补码相同,我们打印的就是数字3

//1.按位“与”,原理与逻辑“与”类似,两个二进制补码同时为1才为1,其它情况为0,而且两个补码位数上下一一对应
int main()
{
	int a = 3;//我们先写出a的补码:00000000000000000000000000000011
	int b = -5;//再把b的补码写出来11111111111111111111111011
	int c = a & b;
 printf("%d", c);
	return 0;//通过原理我们得到,00000000000000000000000000000011,正数原反补相同,我们打印结构就是3
}

2.按位或(|):其原理就是两个二进制位数同时为0才为0,其它情况都为1

按照按位或之后的结果就是11111111111111111111111111111011,这个是补码,我们再求得其原码是10000000000000000000000000000101,打印的结果是-5

//2.按位“或”,原理与逻辑“或”类似,只有两个二进制补码同时为0才为0,其它情况都为1,而且两个补码位数上下一一对应
int main()
{
	int a = 3;//我们先写出a的补码:00000000000000000000000000000011
	int b = -5;//再把b的补码写出来11111111111111111111111011
	int c = a | b;
	printf("%d", c);//通过原理我们得到,11111111111111111111111111111011,正数原反补相同,我们打印结构就是-5
	return 0;
}

3.按位异或(^): 原理就是两个二进制位的上下位的数相同为0,相异为1

按照按位异或之后的结果就是1111 1111 1111 1111 1111 1111 1111 1101,这个是补码,我们还原成原码:1000 0000 0000 0000 0000 0000 0000 0011,结果是-3

//3.按位“异或”,原理是两个二进制补码相同为1,相异位0
int main()
{
	int a = 6;//我们先写出a的补码:00000000000000000000000000000110
	int b = -5;//再把b的补码写出来11111111111111111111111011
	int c = a ^ b;//通过原理我们得到补码:11111111111111111111111111111101——(我们转化为原码进行打印)10000000000000000000000000000011,结果是-3
	printf("%d",c);
	return 0;
}

4. 按位取反(~):原理顾名思义就是把1变成0,0变成1
比如我们规定一个变量a等于0,其二进制编码为:00000000000000000000000000000000

安慰取反之后的补码就是:11111111111111111111111111111111

我们求其原码之后的结果就是1000 0000 0000 0000 0000 0000 0000 0001,打印数字-1

int main()
{
	int a = 0;//我们先写出a的补码:00000000000000000000000000000000
	int b = ~a;//我们先写出取反后的补码:11111111111111111111111111111111——(我们转化成原码进行打印)10000000000000000000000000000001,结果是-1
	printf("%d", b);
	return 0;
}

####我们做个例题,一道比较变态的面试题,据说是谷歌曾经的面试题:

不创建临时变量,实现两个数交换

你可能会想到这种解法:a=a+b;b=a-b,a=a-b,这种写法会存在数据溢出风险,我们另辟蹊径:

//另外一种写法,这种一般不推荐写,降低了代码的可读性,让人抓狂,不易理解
int main()
{
	int a = 1;
	int b = 2;
	a = a ^ b;//我们来翻译下:这个式子中新的a的值为a'=a^b
	b = a ^ b;//这个式子中a是上一题式子的新的a的值a',则b=a'^b=a^b^b=a(a'的值又等于第一个式子,b^b的值是为0的),此时b的值变更为b=a
	a = a ^ b;//这个式子的a还是第一个式子中新的a的值a',则a=a'^b=a^b^a=b(b的值在上一个式子中已经变成了a)
	printf("%d %d", a, b);//这样就是实现了数据的交换,感觉还是挺变态的题
	return 0;
}

####我们再来求一个整数存储在内存中(补码)二进制1的个数

我们有多种方案,首先是最低效的方案

int count_bit_one(int n)
{
	int count = 0;
	while (n)
	{
		if ((n % 2) == 1)//通过取模上2判断余数是否为1
		count++;
		n /= 2;
	}
	return count;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = count_bit_one(n);
	printf("%d", ret);
	return 0;
}

但是这样子是不是太低效了,每次还要和32个数比较,那我们来想优化办法,而且如果存入负数,也会有问题
我们让n按位“与”上1,如果其结果为1,则最低位为1,否则为0,下一次我们再让n的第二位(从右往左数)再与1比较,我们通过循环让每一位都与1按位“与”比较,优化后代码写法

int count_bit_one(int n)
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)//32是因为二进制数是32位
	{
		if (((n >> i) & 1))//每次移动一位,每一位都与1按位“与”
			count++;
	}
	return count;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = count_bit_one(n);
	printf("%d", ret);
	return 0;
}

但是我们可不可以更优化一点,原理是n=15,n=n&(n-1)
假设n=15,则n二进制位:1111,n-1的二进制位:1110
此时n变为n’=1110,那新的n-1的二进制位(n-1)’=1101
此时n’变为n''=1100,那新的(n-1)’=1011
此时n''变为n'''=1000,那新的(n-1)''=0000
你会发现最后1都没有了,而且每次n末尾(从左往右看)的1都会左移一个单位,那我们是否可以利用这个特性,来更优化我们的代码 

int count_bit_one(int n)
{
	int count = 0;
	while (n)
	{
		count++;
		n = n & (n - 1);//例如n和n-1比较,假设n=15,则n二进制位:1111,n-1的二进制位:1110,我们进行按位“与”比较
	}
	return count;
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = count_bit_one(n);
	printf("%d", ret);
	return 0;
}

####我们再来举一反三下,判断n是否是2的次方数

原理:我们观察4是2平方,8是2的三次方,通过其对应的二进制原码我们看到2次方数32位编码中只有一个1,而倍数变化1的位置也变化
那我们的判断代码就可以写成(n&n-1)==0,如果成立就一定是2的次方数


####再来一题,二进制改0或1,将13的二进制序列第五位改为1,再改回0

int main()
{
	int a = 15;
	a |= (1 << (5 - 1));
	//若果想改回来呢,那我们把不想变化的位“与”上1的二进制取反
	a &= ~(1 << (5 - 1));
	return 0;
}

六、逗号表达式

##原理:从左向右以此计算,整个表达式的结果以最后一个表达式结果为准,例如:

int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);
	//原理就是首先判断a>b,这个条件是无效条件,接着a再赋上b的值加上10的值,此时a=12,接着一个单独的a
	//这个和之前a>b一样是无效表达式,再看最后一个b赋值上a的值加上1,结果就是13
	printf("%d", c);
}

##再比如:

int d = 0;
if(a=b+1,c=a/2,d>0)//这个式子只会看最后一个表达式条件,其他忽略

七、下标引用

##举例:比如数组的方括号,我们在使用数组的时候的[]就是下标引用操作符例如arr[5],同时arr和5是[]的两个操作数

八、函数调用

函数调用操作符,就是“{}”,例如对于int r =add(3,5)
/其中函数名和它的参数都是“()”的操作数,而且函数至少有一个操作数,那就是函数名,因为你可以不传参


作者基础知识不牢,若果错误欢迎指正,我们友好交流


END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值