简介:
以下内容是我学习关于运算符中最刁钻的 ? : 与逗号表达式的内容(使用AI agent) 包括进阶的实战应用:)
- 条件表达式与三目运算符:把 if 压成一行,以及它最阴的那个坑
- 逗号表达式:值取最后、其余全丢,for 双计数器和变量交换都靠它
- 综合实战:登录校验、成绩等级、闰年判断——?: 与 && || 的真实写法
01 | 条件表达式与三目运算符:把 if 压成一行,以及它最阴的那个坑
/*
* ============================================================
* 条件运算符 ?: 与 条件表达式
* 编译: gcc 01_conditional_op.c -o 01_conditional_op.exe
* 运行: ./01_conditional_op.exe
* ============================================================
*/
#include <stdio.h>
int main(void)
{
/* ---------------------------------------------------------
* 【1】条件表达式:所有"条件判断"最后都变成一个数字
*
* C 语言里没有真正的 true / false,它只有 int。
* 关系运算符(> < >= <= == !=)算出来的结果只有两个值:
* 条件成立 -> 1 (真)
* 条件不成立 -> 0 (假)
* 注意:结果不是 YES/NO,也不是"真的字符串"。
* --------------------------------------------------------- */
printf("5 > 3 的结果是: %d\n", 5 > 3); /* 1 */
printf("5 < 3 的结果是: %d\n", 5 < 3); /* 0 */
printf("'A' > 'B' 的结果是: %d\n", 'A' > 'B');/* 0 */
/* ---------------------------------------------------------
* 【2】真值的规则:非 0 即真
*
* 这条规则决定了 C 里几乎所有 if 的写法。
* -3 是真,-1 是真,1 是真,0.5 是真(浮点数也可以当条件)
* 0 是假,只有 0 是假
* --------------------------------------------------------- */
int score = -10;
if (score) {
printf("score = %d, 非 0 -> 进入 if 分支\n", score);
} else {
printf("进入 else 分支\n");
}
/* ---------------------------------------------------------
* 【2.5】条件表达式最经典的坑:把 == 写成了 =
*
* 赋值表达式本身也是一个"条件表达式",它的值 = 被赋进去的那个数。
* 所以下面这个代码能正常编译、正常跑,只是永远走 if 分支 ——
* 而且会把 x 冲成 3。这就是 C 里最著名的一类 bug。
* --------------------------------------------------------- */
int cx = 3, cy = 7;
if (cx = cy) { /* 危险:这是赋值,不是比较 */
printf("BUG 复现: cx 被改成了 %d\n", cx); /* 永远会打印 */
}
cx = 3; /* 复位 */
if (cx == cy) { /* 正确:== 才是比较 */
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 处理,这是工程上的好习惯) */
cx = 3;
if (0 == (cx == cy)) { /* cx==cy 为 0,0==0 成立,所以进 if */
printf("常量在前: cx == cy\n");
} else {
printf("常量在前: cx != cy\n");
}
/* ---------------------------------------------------------
* 【3】条件运算符 ?: 是 C 语言【唯一】的三目运算符
*
* 语法: 条件 ? 值A : 值B
* 读作: "如果条件成立,就取 值A;否则取 值B"
*
* 它是一个【表达式】,不是语句 —— 所以整个三元结构本身有值,
* 可以直接赋值、可以参与运算、可以放进参数里。
* --------------------------------------------------------- */
int a = 15;
int b = 8;
/* 用法一:取两数最大值(不用 if 一行搞定) */
int max_ab = (a > b) ? a : b;
printf("a = %d, b = %d, max = %d\n", a, b, max_ab); /* 15 */
/* 用法二:取两数最小值 */
int min_ab = (a < b) ? a : b;
printf("min = %d\n", min_ab); /* 8 */
/* 用法三:判断奇偶,得到一个字符串 */
int n = 7;
printf("%d 是 %s\n", n, (n % 2 == 0) ? "偶数" : "奇数");
/* 用法四:把条件结果转成 +1 / -1(这是真实项目里的常见写法)
* 比如网络攻防里探测服务返回什么标志位 */
int flag = (n > 0) ? 1 : -1;
printf("flag = %d\n", flag); /* 1 */
/* 用法五:三元运算符可以【嵌套】,但可读性会下降
* 下面这句:若 x<0 取 -x,若 x<10 取 10,否则取 x */
int x = -5;
int abs_like = (x < 0) ? -x : ((x < 10) ? 10 : x);
printf("x = %d, 嵌套三元结果 = %d\n", x, abs_like); /* 5 */
/* ---------------------------------------------------------
* 【4】【重点】?: 的优先级很低,比赋值高,但比几乎所有运算都低
*
* 下面是新手最常踩的坑。编译器不会报错,程序也不会崩,
* 只会"算出来不是你要的东西" —— 这种 bug 最难查。
* --------------------------------------------------------- */
int p = 10, q = 20;
int r1, r2;
/* 先说规则(从高到低排):
* () [] -> . 最高
* ! ~ ++ --
* * / %
* + -
* << >>
* < <= > >=
* == !=
* &
* ^
* &&
* || <- 到这里为止,都比 ?: 高
* ?: <- 条件运算符在这里
* = += -= ... <- 赋值比 ?: 低
* , <- 逗号最低
*
* 也就是说:?: 优先级【高于赋值,低于一切其他运算】。 */
/* 错误示范:?: 左边夹了一个赋值
* 编译器怎么理解?既然 = 比 ?: 低,= 就"抓到"右边的整个三元:
* r1 = (q = ((p > q) ? 1 : 2));
* 执行:p>q 为假 -> 取 2 -> q 被赋成 2 -> r1 再被赋成 2
* 结果:r1 = 2, q = 2 <- q 被偷偷改掉了! */
r1 = q = p > q ? 1 : 2;
printf("错误示范 r1 = %d, 而 q 被改成了 %d\n", r1, q); /* r1=2, q=2 */
/* 正确写法:加括号,让意图一眼可见
* r2 = ((q = (p > q)) ? 1 : 2);
* 执行:p>q 为假 -> q 被赋成 0 -> 0 为假 -> r2 取 2
* 结果:r2 = 2, q = 0
*
* 【这才是这个坑真正阴的地方】
* 两种写法 r1 和 r2 都等于 2,表面上"结果一样、程序也没崩",
* 差别只在 q 上:一个把 q 变成 2,一个让 q 变成 0。
* 你要是后面还用 q 做别的事,这个 bug 会在几十行之外冒出来。 */
p = 10; q = 20; /* 复位,方便对比 */
r2 = (q = p > q) ? 1 : 2;
printf("正确写法 r2 = %d, q = %d\n", r2, q); /* r2=2, q=0 */
/* 【口诀】
* 只要 ?: 的左边或右边夹着赋值,先用键盘敲出一对小括号。
* 括号在 C 里不花钱,但在 debug 时能省你半天。 */
/* ---------------------------------------------------------
* 【5】if-else 与 ?: 的区别(面试常问,实用层面也常问)
* --------------------------------------------------------- */
int v1 = 3, v2 = 7;
/* 写法 A:if-else,值要先声明再赋值。适合逻辑复杂、要分支多的情况 */
int res_if;
if (v1 > v2) {
res_if = v1;
} else {
res_if = v2;
}
/* 写法 B:三元,一行,且天然是一个值。适合二选一 */
int res_ter = (v1 > v2) ? v1 : v2;
printf("if 版 = %d, 三目版 = %d\n", res_if, res_ter); /* 都是 7 */
/* 结论:
* 二选一 -> 用 ?:(短、就是一个值)
* 多分支 / 逻辑复杂 / 每支要做很多事 -> 用 if-else if */
return 0;
}
**
02 | 逗号表达式:值取最后、其余全丢,for 双计数器和变量交换都靠它
**
/*
* ============================================================
* 逗号表达式 ( , )
* 编译: gcc 02_comma_op.c -o 02_comma_op.exe
* 运行: ./02_comma_op.exe
* ============================================================
*/
#include <stdio.h>
int main(void)
{
/* ---------------------------------------------------------
* 【1】逗号表达式的规则
*
* 语法: 表达式1 , 表达式2 , 表达式3
* 规则: 从左往右【依次】求值,整个表达式的值 = 最后一个表达式的值
*
* 生活类比:
* "先做 A,再做 B,最后交付 C 的结果" —— 整件事的价值取决于 C,
* A 和 B 只是路上的动作,它们的值会被丢掉。
* --------------------------------------------------------- */
int i = 0;
int j = 10;
int result = (i++, j++, i + j);
/* 拆解:
* 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++, i+j),
* 参数里的逗号是【分隔符】,不是逗号运算符,
* printf 会收到 3 个参数而不是 1 个 —— 这是另一个坑,见第 5 节。 */
/* ---------------------------------------------------------
* 【2】经典用途①:for 循环里的"逗号"
*
* 你以后一定会见到这种写法:
* for (i = 0, j = 10; i < j; i++, j--)
* 初始化位置和"每轮执行的位置"都可以是逗号表达式,
* 因为 for 的括号里只要求"是表达式",逗号表达式正好是表达式。
* --------------------------------------------------------- */
int k, m;
printf("for 逗号用法: ");
for (k = 0, m = 5; k < m; k++, m--) {
printf("[k=%d,m=%d] ", k, m);
}
printf("\n");
/* 输出: [k=0,m=5] [k=1,m=4] [k=2,m=3] */
/* ---------------------------------------------------------
* 【3】经典用途②:交换两个变量
*
* 逗号表达式整体是一个表达式,所以它能出现在赋值号右边。
* 约定俗成的写法是: (临时=a, a=b, b=临时)
* 整体的值是最后一个 b 的值(旧 b),此处不用返回值,只看副作用。
* --------------------------------------------------------- */
int s = 5, u = 9, t;
(t = s, s = u, u = t);
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。
* --------------------------------------------------------- */
#define SET_AND_RETURN(i, val) (i = (val), (val))
int q = 0;
int got = SET_AND_RETURN(q, 42);
printf("宏返回值 = %d, q = %d\n", got, q); /* 42, 42 */
/* ---------------------------------------------------------
* 【5】【重点】逗号 vs 分号 vs 函数参数逗号 —— 三个别搞混
* --------------------------------------------------------- */
/* (A) 分号:语句结束符
* 写 `a = 1;` 表示"这条语句结束了",整体不是一个值 */
int z = 0;
(z = 1); /* 加括号后它整体是一个表达式,值为 1 */
z = (z = 2, z + 3); /* 括号里逗号表达式 = 2+3 = 5;分号是语句结束 */
printf("z = %d\n", z); /* 5 */
/* (B) 函数调用里的逗号是【分隔符】,不是逗号运算符 */
printf("%d 和 %d\n", 1, 2);
/* printf 的原型里逗号在分隔不同的实参;
* 只有写成 printf("%d\n", (1, 2)) 时,括号里那个才轮到逗号运算符上场,
* 整体的值是 2。 */
int comma_val = (1, 2); /* 先算 1 丢掉,再算 2,整体 = 2 */
printf("括号里的逗号: %d\n", comma_val); /* 2 */
/* (C) 赋值号优先级高于逗号
* `b1 = 1, b2 = 2` 等价于 `(b1 = 1), (b2 = 2)`,即两个独立赋值 */
int b1, b2;
b1 = 1, b2 = 2;
printf("b1 = %d, b2 = %d\n", b1, b2); /* 1, 2 */
return 0;
}
03 | 综合实战:登录校验、成绩等级、闰年判断——?: 与 && || 的真实写法
/*
* ============================================================
* 条件运算符 / 条件表达式 / 逗号表达式 —— 实战演练
* 编译: gcc 03_practice.c -o 03_practice.exe
* 运行: ./03_practice.exe
*
* 本文件只用到你已经学过的东西: int/float 变量、常量、
* 算术与关系与逻辑运算符、if、for、printf。
* 没有指针、没有数组、没有自定义函数。
* ============================================================
*/
#include <stdio.h>
int main(void)
{
/* =========================================================
* 【实战1】模拟登录校验 —— 逻辑运算符 + 条件表达式
*
* 这是最常见的组合:&& 表示"并且",|| 表示"或者"。
* 短路特性:&& 左边为假,右边【根本不执行】。
* 这点在安全领域很关键,比如校验前先确认指针非空。
* ========================================================= */
int input_flag = 1337; /* 模拟用户提交的 flag */
int is_admin = 1; /* 模拟账号状态 */
/* 判断条件本身就是一个值:成立为 1,不成立为 0 */
int passed = (input_flag == 1337) && is_admin;
printf("[1] flag 校验: %s (passed = %d)\n",
passed ? "通过" : "拒绝", passed);
/* 三元运算符直接生成一句人类可读的提示语 —— 这就是它最大的用处 */
printf("[1] %s\n",
passed ? "欢迎回来,flag 正确" : "flag 错误,请再试一次");
/* 实战里常见:多个条件失败时,先报最严重的那个。
* 注意这里 && 比 || 优先,所以 && 被"绑"得更紧,
* 括号加上更保险(加上括号时永远不要偷懒)。 */
int expired = 0;
int wrong = 1;
const char *msg; /* 指向字符串的指针,你还没学到,先看用法: */
/* 它就是"存一句话"的那种变量,%s 负责打印 */
msg = (expired == 1 && wrong == 1) ? "账号已冻结且密码错误"
: (expired == 1) ? "账号已过期"
: (wrong == 1) ? "密码错误"
: "全部正常";
printf("[1] 状态: %s\n", msg);
/* =========================================================
* 【实战2】不写 if,三行代码求出三个数的最大值、最小值、中间值
*
* 思路:a+b+c 减去最大值和最小值,剩下的就是中间值。
* ========================================================= */
int a = 37, b = 12, c = 68;
int max3, min3, mid3;
max3 = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
min3 = (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c);
mid3 = a + b + c - 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 了,可读性会明显变差。
* ========================================================= */
int score = 83;
const char *grade =
(score >= 90) ? "A" :
(score >= 80) ? "B" :
(score >= 70) ? "C" :
(score >= 60) ? "D" :
"F";
printf("[3] 成绩 %d -> 等级 %s\n", score, grade); /* B */
/* =========================================================
* 【实战4】闰年判断 —— && 和 || 的标准搭配
*
* 规则:能被 400 整除 -> 是;否则能被 4 整除且不能被 100 整除 -> 是。
* ========================================================= */
int year = 2024;
int is_leap = (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
printf("[4] %d 年是闰年吗? %s\n", year, is_leap ? "是" : "否");
/* =========================================================
* 【实战5】逗号表达式实战①:for 里的"双计数器"
*
* 两个变量同时向前跑,这在后面做双指针、模拟队列时会经常遇到。
* ========================================================= */
int i, j;
printf("[5] ");
for (i = 0, j = 10; i < j; i++, j--) {
printf("(%d,%d) ", i, j);
}
printf("\n");
/* 输出: (0,10) (1,9) (2,8) (3,7) (4,6) */
/* =========================================================
* 【实战6】逗号表达式实战②:一次算出"和"与"个数"
*
* 典型场景:遍历过程中要同时累计总数和最大值的场景,
* 都可以靠逗号表达式把几件事塞进一个表达式。
* ========================================================= */
int p = 4, q = 9, sum = 0; /* 两个操作数 + 累加器 */
sum = (p = p + q, p + q); /* p=13 被丢掉,整体值 = 13+9 = 22 */
printf("[6] p=%d q=%d, sum=%d\n", p, q, sum);
/* =========================================================
* 【实战7】三目 + 逗号:把"校验并取值"写成一行的实战写法
*
* 前提值合法就取合法值,否则给一个兜底值。
* 这种"入口先兜底"的写法在解析函数里非常常见。
* ========================================================= */
int raw = -1; /* -1 表示"非法/没读到" */
int val = (raw >= 0) ? raw : -1;
printf("[7] raw=%d -> val=%d (兜底成功)\n", raw, val);
return 0;
}
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2602_96808739/article/details/167496841




