C++核心指导原则: 表达式和语句
C++ Core Guidelines 整理目录
ES: 表达式和语句 (Expressions and statements)
通用规则
ES.1: Prefer the standard library to other libraries and to “handcrafted code”
- 翻译: 优先选择标准库而非其他库或手工编写的代码.
- 原因: 标准库通常经过优化, 测试, 并且易于维护.
ES.2: Prefer suitable abstractions to direct use of language features
- 翻译: 优先使用合适的抽象而不是直接使用语言特性.
- 原因: 使用适当的抽象可以提高代码的可读性和可维护性, 并减少错误的可能性.
ES.3: Don’t repeat yourself, avoid redundant code
- 翻译: 避免重复自己, 不要写冗余代码.
- 原因: 减少重复代码有助于降低维护成本, 并避免因多处修改同一逻辑而产生的错误.
声明规则
ES.5: Keep scopes small
- 翻译: 尽量保持作用域小.
- 原因: 较小的作用域能够减少变量冲突的可能性, 并且让代码更加清晰和易于理解.
ES.6: Declare names in for-statement initializers and conditions to limit scope
- 翻译: 在 for 语句的初始化器和条件中声明名称以限制作用域.
- 原因: 通过限制变量的作用域, 可以避免它们在不必要的地方被访问或修改, 从而提高代码的安全性和可读性.
ES.7: Keep common and local names short, and keep uncommon and non-local names longer
- 翻译: 对于常见的和局部的名字保持简短, 对于不常见和非局部的名字则使用较长的名字.
- 原因: 这种做法有助于快速识别变量的用途, 同时确保名字具有足够的描述性以避免混淆.
ES.8: Avoid similar-looking names
- 翻译: 避免使用看起来相似的名字.
- 原因: 相似的名字容易导致误读, 从而引发错误. 选择明显不同的名字可以帮助防止这种情况发生.
ES.9: Avoid ALL_CAPS
names
- 翻译: 避免使用全大写的名字.
- 原因: 全大写的命名通常用于宏定义, 避免在其他情况下使用这种命名方式可以减少与宏定义的混淆.
ES.10: Declare one name (only) per declaration
- 翻译: 每个声明只声明一个名字.
- 原因: 单独声明每个名字可以使代码更易读, 并且更容易添加注释来解释每个变量的目的.
ES.11: Use auto
to avoid redundant repetition of type names
- 翻译: 使用
auto
关键字来避免类型名称的冗余重复. - 原因: 自动推导变量类型可以减少代码中的冗余, 使代码更简洁且不易出错.
ES.12: Do not reuse names in nested scopes
- 翻译: 不要在嵌套的作用域中重用名字.
- 原因: 重用名字可能会导致混淆和错误, 尤其是在调试复杂代码时. 为不同作用域内的实体使用独特的名字可以提高代码的清晰度和正确性.
ES.20: Always initialize an object
- 翻译: 总是初始化对象.
- 原因: 避免使用未初始化的对象, 因为它们可能包含随机值, 这会导致不可预测的行为.
ES.21: Don’t introduce a variable (or constant) before you need to use it
- 翻译: 不要在需要使用变量(或常量)之前引入它.
- 原因: 这有助于减少代码中的冗余, 并确保变量仅在其实际需要时才存在.
ES.22: Don’t declare a variable until you have a value to initialize it with
- 翻译: 在没有值来初始化变量之前不要声明它.
- 原因: 这可以防止使用未初始化的变量, 从而避免潜在的错误.
ES.23: Prefer the {}
-initializer syntax
- 翻译: 优先使用
{}
初始化语法. - 原因: 这种初始化方式更安全且更具一致性, 避免隐式类型转换和窄化转换问题.
ES.24: Use a unique_ptr<T>
to hold pointers
- 翻译: 使用
unique_ptr<T>
来持有指针. - 原因:
unique_ptr
提供了自动内存管理, 避免了手动释放内存的复杂性和潜在的内存泄漏问题.
ES.25: Declare an object const
or constexpr
unless you want to modify its value later on
- 翻译: 声明对象为
const
或constexpr
, 除非你打算稍后修改它的值. - 原因: 使用
const
或constexpr
可以帮助编译器进行优化, 并防止意外修改对象的值.
ES.26: Don’t use a variable for two unrelated purposes
- 翻译: 不要将一个变量用于两个不相关的目的.
- 原因: 这可以提高代码的可读性和可维护性, 避免混淆和错误.
ES.27: Use std::array
or stack_array
for arrays on the stack
- 翻译: 对于栈上的数组, 使用
std::array
或stack_array
. - 原因: 这些容器提供了更好的类型安全性和边界检查功能, 比传统的 C 风格数组更可靠.
ES.28: Use lambdas for complex initialization, especially of const
variables
- 翻译: 使用 lambda 表达式进行复杂的初始化, 特别是对于
const
变量. - 原因: lambda 表达式可以使复杂的初始化逻辑更加清晰和局部化, 同时保持代码简洁.
ES.30: Don’t use macros for program text manipulation
- 翻译: 不要使用宏来进行程序文本操作.
- 原因: 宏容易出错且难以调试, 推荐使用内联函数, 模板或其他语言特性来替代宏.
ES.31: Don’t use macros for constants or “functions”
- 翻译: 不要使用宏定义常量或"函数".
- 原因: 类型安全的常量和函数可以提供更好的编译时检查和调试信息, 而宏不具备这些优点.
ES.32: Use ALL_CAPS
for all macro names
- 翻译: 所有宏名称都使用全大写.
- 原因: 全大写的宏名称是一种约定俗成的做法, 可以帮助识别宏, 避免与普通变量名混淆.
ES.33: If you must use macros, give them unique names
- 翻译: 如果必须使用宏, 请给它们唯一的名称.
- 原因: 独特的宏名称可以减少与其他宏或变量冲突的可能性, 从而降低错误的风险.
ES.34: Don’t define a (C-style) variadic function
- 翻译: 不要定义 C 风格的可变参数函数.
- 原因: 可变参数函数难以调试且容易出错, 推荐使用标准库中的
<stdarg.h>
或类似的机制来替代.
表达式规则
ES.40: Avoid complicated expressions
- 翻译: 避免复杂的表达式.
- 原因: 复杂的表达式难以阅读和理解, 容易引入错误. 应尽量保持表达式的简单明了.
ES.41: If in doubt about operator precedence, parenthesize
- 翻译: 如果对运算符优先级有疑问, 使用括号明确优先级.
- 原因: 使用括号可以消除歧义, 提高代码的可读性和可维护性.
ES.42: Keep use of pointers simple and straightforward
- 翻译: 保持指针的使用简单直接.
- 原因: 复杂的指针操作容易出错, 保持简单可以减少潜在的错误.
ES.43: Avoid expressions with undefined order of evaluation
- 翻译: 避免具有未定义求值顺序的表达式.
- 原因: 某些表达式的求值顺序未定义, 可能导致不确定的行为. 应避免这种情况以确保代码的正确性.
ES.44: Don’t depend on order of evaluation of function arguments
- 翻译: 不要依赖函数参数的求值顺序.
- 原因: 函数参数的求值顺序在 C++中是未定义的, 依赖它会导致不可预测的结果.
ES.45: Avoid “magic constants”; use symbolic constants
- 翻译: 避免使用"魔法常量", 应使用符号常量.
- 原因: 符号常量(如枚举或
constexpr
)使代码更具可读性和可维护性, 并且易于修改.
ES.46: Avoid narrowing conversions
- 翻译: 避免窄化转换.
- 原因: 窄化转换可能导致数据丢失或未定义行为, 应尽量避免.
ES.47: Use nullptr
rather than 0
or NULL
- 翻译: 使用
nullptr
而不是0
或NULL
. - 原因:
nullptr
是一个类型安全的空指针常量, 比0
或NULL
更清晰且不易出错.
ES.48: Avoid casts
- 翻译: 尽量避免强制类型转换(casts).
- 原因: 强制类型转换可能隐藏错误并降低代码的安全性. 如果必须使用, 请选择合适的命名转换.
ES.49: If you must use a cast, use a named cast
- 翻译: 如果必须使用类型转换, 请使用命名转换.
- 原因: 命名转换(如
static_cast
,dynamic_cast
,const_cast
,reinterpret_cast
)提供了更清晰的意图, 并有助于编译器检查.
ES.50: Don’t cast away const
- 翻译: 不要通过类型转换去除
const
属性. - 原因: 去除
const
属性可能会导致未定义行为, 并破坏程序的安全性. 应尽量避免这种做法.
ES.55: Avoid the need for range checking
- 翻译: 避免需要范围检查.
- 原因: 使用合适的容器和算法可以减少手动进行范围检查的需求, 从而提高代码的安全性和效率.
ES.56: Write std::move()
only when you need to explicitly move an object to another scope
- 翻译: 仅在需要显式地将对象移动到另一个作用域时才使用
std::move()
. - 原因: 过度使用
std::move()
可能导致不必要的性能开销, 并且可能破坏某些优化. 只有在明确需要移动语义时才使用它.
ES.60: Avoid new
and delete
outside resource management functions
- 翻译: 在资源管理函数之外避免使用
new
和delete
. - 原因: 直接使用
new
和delete
容易导致内存泄漏和其他资源管理问题. 推荐使用智能指针或容器来自动管理资源.
ES.61: Delete arrays using delete[]
and non-arrays using delete
- 翻译: 使用
delete[]
删除数组, 使用delete
删除非数组对象. - 原因: 正确使用
delete[]
和delete
可以防止未定义行为, 确保资源正确释放.
ES.62: Don’t compare pointers into different arrays
- 翻译: 不要比较指向不同数组的指针.
- 原因: 比较指向不同数组的指针没有意义, 可能会导致未定义行为.
ES.63: Don’t slice
- 翻译: 避免对象切割(slicing).
- 原因: 对象切割是指将派生类对象赋值给基类对象时丢失派生类部分的行为. 应尽量使用指针或引用以避免这种问题.
ES.64: Use the T{e}
notation for construction
- 翻译: 使用
T{e}
语法进行构造. - 原因:
{}
初始化语法更安全, 避免了窄化转换和其他隐式类型转换问题.
ES.65: Don’t dereference an invalid pointer
- 翻译: 不要解引用无效指针.
- 原因: 解引用无效指针会导致未定义行为, 甚至程序崩溃. 确保指针有效后再进行解引用操作.
语句规则
ES.70: Prefer a switch
-statement to an if
-statement when there is a choice
- 翻译: 当有选择时, 优先使用
switch
语句而不是if
语句. - 原因:
switch
语句通常更清晰且更高效, 尤其是在处理多个离散值的情况下.
ES.71: Prefer a range-for
-statement to a for
-statement when there is a choice
- 翻译: 当有选择时, 优先使用范围
for
循环而不是传统的for
循环. - 原因: 范围
for
循环更简洁且不易出错, 特别适用于遍历容器中的所有元素.
ES.72: Prefer a for
-statement to a while
-statement when there is an obvious loop variable
- 翻译: 当存在明显的循环变量时, 优先使用
for
循环而不是while
循环. - 原因:
for
循环更适合于已知迭代次数的情况, 代码结构更加清晰明了.
ES.73: Prefer a while
-statement to a for
-statement when there is no obvious loop variable
- 翻译: 当没有明显的循环变量时, 优先使用
while
循环而不是for
循环. - 原因:
while
循环更适合于条件驱动的循环, 特别是在循环次数不明确的情况下.
ES.74: Prefer to declare a loop variable in the initializer part of a for
-statement
- 翻译: 优先在
for
循环的初始化部分声明循环变量. - 原因: 这样可以限制变量的作用域, 减少潜在的错误和冲突.
ES.75: Avoid do
-statements
- 翻译: 避免使用
do
语句. - 原因:
do
语句可能导致意外的逻辑错误, 尤其是当条件判断复杂时. 尽量使用while
或for
循环替代.
ES.76: Avoid goto
- 翻译: 避免使用
goto
语句. - 原因:
goto
语句会使代码难以理解和维护, 推荐使用结构化的控制流(如if
,for
,while
等)来替代.
ES.77: Minimize the use of break
and continue
in loops
- 翻译: 尽量减少在循环中使用
break
和continue
. - 原因: 过度使用
break
和continue
可能会使代码逻辑变得复杂且难以跟踪, 尽量保持循环结构简单明了.
ES.78: Don’t rely on implicit fallthrough in switch
statements
- 翻译: 不要依赖
switch
语句中的隐式 fallthrough 行为. - 原因: 使用显式的
[[fallthrough]]
注释来标记有意的 fallthrough, 避免意外的逻辑错误.
ES.79: Use default
to handle common cases (only)
- 翻译: 使用
default
处理常见情况(仅限). - 原因:
default
分支应仅用于处理预期之外的常见情况, 而不是作为默认的行为处理所有情况.
ES.84: Don’t try to declare a local variable with no name
- 翻译: 不要尝试声明一个没有名字的局部变量.
- 原因: 没有名字的变量没有任何意义, 并且可能引发编译器警告或错误.
ES.85: Make empty statements visible
- 翻译: 让空语句可见.
- 原因: 空语句(
;
)容易被忽略或误解, 建议通过注释或其他方式明确其意图.
ES.86: Avoid modifying loop control variables inside the body of raw for-loops
- 翻译: 避免在原始
for
循环体内修改循环控制变量. - 原因: 修改循环控制变量可能导致意外的循环行为, 增加调试难度. 尽量保持循环控制变量的修改在循环的初始化, 条件和增量部分进行.
ES.87: Don’t add redundant ==
or !=
to conditions
- 翻译: 不要在条件中添加冗余的
==
或!=
. - 原因: 冗余的比较操作不仅增加了代码长度, 还可能导致错误. 例如, 直接将布尔表达式用作条件即可.
算术规则
ES.100: Don’t mix signed and unsigned arithmetic
- 翻译: 不要混合使用有符号和无符号算术运算.
- 原因: 混合使用有符号和无符号类型可能导致意外的行为和错误, 特别是在比较和转换时.
ES.101: Use unsigned types for bit manipulation
- 翻译: 使用无符号类型进行位操作.
- 原因: 无符号整数在位操作中更合适, 因为它们不会涉及符号扩展问题, 并且可以避免与有符号数相关的陷阱.
ES.102: Use signed types for arithmetic
- 翻译: 使用有符号类型进行算术运算.
- 原因: 有符号类型更适合用于算术运算, 因为它们能正确处理负数, 而无符号类型在处理负数时可能会产生意外的结果.
ES.103: Don’t overflow
- 翻译: 避免溢出.
- 原因: 整数溢出会导致未定义行为或逻辑错误. 应确保计算结果在允许的范围内, 或者使用更大的数据类型来防止溢出.
ES.104: Don’t underflow
- 翻译: 避免下溢.
- 原因: 下溢(如浮点数减小到低于最小表示范围)也可能导致未定义行为或逻辑错误. 确保数值在合理范围内变化.
ES.105: Don’t divide by integer zero
- 翻译: 不要用整数除以零.
- 原因: 除以零会导致运行时错误或未定义行为. 应在执行除法之前检查除数是否为零.
ES.106: Don’t try to avoid negative values by using unsigned
- 翻译: 不要试图通过使用无符号类型来避免负值.
- 原因: 使用无符号类型并不能真正解决负值的问题, 反而可能引入其他类型的错误, 如意外的数值解释和比较问题.
ES.107: Don’t use unsigned
for subscripts, prefer gsl::index
- 翻译: 不要使用无符号类型作为下标, 推荐使用
gsl::index
. - 原因: 使用无符号类型作为数组下标可能导致边界条件错误, 特别是当索引值为负数或需要处理空范围时.
gsl::index
提供了一种更安全的替代方案.
Tags: