算术操作符
除法运算符
#include <stdio.h>
int main()
{
int a = 5 / 2;
printf("a = %d\n", a);
return 0;
}
a = 2
上述代码除号两端的数字都是整数,整数除法不会得到小数。如果除号两边的任意一个数字是小数形式,计算出的结果也是浮点数,类型改成double可以计算出小数(默认打印小数点后6位)。
#include <stdio.h>
int main()
{
double a = 5 / 2.0;
printf("a = %lf\n", a);
return 0;
}
a = 2.500000
取模运算符
#include <stdio.h>
int main()
{
int a = 5 % 2;
printf("a = %d\n", a);
return 0;
}
a = 1
取模运算中左操作数和右操作数都要是整数。 ::: tip 除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。 对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。 %操作符的两个操作数必须为整数。返回的是整除之后的余数。 :::
移位操作符
#include <stdio.h>
int main()
{
int a = 16;
int b = a >> 1; // >> -- 右移操作符,移动的是二进制位
printf("b = %d", b);
return 0;
}
b = 8
上述代码中,16的二进制数是10000,a是整型变量,有32个比特位,16在变量a中的排列如下:00000000000000000000000000010000。 a的二进制序列向右移动一位,最右边的数字被丢弃。通过下面的代码确定了编译器采用了算术右移,左边补0,此时16在变量b中的排列如下:00000000000000000000000000001000。即二进制数中的数字1右移一位,十进制数变为8。
::: danger 移位运算符不要移动负数位和浮点数位,如a >> -1、a >> 1.5。 这个标准未定义,编译器执行结果会出错。 :::
右移操作符
::: tip 右移操作符的两种方法: 1.算术右移:右边数字丢弃,左边补原符号位(正数补0,负数补1) 2.逻辑右移:右边数字丢弃,左边补0 ::: 判断编译器是算术右移还是逻辑右移:
#include <stdio.h>
int main()
{
int a = -1;
int b = a >> 1; // >> -- 右移操作符,移动的是二进制位
printf("b = %d", b);
return 0;
}
b = -1
如上代码,当a为-1,如果编译器是逻辑右移,前面补0,a右移后变为正数b。如果编译器是算术右移,左边补原符号位1,a右移后的b还是负数。
左移操作符
左移操作符:左边丢弃,右边补0。
#include <stdio.h>
int main()
{
int a = 5;
int b = a << 1; // << -- 左移操作符,移动的是二进制位
printf("b = %d", b);
return 0;
}
b = 10
如上代码,当a为5,二进制表示为:00000000000000000000000000000101(2的2次方+2的0次方),左移1位后为10,二进制表示为:00000000000000000000000000001010。(2的3次方+2的1次方)
补充
::: warning 整数的二进制表示有3种:源码、反码、补码。 存储到内存中的是补码。移位时也是移动补码。首先要写出a的补码。 正整数的原码反码补码是相同的。 负整数的原反补(以-1为例): 源码:10000000000000000000000000000001。负数最高位符号位为1,最后一位表示实际数字1。 反码:11111111111111111111111111111110。反码是源码的基础上,符号位不变,其余位置按位取反。 补码:11111111111111111111111111111111。反码+1。 :::
位操作符
&按位与
&是按二进制位与,需要先写出整数的二进制形式(补码)。 对应二进制位只要有一个是0就为0,两个都为1才为1。
#include <stdio.h>
int main()
{
int a = 3; //000000000000000000000011
int b = 5; //000000000000000000000101
int c = a & b; //000000000000000000000001
printf("c = %d", c);
return 0;
}
c = 1
|按位或
|是按二进制位或,需要先写出整数的二进制形式(补码)。 对应二进制位只要有一个是1就为1,两个都为0才为0。
#include <stdio.h>
int main()
{
int a = 3; //000000000000000000000011
int b = 5; //000000000000000000000101
int c = a | b; //000000000000000000000111
printf("c = %d", c);
return 0;
}
c = 7
^按位异或
^是按二进制位异或,需要先写出整数的二进制形式(补码)。 对应二进制相同为0,相异为1。(同假异真) 交换两个变量的值,不使用新变量:
#include <stdio.h>
int main()
{
int a = 5;
int b = 3;
printf("a = %d,b = %d\n", a, b);
a = a + b; //a = 8
b = a - b; //b = 5
a = a - b; //a = 3
printf("a = %d,b = %d", a, b);
return 0;
}
a = 5,b = 3
a = 3,b = 5
::: warning 上述代码可能存在的问题是,a和b都是整型值,占4个字节(32比特),如果a和b的值非常大,各自没有超出整型值所能容纳的最大值,但是相加后超出了,就会发生溢出,丢失部分内容,后面相减时会出错。 :::
#include <stdio.h>
int main()
{
int a = 5; //011
int b = 3; //101
printf("a = %d,b = %d\n", a, b);
a = a ^ b; //a = 110
b = a ^ b; //b = 011
a = a ^ b; //a = 101
printf("a = %d,b = %d", a, b);
return 0;
a = 5,b = 3
a = 3,b = 5
上述代码可以描述为:a和b异或产生密码a,密码a和b异或得到原来的a,放在b中,密码a和b(原来a)异或得到原来的b,放在a中,完成a和b的交换。 ::: warning 异或的方式交换两个值时,代码执行效率不高,可读性差。 在实际操作中,两个变量值的交换,一般采用引入临时变量的方法。 :::
练习
求一个整数存储在内存中的二进制数中1的个数 ::: tip 代码思路: 数字的二进制形式和1做按位与,如果二进制数最后一位为1,按位与后的结果就是1。 然后使用右移操作符每次将二进制数的最后一位丢掉,在和1做按位与,直到32位比特的每一个数字都和1做了按位与。 统计32次按位与后结果为1的次数,就是这个整数存储在内存中的二进制数中1的个数。 :::
#include <stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)
{
if ((num >> i & 1) == 1)
{
count++;
}
}
printf("1的个数为%d", count);
return 0;
}
3
1的个数为2
赋值操作符
赋值操作时不建议使用如下的连续赋值:
#include <stdio.h>
int main()
{
int a = 10;
int b = 5;
int c = 8;
a = b = c + 2;
printf("%d", a);
}
拆分后更容易阅读。
复合赋值符包括如下:
+= -= *= /= %= >>= <<= &= |= ^=