第 4 章:运算符与表达式
在编程中,我们经常需要对数据进行各种计算和比较,就像在数学中一样。C 语言提供了一系列运算符,让我们能够对变量和值进行赋值、计算、比较、逻辑判断等操作。将变量、常量和运算符按照语法规则组合在一起,就形成了表达式。表达式会产生一个具体的值,是构建程序逻辑(如条件判断、循环执行、数据计算)的基础组成部分。
4.1 算术运算符
算术运算符用于执行基本的数学运算,包括加、减、乘、除、取模(求余数),适用于数值类型(整型、浮点型)数据。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
| + | 加法 | 5 + 3 | 8 |
| - | 减法 | 10 - 4 | 6 |
| * | 乘法 | 6 * 7 | 42 |
| / | 除法 | 15 / 4 | 3 (整数除法) |
| % | 取模 (求余数) | 15 % 4 | 3 |
关键注意事项:
1、整数除法:两个整型数据相除时,结果会自动舍弃小数部分(仅保留整数),而非四舍五入。
2、浮点数除法:若至少有一个操作数是浮点型(float/double),则结果为浮点型,保留小数部分。
3、取模运算:仅适用于整型数据,用于计算除法后的余数,结果的正负号与被除数一致(如10 % 3 = 1,-10 % 3 = -1)。
示例:
#include<stdio.h>intmain(){inta=15,b=4;floatc=15.0f;// 浮点型操作数。intd=-15;printf("15 + 4 = %d\n",a+b);// 输出:19(整数加法)。printf("15 - 4 = %d\n",a-b);// 输出:11(整数减法)。printf("15 * 4 = %d\n",a*b);// 输出:60(整数乘法)。printf("15 / 4 = %d\n",a/b);// 输出:3(整数除法,舍弃小数)。printf("15.0f / 4 = %.2f\n",c/b);// 输出:3.75(浮点数除法)。printf("15 %% 4 = %d\n",a%b);// 输出:3(取模,正数余数)。printf("-15 %% 4 = %d\n",d%b);// 输出:-3(取模,余数与被除数同号)。return0;}4.2 赋值运算符
赋值运算符用于将右侧的值或表达式结果赋给左侧的变量,最基础的是=,C 语言还提供了复合赋值运算符(结合算术 / 位运算的简化写法),让代码更简洁。
| 运算符 | 描述 | 示例 | 等价于 |
|---|---|---|---|
| = | 简单赋值 | x = 5 | x = 5 |
| += | 加后赋值 | x += 3 | x = x + 3 |
| -= | 减后赋值 | x -= 2 | x = x - 2 |
| *= | 乘后赋值 | x *= 4 | x = x * 4 |
| /= | 除后赋值 | x /= 2 | x = x / 2 |
| %= | 取模后赋值 | x %= 3 | x = x % 3 |
关键注意事项:
1、赋值运算符的左侧必须是 “可修改的变量”(如a = 5合法,5 = a非法)。
2、复合赋值运算符会先执行运算,再赋值,优先级低于算术运算符。
3、赋值表达式本身也有值,值为 “赋值后变量的结果”(如a = b = 5等价于b = 5; a = 5,右结合)。
示例:
#include<stdio.h>intmain(){intx=10;x+=5;// 等价于 x = x + 5 → x = 15。printf("x += 5 后,x = %d\n",x);// 输出:15。x*=2;// 等价于 x = x * 2 → x = 30。printf("x *= 2 后,x = %d\n",x);// 输出:30。x%=7;// 等价于 x = x % 7 → 30 ÷ 7 = 4余2 → x = 2。printf("x %%= 7 后,x = %d\n",x);// 输出:2。// 赋值表达式的右结合性。inta,b;a=b=8;// 先执行 b = 8,再执行 a = b。printf("a = %d, b = %d\n",a,b);// 输出:a = 8, b = 8。return0;}4.3 关系运算符
关系运算符用于比较两个值的大小或相等关系,结果是逻辑值 ——1 表示 “真”(条件成立),0 表示 “假”(条件不成立)。关系运算的结果常用于条件判断(如 if 语句、循环条件)。
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
| == | 等于 | 5 == 3 | 0 (假) |
| != | 不等于 | 5 != 3 | 1 (真) |
| > | 大于 | 5 > 3 | 1 (真) |
| < | 小于 | 5 < 3 | 0 (假) |
| >= | 大于等于 | 5 >= 5 | 1 (真) |
| <= | 小于等于 | 5 <= 3 | 0 (假) |
关键注意事项:
1、区分==(相等判断)和=(赋值):这是最常见的编程错误之一(如if (a == 5)是判断,if (a = 5)是赋值,永远为真)。
2、浮点数比较:由于精度问题,不建议直接用==比较浮点数(如3.14 == 3.1400000001可能为假),应判断两者差值的绝对值是否小于极小值(如fabs(a - b) < 1e-6)。
示例:
#include<stdio.h>#include<math.h>// 用于fabs函数(浮点数绝对值)。intmain(){inta=10,b=20;floatpi1=3.14f,pi2=3.1400001f;printf("%d == %d 的结果是:%d\n",a,b,a==b);// 输出:0(假)。printf("%d != %d 的结果是:%d\n",a,b,a!=b);// 输出:1(真)。printf("%d > %d 的结果是:%d\n",a,b,a>b);// 输出:0(假)。printf("%d < %d 的结果是:%d\n",a,b,a<b);// 输出:1(真)。// 浮点数比较的正确方式。if(fabs(pi1-pi2)<1e-6){// 差值小于10的-6次方,视为相等。printf("pi1 和 pi2 相等\n");}else{printf("pi1 和 pi2 不相等\n");}return0;}4.4 逻辑运算符
逻辑运算符用于组合多个关系表达式(或逻辑值),形成更复杂的条件判断,结果同样是 1(真)或 0(假)。C 语言支持 “短路求值”,能提高运算效率。
| 运算符 | 描述 | 示例 | 结果 | 短路求值规则 |
|---|---|---|---|---|
| && | 逻辑与 (AND) | (5 > 3) && (5 < 10) | 1 (真) | 左侧为假则右侧不执行 |
| || | 逻辑或 (OR) | (5 > 3) || (5 < 2) | 1 (真) | 左侧为真则右侧不执行 |
| ! | 逻辑非 (NOT) | !(5 > 3) | 0 (假) | 无(单目运算符) |
关键注意事项:
1、逻辑与(&&):所有条件都为真,结果才为真;只要有一个为假,结果为假。
2、逻辑或(||):只要有一个条件为真,结果就为真;所有条件都为假,结果才为假。
3、逻辑非(!):将真变假、假变真(如!0 = 1,!5 = 0——C 语言中 “非 0 值都视为真”)。
4、短路求值:逻辑与左侧为假时,右侧表达式不执行;逻辑或左侧为真时,右侧表达式不执行(避免无效计算或副作用)。
示例:
#include<stdio.h>intmain(){intage=20;inthas_license=1;// 1表示真(有驾照),0表示假(无驾照)。intx=5,y=10;// 逻辑与:两个条件都满足才为真。intcan_drive=(age>=18)&&has_license;printf("可以开车:%d\n",can_drive);// 输出:1(真)。// 逻辑或:满足一个条件即为真。intis_young_or_old=(age<18)||(age>65);printf("是年轻人或老年人:%d\n",is_young_or_old);// 输出:0(假)。// 逻辑非:取反。intno_license=!has_license;printf("没有驾照:%d\n",no_license);// 输出:0(假)。// 短路求值示例:逻辑与左侧为假,右侧x++不执行。intresult1=(x>10)&&(x++);printf("result1 = %d, x = %d\n",result1,x);// 输出:0,x = 5(x++未执行)。// 短路求值示例:逻辑或左侧为真,右侧y++不执行。intresult2=(y>5)||(y++);printf("result2 = %d, y = %d\n",result2,y);// 输出:1,y = 10(y++未执行)。return0;}4.5 自增和自减运算符(⚠️不建议大量使用)
自增(++)和自减(–)是单目运算符,用于将变量的值 “直接增加 1” 或 “减少 1”,分为前缀和后缀两种形式,核心区别是 “增减操作” 和 “取值操作” 的执行顺序。
| 运算符 | 描述 | 示例 | 核心区别 |
|---|---|---|---|
| ++ | 自增 | ++x(前缀) | 先自增,再取值 |
| ++ | 自增 | x++(后缀) | 先取值,再自增 |
| – | 自减 | –x(前缀) | 先自减,再取值 |
| – | 自减 | x–(后缀) | 先取值,再自减 |
关键注意事项:
1、仅适用于变量(如++5非法,x++合法),因为需要修改变量的值。
2、避免在同一表达式中多次使用(如x++ + ++x),结果依赖编译器实现,无统一标准,易出错。
示例:
#include<stdio.h>intmain(){intx=5,y,z;// 后缀自增:先赋值(y = x=5),再自增(x=6)。y=x++;printf("x = %d, y = %d\n",x,y);// 输出:x = 6, y = 5。x=5;// 重置x。// 前缀自增:先自增(x=6),再赋值(y=6)。y=++x;printf("x = %d, y = %d\n",x,y);// 输出:x = 6, y = 6。// 自减示例。z=10;printf("z-- = %d\n",z--);// 后缀:先取值(10),再自减(z=9)。printf("z = %d\n",z);// 输出:9。printf("--z = %d\n",--z);// 前缀:先自减(z=8),再取值(8)。return0;}4.6 条件运算符(三元运算符,⚠️了解即可)
条件运算符是 C 语言中唯一的三目运算符,语法简洁,用于简化二选一的条件判断,等价于简单的if-else语句。
语法格式:条件表达式 ? 表达式1 : 表达式2。
● 执行逻辑:先判断 “条件表达式” 的真假。
● 若为真(非 0),整个表达式的值为 “表达式 1” 的值。
● 若为假(0),整个表达式的值为 “表达式 2” 的值。
关键注意事项:
1、优先级低于关系运算符和算术运算符,高于赋值运算符。
2、结合性为 “右结合”(如a ? b : c ? d : e等价于a ? b : (c ? d : e))。
3、表达式 1 和表达式 2 的类型应一致(或可隐式转换),避免类型不匹配。
示例:
#include<stdio.h>intmain(){inta=10,b=20;intmax,min;intnumber=15;// 求两个数的最大值(等价于 if(a>b) max=a; else max=b;) 。max=(a>b)?a:b;printf("较大的数是:%d\n",max);// 输出:20。// 求两个数的最小值。min=(a<b)?a:b;printf("较小的数是:%d\n",min);// 输出:10。// 判断奇偶数。printf("%d是%s\n",number,(number%2==0)?"偶数":"奇数");// 输出:15是奇数。// 右结合示例:a=10为真,直接返回b=20,不执行后续判断。intresult=(a>5)?b:(b>15)?30:40;printf("result = %d\n",result);// 输出:20。return0;}4.7 逗号运算符(⚠️了解即可)
逗号运算符用于将多个表达式分隔开,按从左到右的顺序依次执行,整个逗号表达式的值为 “最后一个表达式” 的值。
语法格式:表达式1, 表达式2, ..., 表达式n。
● 执行逻辑:先执行表达式 1,再执行表达式 2,…,最后执行表达式 n,最终结果为表达式 n 的值。
● 优先级:所有运算符中最低,低于赋值运算符(如需改变优先级,需加括号)。
适用场景:在允许单个表达式的位置(如 for 循环的初始化 / 更新部分)执行多个操作。
示例:
#include<stdio.h>intmain(){inta=1,b=2,c,d;// 逗号表达式:先执行a++,再执行b++,最终值为b++后的结果(3)。c=(a++,b++,b+1);printf("a = %d, b = %d, c = %d\n",a,b,c);// 输出:a=2, b=3, c=4。// 无括号时:赋值运算符优先级更高,先执行c = a++,再执行b++,逗号表达式值为b++(3)。a=1,b=2;// 重置。d=a++,b++,b+1;printf("a = %d, b = %d, d = %d\n",a,b,d);// 输出:a=2, b=3, d=1(d = a++的结果)。// for循环中使用:初始化a=0、b=5,更新时a++、b--。for(inti=0;i<3;i++,a++,b--){printf("i = %d, a = %d, b = %d\n",i,a,b);}// 输出:// i = 0, a = 2, b = 3// i = 1, a = 3, b = 2// i = 2, a = 4, b = 1return0;}4.8 运算符的优先级与结合性
当一个表达式包含多个运算符时,计算顺序由 “优先级” 和 “结合性” 共同决定 —— 优先级决定 “先执行哪个运算符”,结合性决定 “优先级相同时的执行顺序”。
4.8.1 常见运算符优先级(从高到低)
| 优先级 | 运算符类别 | 具体运算符 |
|---|---|---|
| 1 | 括号 | () |
| 2 | 自增 / 自减(单目) | ++, --(前缀)、!、~(位非) |
| 3 | 算术运算符 | *, /, %(乘、除、取模) |
| 4 | 算术运算符 | +, -(加、减) |
| 5 | 关系运算符 | <, <=, >, >= |
| 6 | 关系运算符 | ==, !=(相等 / 不等) |
| 7 | 逻辑运算符 | &&(逻辑与) |
| 8 | 逻辑运算符 | ||(逻辑或) |
| 9 | 条件运算符 | ?: |
| 10 | 赋值运算符 | =, +=, -=, *=, /=, %= |
| 11 | 逗号运算符 | , |
4.8.2 结合性规则
左结合:优先级相同时,从左到右执行(如算术运算符、关系运算符、逗号运算符)。
- 示例:
a + b - c等价于(a + b) - c,a = b = 5是例外(赋值运算符右结合)。
- 示例:
右结合:优先级相同时,从右到左执行(如赋值运算符、条件运算符)。
示例:
a = b = 5等价于a = (b = 5),a ? b : c ? d : e等价于a ? b : (c ? d : e)。核心建议:当不确定优先级或结合性时,直接用括号明确执行顺序—— 既避免出错,又让代码更易读(括号优先级最高)。
示例:
#include<stdio.h>intmain(){inta=5,b=3,c=2,result;// 无括号:先乘后加(优先级:* > +)result=a+b*c;printf("a + b * c = %d\n",result);// 输出:11(5 + 3*2)// 有括号:先加后乘(括号改变优先级)result=(a+b)*c;printf("(a + b) * c = %d\n",result);// 输出:16((5+3)*2)// 结合性示例:赋值运算符右结合intx,y;x=y=10;// 等价于 x = (y = 10)printf("x = %d, y = %d\n",x,y);// 输出:10, 10return0;}4.9 位运算符基础
位运算符用于直接操作二进制位,适用于整型数据(char、int、long 等),底层编程(如硬件操作、数据压缩)中常用,基础阶段需掌握核心用法。
| 运算符 | 描述 | 示例(二进制) | 结果(二进制) |
|---|---|---|---|
| & | 按位与 | 1010 & 0111 = | 0010 |
| 按位或 | 1010 | 0111 = | 1111 | |
| ^ | 按位异或 | 1010 ^ 0111 = | 1101 |
| ~ | 按位非(取反) | ~1010 = | 0101(假设 4 位) |
| << | 左移 | 1010 << 1 = | 10100 |
| >> | 右移 | 1010 >> 1 = | 0101 |
关键说明:
- 按位与(&):对应位都为 1 时结果为 1,可用于 “清 0 某些位”。
- 按位或(|):对应位有一个为 1 时结果为 1,可用于 “置 1 某些位”。
- 按位异或(^):对应位不同时结果为 1,可用于 “交换变量”“翻转某些位”。
- 左移(<<):二进制位向左移动 n 位,右边补 0,等价于乘以 2^n(如
a << 1等价于a * 2)。 - 右移(>>):二进制位向右移动 n 位,左边补符号位(正数补 0,负数补 1),等价于除以 2^n(取整)。
示例:
#include<stdio.h>intmain(){inta=10,b=7;// a=1010(二进制),b=0111(二进制)printf("a & b = %d\n",a&b);// 0010 → 2printf("a | b = %d\n",a|b);// 1111 → 15printf("a ^ b = %d\n",a^b);// 1101 → 13printf("~a = %d\n",~a);// 按位取反(依赖系统位数,结果为负数)printf("a << 1 = %d\n",a<<1);// 10100 → 20(10*2)printf("a >> 1 = %d\n",a>>1);// 0101 → 5(10/2)return0;}笔记:
- 算术运算符中,整数除法会舍弃小数,取模仅适用于整型,浮点数比较需用 “差值绝对值” 避免精度问题。
- 区分
==(相等判断)和=(赋值),这是高频编程错误。 - 逻辑运算符支持 “短路求值”,左侧结果可确定时,右侧表达式不执行,需避免依赖右侧副作用(如
x++)。 - 自增 / 自减的前缀和后缀核心区别:前缀 “先增减再取值”,后缀 “先取值再增减”,避免同一表达式多次使用。
- 条件运算符(?:)简化二选一逻辑,结合性为右结合;逗号运算符优先级最低,结果为最后一个表达式的值。
- 优先级:括号 > 算术 > 关系 > 逻辑 > 条件 > 赋值 > 逗号,不确定时用括号明确顺序。
- 位运算符直接操作二进制位,左移等价于乘 2、右移等价于除 2(取整),仅适用于整型数据。