本文已参与「新人创作礼」活动,一起开启掘金创作之路。
编译期计算
我们可以利用C++的模板在编译期做一些有意思的事情
比如演算阶乘
template <int N> struct fact {
static constexpr int value = fact<N - 1>::value * N;
};
template <> struct fact<0> { static constexpr int value = 1; };
然后我们就可以 fact<10>::value这样来得到一个常量值
注意,上述所展示的模板计算全是在编译期进行的,所以我们就可以O(1)计算阶乘了?
理论上是这样,只要你保证不溢出就ok
这个例子也是模板编程解决一类问题的典型示例
又比如另一个模板技巧
template <typename V, int N> size_t len(const V (&)[N]) { return N; }
上述可以计算一个编译期确定的数组所拥有的长度
编译期循环
现在,想一想,要实现编译期的循环如何实现?
利用特化手段?
好像可行耶
来搞一搞
template <int begin, int end> struct for_each {
static void run() {
std::cout << begin << " \n";
for_each<begin + 1, end>::run();
}
};
template <int begin> struct for_each<begin, begin> {
static void run() { std::cout << begin << " \n"; }
};
好,轻松实现一个 for_each<1, 10>::run(); 打印 1 - 10的值,虽然是一个递归
但是,对于模板编程中的有一些操作来说,够用了,更多的操作为什么不留到运行期做呢?
上面的操作写得麻烦啊,有没有什么解决的办法
下面介绍两个可以简化的方法
if constexpr
利用C++ 17的 常量期 if
template <int begin, int end> void for_each() {
std::cout << begin << " ";
if constexpr (begin < end)
for_each<begin + 1, end>();
}
看得出来,常量器if还是要简化代码不少的
利用 index_sequence
index_sequence 是 C++14出的一个新玩意 我们可以用它来生成一串序列参数
template <int> void for_each_help() {}
template <int begin, typename T, typename... V>
void for_each_help(T t, V... v) {
std::cout << begin + t << " ";
for_each_help<begin>(v...);
}
template <int begin, std::size_t... Is>
void for_each_(std::index_sequence<Is...>) {
for_each_help<begin>(Is...);
}
template <int begin, int end> void for_each() {
for_each_<begin>(std::make_index_sequence<end - begin>());
}
index_sequence + flow expression
上面的代码太复杂了,我们来简化简化
C++ 17 出了个 flow expression
我们可以利用一下
template <int begin, std::size_t... Is>
void for_each_(std::index_sequence<Is...>) {
((std::cout << begin + Is << " "), ...);
}
template <int begin, int end> void for_each() {
for_each_<begin>(std::make_index_sequence<end - begin>());
}
来写一道题 ?
光说不练假把式,我们来看一道在线OJ上的题目
青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
数据范围:
0 <= n <= 100
解法
一般的解法太无趣了,我们来一个编译期的解法(0 开销?)
case 1:
class Solution {
public:
static constexpr int MOD = 1e9 + 7;
unordered_map<int, int> mp;
template <int N> struct fun {
// enum { value = (fun<N - 1>::value + fun<N - 2>::value) % MOD };
static constexpr int value = (fun<N - 1>::value + fun<N - 2>::value) % MOD;
};
template <> struct fun<1> {
// enum { value = 1 };
static constexpr int value = 1;
};
template <> struct fun<0> {
// enum { value = 1 };
static constexpr int value = 1;
};
template <int NOW, int END> void for_each() {
mp.insert({NOW, fun<NOW>::value});
if constexpr (NOW != END) {
for_each<NOW + 1, END>();
}
}
Solution() { for_each<0, 100>(); }
int numWays(int n) { return mp[n]; }
};
case 2
class Solution {
public:
static const int MOD = 1e9 + 7;
unordered_map<int, int> mp;
template <int N> struct fun {
static constexpr int value = (fun<N - 1>::value + fun<N - 2>::value) % MOD;
};
template <> struct fun<1> { static constexpr int value = 1; };
template <> struct fun<0> { static constexpr int value = 1; };
template <std::size_t begin, std::size_t... Is>
void for_each_help(std::index_sequence<Is...>) {
((mp.insert({begin + Is, fun<Is>::value})), ...);
}
template <int begin, int end> void for_each() {
for_each_help<begin>(std::make_index_sequence<end - begin>{});
}
Solution() { for_each<0, 101>(); }
int numWays(int n) { return mp[n]; }
};
上述两种写法都是编译期计算的dp值
有点类似于记忆化搜索的样子
很好的展示了模板循环展开和模板编译期计算的功能