姜蒸鱼。头像
关注
C 语言运算符收尾:条件运算符 ?: 与逗号表达式,以及它们的实战组合封面图

C 语言运算符收尾:条件运算符 ?: 与逗号表达式,以及它们的实战组合

简介:
以下内容是我学习关于运算符中最刁钻的 ? : 与逗号表达式的内容(使用AI agent) 包括进阶的实战应用:)

  1. 条件表达式与三目运算符:把 if 压成一行,以及它最阴的那个坑
  2. 逗号表达式:值取最后、其余全丢,for 双计数器和变量交换都靠它
  3. 综合实战:登录校验、成绩等级、闰年判断——?: 与 && || 的真实写法

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

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--