C++ 调试模板
C++ 调试模板
可以直接滚到最下面自取。
结合了大量 C++11 和 C++14 的新特性,如有误欢迎 喷 指正。
随便挂个博客食用更佳。
再随便打个广告:本文由洛谷题解格式化工具格式化了一行以符合要格式要求。
0. 最初的最初
某一大群人用 vscode 不会调试,我告诉他下
C/C++ Extension Pack和C/C++ Compile Run两个扩展,再把mingw64\bin添加到环境变量,F5 一键调试,他还告诉我挂了,我一看,竟然路径名中有中文。。。
gdb 太高级了,不会。
1. 所以直接输出
如果运行时我想知道某个值的变化,怎么办?
直接 cout << x << '\n'; 或 printf("%d\n", x); 即可。
但是调完后需要删掉或注释掉,不然直接
2. 所以直接输出到错误流
聪明的小孩子想到 cerr/fprintf(stderr),嗯,这才是正确的做法。
但是如果要输出很多个,前一种格式太烦了,后一种又太长了,怎么办。
同时输出多了不知道哪一行对应的是哪一个,怎么办???
慢慢来,一个一个解决。
3 所以用可变参数宏 & 可变参数函数模板
宏定义
注意到宏定义支持字符串替换,举个例子。
#define debug(x) cerr << #x << " = " << (x) << '\n';
如果我写 debug(a + b),编译时就会自动替换为 cerr << "a + b" << " = " << (a + b) << '\n';。
这样,我只需要在需要输出的地方写 debug(x) 即可。
但是如果我要同时检查多个值,怎么办?
可变参数函数模板
在 C++11 之前,类模板和函数模板都只能接受固定数目的模板参数。
C++11 引入了模板参数包,可以接受任意数量和任意类型的模板参数。
由于 fprintf 并不像 cerr 一样多态,所以以下都用 cerr 实现。
可变参数模板类似于:
template <typename... Clazz>
void fun(Clazz... paras) {}
所以我们可以这样输出多个值:
template <typename T>
void debug(const T &t) { cerr << t << '\n'; }
template <typename T, typename... Args>
void debug(const T &t, const Args &...rest)
{
cerr << t << ' ';
debug(rest...);
}
注意到需要一个终止函数,否则最后只剩一个参数时会报错。
现在可以这样调用:debug(a, b, a + b)。
突然发现输出成一行了更不知道谁是谁了,怎么办???
所以接着用宏定义
可变参数宏,是一种类似于可变参数函数模版的宏定义。
使用方法也很简单:#define debug(...) _debug(#__VA_ARGS__ " =", __VA_ARGS__) 就行了。
所以拼起来
template <typename T>
void _debug(const T &t) { cerr << t << '\n'; }
template <typename T, typename... Args>
void _debug(const T &t, const Args &...rest)
{
cerr << t << ' ';
_debug(rest...);
}
#define debug(...) _debug(#__VA_ARGS__ " =", __VA_ARGS__)
这时候同样调用 debug(a, b, a + b); 就会展开成 _debug("a, b, a + b" " =", a, b, a + b),完美解决以上问题。
注:这里用了编译时自带的字符串拼接。
新问题
其实并没有解决所有问题,因为即使输出到错误流,也是要时间的,详见某道入门赛题。
4. 所以接着上宏定义
如何不用时间?
不执行就不用时间,所以如果在 oj 上评测,就可以使用 ONLINE_JUDGE 宏。
例如我最常用的一个模板:
#ifdef ONLINE_JUDGE
freopen("???.in", "r", stdin);
freopen("???.out", "w", stdout);
#endif
本地测是不会重定向输入输出流的,交到 oj 上时会打开文操。
直接套上:
#ifndef ONLINE_JUDGE
template <typename T>
void _debug(const T &t) { cerr << t << '\n'; }
template <typename T, typename... Args>
void _debug(const T &t, const Args &...rest)
{
cerr << t << ' ';
_debug(rest...);
}
#define debug(...) _debug(#__VA_ARGS__ " =", __VA_ARGS__)
#else
#define debug(...) 0
#endif
这样在 oj 上放循环语句里的 debug(x) 都会替换为无用语句,不会影响程序运行效率(同时减少编译时间)。
新问题
所以输出数组还是要打 for 是吧。
但是你可以用 copy 函数,原理我也不知道,反正可以这么用来输出一个数组:copy(a + 1, a + 1 + n, ostream_iterator<int>(cerr, " ")), cerr << '\n';。
发现打起来很麻烦,又要注释,又不知道是哪个数组,怎么办???
5. 所以新建一个类(结构体)!!!
好吧,我们先看看直接 debug(a + 1, a + 1 + n); 会输出什么?
两坨指针,不用试了,那 vector 呢。
直接 CE。
no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'const __gnu_cxx::__normal_iterator<long long int*, std::vector<long long int> >')
所以考虑新建一个类,再重载输出流运算。
结合之前的新特性,很轻松就能写出来:
template <typename Iterator>
struct _Range
{
Iterator l, r;
friend ostream &operator<<(ostream &os, const _Range &t)
{
if (t.l >= t.r)
{
return os << "[]";
}
os << '[' << *t.l;
for (auto i = next(t.l); i != t.r; ++i)
{
os << ',' << *i;
}
return os << ']';
}
};
兴高采烈的你试了试 _Range(a.begin(), a.end()),还是 CE。
因为没有填充对应模板还是别的什么问题,反正怎么都调不出来。
所以我们新建一个辅助函数,自动推导类型并填充进模板,同时支持 C-style 数组和 vector 等 STL 容器:
为了自动推导类型,我们需要用到 auto 和 decltype 关键字。
这都是 C++14 的新特性,但我的主要目的并不是介绍新特性,自己看吧。
template <typename Container>
auto Range(const Container &c, size_t l, size_t r) -> _Range<decltype(begin(c))>
{
auto start = begin(c) + min(l, c.size());
auto end = begin(c) + min(r, c.size());
return {start, end};
}
template <typename T, size_t N>
auto Range(const T (&arr)[N], size_t l, size_t r) -> _Range<const T *> { return {arr + min(l, N), arr + min(r, N)}; }
这样可以 cerr << Range(a, 0, a.size()) 来输出整个 vector 了,是不是很又臭又长?
所以我们再实现一个返回整个容器的辅助函数 Range:
template <typename Container>
auto Range(const Container &c) -> _Range<decltype(begin(c))> { return {begin(c), end(c)}; }
template <typename T, size_t N>
auto Range(const T (&arr)[N]) -> _Range<const T *> { return {arr, arr + N}; }
因为同时支持 C-style 数组,你可以这么用:
int a[] = {1, 1, 4, 5, 1, 4};
debug(Range(a));
明显的,initializer_list 和 array 也支持,不做过多演示。
我还想输出 pair!!
pair 没有 begin/end 函数,因此我平时都用 array<int, 2>。
简单重载一下就行了。
template <typename T1, typename T2>
ostream &operator<<(ostream &os, const pair<T1, T2> &p) { return os << '(' << p.first << ',' << p.second << ')'; }
什么!!你想要输出 tuple???那你还不如自定义一个类(结构体)同时重载输出流运算符呢。
6. debug 还是太长了!!!
我直接【请输入文本】。
#define D debug
#define Rg Range
#define ifD(cond, ...) \
if (cond) \
{ \
debug(__VA_ARGS__); \
}
聚合一!!召唤完全体!!
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define int ll
#ifndef ONLINE_JUDGE
template <typename T>
void _debug(const T &t) { cerr << t << '\n'; }
template <typename T, typename... Args>
void _debug(const T &t, const Args &...rest)
{
cerr << t << ' ';
_debug(rest...);
}
#define debug(...) _debug(#__VA_ARGS__ " =", __VA_ARGS__)
#else
#define debug(...) 0
#endif
template <typename Iterator>
struct _Range
{
Iterator l, r;
friend ostream &operator<<(ostream &os, const _Range &t)
{
if (t.l >= t.r)
{
return os << "[]";
}
os << '[' << *t.l;
for (auto i = next(t.l); i != t.r; ++i)
{
os << ',' << *i;
}
return os << ']';
}
};
template <typename Container>
auto Range(const Container &c) -> _Range<decltype(begin(c))> { return {begin(c), end(c)}; }
template <typename T, size_t N>
auto Range(const T (&arr)[N]) -> _Range<const T *> { return {arr, arr + N}; }
template <typename Container>
auto Range(const Container &c, size_t l, size_t r) -> _Range<decltype(begin(c))>
{
auto start = begin(c) + min(l, c.size());
auto end = begin(c) + min(r, c.size());
return {start, end};
}
template <typename T, size_t N>
auto Range(const T (&arr)[N], size_t l, size_t r) -> _Range<const T *> { return {arr + min(l, N), arr + min(r, N)}; }
template <typename T1, typename T2>
ostream &operator<<(ostream &os, const pair<T1, T2> &p) { return os << '(' << p.first << ',' << p.second << ')'; }
#define D debug
#define Rg Range
#define ifD(cond, ...) \
if (cond) \
{ \
debug(__VA_ARGS__); \
}
signed main()
{
int a[] = {1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0};
vector<int> b(a + 3, a + 10);
array<int, 7> c = {1, 9, 1, 9, 8, 1, 0};
static_assert(__cplusplus >= 201402L, "C++14 or higher required");
// Some test cases
debug(Range(a)); // Entire C-array
debug(Range(a, 3, 10)); // Subrange of C-array
debug(Range(b)); // Entire vector
debug(Range(b, 1, 4)); // Subrange of vector
debug(Range(a, 0, 3), Range(b), Range(a, 10, 13)); // Multiple ranges
debug(make_pair(114514, "1919810")); // Pair
debug(Range({78, 1, 1, 4, 5, 1, 4, 91}, 1, 7)); // Range from initializer list
debug(Range(c)); // Range from array
// Group test cases
int x = 1;
string y = "145";
ifD(114514 != 1919810, x, y, Rg(y, 0, 2), make_pair('1', "9"), Rg({"19", "81", "0"})); // Mix it all up
ifD(114514 == 1919810, "What the hell?", 114514, '=', 1919810); // The condition is false
return 0;
}
/*
Expected output:
Range(a) = [1,1,4,5,1,4,1,9,1,9,8,1,0]
Range(a, 3, 10) = [5,1,4,1,9,1,9]
Range(b) = [5,1,4,1,9,1,9]
Range(b, 1, 4) = [1,4,1]
Range(a, 0, 3), Range(b), Range(a, 10, 13) = [1,1,4] [5,1,4,1,9,1,9] [8,1,0]
make_pair(114514, "1919810") = (114514,1919810)
Range({78, 1, 1, 4, 5, 1, 4, 91}, 1, 7) = [1,1,4,5,1,4]
Range(c) = [1,9,1,9,8,1,0]
x, y, Range(y, 0, 2), make_pair('1', "9"), Range({"19", "81", "0"}) = 1 145 [1,4] (1,9) [19,81,0]
*/
总结
终于解决了,尽管赛时不会脑抽打一遍这种东西,但平时用起来还是挺方便的。
赛时打到 3 就行了,因为建议不要依赖 ONLINE_JUDGE 宏,删注释才是最保险的。
所以点个赞再走吧!
很难想象 C++ 更新了这么多支持多态的特性,但还是很开心能用到这些技巧。
为了赛时能用,所以只用了 C++14 即以前的特性,如果有更优秀的实现方法,请不吝赐教。