大家好,很高兴又和大家见面了!现在我们以及结束了数组与函数知识板块的学习,今天我们将进入下一个板块——操作符板块的学习,下面开始介绍我们今天的内容吧。
操作符
一、操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构体成员
二、算术操作符
成员
算术操作符的成员有:
‘+’——算术加,用于计算两数之和;
从测试中我们可以看到,算术加法可以用于整数之间的相加,小数之间的相加以及整数和小数之间的相加;
这里大家需要注意的点是以浮点型打印的时候,小数位数可以通过%和f之间的数字来控制,就比如图中,我打印%.1f就是打印一位小数,我打印%.2f就是打印两位小数;
‘-’——算术减,用于计算两数只差;
从测试结果中我们可以看到,算术减法可以用于整数之间的相减,小数之间的相减以及整数和小数之间的相减;
‘*’ ——算术乘,用于计算两数之积;
从测试结果中我们可以看到,算术乘法可以用于整数之间的相乘,小数之间的相乘以及整数和小数之间的相乘;
‘/’——算术除,用于计算两数之商;
从测试结果中我们可以看到,算术除法可以用于整数之间的相除,此时的结果取的是整数部分,用于小数之间的相除以及整数和小数之间的相除时,结果取得是小数部分与整数部分组成的小数;
‘%’——算术取模,用于计算两数之余。
从图中可以看到,取模操作符并不能作用于浮点型,只能进行整型之间的取模;
通过与算术除的对比我们可以发现,算术除的整数运算返回值为整数部分,算术取模的整数运算返回值为余数部分;
总结
- 除了%操作之外,其它的几个操作符可以作用于整数和浮点数;
- 对于/操作符,如果两个操作数都为整数,执行整数除法,而只要有浮点数,执行的就是浮点数除法;
- %操作符的两个操作数必须为整数,返回的是整除之后的余数。
三、移位操作符
1.成员
'<<'——左移操作符,尖尖朝向左边;
'>>'——右移操作符,尖尖朝向右边;
注:移位操作符只能操作整数
2.移动内容
在初识C语言14中我们就有介绍过移位操作符的相关内容,有兴趣的朋友可以回顾一下。这里我说一下结论:
位移操作符移动的是二进制序列位,左移就是二进制序列往左边移动,右移就是二进制序列往右边移动。
在进一步探讨移位操作符前,我们先来了解一下原码、反码和补码的知识点;
3.原码、反码、补码
整数的二进制有三种表示形式:原码、反码、补码;/
原码:用机器数的最高位表示数的符号(正数符号位为0,负数符号位为1),其余各位表示数的绝对值;/
正整数的原码=反码=补码=实际值;
负数的原码数值部分=实际值;
负数的反码是除符号位外,原码的数值位按位取反;
负数的补码是除符号位外,原码的数值位按位取反后加1,
4.算术移位
算术移位的对象是有符号数,在移位的过程中符号位保持不变。
正整数移位
正整数的原码=反码=补码,所以在进行移位后移出的部分舍弃,空余的部分补0;
负整数移位
原码移位:负整数的原码数值部分与实际值相同,故在移位时只要使符号位不变,空位补0;
反码移位:负数的反码除符号位外,其余各位与原码相反,故移位时只要使符号位不变,空位与原码相反,即空位补1;
补码移位:补码是由反码加1,当我们从补码的最低位向最高位找到第一个1时,在此1的左边的各位均与反码相同,而在此1的右边各位包括此1在内均与对应的原码相同。
故当负数的补码左移时,因空位出现在低位,也就是1的右边,所以补位的代码与原码相同,即空位补0;
负数的补码右移时,因空位出现在高位,也就是1的左边,所以补位的代码与反码相同,即空位补1;
5.逻辑移位
逻辑移位将操作数视为无符号数。
移位规则:逻辑左移时,高位移动完舍弃,低位补0;逻辑右移时,低位移动完舍弃,高位补0;
6.整数的存储
一个整数不管是正整数还是负整数,它存放在计算机中都是按照二进制补码的形式进行存放的。
7.移位方式的测试
在了解完上述内容后下面我们来对这些移位方式分别测试一下:
从测试结果中我们可以看到,不管是逻辑左移还是算术左移,移动后的值都相同,但是在右移操作中,逻辑右移与算术右移的结果相差甚远。
下面我来测试一下如果我们移动负数位又会是什么结果:
从结果中我们可以看到,不管是算术移位还是逻辑移位,系统都会报出警告计数为负,其行为未定义。
8.总结
经过上述的介绍与测试,我们可以对左移、右移操作符做一个总结:
- 左移操作符的移位规则为,二进制序列往左移动,空位补0;
- 右移操作符在逻辑右移时,二进制序列往右移动,空位补0;
- 右移操作符在算术右移时,二进制序列往右移动,空位补1;
- 警告:对于移位运算符,不要移动负数位,这个是标准未定义的。
四、位操作符
成员
'&'——按位与
'|'——按位或
'^'——按位异或
操作内容
位操作符操作对象与移位操作符相同,也是二进制位。下面我们就来介绍它的运算规则;
运算规则
'&'——按位与操作符:当两个数的二进制位都为1时,结果为1,否则为0;
从测试结果中我们可以看到,当两个数对应的二进制位都为1时,结果才为1,只要有对应的二进制位为0,则结果为0;
'|'——按位或操作符:当两个数的二进制位有1时,结果为1,否则为0;
从结果中我们可以看到,当两个数对应的二进制位只要有1,结果就为1,如果对应的二进制位都为0,结果才为0;
'^'——按位异或操作符:当两个数的二进制位不同时,结果为1,否则为0;
从结果中我们可以看到,当两个数对应的二进制位不相同时,即一个为1,另一个为0,此时结果为1,如果同为1或者同为0,则结果为0;
总结
- &——按位与操作符:当两个数对应的二进制位同为1时,结果为1,否则为0;
- |——按位或操作符:当两个数对应的二进制位有1时,结果为1,否则为0;
- ^——按位异或操作符:当两个数对应的二进制位不同时,结果为1,否则为0;
五、赋值操作符
成员
'='——赋值操作符,给操作对象赋一个新值;
从测试结果中我们可以看到,通过赋值操作符,刚开始,我们将a初始化为3,之后再进行赋值将其变为5;
'+='——复合算术加赋值操作符,给操作对象赋值自加后的值,如a = a + 1
可以写成a += 1
;
通过复合算术加后,我们可以看到a的值由初始化的值变为了5;
a通过自加2,也是将a的值由初始化的值变为5,从这里我们可以看到,复合算术加等价于自加;
这里我们可以得出结论,复合算术操作符等价于对自己本身进行算术操作;
'-='——复合算术减赋值操作符,给操作对象赋值自减后的值,如a = a - 1
可以写成a -= 1
;
'*='——复合算术乘赋值操作符,给操作对象赋值自乘后的值,如a = a * 1
可以写成a *= 1
;
'/='——复合算术除赋值操作符,给操作对象赋值自除后的值,如a = a / 1
可以写成a /= 1
;
'%='——复合算术取模赋值操作符,给操作对象赋值自取模后的值,如a = a % 1
可以写成a %= 1
;
'>>='——复合右移赋值操作符,给操作对象赋值自右移后的值,如a = a >> 1
可以写成a >>= 1
;
我们在复合右移操作符后,a的值由3变为了1;
从测试结果我们可以看到,我们将a自己右移后的值赋值给a得到的同样还是1,也就是说复合移位操作符等价于自身进行移位操作;
'<<='——复合左移赋值操作符,给操作对象赋值自左移后的值,如a = a << 1
可以写成a <<= 1
;
'&='——复合按位与赋值操作符,给操作对象赋值自按位与后的值,如a = a & 1
可以写成a &= 1
;
复合按位与后,a的值由3变为了1;
从测试结果可以看到,在自身按位与后,结果也是1,也就是说复合位操作符等价于自身进行位操作。
'|='——复合按位或赋值操作符,给操作对象赋值自按位或后的值,如a = a | 1
可以写成a |= 1
;
'^='——复合按位异或赋值操作符,给操作对象赋值自按位异或后的值,如a = a ^ 1
可以写成a ^= 1
;
总结
- 赋值操作符可以将一个新的值赋值给操作对象;
- 当操作对象对自身进行算术操作、移位操作以及位操作时,可以通过复合赋值操作符来表示;
六、单目操作符
单目操作符是指操作对象只有一个的操作符,下面我们就来看看都有哪些操作符;
成员
'!'——逻辑反操作;
'-'——负值;
'+'——正值;
'&'——取地址;
'sizeof'——计算操作对象所占空间大小(以字节为单位);
'~'——对一个数的二进制按位取反;
'--'——前置、后置--;
'++'——前置、后置++;
'*'——间接访问操作符(解引用操作符);
'(类型)'——强制类型转换;
多种含义的操作符——'+'、'-'、'*'、'&'
作为双目操作符时,它们分别代表算术加、算术减、算术乘、按位与。它们的作用如下:
'+'——将两个操作对象进行算术加,详细介绍请回看算术操作符部分;
'-'——将两个操作对象进行算术减,详细介绍请回看算术操作符部分;
'*'——将两个操作对象进行算术乘,详细介绍请回看算术操作符部分;
'&'——将两个对象对应的二进制位进行按位与运算,详细介绍请回看位操作符部分;
作为单目操作符时,它们分别代表负值、正值、解引用操作符、取地址操作符。作用分别是:
'-'——负值,取操作对象的相反数;
'+'——正值,取操作对象本身,一般会省略;
从测试结果中我们可以看到,在对操作对象进行正值操作后,得到的值仍是它本身;对操作对象进行负值操作后,得到的值是它的相反数;
'*'——解引用操作符,常用于指针,将指针进行解引用操作后,可以取出存放在地址中内容;
'&'——取地址,将操作对象在内存中存储的地址提取出来,常用在指针中,将提取出来的地址存放进指针;
从测试结果中,我们可以看到,取地址操作符可以将操作对象的地址提取出来,如果存放进指针后,还可以通过解引用操作符将地址中的内容提取出来并进行操作。
提到指针后可能有朋友还不太理解这两个操作符的具体作用,这里我来给大家再详细介绍一下这两个操作符;
取地址与解引用操作符
首先我们要复习一下地址是如何产生的?
这里我们可以简单的理解为,地址在32位系统中也就是32个比特位,从2^0到2^31,每一个数的二进制位代表一个地址,同样,在64位系统中则是64个比特位,从2^0到2^63,每一个数的二进制位代表一个地址。
其次我们要知道地址是用来干什么的?
这个问题我们同样可以简单的理解为是来存放数据的,我们输入给计算机的信息会存放在这些地址中,计算机会通过在内存中读取相应的地址内存放的信息来执行相应的操作。
最后我们就可以介绍取地址和解引用操作符的作用了。
刚刚有提到,计算机在执行操作前,是需要从内存中先读取相应地址中所存储的信息才能完成对应操作,这里我们可以直接类比一下,在内存中,计算机是自己读取地址,但是在现实生活中,程序员无法做到直接读取想要操作的内容的相应的地址,这时候就需要借助取地址操作符了。
我们最开始接触取地址操作符时,是在第一次使用scanf函数时。如果我们在使用scanf函数来将输入的值存放在变量中,这时我们相当于是直接改变了变量,或者说是实参,此时我们不能直接修改实参,而需要从实参的地址中进行修改,所以这就是为什么在使用scanf时需要取地址操作符了;
而解引用操作符常用在存放地址的指针中,当我们取出操作对象的地址并存放在指针内之后,我们想要对操作对象进行任何操作我们都可以通过解引用操作符来对指针进行操作;
此时的*b等价于操作对象a;
我们又应该怎么理解地址上存放的内容呢?
通过上面测试打印我们可以看到,此时b内存放的地址是006FF934,它对应的二进制序列是
0000 0000 0110 1111 1111 1001 0011 0100
这个序列就好比一个门牌号,存放的内容就好比房间里的住户,通过代码我们得知这个房间大小是4个字节,在这四个字节的空间大小内,存放着5的二进制序列:
如上图所示,取地址操作符取的是左边的部分,内存空间的地址,解引用操作符取的是右边部分,也就是地址内存放的内容。在指针篇章,我们会经常用到这两个操作符。
这两个操作符的使用,我们可以理解为&是用来寻找门牌号,*则是房门的钥匙。
对于已知门牌号房子,我们只需要用钥匙打开房门就能找到房间里的住户;
对于不知道门牌号的房子,我们需要先使用&找到对应的门牌号,再通过*打开房门才能找到房间里的住户。
取地址操作符和解引用操作符我就先介绍到这里了,接下来我们来介绍一下只有一种含义的单目操作符;
一种含义的单目操作符
'!'——逻辑反操作
下面我们借助分支语句来说明逻辑反操作的用法,分支与循环语句的判断条件只有在条件为真时,才能执行语句内的执行指令。
从测试结果中我们可以看到,当操作对象为非0真值,我们可以通过'!'将其转变为假,值为0,操作对象为0假值,我们可以通过'!'将其转变为真,值为1;
'sizeof'——计算操作对象所占空间大小(以字节为单位)
sizeof这个操作符对咱们来说并不陌生了,它的作用是计算变量、数据类型、数组所占内存空间的大小。这里我们再介绍一下什么是内存。
内存
计算机的硬件系统由输入设备、输出设备、存储器、运算器和控制器5大部件组成。
其中存储器分为主存储器和辅助存储器。
主存储器又叫做内存储器,也就是我们所说的内存。CPU能够直接访问的存储器是主存储器。
主存储器在存放数据时,会将其存放在一个个小的内存单元中,每个内存单元都有它相应的编号,这些编号我们将其称为内存单元的地址。每个内存单元都是有一定大小的,一个内存单元的大小是1个字节,也就是8个比特位。
sizeof的作用就是来计算操作对象在内存中所占内存单元的数量,也可以说是所占空间大小。
sizeof的用法
下面我们通过代码来进一步了解它的用法:
从代码测试结果中我们可以得到以下结论:
- sizeof可以计算变量、数组以及数据类型所占空间大小;
- sizeof计算变量所占空间大小时,可以省略括号;
- sizeof在计算数组时,可以通过数组名来计算,也可以通过数组元素类型加数组大小来计算,前者可以省略括号,后者不能省略;
- 数组的空间大小=数组类型*数组大小;
- 指针所占空间大小是一个定值,不会根据类型的不同而改变大小。
上面的这些结论对咱们来说都不陌生了,那接下来我们来思考一个问题,如果我们将sizeof括号里的内容换成一个表达式,又会是什么结果?
//单目操作符
//sizeof——计算操作对象所占空间大小
int main()
{
short a = 0;
int b = 10;
printf("%d\n", sizeof(a = b + 5));
printf("%d\n", a);
return 0;
}
在这个代码中我们可以看到,sizeof的括号里放入的是一个表达式,表达式的内容是将整型b加上5后放入短整型a中,按照正常的逻辑来说,此时a的值会发生变化由0变成15,a的类型可能会进行整型提升,也可能会进行截断,结果会是什么呢?我们来运行一下:
从结果中我们可以看到,a的数据类型不仅没有发生变化,而且a的值也没有任何改变,为什么呢?我们来调试一下看看这一步运行完a的具体情况:
从监视窗口我们可以看到,在运行完printf后,a的值和类型并未有任何改变,下面我们将这个式子提取出来看一下会是什么结果:
从这个结果中我们可以看到,当我们将整型的运算结果放入短整型中,并不会改变变量的数据类型,在将表达式提取出来后,表达式能正常运算,由此我们可以得出结论:
sizeof的括号中如果放入的是表达式,表达式并不会运行。
到这里sizeof的相关内容咱们就全部介绍完了,大家可以好好消化一下相关知识点。接下来我们继续介绍其它的单目操作符;
'~'——对一个数的二进制按位取反
经过前面对移位操作符和位操作符的介绍,想必大家对二进制位已经不陌生了,下面我们来看一下按位取反操作符是如何运行的:
//单目操作符
//按位取反
int main()
{
int a = 5;
//补码——0000 0000 0000 0000 0000 0000 0000 0101
// 按位取反
//补码——1111 1111 1111 1111 1111 1111 1111 1010
//反码——1111 1111 1111 1111 1111 1111 1111 1001
//原码——1000 0000 0000 0000 0000 0000 0000 0110——-6
printf("%d\n", ~a);
return 0;
}
通过对二进制位的一系列转换,我们笔算得到了答案为-6,下面我们就来运行一下,验证我们的答案:
可以看到运行结果的确如此,我们推算的过程来看,按位取反是符号位和数组位都进行按位取反,而我们从原码得到反码时是符号位不变,数值位按位取反,这个还是有些区别的。,大家别弄混咯。
前置--、后置--与前置++、后置++
++、--的作用
首先我们要知道这个++、--都是什么意思,下面我们来编码测试一下:
从测试结果中我们可以看到,不管是前置还是后置,++的作用都是给操作对象+1,而--的作用都是给操作对象-1;
前置、后置的区别
下面我们来看一下这个前置和后置究竟有什么不同:
//单目操作符
int main()
{
int a = 2;
//前置++
printf("%d\n", ++a);
printf("%d\n", a);
//后置++
printf("%d\n", a++);
printf("%d\n", a);
//前置--
printf("%d\n", --a);
printf("%d\n", a);
//后置--
printf("%d\n", a--);
printf("%d\n", a);
return 0;
}
各位朋友你们在看结果之前,可以先自己将这个代码的运行结果写出来,然后再来对照结果看一下,结果跟你想的会不会有区别:
从结果中我们看到了,在操作符前置的时候,计算机是先完成的++和--的操作指令,所以在前置的结果中两次的打印结果相同;
但是在操作符后置的时候,计算机是先打印的操作对象,再对操作对象进行的++和--的操作指令,所以在后置的结果中,两次的打印结果不同;
由此我们可以得到结论:
- '++'与'--'操作符在前置时,计算机会先对操作对象进行++或者--,再使用操作对象;
- '++'与'--'操作符在后置时,计算机会先使用操作对象,再对操作对象进行++或者--;
这两个操作符我们只需要掌握它们在前置和后置的区别就行,下面我们来看看最后一个单目操作符;
'(类型)'——强制类型转换
强制类型转换字面意思理解就是强制性的将操作对象的类型进行转换。下面我们通过代码来认识一下这个操作符:
//单目操作符
int main()
{
short a = 5;
printf("%d\n", a);
printf("%d\n", sizeof a);
//强制类型转换
printf("%.2f\n", (float)a);
printf("%d\n", sizeof((float)a));
return 0;
}
大家可以尝试着先手写一下这个代码的运行结果,然后再来对照运行结果看看答案和自己的是不是能对上,通过这个方法来加深对这个操作符的理解:
从结果中我们可以看到,通过强制类型转换我们将short类型的变量转变成了float类型,并且变量所占空间大小也变成了强制转换后的类型所占空间大小。
这个操作符我们目前见到的还不多,不知道大家对前面的游戏编写还有没有印象,我们在使用srand函数时就使用过这个操作符,因为srand的参数是无符号整型的,我们在设置随机数起点时要先将有符号长整型的time进行强制类型转换成无符号整型才能正常使用,所以设置随机数起点的代码为srand((unsigned int)time(NULL))
。
单目操作符的内容到这里咱们就全部介绍完了,不知道大家对内容的掌握情况如何,没关系,后面我们会通过习题演练来加深对操作符内容的理解的。
结语
咱们今天要分享的内容到这里也就全部分享完了,当然今天只是分享了操作符的部分内容,还有一部分内容,我们在下一篇博客继续给大家分享的。我希望本篇章可以帮助大家更好的理解这五类操作符的相关知识点,接下来随着学习的深入,我会继续给大家分享我在学习过程中的感受与对学习内容的理解,感谢大家的翻阅,咱们下一篇见。