惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
Y
Y Combinator Blog
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
S
SegmentFault 最新的问题
IT之家
IT之家
Recent Announcements
Recent Announcements
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
WordPress大学
WordPress大学
博客园 - Franky
L
LangChain Blog
人人都是产品经理
人人都是产品经理
小众软件
小众软件
博客园 - 叶小钗
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
雷峰网
雷峰网
腾讯CDC
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Help Net Security
Help Net Security
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Schneier on Security
Schneier on Security
博客园 - 聂微东
A
Arctic Wolf
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
lil_tea c++ 2026 style guide
lil_tea · 2026-06-20 · via 博客园_首页

lil_tea c++ 2026 style guide

本文使用 yoga air + wsl + ubuntu 26.04 + vscode 编写.

把所有 debug 完成, 我保证, codin 是我一辈子的朝圣.

这是我在开发和算竞都使用的代码风格.

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

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

think twice, code once. enjoy~

调试

最重要的当然是打印函数:

void	log(long line_num) {
#ifdef	lil_tea
	std::println(std::cerr, "line: {} | hey siri, play <hit`em up> please", line_num);
#endif
}

但是向量加法函数可以有新的办法了:

std::vector<long> add(const std::vector<long> &a, const std::vector<long> &b)
	pre(a.size() == b.size()) {	// c++ 2026 新特性, 见 [contracts](https://en.cppreference.com/w/cpp/language/contracts.html)
	std::vector<long> c(a.size());

	for (long x : std::views::iota(0, c.size()))
		c[x] = a[x] + b[x];

	return c;
}

用 contracts 配合 g++-16 -fcontract-evaluation-semantic=quick_enforce 就又可以让代码 fail fast 了.

代码框架

标识符

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

这方面我和 bjarne 的意见相同, 可以在 bjarne stroustrup q&a2how do you name variables? do you recommend "hungarian"? 条目见到 bjarne 观点的详细陈述.

头文件

#include <bits/stdc++.h>, 或者直接用 import std.

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

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

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

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

命名空间

禁止 using namespace std;.

推荐的有 using namespace std::literals;.

一般不要在代码里用宏.

这方面我和 bjarne 的意见再次相同, 可以在 bjarne stroustrup f&q2so, what's wrong with using macros? 条目见到 bjarne 观点的详细陈述.

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

void	log(long line_num) {
#ifdef	li_hua
	std::println(std::cerr, "line: {} | i`m not good at English", line_num);
#endif
}

常量

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

比如说:

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

变量

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

引用符号和指针符号紧贴变量, 如 tree *ld_const std::vector<long> &a.

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

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

auto tuple = std::make_tuple(1, 2, 3);
auto x = std::move(y);
for (std::size_t x : std::views::iota(0uz, v.size()))
	std::println("{}", v[x]);

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

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

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

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

全局变量

如果可能会被多个线程修改就加锁, 如果确定只会被单线程修改就不用加锁.

namespace 管理全局变量和对应的锁.

namespace	total {	// 实际变量名
long val;	// 占位变量名
std::mutex lock;
}

全局变量名不加 g_ 前缀, 查询或修改全局变量的函数名需要加 g_ 前缀.

函数

大多数函数的返回值应该只跟参数有关, 一个函数只做好一件事, 尽量不动全局变量, 动全局变量的函数的函数名需要加 g_ 前缀.

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

如果两个参数指向的内容不会重叠, 一定要加 restrict. 优化的作用对我来说并不重要因我我信任 -O2, 但这样可以提醒我多次检查不要传入重叠的东西.

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

引用符号和指针符号紧贴变量, 如果有 restrict 则写类似 long *restrict a, long *restrict b.

匿名函数

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

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

很明显这个例子并不好, 完全可以用 std::sort(a.rbegin(), a.rend()) 一行搞定的事非要用匿名函数, 但这是为了演示匿名函数所以情有可原, 实际应用中最好是使用 std::sort(a.rbegin(), a.rend()).

main 函数

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

没有出错则 return 0, 否则 return 1.

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

类变量

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

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

class	vtx {
public:
	std::vector<vtx*> to_;
	long dfn_, low_;	// for tarjan
	vtx *top_, *dear_mama_, *kid_;	// for 树链剖分

	void add_edge(vtx*);
	void dfs_tarjan(/*anything*/);
	void dfs1_hld(vtx*), dfs2_hld(vtx*);
};

vtx v[k_max_vtx];

类函数

根据需要可以放 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(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 自动释放, 如果有裸指针则在析构函数里以合理方式杀死.

重载运算符

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

千万不要用 shared_from_this, 使用传入的 std::shared_ptr &const std::shared_ptr & 代替.

参数名建议叫 ref_this, 先判断:

if (ref_this.get() != this)
	std::terminate();

当然这里现在用 pre 会更方便了.

std::weak_ptr 的重载版本

比如说有 classx::work(std::shared_ptr<classx> &ref_this), 这时候我还会写一个重载的 classx::work(std::weak_ptr<classx> &ref_this).

内容很简单, 直接调用:

auto	work(std::shared_ptr<classx> &ref_this) {
	return work(ref_this.lock());
}

这里为什么我敢在 lock() 为空的时候也传入呢, 因为 lock() 为空的时候是通不过 if (ref_this.get() != this) 的检测的, 就炸了.

指针

到了最有意思的部分了.

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

裸指针

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

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

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

constexpr long k_max_vtx = 1l << 17;
class	vtx;
extern	vtx v[k_max_vtx];	// 提前声明, 后面就能判断了

class	vtx{
public:
	std::vector<vtx*> to_;

	void	add_edge(vtx *y)
		pre(v <= this && this < v + k_max_vtx && v <= y && y < v + k_max_vtx) {
		to_.emplace_back(y);
	}
};

vtx v[k_max_vtx];

关于 char **argv

我的习惯是这样:

signed	solve(const std::span<std::string_view>&);

signed	main(int argc, char **argv) {
	std::vector<std::string_view> tmp(argv, argv + argc);
	std::span<std::string_view> args(tmp);
	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_vtx {
public:
	std::weak_ptr<list_vtx> pre_;
	std::shared_ptr<list_vtx> nxt_;
	// other
};

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

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

删除顶点 x 直接 x->pop():

void	pop(void) {
	if (nxt_)	// 如果先修改 pre_->nxt_ 则会导致引用计数为 0, 所以先修改 nxt_->pre_
		nxt_->pre_ = pre_;
	pre_.lock()->nxt_ = nxt_;	// 头顶点不存数据, 所以存数据的顶点一定有 pre_
	// 引用计数为 0 自动删除, 会调用析构函数
}

杂项

缩进

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

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

行长限制

我个人喜欢把行长限制为 1mol 个字长, 我觉得过分限制行长 (比如 80 字长) 是不好的 don quixote 风格.

在 1mol 字长限制的情况下, 一行的长度经常超出我的显示器, 那么我就会使用这个设置:

"editor.wordWrap": "bounded",	// 在屏幕宽度和代码行长限制的较小值处自动折行, 实际还在同一行
"editor.wordWrapColumn": 1073741824,	// vscode 最大行长限制
"editor.wrappingIndent": "same",	// 自动折行后维持相同缩进
"editor.wrappingStrategy": "advanced",	// 让 vscode 计算行长, 因为 tab 的字长不一定是 1 所以需要

运算符

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

如果一个地方你想要的优先级符合 iso 的优先级 那就不要加括号, 比如取中点你想要 \(\cfrac{left\_+right\_}{2}\) 就可以直接写 left_ + right_ >> 1.

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

管道运算符

不要用, 不要用, 不要用.

如果你需要遍历一个集合, 你应该做的是先构造这个集合, 无论是 std::vector<long> euler_sieve(long max) 还是 std::views::iota(0uz, v.size()) 都是构造的方式.

但管道是个奇怪的东西, 管道的含义是先构造出一个全集, 然后用淘汰赛的方式选出你需要的集合. 我觉得这非常不明确, 不如直接构造出想要的集合.

goto 运算符

这是运算符吗? 反正我喜欢当成运算符.

可以适当使用, 当且仅当你是往下跳. 不要用来跳到函数最后统一释放资源因为资源应该让智能指针自动释放, 而是用于状态机的设计.

比如分块的区间求和:

long	query(std::size_t p, std::size_t q) const
	pre(p <= q && 0 <= p && q < a.size()) {
	std::size_t from = pos_[p], term = pos_[q];
	long yt = 0;

	if (from != term)
		goto qry_from;

	for (std::size_t x = p; x <= q; ++x)
		yt += a_[x] + tag_[from];
	
	return yt;

qry_from:
	if (from_[from] == p)	// 整块则当成整块
		goto qry_term;

	for (std::size_t x = p; x <= term_[from]; ++x)
		yt += a_[x] + tag_[from];

	from++;

qry_term:
	if (q == term_[term])	// 整块则当成整块
		goto qry_ssr;

	for (std::size_t x = from_[term]; x <= q; ++x)
		yt += a_[x] + tag_[term];

	term--;

qry_ssr:
	for (std::size_t x = from; x <= term; ++x)
		yt += sum_[x];

	return yt;
}

代码块

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

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

if 里赋值是很常见的情况, 这时建议使用双层括号来明确语义, 比如说树剖的 dfs:

void	dfs_kid(vtx *_) {
	if ((dear_mama_ = _))	// if 里做赋值并判断
		depth_ = dear_mama_->depth_ + 1;
	else
		depth_ = 0uz;

	size_ = 1uz;
	kid_ = v;

	for (vtx *y : to_) {
		if (y->kid_)
			continue;

		y->dfs_kid(this);
		size_ += y->size_;

		if (kid_->size_ < y->size_ || kid_->size_ < y->size_ && y < kid_)
			kid_ = y;
	}
}

注释

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

枚举

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

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

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