目录
前言
操作符又称 “运算符”,它是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言中定义了丰富的运算符,熟练地运用操作符能让我们敲代码更加得心应手
1. 知识点补充 — 原码、反码、补码
一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用机器数的最高位的1位是被当作符号位,用来存放符号,剩余的都是数值位
在符号位上,用 0 表示正数,用 1 表示负数
正整数的原、反、补码都相同
负整数的原、反、补码不尽相同
原码:直接将数值按照正负数形式翻译成二进制得到的就是原码
反码:原码的符号位不变,其他位次全部取反得到的就是补码
补码:反码 + 1 得到的就是补码
注意:对于整型来说,数据在内存中是以补码形式存放的
举个 “栗子” :(整型为 4 个字节,共 32 个 bit 位)
int a = 1; // 原、反、补码一样
// 原码:0000 0000 0000 0000 0000 0000 0000 0001
// 反码:0000 0000 0000 0000 0000 0000 0000 0001
// 补码:0000 0000 0000 0000 0000 0000 0000 0001
int b = -1;
// 原码:1000 0000 0000 0000 0000 0000 0000 0001 // 符号位为 1
// 反码:1111 0000 0000 0000 0000 0000 0000 1110 // 符号位不变,其余位按位取反
// 补码:1111 0000 0000 0000 0000 0000 0000 1111 // 反码 + 1
那我们要怎么从补码得到原码呢?有人可能会说把上面的方法逆推回去不就行了嘛。哎,这种想法肯定是没有问题滴
但实际上我们也可以通过 — 补码取反,+ 1 来得到原码 ,如图
以上就是简单的原码、反码、补码的知识普及,为的是让我们更好地理解后面的操作符知识。好,那么正片开始
2. 算术操作符
算数操作符 | 功能描述 |
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法(整除) |
% | 取余(取模) |
注意:
- +、-、*、/ 这四个操作符都可以用与整型和浮点型地运算
- 取余 % 操作符只能用在两个整型相除,此时返回的是余数,故称 取余
- 如果除号 / 的两个操作数都为整型,则运算后得到数也会是整型,如果想得到浮点型,两个操作数必须至少有一个为浮点数
演示:当 a = 20;b = 10;c = 20.0
#include <stdio.h>
int main()
{
int a = 20;
int b = 10;
float c = 20.0;
printf("%d\n", a + b);
printf("%d\n", a - b);
printf("%d\n", a * b);
printf("%d\n", a / b);//整型
printf("%f\n", c / b);//浮点型
printf("%d\n", a % b);
return 0;
}
运行结果为:
3. 赋值操作符
赋值操作符有两种: = 和 复合赋值
在创建变量时,给一个初始化,在变量创建好后,再给一个值,这就叫赋值 =
int a = 10; // 初始化
a = 20; // 赋值,这里使用的 = 就是赋值操作符
赋值操作符也可以连续赋值 (一般不这样写)
int a = 10;
int b = 10;
int c = 0;
c = b = a + 10; // 连续赋值,从左到右依次赋值
而当我们需要对一个数进行自增、自减等操作时,可能会这样写:
int a = 10;
a = a + 3;
a = a - 3;
看起来是不是有些麻烦。事实上,我们可以用复合赋值来让代码看起来更加舒服,如:
int a = 10;
a += 3; =====> a = a + 3
a -= 3; =====> a = a - 3
下面是一些常见的复合赋值操作符:
加等 | 减等 | 乘等 | 除等 | 取模等 |
---|---|---|---|---|
+= | -= | *= | /= | %= |
4. 移位操作符
左移操作符 | << |
右移操作符 | >> |
注意:移位操作符的操作数只能是整数,并且移位是在二进制层面进行的
左移操作符的移位规则为:左边丢弃,右边补 0
右移操作符的移位规则有两种,分别为:
- 逻辑右移:左边补 0,右边丢弃
- 算术右移:左边用符号位的原值补充,右边丢弃
- 具体是哪种取决于编译器,但大部分编译器是算术右移
举个 “栗子”:
int a = 10;
int b = a << 1; // 左移一位
int c = a >> 1; // 右移一位
分析和结果:
b = a << 1 = 10 << 1 = 20; c = a >> 1 = 10 >> 1 = 5;
注意:
- 位移操作后不改变原值,a 的值仍为 10
- 移位是不能移负数位的
5. 位操作符
按位与 | & |
按位或 | | |
按位异或 | ^ |
按位取反 | ~ |
注意:
- 位操作符的操作数必须为整数
- 是在二进制层面对操作数进行运算
- 在计算机中,数据存的都是二进制的补码
符号说明:
& ————> 有 0 就 0,同 1 则 1
| ————> 有 1 就 1,同 0 则 0
^ ————> 相同为 0,不同为 1
~ ————> 取反(符号位也要取反)
1. & (按位与:有 0 就 0,同 1 则 1)
举个 “栗子”:
int a = -3 ;
int b = 5 ;
int c = a & b ;
因为在计算机中,数据在内存中都是以二进制的补码存储的,所以在计算前我们需要把操作数都化为补码形式
在开头,我们一起学习了原码、反码、补码之间相互转化的规则,现在就让我们来实践一下吧,如图:
计算结果为:int c = a & b = 5
2. | (按位或:有 1 就 1,同 0 则 0)
再举个 “栗子”:
int a = -3 ;
int b = 5 ;
int d = a | b ;
如图:
计算结果为:int d = a | b = -3
3. ^ (按位异或:相同为 0,不同为 1)
再再举个 “栗子”:
int a = -3 ;
int b = 5 ;
int e = a ^ b ;
如图:
计算结果为:int e = a ^ b = -8
4. ~ (取反:全部取反,符号位也不例外)
最后举个 “栗子”:
int a = 0 ;
int f = ~ a ;
如图:
计算结果为:int f = ~a = -1
知识补充:
- 对于按位异或操作符 ^ :a ^ a = 0 ; 0 ^ a = a(支持交换律)
- 对于按位与操作符 & :若 a & 1 == 1,说明 a 的二进制中最低为是 1;若 a & 1 == 0,说明 a 的二进制中最低位是 0
- 公式(用于去掉位次最右边的 1): a = a & ( a - 1 )
例题一:不能创建临时变量,实现两个数的交换(知识点1)
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("a = %d b = %d\n", a, b);
a = a ^ b;
b = a ^ b; // b = (a ^ b) ^ b
a = a ^ b; // a = (a ^ b) ^ a
printf("a = %d b = %d\n", a, b);
return 0;
}
例题二:求一个整数存储在内存中的二进制中 1 的个数(知识点2)
(提示:负数在内存中是以补码形式存储的)
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if ((n & 1) == 1)
{
count++;
}
}
printf("二进制中 1 的个数:%d", count);
return 0;
}
在上面的代码中,因为一个整数有 32 个比特位,所以我们必须循环 32 次才能完成统计,有没有什么优化办法呢(知识点3)
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
while (n)
{
count++;
n = n & (n - 1);
}
printf("二进制中 1 的个数:%d", count);
return 0;
}
例题三: 将 13 二进制序列的第 5 位修改为 1,然后再改为 0
13 的二进制序列:0000 0000 0000 0000 0000 0000 0000 1101
将第5位改为 1 后:0000 0000 0000 0000 0000 0000 0001 1101
将第5位改回 0 后:0000 0000 0000 0000 0000 0000 0000 1101
思路:灵活运用移位操作符和位操作符
#include <stdio.h>
int main()
{
int n = 13;
n = n | (1 << 4);
printf("n = %d\n", n);
n = n & ~(1 << 4);
printf("n = %d\n", n);
return 0;
}
6. 单目操作符
逻辑取反 | ! | 取地址 | & |
前置、后置+ | ++ | 解引用 | * |
前置、后置- | -- | 二进制取反 | ~ |
正号 | + | 求字节长度 | sizeof |
负号 | - | 强制类型转换 | ( ) |
举例说明:
!(逻辑反)
int a = 10;
if (a != 10) // 逻辑反
{
……
}
前置++、后置++
int a = 10;
int b = ++a; //此时 b = 11; a = 11 ———— 前置++:先+1,后使用
a = 10; //重置 a 的值
int c = a++; //此时 c = 10; a = 11 ———— 后置++:先使用,后+1
前置--与后置--同理,因此不再赘述
+ (正号)、 - (负号)
int a = 10;
int b = -10;
& (取地址操作符)
int a = 10;
printf("a 的地址为:%p\n", &a);//取出 a 的地址
// 打印地址要用 %p 来打印
* (解引用操作符)
int a = 10;
int* p = &a;
*p = 20; //此时 a 的值就会变成 20
~ (二进制取反)
int a = 0;
int b = ~a; // 对一个数的二进制按位取反
// b = -1
sizeof (求字节长度)
int a = 10;
size_t b = sizeof(a); // b = 4
//size_t 等同于无符号整型,专门用于接收sizeof
( ) (强制类型转换)
int a = (int)1.5;
printf("%d\n", a);
//打印结果为 1
注意:
- sizeof 是一个操作符,并不是函数,求的是操作数的类型长度(单位是字节)
- sizeof 使用时不要省略括号,求变量长度时除外
- !操作符时对一个数逻辑取反, ~ 操作符是对一个数的二级制序列按位取反,两者不一样
7. 关系操作符
大于 | 小于 | 大于等于 | 小于等于 | 不等于 | 等于 |
---|---|---|---|---|---|
> | < | >= | <= | != | == |
注意: 要把赋值符号 = 和关系等符号 == 区分开
8. 逻辑操作符
逻辑与 | 逻辑或 |
---|---|
&& | | | |
一假全假 | 一真就真 |
- 要注意按位与和逻辑与,按位或和逻辑或的区别
- 逻辑与中,只要有一个表达式为假,便不再执行后面的表达式,直接返回假
- 逻辑或中,只要有一个表达式为真,便不再执行后面的表达式,直接返回真
9. 逗号操作符
表达式1,表达式2,表达式3……表达式n
- 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
a = test1();
test2(a);
while (a > 0)
{
……;
a = test1();
test2(a);
}
我们可以用逗号表达式优化上述代码:
while (a = test1(),test2(a),a > 0)
{
……;
}
10. 下标引用、函数调用
1. [ ] (下标引用操作符)
下标引用出现在数组中,操作数为一个数组名+一个索引值
int arr[10] = { 0 };
arr[0] = 10;
// [ ] 的操作数是 ar r和 0
2. ( ) (函数调用操作符)
操作数为一个或者多个,第一个操作数就是函数名,剩余的操作数就是传递给函数的参数
test1(); //1 个操作数
test2(a, b); //3 个操作数
11. 结构成员访问
结构体对象.成员名 | 结构体指针->成员名 |
. | -> |
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[10]; //名字
int age; //年龄
};
void set_stu(struct Stu* ps)
{
strcpy(ps->name, "李四"); // 结构体成员访问
ps->age = 25; // 结构体成员访问
}
int main()
{
struct Stu s = { "张三",20 };
printf("%s %d\n", s.name, s.age); // 结构体成员访问
set_stu(&s);
printf("%s %d\n", s.name, s.age); // 结构体成员访问
return 0;
}
12. 操作符的优先级和结合性
- 优先级:如果在一个表达式中包含多个操作符,哪个操作符一个最先执行
- 结合性:如果两个操作符优先级相同,那么就得看结合性。操作符都有自己的结合性,一些是左结合,一些是右结合,得根据实际操作符来判断。大部分操作符都是左结合(从左到右执行),少数是右结合(从右到左执行),例如赋值操作符 =
下面的表格展示了大部分操作符的优先级顺序和它们各自的结合性
(表格来源于网络,如有侵权,联系删除)
结语
今天我们一起学习了各类操作符和它们的相关知识,有总结不到位的地方还请多多谅解,若有出现纰漏,希望大佬们看到错误之后能够在私信或评论区指正,博主会及时改正,共同进步!
欢迎各位在评论区友好讨论。如果觉得不错的话,麻烦您点个赞吧,十分感谢!