CppCoreGuidelines P.3 明确地表达程序的意图
P.3 Express intent
理由
如果要读代码的注释才能理解代码的意图,那可能就意味着,你写的代码不够清晰,你没有很明确地表达出代码的意图。
例子
#+attr_html :class bad
vector<int> v = {1, 2, 3, 4, 5}; gsl::index i = 0; while (i < v.size()) { cout << v[i] << " "; i++; }
1 2 3 4 5
这里,我们的意图其实就是要循环一遍向量 v
,把它的元素的值一个个打印出来。但是呢,读别人拿到这个代码,可能就无法一眼看出来代码要做的事情。并且,代码里还多余的定义了一个索引变量 i
,这个变量的作用域范围还超出了循环体。
倘若把 while
循环改成以下 for
循环,就比较直截了当了:
vector<int> v = {1, 2, 3, 4, 5}; for (const auto& x : v) { cout << x << " "; }
1 2 3 4 5
并且,在这里,我们还用了 const
关键字修饰 x
,我们限定了,在这个循环里,我们不会修改 x
的值。如果不小心在循环中对 x
的值进行修改,编译器就会报错。那么,根据编译出错信息,我们就比较容易在编码早期发现问题了。
例如,我们在循环体内,做一个自增操作,就收到了编译器的警告:
vector<int> v = {1, 2, 3, 4, 5}; for (const auto& x : v) { cout << ++x << " "; }
C-src-eO7Jst.cpp: In function 'int main()': C-src-eO7Jst.cpp:14:15: error: increment of read-only reference 'x' 14 | cout << ++x << " "; | ^
如果我们想要明确表明,我们要在循环中修改变量 x
,那么就可以去掉 const
:
vector<int> v = {1, 2, 3, 4, 5}; for (auto& x : v) { cout << ++x << " "; }
2 3 4 5 6
除了 for
循环,我们也可以用std::ranges::for_each
更清晰的表达程序意图:
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; std::ranges::for_each(v, [](const auto &x) { cout << x << " "; });
1 2 3 4 5 6 7 8 9 0
需要注意的是,在 algorithm
中,也有一个std::for_each
,它的前两个参数是 InputIterator
:
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; for_each(begin(v), end(v), [](int x) { cout << x << " "; });
1 2 3 4 5 6 7 8 9 0
如果,在循环访问的时候,不需要按照顺序一个一个访问,可以添加
execute::par
参数,来表示并行访问。因为在这里 v
的元素不够多,不会有太明显的变化:
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; for_each(execution::par, v.begin(), v.end(), [](int x) { cout << x << " "; });
1 2 3 4 5 6 7 8 9 0
另外举一个例子,如果要表示在两点间画线,以下两种写法里,第二种就比较明确:
#+attr_html :class bad
draw_line(int, int, int, int); // obscure
draw_line(Point, Point); // clearer
一个优秀的程序员应该:
- 熟悉掌握指南支持的程序库(guidelines support library, gsl)
- 熟悉了解ISO C++标准库
- 其他项目中使用的基础库
强化
- 弄清楚简单的
for
循环和带范围的for_each
的差异,什么时候可以选用for_each
- 了解
f(T*, int)
接口和f(span<T>)
接口的差异 - 避免只在循环中用到的变量,不要超出循环体范围
- 避免直接用
new
或delete
的操作 - 注意有过多内置类型的函数参数,通常可以通过自定义类型减少参数数量