目录
前言
一、原码、反码、补码的基础概念
1.原码
2.反码
3.补码
二、原码、反码、补码的计算方法
1.原码
2.反码
3.补码
三、算术操作符
四、移位操作符
1. 左移操作符
移位规则:
2. 右移操作符
移位规则:
(1) 逻辑移位
(2) 算术移位
五、位操作符
1. 按位与运算(AND)
2. 按位或运算(OR)
3. 按位异或运算(XOR)
4. 取反运算(NOT)
5. 位运算的应用
(1)判断整数奇偶
(2)二进制数选取指定位
(3)将指定位设置为 1
(4)反转指定位
(5)交换两个数 —— 不借助第三变量
(6)将二进制最右侧为 1 的二进位改为 0
(7)计算二进制中二进位为 1 的个数
(8)判断某数是否为 2 的幂次方
六、赋值操作符
七、单目操作符
八、关系操作符
九、逻辑操作符
1.区分逻辑与和按位与
2.区分逻辑或和按位或
十、条件操作符(三目操作符)
使用条件表达式得到两个数的较大值
十一、逗号表达式
十二、下标引用、函数调用和结构成员
1. [ ] 下标引用操作符
2. ( ) 函数调用操作符
3. 访问一个结构的成员
十三、操作符的属性
1. 复杂表达式的求值有三个影响的因素
操作符优先级
表格说明
学了这么长时间的C语言,我也刷了不少的题目,其中每到遇见关于操作题这样的概念类型的选择题我都要标记一下,或者直接瞎选一个。现在趁着刚刚考试完期末周的劲头还没有过去,硕硕就赶紧复习加上查找资料创作出来了一篇关于C语言操作符的博客。各位看官坐稳扶好了,我们要发车了
在要学习下面的位操作符之前,让我们先了解原码、反码和补码的概念。对于一个数计算机要使用一定的编码方式进行存储,原码、反码、补码是机器存储一个具体数字的编码方式。
原码就是符号位加上真值的绝对值,即:用第一位表示符号,其余位表示值。
比如:如果是8位二进制:
[+1] 正一的原码 = 0 000 0001
[-1] 负一的原码 = 1 000 0001
第一位是符号位
因为第一位是符号位,所以8位二进制数的取值范围就是:(即第一位不表示值,只表示正负。)
[1111 1111 , 0111 1111] 即 [-127 , 127]
总结一句话:原码是人脑最容易理解和计算的表示方式。
反码的表示方法是:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
值 | 原码 | 反码 |
+1 | 0 000 0001 | 0 000 0001 |
- 1 | 1 000 0001 | 1 111 1110 |
可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值。通常要将其转换成原码再计算。
补码的表示方法是:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(也即在反码的基础上+1)
值 | 原码 | 反码 | 补码 |
+1 | 0 000 0001 | 0 000 0001 | 0 000 0001 |
- 1 | 1 000 0001 | 1 111 1110 | 1 111 1111 |
对于负数,补码表示方式也是人脑无法直观看出其数值的。通常也需要转换成原码再计算其数值。
原码:将最高位作为符号位(0表示正,1表示负),其它数字位代表数值本身的绝对值的数字表示方式。
反码:如果是正数,则表示方法和原码一样;如果是负数,符号位不变,其余各位取反,则得到这个数字的反码表示形式。
补码:如果是正数,则表示方法和原码一样;如果是负数,则将数字的反码加上1(相当于将原码数值位取反然后在最低位加1)。
算数操作符一共有五个
1、+(加)
2、-(减)
3、*(乘)
4、/(除)
5、%(取模)
1. 除了 %(取模) 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / (除)操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % (取模)操作符的两个操作数必须为整数,返回的是整除之后的余数。
<< | 左移操作符 |
>> | 右移操作符 |
注:移位操作符的操作数只能是正数。
注:移位操作符的操作数只能是整数。
首先右移运算分两种:
⭕ 逻辑移位
⭕ 算术移位
左边用0填充,右边丢弃
左边用原该值的符号位填充,右边丢弃
警告
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10;
num>>-1;//error
✅位操作符有:
& | ^ |
//按位与 //按位或 //按位异或 |
注:他们的操作数必须是整数。
✅按位与运算符为 &。其功能是对两个二进制数的每一个二进位进行与运算。
& 按位与: 两个条件同时为真(1)的情况下,运算结果为真,换句话说就是两个条件都是1才为1,否则为0。
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
✅按位或运算符为 |
。其功能对两个二进制数的每一个二进位进行或运算。
| 按位或 :任意一个条件为真(1)的情况下,运算结果为1,就是只要有一个1则为1,否则为0。
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
✅按位异或运算符为 ^。其功能是对两个二进制数的每一个二进位进行异或运算。
^ 按位异或 :两个条件中只有一个条件为真(1)的情况下,运算结果为真。也就是说,相异才为 真,相同则为假。
0 ^ 0 = 0
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 0
✅取反运算符为 ~
。其功能是对一个二进制数的每一个二进位进行取反运算。
取反运算规则:使数字 1 变为 0,0 变为 1。
~0 = 1
~1 = 0
一个整数,只要是偶数,其对应二进制数的末尾一定为 0;只要是奇数,其对应二进制数的末尾一定为 1。所以,我们通过与 1 进行按位与运算,即可判断某个数是奇数还是偶数。
(x & 1) == 0
为偶数。(x & 1) == 1
为奇数 如果我们想要从一个二进制数 X中取出某几位,使取出位置上的二进位保留原值,其余位置为 0,则可以使用另一个二进制数 Y ,使该二进制数上对应取出位置为 1,其余位置为 0。然后令两个数进行按位与运算(X & Y
),即可得到想要的数。
如果我们想要把一个二进制数 X中的某几位设置为 1,其余位置保留原值,则可以使用另一个二进制数 Y,使得该二进制上对应选取位置为 1,其余位置为 0。然后令两个数进行按位或运算(X | Y
),即可得到想要的数。
如果我们想要把一个二进制数 X 的某几位进行反转,则可以使用另一个二进制数 Y ,使得该二进制上对应选取位置为 1,其余位置为 0。然后令两个数进行按位异或运算(X ^ Y
),即可得到想要的数。
通过按位异或运算可以实现交换两个数的目的(只能用于交换两个整数)。
#include
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
如果我们想要将一个二进制数 X 最右侧为 1 的二进制位改为 0,则只需通过 X & (X - 1)
的操作即可完成。
从“将二进制最右侧为 1 的二进位改为 0 ”中得知,通过 X & (X - 1)
我们可以将二进制 X 最右侧为 1 的二进制位改为 0,那么如果我们不断通过 X & (X - 1)
操作,最终将二进制 X 变为 0,并统计执行次数,则可以得到二进制中二进位为 1 的个数。
#include
int TheOne(int x)
{
int count = 0;
while(x)
{
x = x & (x - 1);
count++;
}
return count;
}
通过判断 X & (X - 1) == 0
是否成立,即可判断 X 是否为 2 的幂次方。这是因为:
凡是 2 的幂次方,其二进制数的某一高位为 1,并且仅此高位为 1,其余位都为 0。
不是 2 的幂次方,其二进制数存在多个值为 1 的位。
接下来我们使用 X & (X - 1)
操作,将原数对应二进制数最右侧为 1 的二进位改为 0 之后,得到新值:
- 如果原数是 2 的幂次方,则通过
X & (X - 1)
操作之后,新值所有位都为 0,值为 0。- 如果该数不是 2 的幂次方,则通过
X & (X - 1)
操作之后,新值仍存在不为 0 的位,值肯定不为 0。
所以我们可以通过是否为 0 即可判断该数是否为 2 的幂次方。
赋值操作符是一个很好用的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值
复合赋值符
1、 + =
2、 - =
2、 * =
4、 / =
5、 %=
6、 >>=
7、 <<=
8、 &=
9、 |=
10、^=
这些运算符都可以写成复合的效果
int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁。
! | 逻辑反操作 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制按位取反 |
- - | 前置、后置- - |
+ + | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
> | 用于比较左右两个值的大小 |
>= | 用于比较左右两个值的大小 |
< |
用于比较左右两个值的大小 |
<= |
用于比较左右两个值的大小 |
! = | 用于测试“不相等” |
== | 用于测试“相等” |
这些关系运算符比较简单,但是我们要注意一些运算符使用时候的陷阱。
警告:在编程的过程中== 和=不小心写错,导致的错误
&& | 逻辑与 |
|| | 逻辑或 |
按位与:1&2----->0
逻辑与:1&&2---->1
按位或:1|2----->3
逻辑或:1||2---->1
exp1 ? exp2 : exp3
这个也是三目操作符
⭕如果表达式1为真,那么就计算表达式2,表达式2的结果为整个式子的 结果;
⭕如果表达式1为假,那么就计算表达式3,表达式3的结果为整个式子的结果。
例:问:将下列式子转化为条件表达式是什么样的?
if (a > 5)
b = 3;
else
b = -3;
答案:
b = (a > 5 ? 3 : -3);
#include
int main(void)
{
int a = 0, b = 0, c = 0;
printf("请输入两个数:\n");
scanf_s("%d %d", &a, &b);
c = (a > b ? a : b);
printf("较大的数为:%d\n", c);
return 0;
}
exp1, exp2, exp3, …expN
⭕逗号表达式,就是用逗号隔开的多个表达式。
⭕逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
上面C的值最后等于13
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
. | 结构体.成员名 |
-> | 结构体指针->成员名 |
#include
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
}
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
优先级 |
运算符 |
名称或含义 |
使用形式 |
结合方向 |
说明 |
1 |
[ ] |
数组下标 |
数组名[常量表达式] |
左到右 |
----- |
( ) |
圆括号 |
(表达式)/函数名(形参表) |
----- |
||
. |
成员选择(对象) |
对象.成员名 |
----- |
||
-> |
成员选择(指针) |
对象指针->成员名 |
----- |
||
2 |
- |
负号运算符 |
-表达式 |
右到左 |
单目运算符 |
(类型) |
强制类型转换 |
(数据类型)表达式 |
----- |
||
++ |
前置自增运算符 |
++变量名 |
单目运算符 |
||
++ |
后置自增运算符 |
变量名++ |
单目运算符 |
||
-- |
前置自减运算符 |
--变量名 |
单目运算符 |
||
-- |
后置自减运算符 |
变量名-- |
单目运算符 [4] |
||
* |
取值运算符 |
*指针变量 |
单目运算符 |
||
& |
取地址运算符 |
&变量名 |
单目运算符 |
||
! |
逻辑非运算符 |
!表达式 |
单目运算符 |
||
~ |
按位取反运算符 |
~表达式 |
单目运算符 |
||
sizeof |
长度运算符 |
sizeof(表达式) |
----- |
||
3 |
/ |
除 |
表达式/表达式 |
左到右 |
双目运算符 |
* |
乘 |
表达式*表达式 |
双目运算符 |
||
% |
余数(取模) |
整型表达式/整型表达式 |
双目运算符 |
||
4 |
+ |
加 |
表达式+表达式 |
左到右 |
双目运算符 |
- |
减 |
表达式-表达式 |
双目运算符 |
||
5 |
<< |
左移 |
变量 |
左到右 |
双目运算符 |
>> |
右移 |
变量>>表达式 |
双目运算符 |
||
6 |
> |
大于 |
表达式>表达式 |
左到右 |
双目运算符 |
>= |
大于等于 |
表达式>=表达式 |
双目运算符 |
||
< |
小于 |
表达式 |
双目运算符 |
||
<= |
小于等于 |
表达式 |
双目运算符 |
||
7 |
== |
等于 |
表达式==表达式 |
左到右 |
双目运算符 |
!= |
不等于 |
表达式!= 表达式 |
双目运算符 |
||
8 |
& |
按位与 |
表达式&表达式 |
左到右 |
双目运算符 |
9 |
^ |
按位异或 |
表达式^表达式 |
左到右 |
双目运算符 |
10 |
| |
按位或 |
表达式|表达式 |
左到右 |
双目运算符 |
11 |
&& |
逻辑与 |
表达式&&表达式 |
左到右 |
双目运算符 |
12 |
|| |
逻辑或 |
表达式||表达式 |
左到右 |
双目运算符 |
13 |
? : |
条件运算符 |
表达式1? 表达式2: 表达式3 |
右到左 |
三目运算符 |
14 |
= |
赋值运算符 |
变量=表达式 |
右到左 |
----- |
/ = |
除后赋值 |
变量/=表达式 |
----- |
||
* = |
乘后赋值 |
变量*=表达式 |
----- |
||
% = |
取模后赋值 |
变量%=表达式 |
----- |
||
+ = |
加后赋值 |
变量+=表达式 |
----- |
||
- = |
减后赋值 |
变量-=表达式 |
----- |
||
<< = |
左移后赋值 |
变量 |
----- |
||
>> = |
右移后赋值 |
变量>>=表达式 |
----- |
||
& = |
按位与后赋值 |
变量&=表达式 |
----- |
||
^ = |
按位异或后赋值 |
变量^=表达式 |
----- |
||
| = |
按位或后赋值 |
变量|=表达式 |
----- |
||
15 |
, |
逗号运算符 |
表达式,表达式,… |
左到右 |
从左向右顺序运算 |
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符