C语言学习--操作符
Posted 庸人冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言学习--操作符相关的知识,希望对你有一定的参考价值。
文章目录
算数操作符
算数操作符包括,四则操作符 +
-
*
/
和 取模操作符 %
+
-
*
的运算规则与数学中相同,重点介绍 /
和 %
的注意点。
/
号两边得操作数如果都是整数,执行的是整数除法,结果为整数商,余数被舍弃。
int main()
{
// 两边为整数
int a = 5 / 2; // == 2
// 有一个浮点数
float f = 5 / 2.0; // == 2.500000 结果默认小数点后六位
return 0;
}
%
号两边的操作数必须都是整数,结果为整除后的余数
int main()
{
// 两边为整数
int a = 5 % 2; // == 2
// 有一个浮点数
int b = 5 % 2.0; // 非法,左右操作数必须为整型
float f = 5 % 2.0; // 非法
return 0;
}
- 除了
%
其他的算数操作符都可以作用于整数和浮点数。
移位操作符
移位操作符包括,右移操作符>>
和左移操作符<<
。
移位操作符移动的是数二进制位,移位操作符只能作用与整数。
二进制数
我们通常是使用10进制来表示一个数,例如1234,千位1,百位2,十位3,个位4,个位的权重为100,十位为101,百位为102,千位为103 …
用每一位上的数 × 权重,再相加就能得到这个10进制数。
1234
=
1
×
1
0
3
+
2
×
1
0
2
+
3
×
1
0
1
+
4
×
1
0
0
1234 = 1 \\times 10^3 + 2 \\times 10^2 + 3 \\times 10^1 + 4 \\times 10^0
1234=1×103+2×102+3×101+4×100
而计算中在存储数据是以二进制数来存储,二进制数只有1,0二种表示方式,逢2进1。例如二进制数0111,它的第0位的权重是20,第1位的权重是21,第二位的权重是22。
用每一位的数 × 权重,再相加就能得到这个2进制数的十进制表示。
7
=
1
×
2
2
+
1
×
2
1
+
1
×
2
0
7 = 1 \\times 2^2 + 1 \\times 2^1 + 1 \\times 2^0
7=1×22+1×21+1×20
原码、反码、补码
计算机中用二进制数的最高位来表示正负,也称为符号位,0表示正数,1表示负数,剩余的位用来表示二进制数的绝对值。
有符号位数的二进制表示有:原码、反码、补码。
原码
最高位表示数的符号,其他位表示数值
7原 == 00000111
-7原 == 10000111
反码
-
正数的反码和原码相同
7原 == 00000111 == 7反
-
负数的反码等于原码符号位不变,其他位按位取反。
-7原 == 10000111 <==> 11111000 == -7反
补码
-
正数的补码、反码、原码相同
7原 == 7反 == 7补 == 00000111
-
负数的补码等于反码
+
1-7原 == 10000111 <=> 11111000 = -7反
<=>
11111001 = -7补
计算机在存储数据时中存储的是二进制的补码,使用补码的方式存储是因为可以用加法同时处理加法与减法运算。
例如: 1 - 1 = 1 + (-1) = 0
-
用原码方式相加
1原 = 00000001,-1原 = 10000001
00000001 + 10000001 = 10000002 = -2 (错误)
-
用反码方式相加
1反 = 00000001,-1反 = 11111110
00000001 + 11111110 = 11111111反 = 10000000原 = -0
-0 虽然和 0 相同,但是用2个组合来表示一个数有些浪费,因此不可取。
-
用补码方式相加
1补 = 00000001,-1补 = 11111111
00000001 + 11111111 =
100000000补 = 0 (正确)经过计算,1会进到第8位,多出的最高位溢出被舍弃,0则变为符号位,因此得出的结果为正数,他的原反补相同。
右移操作符 >>
因为计算机中存储的是补码,所以在移位操作时,操作的也是补码的位。
右移操作又分为:
-
算数右移
右边舍弃,左边补原符号位。
-
逻辑右移
右边丢弃,左边补0。
VS中为算数右移
// 正数原反补相同
int main()
{
int a = 16;
int b = a >> 1; // 右移操作符,将16的二进制位向右移动一位
//16 == 00000000000000000000000000010000 >> 1
// b == 00000000000000000000000000001000 == 8
int c = a >> 2;
//16 == 00000000000000000000000000010000 >> 2
// c == 00000000000000000000000000000100 == 4
printf("%d\\n", b); // 8
printf("%d\\n", c); // 4
// 右移一位相当于 十进制数 / 2;
}
// 负数
int main()
{
int a = -16;
int b = a >> 1;
// -16原码 == 1000 0000 0000 0000 0000 0000 0001 0000
// -16反码 == 1111 1111 1111 1111 1111 1111 1110 1111
// -16补码 == 1111 1111 1111 1111 1111 1111 1111 0000 >> 1
// b的补码 == 1111 1111 1111 1111 1111 1111 1111 1000
// b的反码 == 1111 1111 1111 1111 1111 1111 1111 0111
// b的原码 == 1000 0000 0000 0000 0000 0000 0000 1000 == -8
printf("b = %d\\n", b);
int c = a >> 5;
// -16补码 == 1111 1111 1111 1111 1111 1111 1111 0000 >> 5
// c的补码 == 1111 1111 1111 1111 1111 1111 1111 1111
// c的反码 == 1111 1111 1111 1111 1111 1111 1111 1110
// c的原码 == 1000 0000 0000 0000 0000 0000 0000 0001 == -1
printf("%d\\n",c);
return 0;
// c的结果可以看出,负数右移时最大为-1,因为每次补的都是1,最终补码的值是全1,而全1转换为原码就是-1
}
左移操作符 <<
左移操作的规则只有:左边丢弃,右边补0
// 正数
int main()
{
int a = 16;
int b = 16 << 1;
// a == 0000 0000 0000 0000 0000 0000 0001 0000 << 1
// b == 0000 0000 0000 0000 0000 0000 0010 0000 == 32
printf("b = %d\\n", b);
// 左移操作符在移动时可能会改变符号
short c = 16385; // 0100 0000 0000 0001
short d = c << 1;
// c的补码 == 0100 0000 0000 0001 << 1
// d的补码 == 1000 0000 0000 0010 得到负数
// d的反码 == 1000 0000 0000 0001
// d的原码 == 1111 1111 1111 1110 == -32766
printf("d = %d\\n",d)
short e = -1;
short f = e << 1;
// e的原码 == 1000 0000 0000 0001
// e的反码 == 1111 1111 1111 1110
// e的补码 == 1111 1111 1111 1111 << 1
// f的补码 == 1111 1111 1111 1110
// f的反码 == 1111 1111 1111 1101
// f的原码 == 1000 0000 0000 0010 == -2
return 0;
}
注意
移位操作符,不要移动负数位,这个标准是未定义的。
int a = 10;
int b = a >> -1; // 未定义,在VS2019中结果为0
位操作符
位操作符:按位与&
按位或|
按位异或^
,
位操作符,同样是操作补码得二进制位,并且只能操作整数。
按位与&
同二进制位都为1
时得1
,其余情况得0
二进制数1 | 二进制数2 | 按位与& 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
符号位都为1
时,得负数,其余情况得正数
// 同时为正数,原返补相同
int main()
{
short a = 123;
short b = 101;
short res = a & b;
// a == 0000 0000 0111 1011
// b == 0000 0000 0110 0101
// res == 0000 0000 0110 0001 == 97
printf("%d\\n", res);
return 0;
}
// 同时为负数,原反补不同
int main()
{
short a = -123;
short b = -101;
short res = a & b;
// a原码 == 1000 0000 0111 1011
// a反码 == 1111 1111 1000 0100
// a补码 == 1111 1111 1000 0101
// b原码 == 1000 0000 0110 0101
// b反码 == 1111 1111 1001 1010
// b补码 == 1111 1111 1001 1011
// a & b
// a补码 == 1111 1111 1000 0101
// b补码 == 1111 1111 1001 1011
// res补 == 1111 1111 1000 0001
// res反 == 1111 1111 1000 0000
// res原 == 1000 0000 0111 1111== -127
printf("%d\\n", res);
return 0;
}
// 一负一正,符号位会变为0,也就是一正一负按位与&得正数
int main()
{
short a = 123;
short b = -101;
short res = a & b;
// a == 0000 0000 0111 1011
// b补 == 1111 1111 1001 1011
// res == 0000 0000 0001 1011 == 27
// 正数原反补相同
printf("%d\\n", res);
return 0;
}
按位或|
同二进制位都为0
时得0
,其余情况得1
二进制数 | 二进制数 | 按位或| 结果 |
---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
// 符号位都为0时,按位或`|`得正数,其余情况得负数
int main()
{
// 符号位为0
short a = 123;
short b = 101;
short res = a | b;
printf("%d\\n", res); // 127
// 符号位为1
short a1 = -123;
short b1 = -101;
short res1 = a1 | b1;
printf("%d\\n", res1); // -97
// 符号位不同
short a2 = 123;
short b2 = -101;
short res2 = a2 | b2;
printf("%d\\n", res2); // -5
return 0;
}
按位异或^
同二进制为不同时为1
,相同时为0
二进制数 | 二进制数 | 按位异或^ 结果 |
---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
// 符号位相同时,按位异或^ 得正数,符号位不同时得负数。
int main()
{
// 符号位为0
short a = 123;
short b = 101;
short res = a ^ b;
printf("%d\\n", res); // 30
// 符号位为1
short a1 = -123;
short b1 = -101;
short res1 = a1 ^ b1;
printf("%d\\n", res1); // 30
// 符号位不同
short a2 = 123;
short b2 = -101;
short res2 = a2 ^ b2;
printf("%d\\n", res2); // -32
return 0;
}
练习
练习1
不创建变量,交换两个数的值
int main()
{
int a = 10;
int b = 20;
a = a ^ b; // 30 a^b 得到一个结果
b = a ^ b; // 10 a(结果)^b 得到原a的数值
a = a ^ b; // 20 a(结果)^b(原a的值) 得到原b的值
printf("a = %d , b = %d \\n",a, b);
// 加减实现,原理相同,缺点当两个值非常大时,+运算可能会溢出,导致二进制位丢失
a = a + b; // 20 + 10 = 30
b = a - b; // 30 - 20 = 10
a = a - b; // 30 - 10 = 20
// 结果又交换回来
printf("a = %d , b = %d \\n",a, b);
return 0;
}
// 0000 1010 // a
// 0001 0100 // b
// 0001 1110 // a = 16+8+4+2 = 30
// 0001 0100 //
// 0000 1010 // b = 10
//
练习2
编写代码求一个整数在内存的二进制中1的个数。
注意:计算机在计算时,计算的是二进制补码中1的个数。
方法1:(不能计算负数)
-
创建一个变量
num
接收一个整数。 -
算出这个整数的每一位二进制数。
十进制转二进制,是十进制不断除以2,直到商为0,去商为
0
的余数往前拼接每一位余数得到的就是2进制数。-
先让
num % 2
,得到当前数的余数。 -
再让
num = num / 2
,获得下次做运算的数。 -
一直循环直到
num = 0
-
-
创建变量用于统计1的个数。
-
在循环过程中,只要
num % 2 == 1
,count++,最后count的值就是这个二进制数中1的个数。
// 这种算法不能计算负数!
int main()
{
int num = 0;
scanf("%d", &num);
int count = 0;
while(num)
{
if (num % 2 == 1)
{
count++;
}
num = num / 2;
}
printf("%d\\n", count);
return 0;
}
方法2:
每次判断数的二进制最低位是否为1,判断方法如下:
- num & 1 ,如果num的最低位为1则结果为十进制数1,count++;
- 让num右移一位,继续判断,直到这个类型的数中每一位都被判断。
int main()
{
int num = 0;
int count = 0;
scanf("%d",&num);
for(int i = 0; i < 32;i++) // int占32位空间
{
if( 1 == ((num>>i) & 1))
{
count++;
}
}
printf("%d\\n",count);
return 0;
}
// 正数 原反补相同
// 例如 num = 15
// 0000 0000 0000 0000 0000 0000 0000 1111
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0001 结果为1
// count++;
// 右移一位
// 0000 0000 0000 0000 0000 0000 0000 0111
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0001 结果为1
// count++;
// 右移一位
// 0000 0000 0000 0000 0000 0000 0000 0011
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0001 结果为1
// count++;
// ... 直到最后一位判断完毕
// 负数 原反补不同
// 例如num = -15
// 1000 0000 0000 0000 0000 0000 0000 1111
// 1111 1111 1111 1111 1111 1111 1111 0000 反码
// 1111 1111 1111 1111 1111 1111 1111 0001 补码,29个1
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 1000 0000 0000 0000 0000 0000 0000 0001 结果
// count++; count == 1
// 右移一位
// 1111 1111 1111 1111 1111 1111 1111 1000
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0000 结果
// ...
//右移到第4位,此时二进制位全为1
// 1111 1111 1111 1111 1111 1111 1111 1111
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0000 结果
// count++; count == 2
// 再右移一位
// 1111 1111 1111 1111 1111 1111 1111 1111
// 0000 0000 0000 0000 0000 0000 0000 0001 & 1
// 0000 0000 0000 0000 0000 0000 0000 0000 结果
// count++; count == 3
// ... 直到右移到最高位
方法3:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = -1;
int i = 0;
int count = 0;
while (num)
{
count++;
num = num&(num-1);
}
printf ("二进制中1的个数 = %d\\n",count);
system ("pause");
return 0;
}
// num = -1
// num原码:1000 0001
// num反码:1111 1110
// num补码:1111 1111
// 第一次循环
// 1. count++; count == 1
// 2. 执行num - 1
// num补码: 1111 1111
// 1的补码: 0000 0001
// num - 1结果: 1111 1110 // -2
// 3. 执行num & (num - 1)
// num补码 1111 1111
// num - 1 1111 1110
// 结果,补码 1111 1110
// 结果,反码 1111 1101
// 结果,原码 1000 0010
// num == -2,进入第二次循环
// 第二次循环
// 1. count++; count == 2
// 2. 执行num - 1
// num补码: 1111 1110
// 1的补码: 0000 0001
// num - 1结果: 1111 1101 // -3
// 3. 执行num & (num - 1)
// num补码 1111 1110
// num - 1 1111 1101
// 结果,补码 1111 1100
// 结果,反码 1111 1011
// 结果,原码 1000 0100
// num == -4,进入第三次循环
// 第三次循环
// 1. count++; count == 3
// 2. 执行num - 1
// num补码: 1111 1100
// 1的补码: 0000 0001
// num - 1结果: 1111 1011 // -5
// 3. 执行num & (num - 1)
// num补码 1111 1100
// num - 1 1111 1011
// 结果,补码 1111 1000
// 结果,反码 1111 0111
// 结果,原码 1000 1000
// num == -8,进入第四次循环
赋值操作符
把 =
右边的值赋给左边,左值必须是可操作数,不能是常量 。
连续赋值
这种写法不推荐,语义表达不清楚
a = x = y + 1;
// ⬅⬅⬅⬅⬅⬅⬅⬅⬅
// 执行顺序从右往左
// 先将y + 1赋值给 x;
// 再将 x 赋值给 y
符合赋值操作符
操作符 | 用法 | 等价 |
---|---|---|
+= | a += 1 | a = a + 1 |
-= | a -= 1 | a = a - 1 |
*= | a *= 1 | a = a * 1 |
/= | a /= 1 | a = a / 1 |
%= | a %= 2 | a = a % 2 |
>>= | a >>= 1 | a = a >> 1 |
<<= |