目录
前言
一、原码、反码、补码的基础概念
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语言操作符的博客。各位看官坐稳扶好了,我们要发车了🥰🥰
一、原码、反码、补码的基础概念
在要学习下面的位操作符之前,让我们先了解原码、反码和补码的概念。对于一个数计算机要使用一定的编码方式进行存储,原码、反码、补码是机器存储一个具体数字的编码方式。
1.原码
🍔原码就是符号位加上真值的绝对值,即:用第一位表示符号,其余位表示值。
比如:如果是8位二进制:
[+1] 正一的原码 = 0 000 0001
[-1] 负一的原码 = 1 000 0001
🍪第一位是符号位
🥝因为第一位是符号位,所以8位二进制数的取值范围就是:(即第一位不表示值,只表示正负。)
[1111 1111 , 0111 1111] 即 [-127 , 127]
💧总结一句话:原码是人脑最容易理解和计算的表示方式。
2.反码
🍔反码的表示方法是:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
值 | 原码 | 反码 |
+1 | 0 000 0001 | 0 000 0001 |
- 1 | 1 000 0001 | 1 111 1110 |
💧可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值。通常要将其转换成原码再计算。
3.补码
🍔补码的表示方法是:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(也即在反码的基础上+1)
值 | 原码 | 反码 | 补码 |
+1 | 0 000 0001 | 0 000 0001 | 0 000 0001 |
- 1 | 1 000 0001 | 1 111 1110 | 1 111 1111 |
💧对于负数,补码表示方式也是人脑无法直观看出其数值的。通常也需要转换成原码再计算其数值。
二、原码、反码、补码的计算方法
1.原码
🍪原码:将最高位作为符号位(0表示正,1表示负),其它数字位代表数值本身的绝对值的数字表示方式。
2.反码
🍪反码:如果是正数,则表示方法和原码一样;如果是负数,符号位不变,其余各位取反,则得到这个数字的反码表示形式。
3.补码
🍪补码:如果是正数,则表示方法和原码一样;如果是负数,则将数字的反码加上1(相当于将原码数值位取反然后在最低位加1)。
三、算术操作符
🍔算数操作符一共有五个
1、+(加)
2、-(减)
3、*(乘)
4、/(除)
5、%(取模)
🔴1. 除了 %(取模) 操作符之外,其他的几个操作符可以作用于整数和浮点数。
🔴2. 对于 / (除)操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
🔴3. % (取模)操作符的两个操作数必须为整数,返回的是整除之后的余数。
四、移位操作符
<< | 左移操作符 |
>> | 右移操作符 |
🚨注:移位操作符的操作数只能是正数。
🚨注:移位操作符的操作数只能是整数。
1. 左移操作符
🍟移位规则:左边抛弃、右边补0
2. 右移操作符
🍟移位规则:
首先右移运算分两种:
⭕ 逻辑移位
⭕ 算术移位
(1) 逻辑移位
左边用0填充,右边丢弃
(2) 算术移位
左边用原该值的符号位填充,右边丢弃
警告🚨
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10;
num>>-1;//error
五、位操作符
✅位操作符有:
& | ^ | //按位与 //按位或 //按位异或 |
🚨注:他们的操作数必须是整数。
1. 按位与运算(AND)
✅按位与运算符为 &。其功能是对两个二进制数的每一个二进位进行与运算。
& 按位与: 两个条件同时为真(1)的情况下,运算结果为真,换句话说就是两个条件都是1才为1,否则为0。
-
1 & 1 = 1
-
1 & 0 = 0
-
0 & 1 = 0
-
0 & 0 = 0
2. 按位或运算(OR)
✅按位或运算符为 |
。其功能对两个二进制数的每一个二进位进行或运算。
| 按位或 :任意一个条件为真(1)的情况下,运算结果为1,就是只要有一个1则为1,否则为0。
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
3. 按位异或运算(XOR)
✅按位异或运算符为 ^。其功能是对两个二进制数的每一个二进位进行异或运算。
^ 按位异或 :两个条件中只有一个条件为真(1)的情况下,运算结果为真。也就是说,相异才为 真,相同则为假。
-
0 ^ 0 = 0
-
1 ^ 0 = 1
-
0 ^ 1 = 1
-
1 ^ 1 = 0
4. 取反运算(NOT)
✅取反运算符为 ~
。其功能是对一个二进制数的每一个二进位进行取反运算。
取反运算规则:使数字 1 变为 0,0 变为 1。
~0 = 1
~1 = 0
5. 位运算的应用
(1)判断整数奇偶
🍔一个整数,只要是偶数,其对应二进制数的末尾一定为 0;只要是奇数,其对应二进制数的末尾一定为 1。所以,我们通过与 1 进行按位与运算,即可判断某个数是奇数还是偶数。
(x & 1) == 0
为偶数。(x & 1) == 1
为奇数
(2)二进制数选取指定位
🍔如果我们想要从一个二进制数 X中取出某几位,使取出位置上的二进位保留原值,其余位置为 0,则可以使用另一个二进制数 Y ,使该二进制数上对应取出位置为 1,其余位置为 0。然后令两个数进行按位与运算(X & Y
),即可得到想要的数。
(3)将指定位设置为 1
🍔如果我们想要把一个二进制数 X中的某几位设置为 1,其余位置保留原值,则可以使用另一个二进制数 Y,使得该二进制上对应选取位置为 1,其余位置为 0。然后令两个数进行按位或运算(X | Y
),即可得到想要的数。
(4)反转指定位
🍔如果我们想要把一个二进制数 X 的某几位进行反转,则可以使用另一个二进制数 Y ,使得该二进制上对应选取位置为 1,其余位置为 0。然后令两个数进行按位异或运算(X ^ Y
),即可得到想要的数。
(5)交换两个数 —— 不借助第三变量
🍔通过按位异或运算可以实现交换两个数的目的(只能用于交换两个整数)。
#include <stdio.h>
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;
}
(6)将二进制最右侧为 1 的二进位改为 0
🍔如果我们想要将一个二进制数 X 最右侧为 1 的二进制位改为 0,则只需通过 X & (X - 1)
的操作即可完成。
(7)计算二进制中二进位为 1 的个数
🍔从“将二进制最右侧为 1 的二进位改为 0 ”中得知,通过 X & (X - 1)
我们可以将二进制 X 最右侧为 1 的二进制位改为 0,那么如果我们不断通过 X & (X - 1)
操作,最终将二进制 X 变为 0,并统计执行次数,则可以得到二进制中二进位为 1 的个数。
#include <stdio.h>
int TheOne(int x)
{
int count = 0;
while(x)
{
x = x & (x - 1);
count++;
}
return count;
}
(8)判断某数是否为 2 的幂次方
🍔通过判断 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.区分逻辑与和按位与
按位与:1&2----->0
逻辑与:1&&2---->1
2.区分逻辑或和按位或
按位或: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<stdio.h>
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
十二、下标引用、函数调用和结构成员
1. [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
2. ( ) 函数调用操作符
🍟接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
3. 访问一个结构的成员
. | 结构体.成员名 |
-> | 结构体指针->成员名 |
#include <stdio.h>
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. 复杂表达式的求值有三个影响的因素
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级(表)
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [ ] | 数组下标 | 数组名[常量表达式] | 左到右 | ----- |
( ) | 圆括号 | (表达式)/函数名(形参表) | ----- | ||
. | 成员选择(对象) | 对象.成员名 | ----- | ||
-> | 成员选择(指针) | 对象指针->成员名 | ----- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | ----- | ||
++ | 前置自增运算符 | ++变量名 | 单目运算符 | ||
++ | 后置自增运算符 | 变量名++ | 单目运算符 | ||
-- | 前置自减运算符 | --变量名 | 单目运算符 | ||
-- | 后置自减运算符 | 变量名-- | 单目运算符 [4] | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | ----- | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式/整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ? : | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | ----- |
/ = | 除后赋值 | 变量/=表达式 | ----- | ||
* = | 乘后赋值 | 变量*=表达式 | ----- | ||
% = | 取模后赋值 | 变量%=表达式 | ----- | ||
+ = | 加后赋值 | 变量+=表达式 | ----- | ||
- = | 减后赋值 | 变量-=表达式 | ----- | ||
<< = | 左移后赋值 | 变量 | ----- | ||
>> = | 右移后赋值 | 变量>>=表达式 | ----- | ||
& = | 按位与后赋值 | 变量&=表达式 | ----- | ||
^ = | 按位异或后赋值 | 变量^=表达式 | ----- | ||
| = | 按位或后赋值 | 变量|=表达式 | ----- | ||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | 从左向右顺序运算 |
表格说明
同一优先级的运算符,运算次序由结合方向所决定。
😍 简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符