CMake 多 main() 入口项目搭建(刷题向)
在 ACM 算法竞赛 / LeetCode 使用 C++ 进行刷题等场景中,通常:
- 需要维护很多的 C++ 源代码
- 需要有多个 main 函数入口方便执行测试
- 有一些自己写的公共函数类库 (如调试输出vector等) 在多个文件中引用
本文介绍了通过 CMake 及一些自生成配置文件的工具,通过一个统一的库,方便管理所有代码的方法。
CMake 是一个平台无关的可以定制 C++ 编译流程的工具,可以生成特定平台的 Makefile 文件。默认被 Intellij CLion 支持。
需要首先通过 CLion 创建一个 C++ 工程:
创建出自己需要的目录结构来:我们把所有的源代码 .cpp 等文件放在 /src 中, 一些工具类放在 /src/utils 中
CMakeLists.txt 文件就是我们的 CMake 编译流程配置文件了。为了支持多个 main() 函数入口,我们用 add_executable 命令添加多个 target,这种才能在每个 main() 入口单独执行:
cmake_minimum_required(VERSION 3.17) project(lc-cpp) set(CMAKE_CXX_STANDARD 17) add_definitions("-DKUN_DEBUG") add_executable(training_p1 src/normal/cat0/cat00/cat000/p1.cpp) add_executable(training_p15 src/normal/cat0/cat00/cat001/p15.cpp) add_executable(biweekly_34_2 src/match/biweekly/biweekly34/p2.cpp) add_executable(biweekly_34_3 src/match/biweekly/biweekly34/p3.cpp) add_executable(biweekly_34_4 src/match/biweekly/biweekly34/p4.cpp)
接下来我们处理公共类库的问题,只需要将 utils 目录声明为编译器的头文件搜索路径之下,这样就能被其他文件引用了。
像是 LeetCode 的 TreeNode, ListNode 等基础数据结构及其解析、debug 工具可以放在这里面:
include_directories("src/utils")
再来解决最后一个问题。我们每次创建一些 .cpp 代码后,就需要去 CMakeLists.txt 文件中添加对应的 add_executable 代码。这部分工作我们可以通过脚本的形式去自动生成,附上 Python3 代码:
import os HEAD = '''cmake_minimum_required(VERSION 3.17) project(lc-cpp) set(CMAKE_CXX_STANDARD 17) add_definitions("-DKUN_DEBUG") include_directories("src/utils") ''' def update_cmake(): file_list = [] for root, dirs, files in os.walk("src"): if len(files) == 0: continue for f in files: file_list.append(root + os.sep + f) res = HEAD for i in sorted(file_list): if 'utils' in i: continue split = i.split(os.sep) name_ids = filter(lambda x: x != 'src', split) name = "_".join(name_ids).replace(".cpp", "") path = "/".join(split) code = f'add_executable({name} {path})\n' res += code with open('CMakeLists.txt', "w") as f: f.write(res) if __name__ == '__main__': update_cmake()
至此,通过把所以题目按照分类文件夹管理,然后自动生成可执行文件编译配置,一个比较完成的方便 C++ 刷题的项目环境就创建好了。
最后附一个 LeetCode 刷题常用的基础类及解析、输出工具(common_ds.hpp):
/** * LeetCode Common DataStruct. */ #include <bits/stdc++.h> using namespace std; using ll = long long; struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} TreeNode(int val, TreeNode *left, TreeNode *right) : val(val), left(left), right(right) {} }; static TreeNode *parse_tree(string s) { const regex re(","); vector<string> v(sregex_token_iterator(++s.begin(), --s.end(), re, -1), sregex_token_iterator()); if (v.empty()) return nullptr; vector<TreeNode *> ns; ns.push_back(new TreeNode(stoi(v.front()))); for (int i = 1; i < v.size(); ++i) { TreeNode *cur = v[i].find("null") != string::npos ? nullptr : new TreeNode(stoi(v[i])); if (i % 2 == 1) ns[(i - 1) / 2]->left = cur; else ns[(i - 1) / 2]->right = cur; if (cur) ns.push_back(cur); } return ns.front(); } static string to_string(TreeNode *root) { if (!root) return "null"; if (!root->left && !root->right) return to_string(root->val); return "{" + to_string(root->val) + ", " + to_string(root->left) + ", " + to_string(root->right) + "}"; } static void print(TreeNode *head, int len = 4, int height = 0, string to = "#") { if (!head) return; print(head->right, len, height + 1, "v"); string val = to + to_string(head->val); int lenM = val.length(), lenL = (len - lenM) / 2, lenR = len - lenM - lenL; val = string(height * len, ' ') + string(lenL, ' ') + val + string(lenR, ' '); cout << val << endl; print(head->left, len, height + 1, "^"); } /// ========================================================================= struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; static ListNode *parse_list(string s) { const regex re("->"); vector<string> v(sregex_token_iterator(s.begin(), s.end(), re, -1), sregex_token_iterator()); ListNode *mock = new ListNode(-1), *p = mock; for (auto &i : v) { p->next = new ListNode(stoi(i)); p = p->next; } return mock->next; } static string to_string(ListNode *node) { if (!node) return ""; return to_string(node->val) + (node->next ? "->" + to_string(node->next) : ""); } static void print(ListNode *node) { cout << to_string(node) << endl; } /// ========================================================================= struct Interval { int start, end; Interval() : start(0), end(0) {} Interval(int start, int anEnd) : start(start), end(anEnd) {} }; /// =========================================================================