简介以下内容是我学习关于运算符中最刁钻的 ? : 与逗号表达式的内容使用AI agent 包括进阶的实战应用条件表达式与三目运算符把 if 压成一行以及它最阴的那个坑逗号表达式值取最后、其余全丢for 双计数器和变量交换都靠它综合实战登录校验、成绩等级、闰年判断——?: 与 || 的真实写法01 | 条件表达式与三目运算符把 if 压成一行以及它最阴的那个坑/* * * 条件运算符 ?: 与 条件表达式 * 编译: gcc 01_conditional_op.c -o 01_conditional_op.exe * 运行: ./01_conditional_op.exe * */#includestdio.hintmain(void){/* --------------------------------------------------------- * 【1】条件表达式所有条件判断最后都变成一个数字 * * C 语言里没有真正的 true / false它只有 int。 * 关系运算符( !)算出来的结果只有两个值 * 条件成立 - 1 真 * 条件不成立 - 0 假 * 注意结果不是 YES/NO也不是真的字符串。 * --------------------------------------------------------- */printf(5 3 的结果是: %d\n,53);/* 1 */printf(5 3 的结果是: %d\n,53);/* 0 */printf(A B 的结果是: %d\n,AB);/* 0 *//* --------------------------------------------------------- * 【2】真值的规则非 0 即真 * * 这条规则决定了 C 里几乎所有 if 的写法。 * -3 是真-1 是真1 是真0.5 是真浮点数也可以当条件 * 0 是假只有 0 是假 * --------------------------------------------------------- */intscore-10;if(score){printf(score %d, 非 0 - 进入 if 分支\n,score);}else{printf(进入 else 分支\n);}/* --------------------------------------------------------- * 【2.5】条件表达式最经典的坑把 写成了 * * 赋值表达式本身也是一个条件表达式它的值 被赋进去的那个数。 * 所以下面这个代码能正常编译、正常跑只是永远走 if 分支 —— * 而且会把 x 冲成 3。这就是 C 里最著名的一类 bug。 * --------------------------------------------------------- */intcx3,cy7;if(cxcy){/* 危险这是赋值不是比较 */printf(BUG 复现: cx 被改成了 %d\n,cx);/* 永远会打印 */}cx3;/* 复位 */if(cxcy){/* 正确 才是比较 */printf(cx cy 成立\n);}else{printf(cx cy 不成立 (cx%d, cy%d)\n,cx,cy);/* 走这里 */}/* 防御写法把常量写在前面不反直觉编译器也更容易帮你抓到手滑 * 上面那行 if (cx cy) 用 gcc -Wall 编译时编译器会直接给你 * warning: suggest parentheses around assignment used as truth value * 看到这种 warning 请当成 error 处理这是工程上的好习惯 */cx3;if(0(cxcy)){/* cxcy 为 000 成立所以进 if */printf(常量在前: cx cy\n);}else{printf(常量在前: cx ! cy\n);}/* --------------------------------------------------------- * 【3】条件运算符 ?: 是 C 语言【唯一】的三目运算符 * * 语法: 条件 ? 值A : 值B * 读作: 如果条件成立就取 值A否则取 值B * * 它是一个【表达式】不是语句 —— 所以整个三元结构本身有值 * 可以直接赋值、可以参与运算、可以放进参数里。 * --------------------------------------------------------- */inta15;intb8;/* 用法一取两数最大值不用 if 一行搞定 */intmax_ab(ab)?a:b;printf(a %d, b %d, max %d\n,a,b,max_ab);/* 15 *//* 用法二取两数最小值 */intmin_ab(ab)?a:b;printf(min %d\n,min_ab);/* 8 *//* 用法三判断奇偶得到一个字符串 */intn7;printf(%d 是 %s\n,n,(n%20)?偶数:奇数);/* 用法四把条件结果转成 1 / -1这是真实项目里的常见写法 * 比如网络攻防里探测服务返回什么标志位 */intflag(n0)?1:-1;printf(flag %d\n,flag);/* 1 *//* 用法五三元运算符可以【嵌套】但可读性会下降 * 下面这句若 x0 取 -x若 x10 取 10否则取 x */intx-5;intabs_like(x0)?-x:((x10)?10:x);printf(x %d, 嵌套三元结果 %d\n,x,abs_like);/* 5 *//* --------------------------------------------------------- * 【4】【重点】?: 的优先级很低比赋值高但比几乎所有运算都低 * * 下面是新手最常踩的坑。编译器不会报错程序也不会崩 * 只会算出来不是你要的东西 —— 这种 bug 最难查。 * --------------------------------------------------------- */intp10,q20;intr1,r2;/* 先说规则从高到低排 * () [] - . 最高 * ! ~ -- * * / % * - * * * ! * * ^ * * || - 到这里为止都比 ?: 高 * ?: - 条件运算符在这里 * - ... - 赋值比 ?: 低 * , - 逗号最低 * * 也就是说?: 优先级【高于赋值低于一切其他运算】。 *//* 错误示范?: 左边夹了一个赋值 * 编译器怎么理解既然 比 ?: 低 就抓到右边的整个三元 * r1 (q ((p q) ? 1 : 2)); * 执行pq 为假 - 取 2 - q 被赋成 2 - r1 再被赋成 2 * 结果r1 2, q 2 - q 被偷偷改掉了 */r1qpq?1:2;printf(错误示范 r1 %d, 而 q 被改成了 %d\n,r1,q);/* r12, q2 *//* 正确写法加括号让意图一眼可见 * r2 ((q (p q)) ? 1 : 2); * 执行pq 为假 - q 被赋成 0 - 0 为假 - r2 取 2 * 结果r2 2, q 0 * * 【这才是这个坑真正阴的地方】 * 两种写法 r1 和 r2 都等于 2表面上结果一样、程序也没崩 * 差别只在 q 上一个把 q 变成 2一个让 q 变成 0。 * 你要是后面还用 q 做别的事这个 bug 会在几十行之外冒出来。 */p10;q20;/* 复位方便对比 */r2(qpq)?1:2;printf(正确写法 r2 %d, q %d\n,r2,q);/* r22, q0 *//* 【口诀】 * 只要 ?: 的左边或右边夹着赋值先用键盘敲出一对小括号。 * 括号在 C 里不花钱但在 debug 时能省你半天。 *//* --------------------------------------------------------- * 【5】if-else 与 ?: 的区别面试常问实用层面也常问 * --------------------------------------------------------- */intv13,v27;/* 写法 Aif-else值要先声明再赋值。适合逻辑复杂、要分支多的情况 */intres_if;if(v1v2){res_ifv1;}else{res_ifv2;}/* 写法 B三元一行且天然是一个值。适合二选一 */intres_ter(v1v2)?v1:v2;printf(if 版 %d, 三目版 %d\n,res_if,res_ter);/* 都是 7 *//* 结论 * 二选一 - 用 ?:短、就是一个值 * 多分支 / 逻辑复杂 / 每支要做很多事 - 用 if-else if */return0;}**02 | 逗号表达式值取最后、其余全丢for 双计数器和变量交换都靠它**/* * * 逗号表达式 ( , ) * 编译: gcc 02_comma_op.c -o 02_comma_op.exe * 运行: ./02_comma_op.exe * */#includestdio.hintmain(void){/* --------------------------------------------------------- * 【1】逗号表达式的规则 * * 语法: 表达式1 , 表达式2 , 表达式3 * 规则: 从左往右【依次】求值整个表达式的值 最后一个表达式的值 * * 生活类比 * 先做 A再做 B最后交付 C 的结果 —— 整件事的价值取决于 C * A 和 B 只是路上的动作它们的值会被丢掉。 * --------------------------------------------------------- */inti0;intj10;intresult(i,j,ij);/* 拆解 * 1) i - i 变成 1这个值 1 被丢掉 * 2) j - j 变成 11这个值 11 被丢掉 * 3) i j 1 11 12 - 整体的值是 12 */printf(逗号表达式 result %d, 此时 i %d, j %d\n,result,i,j);/* 注意上面这行如果只写 printf(..., i, j, ij) * 参数里的逗号是【分隔符】不是逗号运算符 * printf 会收到 3 个参数而不是 1 个 —— 这是另一个坑见第 5 节。 *//* --------------------------------------------------------- * 【2】经典用途①for 循环里的逗号 * * 你以后一定会见到这种写法 * for (i 0, j 10; i j; i, j--) * 初始化位置和每轮执行的位置都可以是逗号表达式 * 因为 for 的括号里只要求是表达式逗号表达式正好是表达式。 * --------------------------------------------------------- */intk,m;printf(for 逗号用法: );for(k0,m5;km;k,m--){printf([k%d,m%d] ,k,m);}printf(\n);/* 输出: [k0,m5] [k1,m4] [k2,m3] *//* --------------------------------------------------------- * 【3】经典用途②交换两个变量 * * 逗号表达式整体是一个表达式所以它能出现在赋值号右边。 * 约定俗成的写法是: (临时a, ab, b临时) * 整体的值是最后一个 b 的值旧 b此处不用返回值只看副作用。 * --------------------------------------------------------- */ints5,u9,t;(ts,su,ut);printf(逗号式交换: s %d, u %d\n,s,u);/* 9, 5 *//* 【反面教材务必知道】 * 网上偶尔能看到这种炫技写法 * (x, y) (y, x); // 直接编译报错 * 报的是: error: lvalue required as left operand of assignment * 原因逗号表达式的结果不是左值不能被赋值。 * 而且它连 x 和 y 的值都没换纯粹是骗人的写法。 * 结论交换变量老老实实用临时变量 t那 3 行才是标准 C。 *//* 顺便验证逗号表达式可以有可写的最终结果 —— u 确实换成了 5 */printf(交换用到的临时变量 t %d\n,t);/* 5 *//* --------------------------------------------------------- * 【4】经典用途③宏里的技巧性写法 * * 写多了会看到这种宏它把多条语句塞进了一个表达式 * 从而让宏看起来像函数一样有值。注意反斜杠 \ 不能漏。 * * 实际项目里我会警告你能不用就别用。 * 因为宏只是文本替换参数里的 i 会被展开成两次 i是经典 bug。 * --------------------------------------------------------- */#defineSET_AND_RETURN(i,val)(i(val),(val))intq0;intgotSET_AND_RETURN(q,42);printf(宏返回值 %d, q %d\n,got,q);/* 42, 42 *//* --------------------------------------------------------- * 【5】【重点】逗号 vs 分号 vs 函数参数逗号 —— 三个别搞混 * --------------------------------------------------------- *//* (A) 分号语句结束符 * 写 a 1; 表示这条语句结束了整体不是一个值 */intz0;(z1);/* 加括号后它整体是一个表达式值为 1 */z(z2,z3);/* 括号里逗号表达式 23 5分号是语句结束 */printf(z %d\n,z);/* 5 *//* (B) 函数调用里的逗号是【分隔符】不是逗号运算符 */printf(%d 和 %d\n,1,2);/* printf 的原型里逗号在分隔不同的实参 * 只有写成 printf(%d\n, (1, 2)) 时括号里那个才轮到逗号运算符上场 * 整体的值是 2。 */intcomma_val(1,2);/* 先算 1 丢掉再算 2整体 2 */printf(括号里的逗号: %d\n,comma_val);/* 2 *//* (C) 赋值号优先级高于逗号 * b1 1, b2 2 等价于 (b1 1), (b2 2)即两个独立赋值 */intb1,b2;b11,b22;printf(b1 %d, b2 %d\n,b1,b2);/* 1, 2 */return0;}03 | 综合实战登录校验、成绩等级、闰年判断——?: 与 || 的真实写法/* * * 条件运算符 / 条件表达式 / 逗号表达式 —— 实战演练 * 编译: gcc 03_practice.c -o 03_practice.exe * 运行: ./03_practice.exe * * 本文件只用到你已经学过的东西: int/float 变量、常量、 * 算术与关系与逻辑运算符、if、for、printf。 * 没有指针、没有数组、没有自定义函数。 * */#includestdio.hintmain(void){/* * 【实战1】模拟登录校验 —— 逻辑运算符 条件表达式 * * 这是最常见的组合 表示并且|| 表示或者。 * 短路特性 左边为假右边【根本不执行】。 * 这点在安全领域很关键比如校验前先确认指针非空。 * */intinput_flag1337;/* 模拟用户提交的 flag */intis_admin1;/* 模拟账号状态 *//* 判断条件本身就是一个值成立为 1不成立为 0 */intpassed(input_flag1337)is_admin;printf([1] flag 校验: %s (passed %d)\n,passed?通过:拒绝,passed);/* 三元运算符直接生成一句人类可读的提示语 —— 这就是它最大的用处 */printf([1] %s\n,passed?欢迎回来flag 正确:flag 错误请再试一次);/* 实战里常见多个条件失败时先报最严重的那个。 * 注意这里 比 || 优先所以 被绑得更紧 * 括号加上更保险加上括号时永远不要偷懒。 */intexpired0;intwrong1;constchar*msg;/* 指向字符串的指针你还没学到先看用法 *//* 它就是存一句话的那种变量%s 负责打印 */msg(expired1wrong1)?账号已冻结且密码错误:(expired1)?账号已过期:(wrong1)?密码错误:全部正常;printf([1] 状态: %s\n,msg);/* * 【实战2】不写 if三行代码求出三个数的最大值、最小值、中间值 * * 思路abc 减去最大值和最小值剩下的就是中间值。 * */inta37,b12,c68;intmax3,min3,mid3;max3(ab)?((ac)?a:c):((bc)?b:c);min3(ab)?((ac)?a:c):((bc)?b:c);mid3abc-max3-min3;printf([2] a%d b%d c%d - max%d mid%d min%d\n,a,b,c,max3,mid3,min3);/* 68, 37, 12 *//* * 【实战3】成绩等级 —— 嵌套三目的经典写法 * * 从高分往低分依次判断命中即返回。 * 超过 3 层嵌套就该换成 if-else if 了可读性会明显变差。 * */intscore83;constchar*grade(score90)?A:(score80)?B:(score70)?C:(score60)?D:F;printf([3] 成绩 %d - 等级 %s\n,score,grade);/* B *//* * 【实战4】闰年判断 —— 和 || 的标准搭配 * * 规则能被 400 整除 - 是否则能被 4 整除且不能被 100 整除 - 是。 * */intyear2024;intis_leap(year%4000)||(year%40year%100!0);printf([4] %d 年是闰年吗? %s\n,year,is_leap?是:否);/* * 【实战5】逗号表达式实战①for 里的双计数器 * * 两个变量同时向前跑这在后面做双指针、模拟队列时会经常遇到。 * */inti,j;printf([5] );for(i0,j10;ij;i,j--){printf((%d,%d) ,i,j);}printf(\n);/* 输出: (0,10) (1,9) (2,8) (3,7) (4,6) *//* * 【实战6】逗号表达式实战②一次算出和与个数 * * 典型场景遍历过程中要同时累计总数和最大值的场景 * 都可以靠逗号表达式把几件事塞进一个表达式。 * */intp4,q9,sum0;/* 两个操作数 累加器 */sum(ppq,pq);/* p13 被丢掉整体值 139 22 */printf([6] p%d q%d, sum%d\n,p,q,sum);/* * 【实战7】三目 逗号把校验并取值写成一行的实战写法 * * 前提值合法就取合法值否则给一个兜底值。 * 这种入口先兜底的写法在解析函数里非常常见。 * */intraw-1;/* -1 表示非法/没读到 */intval(raw0)?raw:-1;printf([7] raw%d - val%d (兜底成功)\n,raw,val);return0;}
阅读完成 · 觉得有帮助?