【第十三章】操作符详解,预处理指令详解
本章作为最后一章,我们会为大家介绍操作符和预处理指令,这是C语言也比较常见的知识点,也是非常通用的知识点。
操作符详解
在第二章当中,我为大家展示了部分的操作符,称为操作符概述。那么C语言结束之际,我们为大家进行总结,总结C语言当中常见的操作符,所以也叫操作符详解。本小节中我也要尽量为大家详细的解释操作符。
我们常见的操作符有以下分类:
• 算术操作符: + 、-、* 、/ 、%
• 移位操作符: << 、>>
• 位操作符: & 、|、 ^、 ~
• 赋值操作符: = 、+= 、 -= 、 *=、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
• 单目操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)
• 关系操作符: > 、>= 、< 、<= 、 == 、 !=
• 逻辑操作符: && 、||
• 条件操作符: ? :
• 逗号表达式: ,
• 下标引用: []
• 函数调用: ()
• 结构成员访问: . 、->
我们只重点介绍移位操作符和位操作符,其余操作符前面的章节均有涉及,这里不再赘述。
移位操作符
移位操作符分为左移操作符和右移操作符。其中右移操作符分为算数右移和逻辑右移。那么就引发思考:移位移的是什么位?🤔
其实移的就是一个数的二进制位。根据二进制的性质,每次向左或者向右移一位,那么对应的数就增大或者缩小2倍。那么移位操作符的操作对象就是整数。
对于左移操作符,我们会抛弃移出来的二进制位,而在最低位补0。
c
#include <stdio.h>
int main()
{
int num = 10;
int n = num<<1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
左移其实对于我们来说就可以理解为增大2倍(仅仅对于整数来说)。
对于右移操作符来说,右移分为逻辑右移和算数右移。
逻辑右移我们可以理解为编译器无脑右移,就是不管是不是符号位还是数值位,编译器将其都右移一位,不存在对于符号位的考虑。
而算数右移则会把符号位考虑在内,舍弃最低位后最高位补一个符号位。
但是对于我们常用的编译器而言,它通常会以算数右移为主,接下来我们写一段代码来验证一下:
c
#include <stdio.h>
int main()
{
int num = -10;
int n = num>>1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
可以看到我们的计算器打印的是-5。但是具体的操作是在数据的补码范围内进行操作。那么对于补码来说,这是计算机组成原理当中的知识,接下来为大家补充一部分:
整数的2进制表示方法有三种,即原码、反码和补码
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。符号位都是用0表示"正",用1表示"负"。
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成2进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
补码得到原码也是可以使用:取反,+1的操作。
对于整形来说:数据存放内存中其实存放的是补码。为什么呢?
在计算机系统中,数值⼀律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
根据上面的提示,我们可以了解到就是补码进行了操作。这里比较简单,不再详细演示。紧接着就是位操作符。
位操作符
位操作符分为以下几种:
&:按位与:两者都是1才为1,两者有一个0就是0
|:按位或:有1就是1,没1就是0
^:按位异或:相同为1,相异为0
~ :按位取反:反着来
这里的位操作符同样的,在计算机内存当中,都是以整数为操作数为基础进行反码的操作,那么这里还是为大家展示一段代码:
c
#include <stdio.h>
int main()
{
int num1 = -3;
int num2 = 5;
printf("%d\n", num1 & num2);
printf("%d\n", num1 | num2);
printf("%d\n", num1 ^ num2);
printf("%d\n", ~0);
return 0;
}
可以看到我们的操作符是按照二进制位进行操作的,并且原码是进行按位取反(最高位符号位除外)再加一得到补码。
比如说
c
10000000 00000000 00000000 00000011//-3 的原码
11111111 11111111 11111111 11111101//-3 的补码
00000000 00000000 00000000 00000101//5的补码(正数三码合一)
计算机当中所有的运算是以补码的形式计算的,接下来是解析:(涉及部分计算机组成原理的知识,可选学)
计算机在进行运算的时候CPU内的ALU(算术逻辑单元)进行运算时都是以补码的形式进行运算的,我们存储数据的时候对于数据进行解析以后才会进入到内存当中存储运算。进行运算后的结果进行解析后打印,比如说:
c
&
00000000 00000000 00000000 00000101//&运算的结果
此时这个数为正数,我们就默认它为原码,因此打印5
c
|
11111111 11111111 11111111 11111101//|运算的结果
此时这个数编译器会解析为负数,我们就按位取反再加一,当然减1以后再按位取反也可以,我们其实可以观察到这就是-3的补码,那么打印出来的就是-3。
c
^
11111111 11111111 11111111 11111000//^运算的结果
此时这个还是一个负数,我们进行原码转换后就得到了下面的结果:
c
10000000 00000000 00000000 00001000
此时就是-8。这就是位运算的奥秘。
那么到这里,我们操作符就介绍完毕了,从本专栏第二篇文章开始我们就陆续为大家介绍操作符,其中贯穿在每一章都会涉及,此处C语言操作符到此为止就结束了,这就是我们学习中常见的操作符。
预处理指令详解
前面的章节当中,对于程序的编译,链接和执行我们略微提到过,那么对于预处理阶段,我么还有预处理指令详解,那么对于预处理指令来说,我尽量挑常用的来讲解。
#define定义的常量
这就是我们进行常量的替换的时候所常用的语句,这里进行定义时也有好处,在一个程序当中,如果多个地方出现了常量,来回替换非常的麻烦,这里使用宏定义就可以很好的避开这个问题,只用修改一次就成。下面是展示:
c
#define MAX 1000
if(condition)
max = MAX;
else
max = 0;
这里就是一个迅速替换的方法。
#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称(macro)或定义宏(define macro)。
对于这个宏,我们可以指定其所进行的运算,那么对于宏的使用,有如下实例:
c
#include <stdio.h>
#define SQUARE( x ) x * x
int main()
{
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
}
大家可能认为这会打印36,但是实际运行程序时打印的却是11,为什么呢🤔
宏定义在程序当中是原样替换展开的!
是的,看似我们的计算是正确的,但是在实际进行数学运算的时候其实是按照下面的算是进行运算的:
c
printf ("%d\n",a + 1 * a + 1 );
那么为了规避这种问题,我们就利用运算的优先级,加上括号来改变运算的性质:
c
#define SQUARE(x) ((x) * (x))
其实宏定义这里就不要吝啬用括号,因为括号是我们确定运算顺序的关键一步。
对于宏替换展开的规则,还有以下几点:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插⼊到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
好的,那么本章就介绍到这里了,本章我们为大家介绍了操作符和预处理指令,操作符在前面各个章节都有介绍,所以说本章深入介绍了移位操作符和位操作符,但这仅涉及部分皮毛知识,详细深入理解需要计算机组成原理的知识;在预处理命令中为大家介绍了常量和宏的定义和使用。好的,那么本章就到这里。到此为止,C语言所有知识性章节已完全结束,感谢大家一路的支持与陪伴!年后将会根据C/C++来为大家系统介绍数据结构与算法的内容。在这里提前祝大家新年快乐,喜欢的朋友给个三连呦~😄