目录
前言:
原反补码:
位操作符:
&
|
^
~
>>
<<
总结:
逻辑操作符
&&
||
其他操作符:
sizeof
++
--
()
?:(三目操作符)
,(逗号运算符)
*
+=
%
整形提升
练习巩固:
前言:
我们学习编程语言就必须会里面的所有操作符,在了解操作符之前需要有一些基础知识,我们必须要了解这些知识才能更好的去看其他内容。
那么接下来我就一一详细介绍,有特别需求者可以直接跳转目录。
原反补码:
我们知道内存里面存放的是二进制数据,内存记录的是二进制,B是电脑存储的基本单位(字节),1字节有8个比特位,就是8个二进制序列。如下:
1B =8bit=8比特
1KB=1024B=1024字节
1MB=1024KB=1,048,576字节
1GB=1024MB=1,073,741,824字节
1TB=1024GB=1,099,511,627,776字节
1PB=1024TB=1,125,899,906,842,624字节
1EB=1024PB=1,152,921,504,606,846,976字节
1ZB=1024EB=1,180,591,620,717,411,303,424字节
1YB=1024ZB=1,208,925,819,614,629,174,706,176字节
如int类型,有4个字节,32个比特位。但是负数该如何表示呢?
于是就把最高位代表符号位,1代表负数,0代表正数。我们一般定义的int a = 1,C语言已经默认是有符号的整形。
进入正题,原码、反码和补码到底是什么呢?在内存中存储的数据其实是以补码的方式存在的,因为计算机其实只会加减和位运算,为了解决负数的计算不会出错,就发明了补码(具体原因可以暂时忽略)。
- 原码:将一个数字以二进制记录,最高位是符号位,负数的最高位是1,正数的最高位是0。
- 反码:符号位不变,其他位按位取反。
- 补码:将反码加1。
我们来看看-1在内存中是如何占据的:
地址为方便表示将一个16进制位代表8个bit位(不影响阅读,详情请看进制的转换-CSDN博客)。 此时你就会考虑到,既然int有4个字节,32个比特位,那么如果把这32位全部填充成1是不是会有上限?明确的告诉你,确实如此。
我们可以看到,当32位全部填充成1时,最大10进制无符号数整形(unsigned int)就保存的是4294967295。但是前面说最高位是符号位,我们一般定义的默认就是有符号的整形(signed int),那么如果最高位是0(就是正数),最高位就是少了一个1。
所以看出整形保存的正数最大存储的数据是2147483647。关于原反补码的转换也有快捷方式,我们也可以将补码直接符号位不变,其他位按位取反以后加1直接得到原码,如下图:
(声明:补码的出现是为了负数方便计算,所以正数的原、反、补码相同)
有了以上基础,我们就可以无障碍阅读一下内容了。
位操作符:
&
这个操作符有两种意思。
- 按位与:位操作符,与数学中的与相似,两真则真,一假则假(真可以理解为1,假可以理解为0)。遇到负数时先转化为补码,之后按位与。如图(负数与正数按位与)
- 取地址:因为每个变量在计算机中都有存储的空间,所以就有对应的地址编号(暂不用了解,涉及指针,详情请看指针(基础篇)-CSDN博客,也可以跳过,不影响阅读),此时就不再是位操作符。
|
按位或:位操作符,一真则真,遇到负数时转化为补码,之后按位或。
^
按位异或:位操作符,相同出零,相异出一。
~
按位取反 :对一个数进行操作,是针对二进制位进行操作。
//这里可以忽略Printf的具体实现
void Printf(int a)
{int count = 32;while (count--){printf("%d", (a >> count) & 1);}
}int main()
{int a = 5;//对应的二进制位//00000000000000000000000000000101//~就是每一位取反//11111111111111111111111111111010//为方便讲述//此时我们使用函数打印其二进制序列//注:这不是printf函数Printf(~a);return 0;
}
>>
右移操作符:将二进制位整体向右移,分为两种情况。
- 算术右移:右边丢弃,左边补原符号位。通常是采用算数右移,右移时,先将数字转化为补码,之后右移,符号位不变,此时为转化后的补码,再将它转化为原码,得到二进制数,之后看符号位,将其转化为十进制即可。如图
每当我们右移一位时,和十进制规律一样,该数会2倍缩小。
- 逻辑右移:右边丢弃,左边补零。因为一般不会使用逻辑右移,所以我们不再举例。
到底是算术右移还是逻辑右移,是取决于编译器,大部分编译器上是算术右移。
<<
对应的,该操作符是左移操作符,但是它没有像右移操作符一样分为逻辑左移和逻辑右移,只要进行左移,先将其转换为补码,之后最高位不变,左边补0即可。该数呈2倍增长。
关于左右移万万不可移动负数位。
总结:
要想学好位操作符就一定要学好原反补码,这样才能更好的学习C语言。位操作符都是按照补码进行位操作的,同理,结果也是补码,所以要转换为原码得出正确结果。按位取反包括符号位。
这里我们结合其他操作符来使用其他例子帮助小伙伴来更好的理解。
逻辑操作符
&&
逻辑与:逻辑操作符,一假则假,1&&0结果为0,5&&0结果为0,5&&3结果为1。
||
逻辑或:逻辑操作符,一真则真,5||0结果为1。
其他操作符:
sizeof
???这也算操作符?是的,它是函数也是操作符,计算该数据类型的大小。计算结果为无符号的整形。
++
++可以理解为自增操作符,分为前置++和后置加加。
- 前置++:先将该数自增1,之后赋值。
int main() {int a = 0, b = 0;b = ++a;//此时先执行++a,就是将a自增1//之后赋值给bprintf("b = %d\n", b);printf("a = %d\n", a);return 0; }
- 后置++:先使用该数原本的值,之后该语句结束执行完成后,自增1。
int main() {int a = 0, b = 0;b = a++;//此时先将a的值赋给b//之后将a自增1printf("b = %d\n", b);printf("a = %d\n", a);return 0; }
--
也是分为 前置-- 和 后置--,其规则和 ++ 一样,这里我们不再过多赘述。
()
注意,这也是一个操作符,是强制类型转换操作符。比如将浮点型类型强制转换为整形。
?:(三目操作符)
什么东西?问号指数:满天星!这其实是三目操作符,它里面必须有变量。
这其实是条件操作符:也称三目操作符,如a>b?a:b翻译的结果就是a>b吗?是大于b,就是a,否则就是b(记住是冒号)。
int main()
{//条件操作符/三目操作符int a = 10, b = 20;int max = 0;max = (a > b ? a : b);//翻译:a大于b吗?//大于b则max = a//小于b则max = b printf("%d", max);return 0;
}
,(逗号运算符)
从左到右依次进行,整个表达式的结果是最后一个表达式的结果。
int main()
{int a = 1, b = 2;int c = (a > b, a = b + 10, a, b = a + 1);printf("%d\n", c);return 0;
}
*
这个操作符有两种意思。
- 定义指针变量操作符:定义一个指针类型的变量。
- 解引用操作符:若已经定义过指针变量,想通过该指针变量去访问指向的空间,就需要解引用。(详情请看指针(基础篇)-CSDN博客不影响阅读该文章)。
+=
这个操作符其实就是……上图吧:
当然其他的操作符也有这种用法。
这里还有很多类似的操作,我们不再一一赘述。
%
取模:其实就是除法取余数。
负数也有取模规则,结果有第一个数的正负而定。
整形提升
我们知道字符在内存中也是2进制序列,那么计算机到底是如何进行字符的操作呢?比如定义的是字符型,输出的是整形,就会有暗箱操作。
表达式的整形运算要在CPU的响应预案算起件内执行,CPU内整型元算器的操作数的字节长度一般就是int的字节长度,同时也是CPU通过寄存器的长度。
因此即使两个char类型相加,也是难以直接实现两个8bit位直接相加运算(虽然机器指令中可能有这种bit位相加的指令),所以表达式中各种长度小于int的整型值,都必须先转换为int或unsigned int,然后才送去CPU执行运算。
在整形提升时,char是8个比特位,有符号的情况下最高位被当为符号位。
int main()
{char a = -160;//10000000000000000000000010100000-原码//11111111111111111111111101011111-反码//11111111111111111111111101100000-补码//char只能访问1个字节01100000//最高位被当为符号位,打印的是整形,前面补符号位//00000000000000000000000001100000printf("%d\n", a);return 0;
}
即使是有符号的整形,在整形提升时,也是按照最高位提升。
int main()
{char a = -1;signed char b = -1;//有符号数补码全为1,取1个字节//最高位为1,打印整形,前面补1,补码,换为原码为-1unsigned char c = -1;//无符号,-1补码还是全1,取1个字节//虽然最高位是1,但是无符号,前面补0printf("a=%d,b=%d,c=%d", a, b, c);return 0;
}
练习巩固:
其实和我们学的数学的加减乘除一样,这些操作符也是有优先级的。而且像读文章一样,我们是从左向右去读文章的,所以计算也大多是从左向右开始计算的,这称之为操作符的结合性。
像有一些垃圾书籍总喜欢在这上面大做文章,出类似以下的出生代码:
我们可以看到在各个编译器下结果不同,因为你无法确定执行的--和++哪一次结果是保留的,下面来看一些正常的练习。
int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ && ++b && d++;printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);return 0;
}
int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ || ++b || d++;printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);return 0;
}
最后,我们给出每个操作符的优先级顺序: