Skip to content

优雅编程

程序变量

  • 最好在声明的同时初始化,尤其是避免仅初始化对象的部分成员
  • 尽可能地缩短变量的生命周期与跨度(即在使用前先初始化、多定义变量)
  • 不要在多个代码片段之间使用temp等不明意义的命名,这样会使不相关的代码看起来相关
  • 警惕隐式含义,比如某变量为正数时为整数类型,负数时表示隐含的“出错”(即隐含布尔类型),改用多个变量
  • 确保所有声明的变量都使用过
  • 变量名称要描述“什么”,而不是“怎么”,要具象
  • 变量名称长度在8-20个字母比较适宜
  • Total、Sum、Max、Record、String、Pointer等等限定词要加到名字最后
  • i、j、k等约定速成的变量名适用于简单的循环,不要用于任何简单循环的下标循环之外的任何场合
  • 多个复杂循环嵌套时,最好不要使用i、j、k
  • 为状态变量起一个比flag更好的名字,比如不使用statusFlag,而是characterType(更加具体描述是什么status)
  • 使用done、error、found、ok等为布尔变量赋值
  • 为布尔变量赋予隐含真假意义的名字,比如sourceFileAvailable、sourceFileFound
  • 使用肯定的布尔变量名,不要包含“not”之类的否定含义
  • 如果枚举类型变量不需要指定域,那么使用类似COLOR_xxx的前缀来指定类别会比较好

程序优化方法

优化需要通过大量测试来验证一致性、性能,因为并非所有优化都是正确或必要的。从重构程序执行流的角度来讲,优化并不是灵丹妙药,并在不同语言、不同编译器、不同环境、不同任务中表现出巨大的差异。以下的优化方法均仅供参考。

利用短路与哨兵

  • 将出现频率高的情况放在优先执行的位置
  • 循环中,搜索到目标后立即退出循环
  • 循环搜索时,也可以将搜索对象放在数组最后(额外空间),最后检查下标。(本质上是保证不会越界搜索,且保证循环一定正确结束)

优化计算效率

  • 程序执行前计算结果,通过常量保存、硬编码、文件保存的方法来避免重复计算
  • 尽量减少循环体内的计算工作,可以在之前计算完毕并保存,之后引用
  • 公共子表达式应当保存在变量里
  • 适当将乘法重写为加法、幂重写为乘法、整数代替浮点数、单精度代替双精度、移位操作代替乘除2、三角恒等式代替三角函数
  • 尽量减少数组维度与数组引用
  • 多重循环时,将循环次数少的放外层

使用低级语言重写代码

  1. 使用高级语言完成程序编写
  2. 进行测试,验证正确性
  3. 进行程序分析,确定热点代码
  4. 对热点代码使用低级语言改写

设计恰当的执行控制流

循环

  • 循环层数最好不要超过三层
  • 短循环可以多使用break、continue等控制,长循环尽量保证唯一出口
  • 循环附近不要写重复的代码(修改需要同步修改多处,不便维护)
  • for适合与“数量”有关的循环,且不要在内部修改下标;while适合与“条件”有关的循环
  • 循环体应当自成子程序,循环条件应当清晰易读
  • 多使用for循环,但不要把与循环无关的语句(比如初始化一个不用于循环的变量)放在循环头
  • 不要写空循环,将空循环改写成while循环
  • 如果一个循环可以做两件事情,把它们拆成两个循环,除非注明在性能上有所提高
  • 不要将循环下标作为结果返回。如果需要使用,可以在循环开始前定义新的变量,然后在循环内部对这一变量进行赋值

建表,以提高代码质量

用查表法替换繁琐的if-else判断

使用大量if-else的坏处:

  • 不易阅读
  • 代码重复度高,无意义
  • 对于复杂情况,容易漏条件
  • 判断逻辑硬编码在代码中,维护不便

使用查表法的好处:

  • 易读
  • 代码简洁且高效
  • 不会漏掉条件
  • 可以将表放在外部文件或者统一放在一起,便于维护;可以在不改变程序的情况下修正运行结果

用法:将要判断的各个参数作为表的维度,将判断结果作为表索引后的结果。

用索引表替换数据表

稀疏的数据表在存储对齐的情况下会浪费大量空间。与之相比,采用索引表可以降低空间浪费量(仍然会产生浪费)。为了进一步减少索引表空间,可以使用阶梯索引表,根据数据的范围(而不是具体的数据值)进行建索引,比如根据百分制成绩计算绩点,建立相应的data-to-key函数,放在数组中。

用结果表替换数学计算结果

考虑到系统函数的精确性,计算速度可能较慢。可以预先手动算出一些数据并建表,计算时直接查表即可,大大提高程序性能。

一些小小的语法特性

C

初始化数组,可以连续赋值

int arr[10] = {
    [0]       = 1,
    [1 ... 4] = 2,
    [5 ... 7] = 4,
};

初始化结构体或联合,可以一起赋值

struct test {
    int a;
    int b;
    int c;
    int d;
};

int main(
    int argc, 
    char const *argv[]
    )
{
    struct test t = {
        .a = 1,
        .b = 2,
        .c = 3,
        .d = 4,
    };

    return 0;
}