操作符
1. 操作符的分类
操作符
操作符的分类想必大家都知道,这里就不赘述了。本文只是介绍其中的一部分操作符。
2. 算术操作符
算术操作符主要有:
+ - * / %
其中除法(/)分为两类:
- 只含整数的除法:运算结果为整数
- 含有浮点数的除法:运算结果为浮点数
注意:%(取模)的结果是余数。并且取模操作符的两个操作数必须都是整数!
3. 移位操作符
<< 左移操作符
>>右移操作符
注意:移位操作符的操作数只能是整数。
【右移】:
补充:整数的二进制表示形式有三种:原码,反码,补码。
- 正数的原码反码补码相同。
- 负数的原码反码补码要进行计算,不能直接判断。
原码反码补码的计算方法:一个整数,无论正负,直接将其翻译为二进制序列,该二进制序列就是原码。
int a = 15 ;
因为15是被存放在int型中的,而int型有32个比特位,其最高位表示符号位,将其翻译成二进制位就是:
00000000000000000000000000001111 //(最高位是0,表示该数为正数)
就得到了a的原码。因为a是15,为正数,所以该原码同时也是a的反码和补码。
int b = -15;
接下来我们将b翻译成二进制序列:
10000000000000000000000000001111 //原码 (最高位是1,表示该数是负数)
11111111111111111111111111110000 //反码 符号位不变,其他位按位取反得到b的反码
11111111111111111111111111110001 //补码 反码+1得到补码
该二进制序列是b的原码,由于b是负数,所以其反码和补码要根据上面的规则进行计算。
注意:数据在内存中是以补码的形式进行存储的。
那么移位操作就是对数据的补码进行操作的。例如:
int a = 15;
00000000000000000000000000001111 //a的原码 同时也是反码和补码,这里我们将其看作补码
int b = a >> 1; //将a右移一位
00000000000000000000000000000111 //a右移之后得到的二进制序列,得到的是补码,但其是正数,所以其原反补相同
printf("%d",b); //打印7
printf("%d",a); //a的值不变,还是15
注意:C语言中的右移操作分位两种:(一般编译器采用的是算术右移)
- 算术右移(右边丢弃,最高位补原来的符号位)
- 逻辑右移(有边丢弃,最高位一律补0)
在内存中存放的是补码,那么操作之后得到的结果也应该是补码,所以我们要想知道操作的值是多少,必须将操作结果转换成原码,这里看到代码第4行,其最高位是0,代表运算结果是负数,所以其原码反码补码相同。所以直接将该二进制序列的值赋值给b变量,b变量的值就是a右移两位的结果。
接下来在讲一个负数右移:
int main()
{
int a = -15;
10000000000000000000000000001111 //a的原码
11111111111111111111111111110000 //原码除了符号位不变,其他按位取反得到反码。
11111111111111111111111111110001 //反码+1得到补码
int b = a >> 1;
11111111111111111111111111111000 //右移一位,最高位补符号位,也就是补1,得到补码
11111111111111111111111111110111 //除了符号位之外,其他按位取反,得到反码
10000000000000000000000000001000 //反码+1,得到原码,翻译成10进制,也就是-8
printf("%d\n", b);//打印 -8
printf("%d\n", a);//打印 15
return 0;
}
得到操作结果之后,再将该结果转换为其原码,再翻译为十进制,就是-8。所以执行结果是打印-8
【左移】:左移就是将数据的补码向左移动,左边被移出的数据直接丢弃,右边补0。得到的操作结果仍然位补码,需要将其转换成原码。举个例子:
int main()
{
int a = 15;
00000000000000000000000000001111 //a的原码,a为正数,原反补相同
int b = a << 1;
00000000000000000000000000011110 //左移一位得到补码,由符号位可知,该二进制序列为正数,原反补相同
printf("%d ", b);//打印30
printf("%d ", a);//打印15
return 0;
}
特别注意:移位操作不能移动负数位,例如:
int a = 15 >> -1;
这种操作是错误的,是C语言标准未定义的。
4. 位操作符
这里要先知道位操作符都是针对数据的二进制补码进行运算的,所以位操作符的操作数必须是整数。
【分类】
- &(按位与)
- |(按位或)
- ^(按位异或)
【按位与】
运算规则:二进制位有0则为0,全是1才为1。例如:
int main()
{
int a = 2;
//00000000000000000000000000000010
int b = 1;
//00000000000000000000000000000001
int c = a & b;
//00000000000000000000000000000000
printf("%d ", c); //打印0
return 0;
}
这里分别写出a和b的二进制补码,将其进行按位与操作,得到全0的二进制序列,结果也就是0。
【按位或】
运算规则:二进制位有1则为1,全为0才是0。例如:
int main()
{
int a = 2;
//00000000000000000000000000000010
int b = 1;
//00000000000000000000000000000001
int c = a | b;
//00000000000000000000000000000011
printf("%d ", c); //打印3
return 0;
}
分别写出a和b的二进制补码,将其进行按位或操作,得到的二进制序列翻译成10进制也就是3。
【按位异或】
运算规则:二进制位相同为0,相异为1。例如:
int main()
{
int a = 5;
//00000000000000000000000000000101
int b = 1;
//00000000000000000000000000000001
int c = a ^ b;
//00000000000000000000000000000100
printf("%d ", c); //打印4
return 0;
}
分别写出a和b的二进制补码,将其进行按位异或操作,得到的二进制序列就是4。
【按位异或的性质】
- 一个数和0异或得到的结果仍是其本身:我们将一个整数a和0进行异或操作,得到的一定是a本身。
- 两个相同的数异或得到的结果一定是0。
- 异或运算满足交换律和结合律。
【按位异或的应用】
Q1:如何交换两个整数?
交换两个整数的值可以有三种方法:
【Answer1】:
int main()
{
int a = 10;
int b = 20;
printf("before: a = %d b = %d\n", a, b);
int tmp = a;
a = b;
b = tmp;
printf("after : a = %d b = %d\n", a, b);
return 0;
}
这种方法会创建新的变量,但是总体问题不大。
【Answere2】:
int main()
{
int a = 10;
int b = 20;
printf("before: a = %d b = %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("after : a = %d b = %d\n", a, b);
return 0;
}
这种方法有个弊端:当a和b的值很大时,将其加在一起可能就会超过int数据类型所能存储的最大数据范围。
【Answer3】:
int main()
{
int a = 10;
int b = 20;
printf("before: a = %d b = %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("after : a = %d b = %d\n", a, b);
return 0;
}
这种方法就比较巧妙了。
这里要结合异或运算的性质进行分析。
【位操作符的应用】
Q2:求一个整数存储在内存中的二进制中1的个数。
【Answer1】:
int main()
{
int num = 10;
int count= 0;//计数
while(num)
{
if(num%2 == 1)
count++;
num = num/2;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
首先强调:**该方法只适用于正数!**该方法先判断该非0数是不是奇数,若是奇数,其最低的二进制位必定是1,计数器加1。接着再将该数除以2,(这里要注意,将一个正整数除以2,等效于将其二进制序列右移一位),再通判断该数的奇偶性间接判断其二进制位的最低位是否为1。知道num为0则停止。
该方法的缺陷是只适用于正整数。
【Answer2】:
我们首先知道,1与0进行按位与操作得到的是0,1与1按位与操作得到的1。
所以可以将该整数的二进制位的最低位和1进行按位与操作,其他高位与0进行按位与操作。此时整个表达式的执行结果就只与该整数的最低二进制位有关了,若最低二进制位是1,则结果为1,若最低二进制位为0,则结果为0。将该最低二进制位判断完毕之后,将整个二进制序列右移1位。将该操作进行32次,因为一个int类型的数据占用32个二进制位,我们需要将每一位都判断一次。
int main()
{
int num = -1;
int i = 0;
int a = 1;
int count = 0;//计数
for (i = 0; i < 32; i++)
{
if (num & a)
count++;
num >>= 1;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
【Answer3】:
int main()
{
int num = 9;
int i = 0;
int count = 0;//计数
while(num)
{
count++;
num = num&(num-1);
//第一次执行该语句时:
//00000000000000000000000000001001
//00000000000000000000000000001000
//00000000000000000000000000001000
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
这种方法难以想到。每执行一次num = num&(num-1);
语句都能除去二进制序列中权值位最小的1。
5. 赋值操作符
赋值操作用于给变量初始化或者改变变量的值。
int a = 10; //初始化
a = 20; //赋值
赋值操作也能写作复合形式:
int a = a+1;
写做int a += 1;
int a = a>>1;
写做int a >>= 1;
……
6. 单目操作符
概念:只有一个操作数的操作符称之为单目操作符。
主要的单目操作符:逻辑反(!),取地址(&),操作符的类型长度(sizeof),强制类型转换(一对小括号)……
6.1 sizeof操作符
#include <stdio.h>
int main()
{
int a = -10;
int *p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//这样写行不行?
printf("%d\n", sizeof int);//这样写行不行? 不可!会报错
return 0;
}
//关于sizeof其实我们之前已经见过了,可以求变量(类型)所占空间的大小。
6.2 sizeof和数组
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2) 打印4/8
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4) 打印4/8
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//(1) 打印40
printf("%d\n", sizeof(ch));//(3) 打印10
test1(arr);
test2(ch);
return 0;
}
注意:
- 当sizeof的括号内是数组名是算出的是整个数组的字节大小。
- 数组传参传递的是数组首元素的地址。所以在test1函数和test2函数中计算的是指针变量的大小,而指针变量的大小在32位平台下占用的空间是4个字节,在64位平台占用的是8个字节。
- sizeof后的括号可以去掉就说明sizeof并不是函数。因为函数的调用必须要括号。
- 在数组中去掉名字,剩下的就是数组的类型。例如:数组
int arr[10];
的类型就是int [10]
当scanf读取失败时,返回的时EOF,值是-1,-1的补码全为1,可以通过以下的代码实现多组输入。
int main()
{
int a = 0;
//假设返回EOF,其二进制序列全是1,取反之后全是0,条件为假,退出循环。
while(~scanf("%d",&a))
{
printf("%d ",a);
}
return 0;
}
7. 关系操作符
关系操作符较为简单。
8.逻辑操作符
主要有两个 &&
(与)和 ||
(或)。
注意:这里要与按位与和按位或做区分。
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
先看一道题:
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
执行结果:
分析:代码第4行a++
表达式的结果是0,也就是假,对于逻辑与来讲,只要遇到执行结果为假的表达式,该语句之后的代码则不会再执行了。
逻辑与和逻辑或的特点:逻辑与和逻辑或会发生“短路现象”。
对于逻辑与来讲,倘若遇到执行结果为假的表达式,则该语句之后的表达式就不会再继续执行了。
对于逻辑或来讲,倘若遇到执行结果为真的表达式,则该语句之后的表达式就不会再继续执行了。
9. 条件操作符
exp1 ? exp2 : exp3
条件操作符与 if else
语句可以相互转换。
Q3:
if (a > 5)
b = 3;
else
b = -3;
转换成条件表达式,是什么样? // b = (a>5)?3:-3;
10. 完结
本章的内容就到这里啦,若有不足,欢迎评论区指正,最后,希望大佬们多多三连吧,下期见!