你们好,我是一名嵌入式工程师。我正在持续更新《嵌入式工程师的C语言实战》系列教程,目前已更新4篇。如果你也在学习嵌入式,欢迎点个关注,我们一起从零到一。
文章目录
- 前言
- 一、算术运算符(Arithmetic Operators)
- 二、关系运算符(Relational Operators)
- 三、逻辑运算符(Logical Operators)
- 四、位运算符(Bitwise Operators)
- 五、赋值运算符(Assignment Operators)
- 六、自增自减运算符(Increment/Decrement Operators)
- 七、条件运算符(Conditional Operator / 三目运算符)
- 八、逗号运算符(Comma Operator)
- 九、sizeof 运算符
- 十、地址与间接访问运算符
- 十一、成员访问运算符
- 十二、其他运算符
- 十三、运算符优先级速查表(从高到低)
- 如果这篇文章对你有帮助,欢迎点赞、收藏、关注,主播是一个马上粉丝量超1个的嵌入式内容主播。谢谢!
前言
C语言的运算符非常丰富,涵盖了算术、关系、逻辑、位运算、赋值等各个领域。下面为你系统性地分类整理,并附上详细的说明和示例。
C语言的运算符覆盖了从基础算术到内存操作的方方面面,按功能可归纳为:
| 分类 | 运算符 |
|---|---|
| 算术类 | + - * / % ++ – |
| 比较类 | > < >= <= == != |
| 逻辑类 | && || ! |
| 位操作类 | & | ^ ~ << >> |
| 赋值类 | = 及其复合形式 |
| 地址与访问类 | & * . -> |
| 其他 | sizeof ?: , () [] (type) |
一、算术运算符(Arithmetic Operators)
用于执行基本的数学运算。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| + | 加法 | a + b | 两数相加 |
| - | 减法 | a - b | 两数相减 |
| * | 乘法 | a * b | 两数相乘 |
| / | 除法 | a / b | 两数相除(整数除法会截断) |
| % | 取模(求余) | a % b | 返回两数相除的余数(仅用于整数) |
#include<stdio.h>intmain(){inta=10,b=3;printf("10 + 3 = %d\n",a+b);// 13printf("10 - 3 = %d\n",a-b);// 7printf("10 * 3 = %d\n",a*b);// 30printf("10 / 3 = %d\n",a/b);// 3(整数除法截断)printf("10 %% 3 = %d\n",a%b);// 1(余数)return0;}二、关系运算符(Relational Operators)
用于比较两个值的大小关系,返回 1(真)或 0(假)。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| > | 大于 | a > b | a 是否大于 b |
| < | 小于 | a < b | a 是否小于 b |
| >= | 大于等于 | a >= b | a 是否大于等于 b |
| <= | 小于等于 | a <= b | a 是否小于等于 b |
| == | 等于 | a == b | a 是否等于 b(注意是两个等号) |
| != | 不等于 | a != b | a 是否不等于 b |
inta=5,b=5;printf("%d\n",a==b);// 输出 1(真)printf("%d\n",a>b);// 输出 0(假)常见错误:把 == 写成 =,会导致赋值而非比较。
三、逻辑运算符(Logical Operators)
用于组合多个条件,返回 1(真)或 0(假)。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| && | 逻辑与 | a && b | 两个条件都为真时才为真(短路求值) |
| || | 逻辑或 | a || b | 至少一个条件为真时为真(短路求值) |
| ! | 逻辑非 | !a | 取反(真变假,假变真) |
intage=20,score=85;if(age>=18&&score>=60){printf("成年且及格\n");}if(!(age<18)){printf("已成年\n");}短路求值示例:
intx=0;if(x!=0&&10/x>1){// 因为 x != 0 为假,短路,不会执行 10 / x,避免除零错误// ...}四、位运算符(Bitwise Operators)
用于对整数的二进制位进行操作。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| & | 按位与 | a & b | 对应位都为 1 时结果为 1 |
| | | 按位或 | a | b | 对应位至少一个为 1 时结果为 1 |
| ^ | 按位异或 | a ^ b | 对应位不同时为 1,相同时为 0 |
| ~ | 按位取反 | ~a | 所有位取反(0变1,1变0) |
| << | 左移 | a << n | 所有位左移 n 位,低位补 0 |
| >> | 右移 | a >> n | 所有位右移 n 位(符号位取决于类型) |
unsignedchara=0b1100;// 12unsignedcharb=0b1010;// 10printf("a & b = %d\n",a&b);// 8 (0b1000)printf("a | b = %d\n",a|b);// 14 (0b1110)printf("a ^ b = %d\n",a^b);// 6 (0b0110)printf("~a = %d\n",~a);// 243 (0b11110011)printf("a << 2 = %d\n",a<<2);// 48 (0b110000)printf("a >> 2 = %d\n",a>>2);// 3 (0b0011)五、赋值运算符(Assignment Operators)
用于给变量赋值,其中复合赋值运算符结合了运算和赋值。
| 运算符 | 含义 | 示例 | 等价于 |
|---|---|---|---|
| = | 简单赋值 | a = 10 | a = 10 |
| += | 加后赋值 | a += 5 | a = a + 5 |
| -= | 减后赋值 | a -= 5 | a = a - 5 |
| *= | 乘后赋值 | a *= 5 | a = a * 5 |
| /= | 除后赋值 | a /= 5 | a = a / 5 |
| %= | 取模后赋值 | a %= 5 | a = a % 5 |
| &= | 按位与后赋值 | a &= 5 | a = a & 5 |
| |= | 按位或后赋值 | a |= 5 | a = a | 5 |
| ^= | 按位异或后赋值 | a ^= 5 | a = a ^ 5 |
| <<= | 左移后赋值 | a <<= 2 | a = a << 2 |
| >>= | 右移后赋值 | a >>= 2 | a = a >> 2 |
intx=10;x+=5;// x = 15x*=2;// x = 30printf("%d\n",x);六、自增自减运算符(Increment/Decrement Operators)
用于将变量的值增加或减少 1。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| ++ | 自增 | a++ 或 ++a | 变量加 1 |
| – | 自减 | a-- 或 --a | 变量减 1 |
前缀 vs 后缀:
前缀(++a):先自增,再使用变量的值。
后缀(a++):先使用变量的值,再自增。
inta=5;intb=++a;// a 变为 6,b 被赋值为 6intc=a--;// c 被赋值为 6(使用 a 的当前值),然后 a 变为 5printf("a=%d, b=%d, c=%d\n",a,b,c);// a=5, b=6, c=6七、条件运算符(Conditional Operator / 三目运算符)
唯一的三目运算符,用于简化 if-else。
| 运算符 | 语法 | 说明 |
|---|---|---|
| ?: | 条件 ? 表达式1 : 表达式2 | 若条件为真,取表达式1的值,否则取表达式2的值 |
intage=20;constchar*status=(age>=18)?"成年":"未成年";printf("%s\n",status);// 输出:成年八、逗号运算符(Comma Operator)
用于将多个表达式串联成一个表达式,按从左到右的顺序执行,整个表达式的值是最右边表达式的值。
inta,b;a=(b=3,b+2);// 先执行 b=3,然后执行 b+2=5,整个表达式值为 5printf("a=%d, b=%d\n",a,b);// a=5, b=3九、sizeof 运算符
sizeof 是 C/C++ 中一个编译时的一元运算符,它用于计算一个类型或变量在内存中占用的字节数。它既不是函数(尽管写法像),也不是关键字,而是一个运算符。
9.1 sizeof 的核心作用
获取类型大小:查询基本类型(int, double)或自定义类型(struct, enum)的存储大小。
获取变量大小:获取任何变量或表达式结果的大小。
辅助内存分配:在 malloc、calloc 或 new 时,用于计算需要分配的内存大小。
计算数组元素个数:通过 sizeof(array) / sizeof(array[0]) 得到数组长度。
9.2 sizeof 的常见用法
#include<stdio.h>intmain(){// 1. 作用在类型上printf("int 大小: %zu\n",sizeof(int));// 通常 4printf("double 大小: %zu\n",sizeof(double));// 通常 8// 2. 作用在变量上inta=10;doubled=3.14;printf("变量 a 大小: %zu\n",sizeof(a));// 4printf("变量 d 大小: %zu\n",sizeofd);// 8(括号可省略)// 3. 计算数组长度intarr[10]={0};size_tlen=sizeof(arr)/sizeof(arr[0]);// 10/1 = 10printf("数组元素个数: %zu\n",len);// 4. 动态内存分配int*p=(int*)malloc(10*sizeof(int));// 分配 10 个 int 的空间if(p)free(p);return0;}9.3 sizeof 的注意事项
9.3.1 sizeof 是编译时求值:sizeof 的结果在编译阶段就确定了,因此它不关心括号内的内容(变长数组除外)。
括号内是 0 还是 100,对结果没有任何影响。
#include<stdio.h>intmain(){intx=10;// 这两种写法结果完全相同,都是 sizeof(int)printf("sizeof(x) = %zu\n",sizeof(x));// 输出 4(假设 int 为 4 字节)printf("sizeof(x+5) = %zu\n",sizeof(x+5));// 输出 4(表达式类型是 int)return0;}关键点:sizeof(x+5) 不会计算 x+5 的值,它只推导出 (x+5) 的类型是 int,所以结果是 4。
区别对待:变长数组(VLA)是唯一的例外
只有变长数组(VLA) 的 sizeof 是在运行时求值的。这时,编译器必须知道其具体长度,因此 sizeof 会“关心”括号内的内容(即数组的长度表达式)。
#include<stdio.h>intmain(){intn=5;intvla[n];// 变长数组(VLA)printf("sizeof(vla) = %zu\n",sizeof(vla));// 输出 20(5*4),在运行时计算printf("sizeof(vla) = %zu\n",sizeof(int[n]));// 输出 20,也是运行时求值return0;}9.3.2 对数组和指针的区分:
intarr[10];int*ptr=arr;printf("数组: %zu\n",sizeof(arr));// 40 (10*4)printf("指针: %zu\n",sizeof(ptr));// 8 (64位系统)结构体对齐填充:sizeof 结构体返回的是包含内存对齐填充后的总大小,可能大于成员大小之和。
9.4 sizeof 与 strlen 的区别
这是初学者最容易混淆的两个概念。sizeof 是运算符,strlen 是函数,它们有本质区别。
| 特性 | sizeof | strlen |
|---|---|---|
| 本质 | 运算符(编译时求值) | 函数(运行时求值) |
| 计算对象 | 类型、变量、任何表达式 | 必须以 ‘\0’ 结尾的字符串 |
| 计算内容 | 在内存中占用的总字节数 | 字符串中 ‘\0’ 之前的字符个数 |
| 是否包含结尾符 | 包含(数组或字符串字面量) | 不包含 |
| 参数要求 | 任何类型 | 必须是 char* 类型 |
示例对比:
#include<stdio.h>#include<string.h>intmain(){charstr[]="Hello";// 实际存储: H e l l o \0printf("sizeof(str) = %zu\n",sizeof(str));// 输出 6 (包含 '\0')printf("strlen(str) = %zu\n",strlen(str));// 输出 5 (不包含 '\0')// 对指针使用 sizeof 与 strlen 的区别char*p="Hello";printf("sizeof(p) = %zu\n",sizeof(p));// 输出 8 (指针本身大小)printf("strlen(p) = %zu\n",strlen(p));// 输出 5 (字符串长度)}总结
sizeof:问的是“这个类型或变量在内存里占多大的空间?”
strlen:问的是“从字符串开头到第一个 ‘\0’ 之前有多少个字符?”
十、地址与间接访问运算符
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| & | 取地址 | &var | 获取变量的内存地址 |
| * | 解引用(间接访问) | *ptr | 访问指针指向的内存内容 |
intx=10;int*p=&x;// p 存储 x 的地址printf("x 的值: %d\n",*p);// 通过指针访问 x 的值,输出 10*p=20;// 通过指针修改 x 的值printf("x 的新值: %d\n",x);// 输出 20十一、成员访问运算符
用于访问结构体或联合体的成员。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| . | 直接成员访问 | struct_var.member | 通过结构体变量访问成员 |
| -> | 间接成员访问 | struct_ptr->member | 通过结构体指针访问成员(等价于 (*ptr).member) |
structPoint{intx,y;};structPointp={10,20};structPoint*ptr=&p;printf("%d\n",p.x);// 10printf("%d\n",ptr->y);// 20(等价于 (*ptr).y)十二、其他运算符
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
| () | 函数调用 | func(a, b) | 调用函数 |
| [] | 下标访问 | arr[3] | 访问数组元素 |
| (type) | 强制类型转换 | (float)3 | 将值转换为指定类型 |
floatf=(float)10/3;// 强制将 10 转为 float,结果 3.333intarr[5]={1,2,3,4,5};intval=arr[2];// val = 3十三、运算符优先级速查表(从高到低)
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 最高 | () [] -> . | 从左到右 |
| ↑ | ! ~ ++ – - + * & (type) sizeof | 从右到左(单目) |
| ↑ | * / % | 从左到右 |
| ↑ | + - | 从左到右 |
| ↑ | << >> | 从左到右 |
| ↑ | < <= > >= | 从左到右 |
| ↑ | == != | 从左到右 |
| ↑ | & | 从左到右 |
| ↑ | ^ | 从左到右 |
| ↑ | | | 从左到右 |
| ↑ | && | 从左到右 |
| ↑ | || | 从左到右 |
| ↑ | ?: | 从右到左(三目) |
| ↑ | = += -= *= /= %= &= ^= |= <<= >>= | 从右到左 |
| 最低 | , | 从左到右 |
建议:不必死记优先级表,在复杂表达式中多用括号 () 来明确运算顺序,既能提高可读性,又能避免优先级错误。