循环,条件与分支 #
这个东西老生常谈了,我本来没有看的欲望,但是有一道题目很有意思,在此记录:
从 1 开始,向上计数,将任何只能被 3 整除的数字替换为“fizz”一词,将任何只能被 5 整除的数字替换为“buzz”一词,将任何可被 3 和 5 整除的数字替换为“fizzbuzz”一词,如果还可被7整除,那么替换为“fizzbuzzpop”。
首先,介绍一个名词:分支预测。
简单来说,现代CPU在进行多分支的判断(if-else)的时候,其实会去猜程序下一步的路径,如果猜对了,那么效率会提升;如果猜错了,那么只能把流水线上面的内容清空,然后重新执行指令。
所以无论如何,应该尽量避免写if-else。
题目给出的答案是这么写的:
// h/t to reader Waldo for suggesting this quiz
#include <iostream>
void fizzbuzz(int count) {
for (int i{1}; i <= count; ++i) {
bool printed{false};
if (i % 3 == 0) {
std::cout << "fizz";
printed = true;
}
if (i % 5 == 0) {
std::cout << "buzz";
printed = true;
}
if (i % 7 == 0) {
std::cout << "pop";
printed = true;
}
if (!printed)
std::cout << i;
std::cout << '\n';
} // end for loop
}
int main() {
fizzbuzz(150);
return 0;
}
能运行吗?能,优雅吗?我不太满意。
所以说,有没有一个办法,不同于这个思路,同时又能更计算机一点?
评论区的思路:
#include <iostream>
void fizzbuzz(int n)
{
for (int i{ 1 }; i <= n; i++)
{
int f = (i%7 == 0)*4 + (i%5 == 0)*2 + (i%3 == 0);
switch (f)
{
case 0:
std::cout << i;
break;
case 1:
std::cout << "fizz";
break;
case 2:
std::cout << "buzz";
break;
case 3:
std::cout << "fizzbuzz";
break;
case 4:
std::cout << "pop";
break;
case 5:
std::cout << "fizzpop";
break;
case 6:
std::cout << "buzzpop";
break;
case 7:
std::cout << "fizzbuzzpop";
break;
}
std::cout << '\n';
}
return;
}
int main()
{
fizzbuzz(22);
}
这个方法非常巧妙,把每一个分支映射到了一个二进制的向量空间上面,这将会极大的提高性能,因为对于CPU来说,移位操作是原生的。但是,带来的就是更多相似字符串的重复占用,不知道这是不是良好的实践呢?
如果还想减少的话,还是用if来去测试位,不过这样又引入了分支。
其实这么用的地方还是很多的,POSIX的权限就是个例子。但是,用的时候就是想不到啊!
标准输出,标准错误和标准输入 #
这三者是这样的:
| 名字 | 文件描述符 | 在c++中的对应 |
|---|---|---|
| 标准输入 | 0 | std::cin |
| 标准输出 | 1 | std::cout |
| 标准错误 | 2 | std::cerr |
类型转换 #
数值提升(numberic promotion): #
同类型,把一个更短的类型转换成一个更长的类型,安全,不会造成精度丢失。
只有两条路径:
bool,有/无负号的短整形,字符型->int->unsigned intfloat->double
注意,从int到long不是提升,是转换。
数值转换(numberic conversion): #
基本类型的转换,包括:
窄化(narrowing conversion):长的类型转化成一个更短的类型,在{}中不被允许。
但是
constexpr是允许的,因为是编译时求值,所以实际上是截断不是转化。
算术转换(arithmetic conversion): #
对基本算术操作符两端的不同类型进行转换,有些规则:
- 整数转浮点。
- 对于无符号/有符号:
- 如果没超过有符号上限,那就转换成有符号。
- 超过了,那就转换成无符号。
static_cast在编译时检查(类型在编译时是确定的),dynamic_cast在运行时检查。
不建议使用C-Style的转换,因为他可能使用上面中的任意一个以及const_cast。
auto #
对于auto来说,必须要先确定得到的结果是什么,才能做推断,不能对没有初始化值的变量做推断。
在用std::cin的时候,给的类型是istream,所以自动推断会推出这个类型,肯定不是你想要的结果。
auto不会保留const的修饰,所以如果需要保留,用const auto。
auto的尾随返回值 #
这其实一点都不auto,只是为了让函数名对齐,然后用->给出返回类型罢了。
auto add(int x, int y) -> int;
auto divide(double x, double y) -> double;
auto printSomething() -> void;
auto generateSubstring(const std::string &s, int start, int len) -> std::string;