当前位置: 首页 > news >正文

lil_tea c++ style guide

lil_tea c++ style guide

部分借鉴自 the cherno, 部分借鉴自 google c++ style guide, 部分借鉴自 linux kernel coding style, 部分借鉴自 算法竞赛进阶指南.
因为我年纪大了所以已经忘记哪一条是从哪借鉴的了, 朋友们看到哪条觉得合理就用吧, 不合理的不用就好了.

总结一句话: 坚持 c like 特色指针主义道路, 以安全性换自由; 姓 bjarne 还是姓 graydon 的问题上我们要坚持 bjarne 领导, 坚持 k&r 的文化自信, 杜绝 graydon 的糖衣炮弹.

调试

我觉得调试是最重要的, 所以放在最开头.

调试, 最最最重要的, sudo apt remove gdb (这只是个玩笑, 不要真的执行).

深入学习贯彻 fail fast 原则, 在出现错误时直接退出程序, 而不是使用 try throw catch. 编写程序的时候假设所有东西不会出错, 然后每当出现程序异常退出就可以知道程序出错了.

检测出错的方式非常简单, 在你认为可能出错的一行语句后输出调试, 输出的可以是任何你喜欢的东西. 可以在分号后直接写, 也可以换行后写, 但一定是在后面写.

这里是个例子, 你可以调用 log(__LINE__) 的方式直接输出调试, 而在没有 -Dyoung_tea 时调试语句不会输出.

注意 ""s 需要 using std::string_literals::operator""s;.

void	log(long line_number) {
#ifdef	lil_teastd::cerr << "line: "s << line_number << " | hey siri, play <hit'em up> please\n"s;
#endif
}

比如我有一个向量加法的函数:

std::vector<long>* add(const std::vector<long> &a, const std::vector<long> &b) {	// 这行不算注释正好 80 字符if (a.size() != b.size()) {log(__LINE__);	// 很显然如果运行到这里, 那应该是死了std::abort();	// 我炸// *(long*)nullptr = 998244353;	// 很显然我以前用的这个方式更粗暴, 不建议使用}auto c = new std::vector<long>(a.size());for (long x : std::views::iota(0, a.size()))(*c)[x] = a[x] + b[x];return c;
}

代码框架

标识符

全部使用 snake_case, 和 STL 保持统一, STL 用 snake_case 那我也用.

头文件

#include <bits/stdc++.h>.

不是说这个代码是竞赛专用, 只要 g++ 提供了就说明这个头文件是有意义的, 开发中使用也有很多好处, 增加的编译时间可以忽略不计.

有人说这个会引入一些符号, 这个要分两方面说:

  1. 函数名容易冲突. 你不 using namespace std 哪来的函数名冲突?
  2. 宏名容易冲突. 首先你应该少定义宏, 其次我不知道你为什么非要定义一个冲突的宏名, 再说了这里面定义的什么宏是你需要再定义一遍的?

而且我用这个头文件有个次要目的是为了避免我的代码被 msvc 编译, 因为我只能确定我的代码在类 unix 系统上不出错, windows 上出任何错误都有可能.

命名空间

禁止 using namespace std;.

推荐的有 using std::complex_literals::operator""i;using std::string_literals::operator""s;.

一般不要在代码里用宏.

例外情况是, 加入你叫李华, 你可以定义 -Dli_hua 表示你在 debug, 然后写:

void	log(long line_number) {
#ifdef	li_huastd::cerr << "line: "s << line_number << " | hey siri, play <hit'em up> please\n"s;
#endif
}

常量

k_ 前缀来代表这是个常量, 能用 constexpr 尽量用, 否则用 const.

比如说:

constexpr long k_inf = 0x3f3f3f3f3f3f3f3fl;	// 用于最大值, 最小值直接用 -k_inf
constexpr long k_mod = 998244353;	// 用于取模
constexpr long k_max_ver = 1l << 17;	// 用于顶点数量, 2^17 <=> 10^5

变量

尽量缩小变量的作用域, 比如 for 用的变量就尽量不要让作用域到 for 外面.

在类型明确的时候可以用 auto, 需要明确类型的时候用类型名.

这个 明确 包括函数返回值的类型, 认为是明确的.

auto tuple = std::make_tuple(1, 2, 3);
auto x = std::move(y);
for (std::size_t x = 0; x < v.size(); ++x)std::cout << v[x] << "\n"s;

当然带权图遍历连边应该用结构化绑定:

for (auto [y, z] : x->to_)y->dfs();

如果要修改 (比如标记一条边) 就用引用:

for (auto &[y, z, delta] : x->to_)if (y->dfs())delta = 1;

函数

如果一个参数不变, 一定要加 const. 比如刚才那个向量加法函数.

合理情况下可以用运算符重载.

匿名函数

只能用于回调函数, 比如:

// std::vector<long> a
std::sort(a.begin(), a.end(), [](long x, long y) {return y < x;
});

很明显这个例子并不好, 完全可以用 std::sort(a.rbegin(), a.rend()) 一行搞定的事非要用匿名函数.

main 函数

signed main, 可以是 signed main(int argc, char **argv) 也可以是 signed main(void), 根据需求来.

没有出错则 return EXIT_SUCCESS, 否则 return EXIT_FAILURE.

无论是单纯存数据还是带有函数, 都用 class.

类变量

变量名后加下划线, 比如 ld_ tot_.

根据需要可以放 privatepublic, 不必全放在 private. 最好的例子是我用于处理图的类:

class	vtx {
public:std::vector<vtx*> to_;long dfn_, low_;	// for tarjanvtx *top, *dear_mama, *kid;	// for 树链剖分void add_edge(vtx*);void dfs_tarjan(/*anything*/);void dfs1_hld(vtx*), dfs2_hld(vtx*);
};vtx v[k_max_ver];void	vtx::add_edge(vtx *y) {if (this < v || v + k_max_ver <= this)std::abort();if (y < v || v + k_max_ver <= y)std::abort();to_.emplace_back(y);
}

类函数

根据需要可以放 privatepublic, 不必全放在 public, 最好的例子是线段树:

class	tree {std::unique_ptr<tree> ld_, rd_;long left_, right_;long val_, tag_;void push_up(void);	// 私有void push_down(void);	// 私有public:tree(long, long, const std::vector<long>&);	// 公有void update(long, long, long);	// 公有long query(long, long, long);	// 公有
};

构造函数

非常推荐, 一定要用初始化列表. STL 容器可以初始化或不初始化. 智能指针见后文指针部分.

tree::tree(long left, long right, const std::vector<long> &a): left_(left), right_(right),val_(0), tag_(0) {if (left_ == right_) {val_ = a[left_];return;}ld_ = std::make_unique<tree>(left_, left_ + right_ >> 1, a);rd_ = std::make_unique<tree>((left_ + right_ >> 1) + 1, right_, a);push_up();
}

构造函数里为类变量区分 复制, 引用, 抢劫

复制是说你要给传入的 object 复制一份, 也就是 ld_ = new tree(*y->ld_).

引用是说你要引用传入的 object, 也就是 ld_ = y->ld_.

抢劫则很明显就是你要让传入的 object 失效, 也就是 ld_ = y->ld_, 注意一定要额外写一行 y->ld_ = nullptr, 或者直接写 ld_ = std::move(y->ld_).

析构函数

非必要不写, 让智能指针和 STL 自动释放, 如果有裸指针则在析构函数里以合理方式杀死.

重载运算符

非常推荐, 比如矩阵乘法, 重载运算符后可以方便的实现矩阵快速幂.

指针

到了最有意思的部分了.

我哥 3f 的指针哲学大多来源于 mycall, 而我的指针哲学部分来源于 一扶苏一女士 另外的来源于 the cherno.

裸指针

用于管理图论的顶点 (树论也属于图论).

尽量尽量尽量不要用裸指针存储 new.

因为图论是复杂的, 但又是不变的, 这恰好也是裸指针的优势, 所以我们这样写:

class	vtx{
public:std::vector<vtx*> to_;void add_edge(vtx*);
};vtx v[k_max_ver];	// 全局数组, 可以放在 graph:: 命名空间或者直接全局命名空间void	vtx::add_edge(vtx *y) {if (this < v || v + k_max_ver <= this)std::abort();	// fail fastif (y < v || v + k_max_ver <= y)std::abort();to_.emplace_back(y);
}

带权图类似, 这样写:

class	vtx{
public:std::vector<std::tuple<vtx*, long>> to_;void add_edge(vtx*, long);
};vtx v[k_max_ver];void	vtx::add_edge(vtx *y, long z) {if (this < v || v + k_max_ver <= this)std::abort();if (y < v || v + k_max_ver <= y)std::abort();to_.emplace_back(y, z);
}

关于 char **argv

我的习惯是这样:

signed	solve(const std::vector<std::string>&);signed	main(int argc, char **argv) {std::vector<std::string> args(argv, argv + argc);return solve(args);
}

很显然这是借鉴了 java 的 String[] args, 但 java 作为友军语言也是可以借鉴的优秀设计.

智能指针

独占指针

独占指针的语义是, 这个内容是你自己的.

很好的例子是线段树, 你的孩子顶点肯定是你独占的, 所以我们用独占指针.

class	tree {std::unique_ptr<tree> ld_, rd_;// other
};

共享指针

共享指针的语义是, 这个内容是公有的.

很好的例子是持久化线段树, 你的孩子是继承的 / 修改的, 所以我们用共享指针.

class	tree {std::shared_ptr<tree> ld_, rd_;// other
};

弱指针

弱指针的语义是, 我还是个共享指针, 但是我不参与引用计数, 用于避免循环引用.

很好的例子是双链表.

class	list_node {
public:std::weak_ptr<list_node> pre;std::shared_ptr<list_node> nxt;// other
};

iso 和 cherno 认为的弱指针语义是, 这个对象可能活着也可能死了, 所以使用之前需要先检查. 但我不认同这个语义.

很显然刚才的双链表就是避免循环引用才用的弱指针, 你无需使用 if (pre.lock()) 就能确定 pre.lock() 是有效的, 如果无效则说明你的双链表是错误的 (当然了, 头顶点的 pre 就应该是空的, 而后面每个顶点的 pre 都应该是有效的), 而如果错了就会因为 pre.lock() 为空而及时 fail fast.

杂项

缩进

使用 tab 可以让你的代码在不同的 ide 里可以按照不同人的喜好来缩进, 而使用空格会导致所有人看到的都是按照你的喜好进行的缩进.

我平时用的 8 字长 tab, 到了 mycall 的电脑上显示的是 2 字长, 这样我们两人看着都方便.

运算符

二元运算符和三元运算符两边加空格, 比较美观, 没什么特别的用处. 一元运算符可加可不加.

额外的, 我还喜欢把 x > y 写成 y < x, 因为我不太喜欢大于号...

代码块

if 后空格再写条件, for 后空格再写循环描述, while 后空格再写条件, 禁用 switch.

因为 switch 没写 break 害我在基本算法单元测试里丢了 \(160\) 分.

注释

行内注释使用 /**/ (不允许跨行), 行末注释使用 //.

枚举

推荐使用 enum class, 比如说我设计 game engine 的时候就有:

enum class color {red = 0xff0000,green = 0x00ff00,blue = 0x0000ff
};

这样就可以确保使用 color x = color::red 而不是 int x = color::red.

http://www.jsqmd.com/news/503213/

相关文章:

  • 云上OpenClaw快速部署指南:从“能用”到“好用”的蓝队云进阶攻略
  • 如何用faster-whisper-GUI实现语音智能解析的技术革命
  • PRO Elements完整指南:免费获取Elementor Pro全部功能的终极解决方案
  • OpenClaw+ollama-QwQ-32B:自动化周报生成与邮件发送实战
  • 低代码开发如何颠覆传统流程?从概念到落地的全维度指南
  • 免Root实现Android应用动态扩展的完整指南:LSPatch终极方案
  • SiameseAOE中文-base实战教程:用ABSA结果驱动产品迭代——从评论到PRD需求提炼
  • C# 常量
  • AUCell实战指南:5步搞定单细胞基因网络可视化(附R代码)
  • 贪心策略的路径寻优——Dijkstra算法核心思想与实现解析
  • Bootstrap4 提示框详解
  • Keynote远程标注全攻略:用旧iPhone改造会议神器(附省电设置)
  • SonarQube中文汉化插件安装失败?5分钟搞定手动配置(附最新下载链接)
  • 模糊PID算法实战解析:从理论到机械臂控制优化
  • AtlasOS终极指南:如何让你的Windows性能提升30%的完整教程
  • Anchor-free时代来临:为什么ActionFormer能成为视频动作定位的新标杆?
  • MusePublic艺术创作引擎:30步黄金参数设置,平衡速度与画质
  • CATIA转3DXML实战:5分钟搞定在线转换与本地导出(附避坑指南)
  • Excel用户必看:xlsx和csv格式的5个关键区别及适用场景
  • 3个突破点:用netease-cloud-music-dl批量采集技术突破音乐资源管理困境
  • 磁盘的分区格式MBR和GPT的区别
  • JoltPhysics物理引擎实战指南:从环境配置到性能优化
  • 【RDMA命令系列之】Mellanox固件管理工具MFT核心命令实战指南:从mlxconfig到mstdump的深度解析
  • PDIA3多克隆抗体如何助力铁死亡与肿瘤治疗的机制研究?
  • Cinema 4D 2026 AI建模实战:5分钟用自然语言生成3D模型(附Redshift渲染对比)
  • 减肥产品品牌怎么选?十大科学减脂品牌营养有效而且服务在线 - 资讯焦点
  • ComfyUI效率翻倍秘籍:Easy-Use插件在商业项目中的5个高阶用法
  • 3.postman全局变量和环境变量
  • 可编程逻辑控制器PLC安装:从方案设计到现场调试的完整指南
  • COMSOL 远场偏振通用计算方法探索:从理论到实践