• 【高质量C/C++】4.表达式和基本语句


    【高质量C/C++编程】—— 4. 表达式和基本语句

    一、运算符的优先级

    以下运算符的优先级是从高到低排列,结合性除了表中给出的的三个从右到左,其余都是从左到右。对这些运算符优先级熟记是比较困难的,我们只需要有一个大概的认识即可(如一元运算符优先级普遍较高,赋值运算符优先级最低等),毕竟我们的目的只是写出正确且易读的代码

    运算符结合性
    聚合运算符()[]->.
    一元运算符!~++--(类型)sizeof+(正)-(负)*(解引用)&(取地址)右到左
    算术运算符*(乘)/%
    +(加)-(减)
    移位运算符<<>>
    比较运算符<<=>>=
    ==!=
    位运算符&(按位与)
    ^(异或)
    |(按位或)
    逻辑运算符&&
    ||右到左
    三目运算符? :右到左
    赋值运算符=+=-=*=/=%=&=^=|=<<=>>=

    规则

    • 如果代码中的运算符较多,可以使用括号来确定表达式的操作顺序,避免使用默认优先级
    if (a | b && a & c);		// 不良的风格
    if ((a | b) && (a & c));	// 良好的风格
    

    二、复合表达式

    a = b = c = 0这样的表达式称为复合表达式。

    复合表达式存在的理由:

    1. 书写简洁
    2. 可以提高编译效率

    规则

    1. 不要编写太复杂的复合表达式
    2. 不要由多用途的复合表达式
    3. 不要把程序中的复合表达式写成真正的数学表达式
    4. 不要出现问题表达式
    1. 问题表达式:相同的表达式的运算结果不一定是相同的,也叫非法表达式
    2. 为什么会出现问题表达式:表达式中的子式运算后会影响其他子式的值
    3. 问题表达式的运算结果与编译器对变量和运算符的加载次序有关,但是我们并不需要过于在意它如何运算出错误的值,只需要避免使用它即可。
    i = a >= b && c < d && c + f <= g + h;		// 不良的风格,过于复杂的表达式
    d = (a = b + c) + r;						// 不良的风格,多用途的表达式
    if (a < b < c);								// 错误的代码,使用了数学表达式
    
    // 问题表达式
    /* 结果是不可预测的,当i--运算后可能被加载进寄存器中,后面的--i和++i未必能在运算前修改到i的值 */
    ret = i-- - --i * (i = -3) * i++ + ++i;
    
    /* 根据优先级--c先执行,但在--c执行之前左边的c被存储到寄存器中,和--c执行后左边的c还是原值,导致运算结果出现错误 */
    ret = c + --c;
    

    三、if语句

    if语句是C/C++中最常用的语句,但是有很多程序员都在用隐含错误方式写它,现在让我们来学习它。

    1. if 语句零值比较规则

    1. 布尔变量与零值比较:布尔变量不与truefalse或者0、1进行比较,而是以变量或!运算的结果表示真假
    // 良好的风格
    if (flag);		// flag表示真
    if (!flag);		// flag表示假
    
    // 不良的风格
    if (flag == true);
    if (flag == false);
    if (flag == 1);
    if (flag == 0);
    
    1. 整型变量与零值比较:整型变量用==!=与0比较
    // 良好的风格
    if (value == 0);
    if (value != 0);
    
    // 不良的风格
    if (value);			// 容易让人误解为布尔值
    if (!value);
    
    1. 浮点变量与零值比较:浮点数不可以用==!=与任何数字比较,它们有精度限制,应该转化为>=<=形式来消除他们的误差
    // 良好的风格
    if ((x >= -EPSINON) && (x <= EPSINON));		// EPSINON是允许的误差
    
    // 不良的风格
    if (x == 0.0);		// 由于浮点数底数数位为0时,指数位是任意值都表示0.0,所以可能出现由于指数不同导致的0.0不等于0.0
    if (x != 0.0);
    
    1. 指针变量与零值比较
      1. 在C语言中,指针变量用==!=NULL比较
      2. 在C++中,指针变量用==!=nullptr比较。C++中的NULL被声明为数值0和指针0,优先使用数值0,所以我们尽量不在C++中使用NULL
    // 在C语言中良好,在C++中不好的风格
    if (p == NULL);
    if (p != NULL);
    
    // C++中良好的风格
    if (p == nullptr);
    if (p != nullptr);
    
    // C语言和C++中都不良的风格
    if (p);
    if (!p);
    if (p == 0);
    if (p != 0);
    

    2. if 语句其他规则

    规则

    1. 在代码中我们常会看到比较表达式中常量前置的情况,这是为了防止将==写成=,由于=也是合法的语法,所以编译器不会报错,这类错误往往很难发现。将常量前置时=表示对常量进行赋值,这是语法错误,编译器会报错,这个错误就会暴露在编译期前。
    if (p == NULL);
    if (p = NULL);		// 该语句漏掉一个=,但是编译器不会报错
    
    if (NULL == p);		// 将常量前置,与p==NULL没有区别
    if (NULL = p);		// 漏掉一个=,此时编译器报错,问题可以及时解决
    
    1. if/else/return的组合要加上大括号或直接使用三目运算表达式
    // 不良的风格
    if (condition)
        return x;
    return y;
    
    // 良好的风格
    if (condition)
    {
        return x;
    }
    else
    {
        return y;
    }
    
    // 或者写为更简练的三目表达式
    return (condition ? x : y);
    

    四、循环语句效率

    在C++/C循环语句中,for循环的使用频率最高,while语句其次,do很少用。提高循环体效率的基本办法是降低循环体复杂性。

    规则

    1. 在多层循环中,如果有可能,应将最长的循环放在内层,最短的循环放在外层,以减少CPU切跨循环层的次数
    // 效率低,长循环在外层
    for (row=0; row<100; row++)
    {
        for (col=0; col<5; col++)
        {
            sum += arr[row][col];
        }
    }
    
    // 效率高,长循环在内层
    for (col=0; col<5; col++)
    {
        for (row=0; row<100; row++)
        {
            sum += arr[row][col];
        }
    }
    
    1. 如果循环体内存在逻辑判断,且循环次数很大,宜将逻辑判读移到循环体外面
      1. 第一个个程序简洁,但是循环中的逻辑判读打断了循环流水线的作业,使编译器不能对循环进行优化处理,降低了效率
      2. 第二个程序不简洁,但是如果N非常大,效率会明显高于第一个程序
      3. 将逻辑判断移到循环体外时,要保证循环不会影响到逻辑判断的结果,否则就不能移到循环体外
    // 效率低,但简洁
    for (i=0; i

    五、for语句的循环控制变量

    规则

    1. 不要在for循环体内改变循环变量,防止for循环失去控制。
    2. 建议for语句的循环控制变量取值采用“半开半闭区间”写法
    // 不良的风格,在for循环体内改变循环变量,建议以下程序的逻辑使用while循环完成
    for (i=0; i<100; i++)
    {
        if (i % 2 == 0)
        {
            i += 2;
        }
        else
        {
            i += 3;
        }
    }
    
    for (i = 0; i < N; i++);		// 半开半闭区间写法
    for (i = 0; i <= N-1; i++);		// 闭区间写法
    

    六、switch语句

    switch语句是多分支选择语句,而if语句只有2个分支可选,虽然可以使用嵌套的if来选择多分支实现,但是程序冗长难读,所以switch语句的出现很有必要

    规则

    1. 每个case语句的结尾都要加上break,否则将导致多个分支重叠(除非有意让分支重叠)
    2. 不要忘记加上default分支,即使程序根本不需要default处理,也应该保留default: break;语句。防止别人误以为你忘记default处理
    switch (variable)
    {
        case value1:
            ... ...
            break;
            
        case value2:
            ... ...
            break;
            
            ... ...
                
        default:
            break;
    }
    

    七、goto语句

    自从提倡结构化设计以来,goto语句就成了有争议的语句:

    1. goto语句跳转灵活,如果不加限制,会破坏结构化设计风格
    2. goto语句经常带来错误隐患,可能跳过某些类的构造、变量的初始化、重要的计算等。

    规则

    • goto语句目前的用途是从多重循环中跳出,不用写很多break语句
    • 我们对于goto语句的态度应该是慎用,而不是禁用
    for (i=0; i
  • 相关阅读:
    泰迪智能科技企业数据挖掘平台使用场景
    领悟《信号与系统》之 连续时间信号的时域分析法
    【Linux成长史】Linux基本指令大全
    Python爬虫打印状态码为521,返回数据为乱码?
    “现在没有人能离开Linux一天”——Linux基金会发布2021年度报告
    Pytorch autograd.grad与autograd.backward详解
    开源公告|LightDiffusionFlow SD工作流保存插件
    ssm和springboot整合
    css设置下划线
    二叉树经典oj面试题
  • 原文地址:https://blog.csdn.net/weixin_52811588/article/details/127112165