✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转C语言
💬保持学习、保持热爱、认真分享、一起进步!!
前言
所有关于运算的操作符,不管是逻辑运算还是位运算,都是先将变量在内存中提取在CPU内存寄存器中进行运算,然后计算的结果在写回内存,所以我们操作的都是补码,因为内存中存储的都是数据的补码,只有我们打印某个数据时才会涉及到原码的概念。
建议在学习运算操作符之前先明白数据(整形)在内存中如何存储
《数据到底在内存中如何存储》
一.深度理解取余/取模运算
对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
想要得到小数部分一定一个操作数得是浮点数
% 操作符的两个操作数必须为整数
。返回的是整除之后的余数。
1.第一个问题整数的除法是如何取整的
为什么是这样的一种取整方式呢。
其实C语言中的默认取整方式是向零取整
向零取整的函数
向负无穷方向取整的函数,返回不大于x的最大整数值
向正无穷方向取整的函数,返回不小x的最小整数值
四舍五入取整
取整方案对比:
这里你会发现就算不同的取整方式可能会得到同样的值。
重点来了:取模和取余一样嘛
先说结论
本质 1 取整:
取余:尽可能让商,向零取整
取模:尽可能让商,向-∞方向取整
而我们前面已经说了C语言默认的取整方案为向零取整
,所以C语言中的%运用其实是在取余数。
被除数、除数、商、余数的关系:
如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ |r|< |d|。。其中,q被称为商,r 被称为余数。
接下来来一个怪一点的:-10%3=?
在VS2013编译器中:
在Python 2.7.12
为什么会出现不同的结果呢
根本原因是
C语言中 % 是取余数 — 得出的商向零取 整
Python中% 是取模 — 得出的商向负无穷取整
这样我们就能解释了,我们知道了商知道被除数和除数就可以求出余数
r余数的大小是取决于商的,而商的大小有取决与取整方式:
对任何一个大于0的数,对其进行0向取整和-∞取整,取整方向是一致的。故取模等价于取余
对任何一个小于0的数,对其进行0向取整和-∞取整,取整方向是相反的。故取模不等价于取余
这句是为什么C语言中通常将取余等价与取模,原因就是我们通常都是正数,而正数的取余才真正等价于取模。
同符号数据相除,得到的商,一定是正数(正数vs正整数),即大于0!=在对其商进行取整的时候,不管是向零取整还是向负无穷取整取整的方向都是一样的所以所得的商也一样,余数也一样
,所以取模等价于取余。
同符号时:取模等价于取余
不同符号时所得的商一定是负数,取模向负无穷取整,取余是向零取整则取整方向不一样,所以所得的商不一样,所得的余数也不一样。
二.续行符与转义字符
注意:续航符的后面不能带任何东西(包括空格)
- \的转义功能:
一道面试题:
解析:
\0 与 数值0,NULL,和字符零’0’的区别
*NULL的数值其实是零,只不过NULL的类型是 void 类型
\0的ACSLL值也就是十进制表示也为0, 只不过\0的类型为字符型。
而 '0’字符零是ACSLL值为48 .
总结:\0 与 数值0,NULL数值相同都为0类型不同,
- 回车与换行
回车与换行本质是两个概念:
回车:光标回到当前行的最开始
换行:光标移动到下一行
只不过 \n 既回车又换行。
利用回车做一个小程序:旋转光标:
因为我们添加了 \r,所以每次打印都回到当前行的最开始覆盖上一次的打印
利用回车做一个小程序:倒计时:
因为我们添加了 \r,所以每次打印都回到当前行的最开始覆盖上一次的打印
三.逻辑运算符
&& 逻辑与
|| 逻辑或
逻辑与的特点:
1.所有表达式都成立才成立
2.从左向右结合
3.若有一个表达式不成立则后面的表达式不执行
所有表达式成立才成立:
若有一个表达式不成立则后面的表达式不执行:
注意:
这里成立与不成立的感念是真假的概念:非0为真,0为假
逻辑或的特点:
1.所有表达式有一个成立则成立
2.从左向右结合
3.若有一个表达式成立则后面的表达式不执行
都不成立才不成立:所有表达式全部执行一遍
笔试题:
解析:
变式1:
解析:
四.位操作符
1. 按位与 按位或 按位取反
所谓双目运算符:有两个操作数,操作数必须是整数
所有的运算都是要经过CPU来执行的,执行的过程中要想内存中提取数据,而存储在内存中的数据全是补码,所以我们的运算全是针对与数据的补码
按位与运算 &
按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应二进制位相与。只有对应的两个二进制位均为1时,结果位才为1 ,否则为0。
按位或运算 |
按位或运算符“|”是双目运算符。 其功能是参与运算的两数各对应的二进制位相或。只要对应的二个二进制位有一个为1时,结果位就为1。
求反运算~
求反运算符~为单目运算符,具有右结合性。 其功能是对参与运算的数的各二进位按位求反。
注意:符号位照样取反
按位异或运算 ^
按位异或运算符“^”是双目运算符。 其功能是参与运算的两数各对应的二进制位相异或,当两对应的二进制位相异时结果为1,相同结果为0,
按为异或的特点:
最好能记住解题块的很
性质:
1.交换律 a^ b = b^a
2.结合律 a^ b ^ c = a^ (b^c)
3.任何数后零异或都是它本身
4. 自身与自身异或为0
其实上面的特点很好证明只要记住:相同为0,相异为1
实战:交换两个数
1.创建临时变量
2. 不创建临时变量
缺陷:当两个数的足够大时相加可能会溢出
3. 异或
其实知道异或的特性很快能做出来
2.移位操作符
左移: 最高位丢弃,最低位补零
右移:
1.无符号数:最低位丢弃,最高位补零[逻辑右移]
2.有符号数:最低位丢弃,最高位补符号位[算术右移]
注意:
有无 符号看的是数据的类型,与内存中保存的数据无关
左移: 最高位丢弃,最低位补零
右移:
1.无符号数:最低位丢弃,最高位补零[逻辑右移]
2.有符号数:最低位丢弃,最高位补符号位[算术右移]
如何理解丢弃:
左移或者右移都是计算,都要在CPU中进行,可是参与移动的变量,是在内存中的。所以需要先把数据移动到CPU内寄存器中,在进行移动,实际移动的过程中,是在寄存器中进行的,即大小固定的单位内。那么,左移右移一定会有位置跑到"外边"的情况
所以还有一个结论:
我们左移右移都是针对内存中的数据而言的,而内存中的存储的数据的补码
。
到底什么时候需要原码:只有我们在打印某个数据时,从内存中拿出来的时候。
实战(重点)
设置数据的指定比特位为1,其他比特位不变
设置数据的指定比特位为0,其他比特位不变
显示数据的所有比特位,并记比特位中有多少个1
void ShowBits(int n)
{
int count = 0;
int num = sizeof(n) * 8 - 1;
while (num>=0)
{
if (n&(1<<num))
{
printf("%d ",1);
count++;
}
else
{
printf("%d ", 0);
}
num--;
}
printf("\n");
printf("1的个数:%d\n", count);
}
实验效果:
左移:一般情况下左移一位就乘以2
//左移
unsigned int a = 10;
printf("%u\n", a << 1);
printf("%u\n", a << 2);
printf("%u\n", a << 3);
return 0;
右移:一般情况下右移一位就除以2
这个我就不分析了, 与左移类似。
特殊情况:-1右移还是-1因为最高位补的是符号位
重点理解右移:
注意:
有无 符号看的是数据的类型,与内存中保存的数据无关
如果对整形如何存储在内存中,或者对无符号类型的变量为什么能存储负数有疑问请看——<数据到底在内存中如何存储>
移位一个要注意的点:
+号的优先级比 移位操作符要高
有先级的概念一定要注意:拿捏不准就加上括号,准没错
五.深入理解 a++
a++ 在任何情况下都是先使用后++嘛
答案是否定的:
当有a++被使用时:
确实是先使用然后在完成自增
当有a++没有被使用时:
a直接自增
接下来看一段复杂代码:
在VS2013编译器中:
在linux下gcc编译器中:
为什么同一份代码在不同的编译器下,结果不同呢
vs2013 -->12
gcc —>10
根本原因写出这种代码:计算的途径有很多种
- vs2013 -->12
根本原因是编译器先将i自增3次在指向i的相加
怎么计算方式并没有违反操作符的优先级与结合性的规则
优先级:只会影响到两个相邻的操作符先执行哪个
- gcc —>10
相当于先执行前面i++ ,让i自增两次i变成3,然后在执行前面两个i相加 得3+3得6,最后让最后一个i++,i变成4,最后相加的3+3+4=10
看完了汇编是不是还是有点没理解:
其实这个式子与我们日常数学中的一个式子差不多:
- 贪心法
C语言有这样一个规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个一个字符的读人,如果该字符可能组成一个符号,那么再读人下一个字符时,判断已经读人的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读人的字符组成的字符串已不再可能组成一个有意义的符号。这个处理的策略被称为“贪心法”。需要注意的是,除了字符串与字符常量,符号的中间不能嵌有空白(空格、制表符、换行符等),比如:== 是单个符号,而= =是两个等号。
贪心法简单点说就是:尽可能多的匹配符号,组成C语言中多字符符号,例如:++ – &= <<= … 所以写这些符号中间不要添加空格
看一个例子就明白了:
六.操作符优先级
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
L-R :从左向右
R-L :从右向左
3. 是否控制求值顺序。
逻辑或 || 逻辑与 && 条件操作符 ? : 逗号表达式
两个相邻的操作符先执行哪个取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级,越上面优先级越高
条件操作符:
格式:
如果表达式1为真,则执行表达式2,表达式2是整个表达式的结果
如果表达式1不成立,则执行表达式3,表达式3是整个表达式的结果
举例:求两个数的较大值
其实也可以实现嵌套,在某个表达式又搞一个 exp1? exp2:exp3
- 逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。