普通视图
C++中的 const和constexpr 比较
C++ const
与 constexpr
:真正的区别是什么?
一眼看都是定义常量。
为什么这很重要
现代 C++ 鼓励编写不可变、高效且表达力强的代码。两个关键字—const
和 constexpr
—是这一理念的核心。它们看起来很相似,但理解它们的不同语义,对于正确利用编译期与运行期行为至关重要。
高层次对比
特性 | const |
constexpr |
---|---|---|
编译期常量? | 可能 | 一定(否则编译报错) |
支持运行期? | 支持 | 支持(在需要时运行期求值) |
用于数组/模板参数? | 仅当确实是常量 | 保证可用 |
允许函数? | 仅限成员函数限定符 | 支持完整函数且可在编译期求值 |
1 声明不可变数据
const
:构造后不可变
const int runtimeConst = std::rand(); // 是 const,但不是编译期常量
当你只想禁止变量被修改,而不在意值是在编译期还是运行期确定的,const
就足够了。
constexpr
:必须在编译期已知
constexpr int arraySize = 10; int arr[arraySize]; // 始终合法
如果值需要参与要求编译期常量的上下文(如数组大小、模板参数、switch 标签等),你必须使用 constexpr
。
2 函数与方法
const
成员函数
class Widget { public: int value() const {/*…*/} // 保证不会修改 this 对象 };
它保护对象状态,但不提供编译期求值能力。
constexpr
函数
constexpr int square(int n) { return n * n; } static_assert(square(4) == 16, "编译期计算");
constexpr
函数在参数是常量表达式时可以在编译期执行,也可以在运行期使用。
3 常见陷阱
// 1. 编译通过:runtimeConst 只是 const const int runtimeConst = std::rand(); // 2. 编译失败:std::rand() 不是 constexpr constexpr int fails = std::rand();
记住:每个 constexpr
变量本质上都是 const
,但并非所有 const
都是常量表达式。
4 如何选择
- 需要强制编译期计算?使用
constexpr
- 需要不可变性但值可能在运行期确定?使用
const
- 不确定时偏向使用
constexpr
,编译器会提示你是否不合法
5 总结片段
constexpr int ctVal = 42; // 编译期常量 const int rtVal = std::rand(); // 运行期确定,但不可变
正确地选择 const
和 constexpr
能让你的 C++ 代码更安全、更高效、更具表达力。默认使用 constexpr
,当且仅当你明确知道值只能在运行期获取时才使用 const
。
C/C++编程
- C++中的 const和constexpr 比较
- 简易教程: C++的智能指针
- C++ 编程练习题: 如何合并两个二叉树?
- C++ 编程练习题 - 找出第三大的数
- C++ 编程练习题 - 最多连续的 1
- C++ 编程练习题 - 左子树叶节点之和 (深度优先+广度优先+递归)
- C++ 编程练习题 - 最多水容器 (递归)
- C++的异步编程: std::future, std::async 和 std::promise
- C编程练习题: 翻转整数位
- C++编程练习题: 找出字符串的所有大小小组合
- C/C++ 中的内存管理器(堆与栈)
- C++编程练习题: 对两单向链表求和

相关文章:
- 简易教程: C++的智能指针 C++ 智能指针教程 C++ 中的智能指针提供了自动且安全的内存管理。它们通过 RAII(资源获取即初始化)机制,帮助开发者避免内存泄漏和悬空指针的问题,确保对象在生命周期结束时被正确释放。 本教程将介绍 C++ 中三种主要的智能指针: std::unique_ptr:独占式所有权 std::shared_ptr:共享式所有权 std::weak_ptr:非拥有式弱引用 1. std::unique_ptr unique_ptr 拥有独占所有权。一个资源只能被一个...
- C++ Ranges 教程 C++20 引入了 ranges(范围),这是一个强大且优雅的抽象,用于处理序列(如数组、vector 等)。相比传统的迭代器或旧式循环,Ranges 提高了代码的可读性、可组合性和性能。 什么是 Range? 在 C++20 中,range(范围) 是一种抽象,代表一个可以迭代的元素序列。它与 views(视图) 和 actions(操作) 如过滤、转换等配合使用非常自然。...
- 被动收入之: 微博红包 今年开始重新经营我的微博帐号 drlai 收到两笔微信红包,应该是来自于官方的支持,150元(成功提现到支付宝)。虽然这不能持久,也没多少,但毕竟实现了零的突破,意义重大。 如果流量上来,内容创作者可能会接受到比较多的赞赏,这也是一个比较简单的变现方法。这也能作为一种被动收入,不过如果不是头部网红,可能杯水车薪,但如果你有好几个类似这样的,也能积少成多! 在用户中心,微博用户可以每天登陆手机微博APP打卡,获取点数和少量的红包钱(几分钱),积少成多! 微博做些小任务可获得积分和几分钱。聊胜于无。 微博的主要盈利模式 微博的主要盈利模式主要包括以下几个方面: 广告收入:微博的大部分收入来源于广告,尤其是品牌广告和效果广告。广告形式包括信息流广告(类似于推文广告)、热门话题广告、开屏广告和视频广告。品牌和企业可以利用微博庞大的用户群和社交互动来提升曝光率、推广品牌和产品。 会员服务:微博提供的VIP会员服务,用户可以支付订阅费用来享受更多的特权,比如个性化的主题、特有的表情包、私密权限设置等。这些会员服务主要面向个人用户,提升其社交体验。 直播和打赏:微博提供直播平台,用户可以通过购买虚拟礼物来支持主播,微博会从这些打赏中抽取一定比例的分成。此外,微博与内容创作者分成,通过内容付费、知识付费等形式变现。 增值服务:针对企业和大V(拥有大量粉丝的用户),微博还提供增值服务,如账号认证、粉丝数据分析、精准推送、推广和营销工具等。这些服务帮助企业提升营销效果,同时也增加了微博的收入来源。 电商和导流:微博上有大量的电商导流业务,尤其是和明星、网红的合作推广。微博用户在浏览社交内容时,可以直接跳转到商品购买链接,微博通过这种方式赚取导流佣金。 游戏联运:微博也会与一些游戏公司合作推出联合运营的游戏,微博负责推广和流量引入,用户充值或付费时,微博可以获得一部分的分成。 这些模式相结合,使得微博能够在广告市场、内容创作和电商等多个领域获利。...
- 借助AI快速开源了三个小工具: 写代码越来越像做产品了, AI 真把我宠坏了(Vibe Coding) 程序员的未来?Vibe Coding + AI 一起上! 借助 AI 快速开源了三个小工具 最近,我利用 ChatGPT-4o 和 o4-mini 快速开发并开源了三个小工具。起因其实很简单——每次想转换 YAML/JSON 或进行...
- 换了个奥迪Q5大灯花了我1000英镑 我那辆奥迪Q5 SUV今年年检没通过,原因是左前车灯坏了,需要更换。车厂告诉我,光是订购零件就要700多英镑,加上人工费,总费用得1000英镑。但没办法,如果不修,车辆年检(MOT)就过不了,车也不能上路。 MOT是英国的机动车强制性安全检测(Ministry of Transport Test)的简称。 近侧前位置灯不工作 drl/位置灯集成(4.2.1(a)(ii)) Nearside Front Position lamp not working drl/position...
- 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
- 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
- 剑桥网红餐厅 The Ivy Cambridge Brasserie 上周刚过40岁生日,媳妇带我去剑桥的一家网红餐厅——The Ivy Cambridge Brasserie。这家餐厅是朋友推荐的,和那家Trinity一样很有名,可惜我们在剑桥待了近10年才知道。 The Ivy Cambridge Brasserie 是剑桥市中心一家备受欢迎的网红餐厅,位于历史悠久的 Trinity Street。餐厅设计时尚典雅,融合了现代与经典元素,为食客提供了一个舒适而奢华的用餐环境。这里的菜单丰富多样,覆盖全天用餐,从早餐到晚餐以及下午茶,提供英式经典美食如松露鸡肉派、英式早餐等,也有一些国际风味的菜肴。得天独厚的位置和独特的氛围使得 The Ivy Cambridge Brasserie...
C++ Ranges 教程
C++20 引入了 ranges(范围),这是一个强大且优雅的抽象,用于处理序列(如数组、vector 等)。相比传统的迭代器或旧式循环,Ranges 提高了代码的可读性、可组合性和性能。
什么是 Range?
在 C++20 中,range(范围) 是一种抽象,代表一个可以迭代的元素序列。它与 views(视图) 和 actions(操作) 如过滤、转换等配合使用非常自然。
传统循环 vs 基于 Range 的循环
#include <iostream> #include <vector> int main() { std::vector<int> v = {1, 2, 3, 4}; // 旧式循环 for (auto it = v.begin(); it != v.end(); ++it) std::cout << *it << ' '; // 基于范围的循环(C++11) for (auto x : v) std::cout << x << ' '; }
Range Views(视图)
View 是惰性的、可组合的范围操作。除非需要,一般不会复制数据。
Filter 和 Transform 示例
#include <iostream> #include <vector> #include <ranges> int main() { std::vector<int> v = {1, 2, 3, 4, 5, 6}; auto even_doubled = v | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * 2; }); for (int n : even_doubled) std::cout << n << ' '; // 输出:4 8 12 }
常见的 Views
View | 描述 |
---|---|
std::views::filter | 保留符合条件的元素 |
std::views::transform | 对每个元素应用函数 |
std::views::take(n) | 获取前 n 个元素 |
std::views::drop(n) | 跳过前 n 个元素 |
std::views::reverse | 反转范围 |
std::views::iota(a, b) | 生成从 a 到 b-1 的范围 |
使用 iota 和 reverse
#include <ranges> #include <iostream> int main() { for (int i : std::views::iota(1, 6) | std::views::reverse) std::cout << i << ' '; // 输出:5 4 3 2 1 }
组合视图操作
你可以使用管道符 |
流式地组合多个视图操作。
#include <vector> #include <ranges> #include <iostream> int main() { std::vector<int> v = {5, 10, 15, 20}; auto result = v | std::views::transform([](int x) { return x + 1; }) | std::views::filter([](int x) { return x % 2 == 0; }); for (int x : result) std::cout << x << ' '; // 输出:6 16 }
实用示例
1. 过滤偶数
#include <iostream> #include <vector> #include <ranges> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6}; auto evens = numbers | std::views::filter([](int n) { return n % 2 == 0; }); for (int n : evens) std::cout << n << ' '; // 输出:2 4 6 }
2. 将奇数翻倍
int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; auto doubled_odds = numbers | std::views::filter([](int n) { return n % 2 != 0; }) | std::views::transform([](int n) { return n * 2; }); for (int n : doubled_odds) std::cout << n << ' '; // 输出:2 6 10 }
3. 反转序列
int main() { std::vector<int> nums = {10, 20, 30}; auto reversed = nums | std::views::reverse; for (int n : reversed) std::cout << n << ' '; // 输出:30 20 10 }
4. 生成数值序列
#include <ranges> int main() { for (int i : std::views::iota(1, 6)) std::cout << i << ' '; // 输出:1 2 3 4 5 }
5. 获取前 N 个元素
int main() { auto infinite = std::views::iota(1); // 无限序列 auto first5 = infinite | std::views::take(5); for (int i : first5) std::cout << i << ' '; // 输出:1 2 3 4 5 }
6. 计算前 5 个奇数的平方和
#include <numeric> int main() { auto odd_squares = std::views::iota(1) | std::views::filter([](int x) { return x % 2 == 1; }) | std::views::transform([](int x) { return x * x; }) | std::views::take(5); int sum = std::accumulate(odd_squares.begin(), odd_squares.end(), 0); std::cout << "和 = " << sum << '\n'; // 输出:和 = 165 }
7. 判断是否所有元素都为正数
#include <ranges> #include <algorithm> #include <vector> #include <iostream> int main() { std::vector<int> nums = {1, 2, 3}; bool all_positive = std::ranges::all_of(nums, [](int n) { return n > 0; }); std::cout << std::boolalpha << all_positive << '\n'; // 输出:true }
8. 自定义管道函数
auto pipeline = [](const std::vector<int>& v) { return v | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * 10; }); }; int main() { std::vector<int> nums = {1, 2, 3, 4}; for (int x : pipeline(nums)) std::cout << x << ' '; // 输出:20 40 }
性能提示
- Ranges 是惰性的:仅在需要时才处理元素。
- 避免不必要的分配与复制。
- 适合处理大型数据或函数管道。
何时不适合使用 Ranges
- 在对性能极度敏感的内循环中,STL 抽象可能较慢。
- 当项目尚未迁移到 C++20。
参考资料
本文一共 415 个汉字, 你数一下对不对.
相关文章:
- 简易教程: C++的智能指针 C++ 智能指针教程 C++ 中的智能指针提供了自动且安全的内存管理。它们通过 RAII(资源获取即初始化)机制,帮助开发者避免内存泄漏和悬空指针的问题,确保对象在生命周期结束时被正确释放。 本教程将介绍 C++ 中三种主要的智能指针: std::unique_ptr:独占式所有权 std::shared_ptr:共享式所有权 std::weak_ptr:非拥有式弱引用 1. std::unique_ptr unique_ptr 拥有独占所有权。一个资源只能被一个...
- 借助AI快速开源了三个小工具: 写代码越来越像做产品了, AI 真把我宠坏了(Vibe Coding) 程序员的未来?Vibe Coding + AI 一起上! 借助 AI 快速开源了三个小工具 最近,我利用 ChatGPT-4o 和 o4-mini 快速开发并开源了三个小工具。起因其实很简单——每次想转换 YAML/JSON 或进行...
- 被动收入之: 微博红包 今年开始重新经营我的微博帐号 drlai 收到两笔微信红包,应该是来自于官方的支持,150元(成功提现到支付宝)。虽然这不能持久,也没多少,但毕竟实现了零的突破,意义重大。 如果流量上来,内容创作者可能会接受到比较多的赞赏,这也是一个比较简单的变现方法。这也能作为一种被动收入,不过如果不是头部网红,可能杯水车薪,但如果你有好几个类似这样的,也能积少成多! 在用户中心,微博用户可以每天登陆手机微博APP打卡,获取点数和少量的红包钱(几分钱),积少成多! 微博做些小任务可获得积分和几分钱。聊胜于无。 微博的主要盈利模式 微博的主要盈利模式主要包括以下几个方面: 广告收入:微博的大部分收入来源于广告,尤其是品牌广告和效果广告。广告形式包括信息流广告(类似于推文广告)、热门话题广告、开屏广告和视频广告。品牌和企业可以利用微博庞大的用户群和社交互动来提升曝光率、推广品牌和产品。 会员服务:微博提供的VIP会员服务,用户可以支付订阅费用来享受更多的特权,比如个性化的主题、特有的表情包、私密权限设置等。这些会员服务主要面向个人用户,提升其社交体验。 直播和打赏:微博提供直播平台,用户可以通过购买虚拟礼物来支持主播,微博会从这些打赏中抽取一定比例的分成。此外,微博与内容创作者分成,通过内容付费、知识付费等形式变现。 增值服务:针对企业和大V(拥有大量粉丝的用户),微博还提供增值服务,如账号认证、粉丝数据分析、精准推送、推广和营销工具等。这些服务帮助企业提升营销效果,同时也增加了微博的收入来源。 电商和导流:微博上有大量的电商导流业务,尤其是和明星、网红的合作推广。微博用户在浏览社交内容时,可以直接跳转到商品购买链接,微博通过这种方式赚取导流佣金。 游戏联运:微博也会与一些游戏公司合作推出联合运营的游戏,微博负责推广和流量引入,用户充值或付费时,微博可以获得一部分的分成。 这些模式相结合,使得微博能够在广告市场、内容创作和电商等多个领域获利。...
- 换了个奥迪Q5大灯花了我1000英镑 我那辆奥迪Q5 SUV今年年检没通过,原因是左前车灯坏了,需要更换。车厂告诉我,光是订购零件就要700多英镑,加上人工费,总费用得1000英镑。但没办法,如果不修,车辆年检(MOT)就过不了,车也不能上路。 MOT是英国的机动车强制性安全检测(Ministry of Transport Test)的简称。 近侧前位置灯不工作 drl/位置灯集成(4.2.1(a)(ii)) Nearside Front Position lamp not working drl/position...
- 试用 Linkedin (领英) 高级帐号 (Premium) Linkedin (领英) 算是比较靠谱的职业社交网站, 在上面有很多猎头, 很多知名公司的HR 无时无刻在招人. 特别领英在被微软收购之后, 名气就变得大了许多. 领英是免费使用的, 但也有付费用户, 有给猎头的, 也有给想找工作的. 价格并不便宜, 对于想找工作的 Job...
- 步步高学生电脑上 Basic 编程语言 peek 用法示例 步步高学生电脑 是8位FC机的经典之作.它上面的BASIC有三个版本 1.0, 2.0 和 2.1 2.1 版本有个在线帮助,实际上是 help.cmd 1.0 是用 Esc 键退回到 DOS 的,...
- 《Steem 指南》之 justyy 在线工具与 API 系列 – Discord 机器人 Discord 聊天频道 Discord 原本是给游戏设计的, 但由于其功能多, 接口开放能力强, 使用的用户越来越多. 我们CN区也有一个Discord 频道, 加入地址为: https://discord.gg/7ctT3Xt 在网页里就可以加入 cnsteem 的大家庭了, 当然也可以下载手机APP或者桌面程序来加入...
- 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
20250517
今天第一次来上海的 Blue Note这种 Club 式的演出场地我还不是很适应。但是边喝酒边听 Jazz 才带感啊。
4/24 第一次听 Brad Mehldau,应该是 ChatGPT 给我推荐的和 Keith Jarrett 类似的音乐家。非常喜欢下面这张专辑,于是上闲鱼买黑胶。没想到一搜就搜出了 Brad Mehldau Trio 五月在国内 Blue Note 演出的票。缘分到这种程度了,不去看真不合适。立即买了两张上海的。

没想到一个月内就听上现场了,除了 Brad Mehldau,贝斯手 Christian Mcbride 简直神了。查了一下他的老师居然是 Ray Brown!还和各种我喜欢的音乐家合作过,能听上他的表演,太开心🥳。

今天又发现了一家很不错的酒店,大华长风华邑。16000 积分兑换,不到六百块钱。酒店门面比六百块钱的亚朵好了一百倍吧,房间升了一级后也是宽敞舒适,居然还有行政酒廊。窗外是个公园,景也不错。早上洗完头发现用的是 Dyson 吹风机,要知道很多洲际现在都是山寨的 Dayson 😂。直接宣布它已经取代瑞金洲际成为我上海看演出的首选。大大降低每次看演出的成本。

20250516
今天五源资本的两个投资人来拜访认识,尽管我一直在拒绝和“投资人”们聊天,但偶尔交流认识一下还行。给他们放了黑胶,也算给他们输出一些东西吧。
中午约博士吃饭,博士送了我们任天堂出的那个闹钟。做这样的周边真有意思,把游戏、角色、声音资源、玩家紧紧连结在一起。我们最近为了去美国,有一些礼物可以拿来送给其他开发者,终于也做起了周边。但是创意贫瘠,只能想到贴纸和徽章。徽章的样本到了,把这个回赠给了博士。我还是很喜欢我们 logo 的设计。

今天一直在听 Holly Cole 的专辑《Temptation》,在闲鱼刷黑胶的时候看到这张专辑的复刻,被介绍深深吸引了。作为一张爵士女声专辑,选曲居然都是 Tom Waits 的歌。Tom Waits 在我心里形象更多是荒诞不经,但是我很喜欢。昨晚在家里 HomePod 播放后没有什么感觉,今天来公司听,不得不说有了完全不同的感觉。应该不是“木耳朵”了!
我的 WordPress 网站安装了哪些插件(2025.06)
WordPress 插件是扩展网站功能的强大工具,用户无需编写代码即可轻松添加各种特性和功能。插件种类繁多,包括 SEO 优化、社交分享、电子商务、网站安全等,用户可以根据需求自由选择和安装,从而显著提升网站的灵活性与可操作性。除了市面上的成熟插件包,我们还可以通过添加增强扩展(如纯代码形式的插件)来进一步扩展功能与性能,例如官方曾经发布的对象存储插件 object-cache.php
。
这些丰富的插件和主题构成了 WordPress 强大的生态系统,正是这一生态让 WordPress 成为全球最受欢迎的建站平台,魅力无可比拟。那么,关于必备插件的推荐,实际上并没有一个固定答案。因为当前的 WordPress 核心程序已经非常完善,从安全性、样式、编辑器等方面都能满足大多数用户的需求。加之海量的主题市场,许多曾经流行的个性功能已经直接集成到系统和主题中了,比如懒加载、灯箱效果、缓存功能等。
因此,我建议你根据实际需求选择和安装插件,这不仅可以节省精力和时间、提高性能,还能避免安装恶意插件导致系统崩溃。如果非要说有哪些插件是必备的,那大概是 Akismet
和 你好多莉
吧。毕竟,当你完成 WordPress 的安装时,它们已经默默地存在了,哈哈。

原文链接: https://www.shephe.com/website/wordpress-plugin-recommendations/
版权声明: Kevin's Space 版权所有,转载请用明链标明本文地址
本站相关: 随机文章 | 站长微博 | 关于本站 | 联系站长 | 捐助作者
游戏组件的一生: 从加载到上屏
1. 小游戏容器与游戏引擎
小游戏容器的设计上可以理解是一种特化版的 WebView,渲染上下文上裁剪了多余的 DOM Element,只保留 Canvas;而脚本引擎上则 JS Polyfill 或是容器 Binding 的方式去对齐 ECMA-262 的标准。此外容器还需要提供 Script 加载与执行、WASM 等新标准处理、以及 Audio 与 Video 等多媒体能力,这些能力都将通过 JSBinding 的形式,将接口包装成 BOM 的形式给到 JS 侧使用。
小游戏容器之所以要设计成符合 Web 标准的容器,是为了兼容不同游戏引擎。这种设计理念的本质是将底层平台能力标准化、通用化,把碎片化的硬件、系统能力屏蔽在容器内部,只向上提供一套与浏览器 BOM、DOM 类似的编程模型,使得各类游戏引擎(如 Cocos、Egret、Laya、Unity WebGL)都可以以 Web 的运行环境的方式接入,避免每个引擎都去适配各家平台的原生能力。这实际上是 WebView 本地化、轻量化的一次再演化,小游戏容器约等于一个轻量浏览器内核。
这个过程中容器负责“平台标准化”,引擎负责“内容生态”,比如
小游戏容器的职责:
- 提供统一的渲染上下文(Canvas/WebGL)。
- 提供统一的脚本运行时(JS/WASM)。
- 提供标准化的输入、音频、视频、多媒体 API。
- 提供网络、存储、支付、分享、广告等平台能力封装。
- 对接安全沙箱、权限管理、性能隔离等系统层。
游戏引擎的职责:
- 提供高层抽象的场景管理、物理引擎、动画、资源管理。
- 提供开发者友好的编辑器、调试工具链。
- 提供跨平台的组件化开发范式(UI、骨骼动画、粒子系统等)。
- 管理游戏生命周期、状态同步、渲染调度。
接下来,以 Cocos 引擎的渲染管线为例,介绍小游戏容器对资源的加载流程以及对游戏组件的渲染流程。
2. 游戏引擎中的三大循环
游戏引擎的渲染管线由三大循环进行驱动,分别是渲染循环、事件循环和游戏循环,以下是梳理出来的三大循环的全景图:

2.1 渲染循环 RenderLoop
首先是渲染循环,它的主流程如下图所示:

整个渲染循环由系统的 Vsync 信号驱动,iOS 由 CADisplayLink 发起,通过应用进程的主线程的 RunLoop 来执行渲染任务,具备一定的帧率控制能力,如 iOS 下可以设定 30/60/90/120 FPS。
在引擎侧,核心流程做了 3 件事:
glFlush
清空 GL 缓冲指令:将上帧未执行的 OpenGL 指令强制刷新,确保显存与帧缓区数据一致,防止由于指令堆积导致的“帧延迟”或“卡顿”。UpdateScheduler
异步任务调度:调度当前帧需要触发的异步任务,例如音频回调、网络事件响应等。保证非渲染逻辑(如数据更新)与渲染解耦,提高主线程并发能力。Tick
驱动 JS 层逻辑:每帧通过 Binding 固定调用 JS 侧 Tick 方法,执行动画、状态更新等与渲染相关的逻辑。从而实现逻辑层与渲染层的解耦,增强跨平台的适配能力。
在容器侧,iOS 通过 CAEAGLayer
处理 GL 指令上屏,主要有两个步骤:
glBindRenderbuffer
绑定 RenderBuffer:将当前帧渲染结果绑定至 RenderBuffer,作为上屏缓冲区。PresentRenderbuffer
显示输出:将 RenderBuffer 内容呈现至屏幕,实现用户可见的最终画面。
在 iOS 渲染体系中,最终负责显示的组件是 CAEAGLLayer。它作为 Layer 树(Layer Tree) 的一部分,直接引用共享内存中的渲染缓冲区(Renderbuffer 数据)。与此同时,系统的 Compositor(合成器) 会将 CAEAGLLayer 的内容与其他 UI 元素(如 UIKit、SwiftUI)进行统一合成,最终输出到屏幕。
在每一帧的 Tick 任务 中,JavaScript 会与游戏引擎协作,生成本帧所需的 Framebuffer(详见 3.5 至 3.10 节)。此时,Core Animation 与 OpenGL ES 通过共享渲染缓冲区实现数据同步。这意味着,OpenGL 渲染结果实质上只是一块 Layer 树中的画布,最终仍需与系统 UI 层级一同被合成为最终显示图像。

当然,本文中涉及的小游戏容器仅使用了 OpenGL 作为渲染后端,随着 Metal、Vulkan 等新一代图形 API 的兴起,RenderBuffer 绑定与上屏流程将更倾向“并行渲染 + 异步上屏”,提升高帧率下的流畅度与低延迟体验。 这个渲染循环的逻辑是同步执行的,因此如果将帧率设置为 60 FPS 时,以上所说的一帧的逻辑没有在 16.6ms 内运行完,便会导致 Jank。

比如在这个 Bad case 中,运行 Tick 任务时,在主线程的 JS 执行了 136ms,就导致了游戏动画卡顿:

因此,为了保证游戏运行的流程性,意味着我们需要不停地打磨性能,尽可能降低同步任务的耗时。性能优化一定要借助 Profiling 工具,以下是一些常用的工具:
- Xcode GPU Frame Debugger:针对 iOS 平台的图形调试工具,能够深入分析 渲染管线级别的性能瓶颈,尤其适合 Metal 与 OpenGL ES 开发场景。
- RenderDoc:业界主流的跨平台图形调试工具,支持捕捉帧数据,分析渲染管线各阶段的资源与性能瓶颈,适用于 OpenGL、Vulkan、DirectX 等 API。
- inspector.js:Web 端可以使用,便于在 WebGL 场景下分析 DrawCall、着色器与资源绑定等性能数据。
- Mali offline shader compiler:https://zhuanlan.zhihu.com/p/161761815,适用于 ARM Mali GPU 的离线着色器编译与分析工具,可用于评估 Shader 复杂度与指令执行成本,优化移动端渲染性能。
- Snapdragon Profiler: 抓帧工具,支持统计 Heavy DrawCall 与 Overdraw,帮助识别渲染瓶颈与冗余计算。
2.2 事件循环 EventLoop
我们向下,从 Tick 任务进入到第二个循环 —— 事件循环。

因为小游戏容器不是 WebView,只有一个 JS 引擎,因此我们需要实现一个事件循环机制,驱动 JS 执行(不一定完全对齐浏览器标准,只需要满足容器要求即可)。由图可见,主要包括 3 个任务:
- 消费 timer 等宏任务:处理通过 setTimeout、setInterval 等方式注册的定时任务,确保定时逻辑的正确触发。
- 消费 rAF 任务:这一步主要是为了驱动 GameLoop 逻辑,游戏主循环通常挂载于 rAF 回调中,用于逐帧更新渲染与逻辑。
- 清空当前帧的 Commands:执行渲染命令、界面更新等待处理的指令,完成本帧渲染周期。
这里重点说一下 rAF 的实现。在早期,rAF 通过 setTimeout(0) 来模拟实现,链路如下:

可以发现这里是存在问题的:
- 不合规范:是使用 setTimeout 0 模拟的,并非 vsync 直接驱动。
- 链路太长:Native 来维护 Timer 队列,等待 vsync 信号消费完之后再回调给 JS。
后来按照 WHATWG 标准进行了重构,

优点如下:
- 标准化:vsync 后直接触发 JS 的调用
- 开销小:JS 维护 Timers 队列,移除原生层中转的 JSBinding 调用开销。
可见渲染性能的优化,关乎在很多实现的细节上,需要挖掘与打磨。
最终,通过以上的事件循环,容器能够维持 JS 引擎与渲染系统之间的协同工作,实现游戏的持续运行与更新。
2.3 游戏循环 GameLoop
这一部分展开来说就是第 3 章——游戏组件的一生:

在展开画卷之前,介绍一下传统的使用 OpenGL 作为渲染后端的小游戏容器的渲染流程:

首先是资源加载,涉及到两种完全不同的资源处理——脚本资源和静态资源。脚本资源由 JS Runtime 进行处理,而静态资源则针对不同类型的文件又有各自的处理方案——包括图片、字体、音频、视频、还有比较特殊的骨骼动画。因为本文主要说渲染,就不展开介绍资源加载流程了。
之后,这些资源被游戏引擎渲染关键处理,由 JS 驱动生成 WebGL 指令,通过 JS Binding 最终调用到 C++ 或 Native 侧的 OpenGL 指令集上 —— WebGL 是 OpenGL 的子集,因此可以一一对应。

这个过程往往会出现很多渲染瓶颈,因此其中会涉及到很多优化项。我们根据硬件资源来看,主要关注 CPU、GPU 和带宽。而在当下移动端硬件资源并不富裕的场景下,对于游戏的优化,本质上变成了“平衡的艺术” —— 我们需要去平衡 CPU、GPU 和带宽资源。即如果瓶颈不能消灭,就需要转移瓶颈,比如经常见的是从 CPU 移到 GPU —— 使用 Computer Shader、GPU skinning、Animation Bake、GPU particles 等等。
对于 CPU,这是最常见的瓶颈。这里不展开说游戏业务侧的优化项(减少 DrawCall 的 Culling、Batching 这些),而是从容器侧提供一些优化思路。
- 比如上面的 JS Binding 调用可能会导致瓶颈,那我们可能会去做合批,从两方面去实现,一方面是调用次数合批,做 CommandBuffer 增加吞吐;另一方面可以做调用实现的合并,比如提供 GFX 高级图形库。
- 还比如一些 JS 同步任务会阻塞主线程,那么就把计算密集型的任务转到 Native 去做。
- 比如 JS 自身解释执行的执行效率,那就想办法用 JIT 或者 WASM。
- 再比如 GC 上,也有一些优化的地方。
对于 GPU,如果产生瓶颈了,一般是由于 Fragment Shader 指令太复杂,或者 Vertex Buffer 过大,比如 3D 渲染中的三角形面数超过阈值,一般移动端场景下需要控制在 50 万面到 150 万面之间。另外,高 Overdraw 也会导致 GPU 多做很多无用功。
对于带宽瓶颈,则主要是靠压缩纹理(桌面端还可以用延迟渲染和后处理技术)。在网上有这么一个结论:
如果你的游戏跑 60 帧,那么每帧可用的带宽将会是 21024/60 = 34M, 假设你的 GBuffer 的分辨率是 1280 \ 1080,那么写一次 GBuffer(RGBA 4 个字节)的带宽大小为: 12801080\4/1024/1024 = 5.2M, 如果 3 张则是 15.6M.
考虑到一般你的游戏都会有 Overdraw, 假设 Overdraw 比较合理在 1.5 左右,那么这样的带宽消耗就能占到 15.6 * 1.5 = 23.4 M。 考虑到你还要渲染场景,ui 和角色等内容,这样很容易就超过了每秒 34M 的推荐带宽占用。
下图是一个常见的同步渲染管线:
- 应用层提供顶点数据
- 构建顶点着色器对顶点进行标准化
- 图元装配构建几何图元
- 光栅化阶段,将图元离散化为片元,每个片元对应屏幕上的像素区域
- 片元着色器对每个片元执行纹理采样、颜色计算、雾效等像素级处理。
- 进行测试与混合操作(Alpha、深度、模板测试),并将结果写入帧缓冲区 Framebuffer。

构建完 Framebuffer 后,就回到了我们 2.1 节所说的 CAEAGLLayer 绘制上屏了。
接下来,我们就展开画卷,看看游戏组件的一生。
3. 游戏组件的一生
对于游戏组件从加载到上屏的流程我画了一张图:

把这个流程可以简单拆成 10 个阶段:

为了介绍清楚这个流程,我准备了一个最简单的 Cocos 游戏 Demo。这个是 Demo 的场景设计:

这个是主场景的代码:
const { ccclass } = cc._decorator;
@ccclass
export default class Helloworld extends cc.Component {
protected onLoad(): void {
console.log('onLoad');
}
start () {
console.log('Hello World');
}
}
3.1 Load Assets

首先是资源加载,前文介绍过游戏资源可以分为静态资源和脚本资源。由于静态资源的加载流程涉及的内容太多了,本节只简单介绍下脚本资源加载。
包括 3 类脚本资源:
- 内置脚本:引擎启动的时候进行加载,包括注册 JS Binding、实现 window 对象(基础的 BOM 和 Canvas DOM 对象)、polyfill 补齐 ES 标准等等。这个脚本内置在容器里,容器启动 JS 引擎的时候直接加载。这一步可以做多实例和预执行,以加快启动速度。
- 入口脚本:容器需要一个入口脚本,类似与 Web 里的 HTML,以便引入游戏入口资源。
- 动态加载的脚本:由游戏入口资源引入,比如游戏框架代码、游戏包里的 JS 资源等等。
这里可以容器侧可以提供离线资源、preload、prefetch、预执行等方式进行优化,同时在 JS 引擎方面也可以扩展做下 Code Cache,避免重复的编译耗时。
3.2 Component Scheduler

脚本资源加载执行后,游戏组件代码会进入到组件调度器中进行优先级调度。
Cocos 组件的生命周期如下图左所示,在 3 个关键的生命周期环境分别存在对应的调度器,每个调度器里设计了三个优先级队列,本质上每个队列的内容是由链表进行组织,顺序执行注册好的 invoker。

具体而言,从业务侧视角来看:在场景编辑器中创建节点(Node)时,业务方可以为其命名,并通过勾选“active”属性来决定该节点是否默认激活。一旦节点被标记为激活,加载阶段将由 Node Activator 负责激活该节点,接着 Component Activator(组件调度器的一部分)会依次激活该节点所挂载的各个组件,同时触发组件所在场景(Scene)的激活流程。最终,激活后的场景会将节点挂载入层级树,并完成组件 Invoker 的注册,交由调度器统一调度与管理。
整体流程如下图所示:

我们的 Demo 游戏组件的 start
生命周期下打印了一个 “Hello World”,调度堆栈如下所示:



3.3 Render Scene

当场景激活并挂载了对应组件之后,接下来便是渲染场景,这一步就涉及到从 JS 调用到了 Native —— 即需要将 Scene 数据传递给 Native 侧,从而触发 Native 的 Render 流程。

JS 和 Native 互相调用的方式有很多,适用场景也不同,这里也不展开说了。需要注意的是,在架构设计上,这里可以对 Binding 层做一层抽象,以便容器对接不同的 JS 引擎实现。

另外需要注意的是 Binding 要做好两端的 GC,因此 Binding 的实现上需要符合 RAII 原则:

3.4 Batcher

当 Native 拿到节点之后,便需要进行 Batch,这一步属于计算密集型,因此选择放在 Native 侧去做。
Batch 的流程比较复杂,核心思想是通过 DFS 对场景中的 Node 进行遍历,计算并装配(Assembler)顶点数据,得到顶点缓冲(VertexBuffer)和索引缓冲(IndexBuffer):

我们 Demo 游戏的场景树结构相对简单,遍历从 root 开始向下遍历(别忘了 Camera):

装配的计算流程比较复杂,下面仅对装配的结果做一个拆解,方便读者理解数据的由来。对于小恐龙而言,它是一个 Sprite2D,装备时会转成 Texture2D 处理,而后者在这个环节的核心,是需要拿到网格数据(Mesh Buffer)。下图是最后计算得到的 Mesh Buffer。

Mesh Buffer 由 Vertex Buffer 构成,这里装配的 Mesh Buffer,共 80 字节,其中每个顶点 20 字节,那么可以容易拆出 4 个 Vertex Buffer,同时根据 a_uv 的定义和偏移能拿到各自的 uv 坐标:

例如,根据顶点着色器的代码我们知道这个 Vertex Buffer 包括 3 部分数据:
a_position
: 偏移量 0,8 字节。vec2,能算出来一个坐标。a_uv0
: 偏移量 8,8 字节。vec2,就是 x,y,算出来之后是(0,1)。a_color
: 偏移量 16,4 字节。vec4,RGBA,数值是 0xFFFFFFFF,即白色透明。

我们把四个顶点的坐标都算一下,可以拿到宽高和左上角的坐标,其实可以发现,这个数据就是业务侧在场景编辑器里对 Node 的宽高和坐标设置:

顶点装配完毕之后的 Node 会被放进 Models 里,最后做成 Scece Tree 中的 models 节点:

3.5 Setup

这个环节主要由两个逻辑组成:
- 设置 Framebuffer 和 Viewport
- 将 Scene 里的各个 Model 转成 drawItems 队列
首先是第一个部分,设置 Framebuffer 和 Viewport。具体而言,包括以下步骤:

- 通过
setFrameBuffer
函数调用glBindFramebuffer
绑定 Framebuffer 帧缓冲对象,并分别附加颜色缓冲(COLOR_ATTACHMENT
,存储渲染的颜色信息)、深度缓冲(DEPTH_ATTACHMENT
,存储每个像素的深度信息,用于深度测试)和模板缓冲(STENCIL_ATTACHMENT
,存储模板测试的结果),确保后续绘制有正确的渲染目标。 setViewport
调用glViewport
设置视口,决定最终渲染区域在屏幕上的映射范围setup clear
依次执行glClearColor
、glClearDepth
和glClearStencil
,初始化颜色、深度和模板缓冲的清除值,为每一帧绘制提供干净的初始状态。
unsigned int fbo;
glGenFramebuffers(1, &fbo);
接下来,游戏引擎会将 Scene 里的各个 Model 转成一对一的 DrawItem,一个 DrawItem 的数据结构如下所示:

最后,引擎将这些 DrawItem 组装成 DrawItems 队列,以便后续流程处理。

3.6 Render Stage

接下来进入 Render Stage 阶段,渲染管线会开始对 DrawItems 进行分类处理。根据渲染的 Material 的需求,DrawItems 会被分发至三个不同的 Pass,分别对应 Opaque、Shadowcast 和 Transparent 三个阶段,关系到材质属性和阴影投射:
- Opaque:用于绘制完全遮挡光线的物体,如墙面、地板、角色模型等。这类物体会首先渲染,通过深度缓冲区(Z-Buffer)完成遮挡剔除,避免后续无效绘制,提升渲染效率。
- Shadowcast:专门处理场景中的阴影投射。此阶段会根据光源信息,对具有投影能力的物体进行阴影绘制,为场景添加真实感与空间深度,尤其适用于强光源或需要表现光影效果的环境。
- Transparent:负责绘制允许光线穿透的半透明物体,如玻璃、水面、特效粒子等。透明物体通常需要根据视角进行深度排序,以保证前后层次正确渲染,避免视觉穿插错误。
通过将 DrawItems 按照物体特性分发至不同 Pass,渲染管线能够有针对性地对 Effect 进行实现。
业务侧可以在代码里创建一个指定的 Material,之后管线就会走到对应的 pass 进行处理:
// 创建一个立方体网格
const cube = new cc.MeshRenderer();
cube.mesh = cc.GizmoMesh.createBox(1, 1, 1);
// 设置材质为不透明
const opaqueMaterial = cc.Material.create();
opaqueMaterial.initialize({
effectName: 'builtin-unlit',
technique: 'opaque',
});
cube.setMaterial(opaqueMaterial, 0);
因为我们 Demo 较为简单,因此最后生成的 StageInfo 只包含 Opaque Pass:

当然,在 Cocos 中也是支持自定义渲染管线,实际上就是自定义这个环节的 Passes,定义完之后可以直接应用在 Opaque、Shadowcast 和 Transparent 三个阶段之上:

3.7 ModelView Transformation

经过 Passes 之后,场景中的 DrawItems 会根据其属性被分别送入 OpaqueStage Renderer、Shadowcast Renderer 和 Transparent Renderer 进行初步处理。各个 Renderer 在此阶段主要负责更新与视图相关的 Uniforms(如矩阵、材质参数等),以确保后续渲染过程中所需的视角、空间信息正确。这一部分可归类为 View Transformation 阶段,统一完成视图坐标系下的变换数据准备。
紧接着不同的渲染阶段会有差异化的预处理操作:不透明物体和透明物体会分别执行 SubmitLight 以提交光照信息,而投影阶段则专门进行 SubmitShadow 以生成阴影数据。同时,透明阶段由于涉及深度排序问题,还会额外执行 Calculate zdist 以计算对象的深度信息。
所有这些预处理完成之后,最终将统一进入 ModelView Transformation 阶段,得到视图投影矩阵,从而完成从模型空间到屏幕空间的最终变换,以便于后续的图元栅格化与像素着色工作。
在讲解 ModelView Transformation 之前,先来介绍下游戏系统中的坐标系统的定义。一般会涉及物体坐标、世界坐标与相机坐标三种主要坐标系。
- 物体坐标系:以物体自身的中心点(anchor 通常设置为(0.5, 0.5))为原点,用于描述物体内部各个部位的位置关系,便于定义复杂物体内部的原子结构关系。
- 世界坐标系:则是以整个场景的中心作为原点,用来统一描述场景内所有物体、相机以及光源的位置关系,确保场景整体的空间一致性。
- 相机坐标系:以相机的位置作为原点,是为了将 3D 空间转化为 2D 图像,以便进行计算和渲染。

基于这套坐标系统下,观测变换(Viewing Transformation)主要包括视图变换、模型变换与投影变换三个步骤。
- 视图变换:可看作是将相机放置到场景中的过程,主要是定义相机的朝向和位置。
- 模型变换:对物体进行放置或调整位置、旋转以及缩放等操作。
- 投影变换:类似于摄影,通过投影方式,将三维物体的信息映射到二维的屏幕空间。

下面重点说说投影变换(Projection Transformation),它分为正交投影(Orthographic Projection)与透视投影(Perspective Projection)两种方式。
- 正交投影常用于工程制图软件,不体现远近透视效果;
- 透视投影广泛应用于游戏、渲染引擎中,能更真实地模拟人眼观察到的空间透视效果。
而透视投影的数学本质是压缩加上正交投影的结合,实际将一个无限延伸的观察空间(视锥体)转化为一个便于计算的立方体。

这里简单画了个图来介绍透视变换的实现,fov(视角)定义相机的视场宽度,可以分为水平fov与垂直 fov;distance 定义投影平面与相机之间的距离。视景空间通过近裁剪平面(near)和远裁剪平面(far)定义渲染的范围,通过相似三角形的计算,最终将 3D 空间映射到 2D 屏幕(Canvas)。

说完了透视投影,我们再看看投影变换的另一种方式——正交投影。其通常有实现的方式有两种:
- 直接舍弃Z坐标,将 3D 物体转化为 2D 物体,直观但无法表现空间深度;
- 将观察空间变换成标准的立方体后,利用变换矩阵进行计算。

综上,坐标转换流程具体包括物体坐标到世界坐标,再到相机坐标,接着到投影坐标,最终映射到屏幕坐标。
- 首先,在编辑器中定义坐标相加的关系,将物体放置到场景中;
- 之后,通过视图变换调整相机位置、模型变换调整物体位置;
- 再经过投影变换将 3D 空间投射到 2D 空间;
- 最后进行坐标系转换,确保渲染到正确的屏幕位置。

在这个过程中,会计算得到视图矩阵(View Matrix)、投影矩阵(Projection Matrix),最终矩阵相乘拿到视图投影矩阵(Model-View-Projection Matrix)。我们结合 Demo 游戏的断点数据,分别看看他们仨是怎么计算得到的。
首先是视图矩阵,它负责将世界坐标系转化为相机坐标系,其中包含坐标轴的缩放和平移操作。实际计算中,通常涉及坐标轴补齐,即齐次坐标的补齐过程,确保矩阵运算的有效性。

之后是计算投影矩阵,它用于将相机空间进一步映射到标准化的设备空间(Normalized Device Coordinates, NDC),矩阵中的缩放系数根据屏幕的宽高比和设定的正交高度来计算。

最终的渲染过程通常使用视图投影矩阵(Model-View-Projection Matrix, MVP)。视图投影矩阵是视图矩阵与投影矩阵的组合,用于最终的顶点变换和着色器渲染计算。

3.8 Link Program

接下来进入到着色器的创建与 Link 阶段,首先是创建图元:

之后是创建顶点着色器和片元着色器:

值得关注的是,在 Cocos 中有一共有 11 个内置着色器,其中前 5 个处理 2D 渲染相关,builtin-clear-stencil|vs|fs 用于清楚模板缓冲区,7-10 3D 渲染相关,最后一个用于处理 3D 光照:
- builtin-2d-spine|vs|fs
- builtin-2d-graphics|vs|fs
- builtin-2d-label|vs|fs
- builtin-2d-sprite|vs|fs
- builtin-2d-gray-sprite|vs|fs
- builtin-clear-stencil|vs|fs
- builtin-3d-trail|particle-trail:vs_main|tinted-fs:add
- builtin-3d-trail|particle-trail:vs_main|tinted-fs:multiply
- builtin-3d-trail|particle-trail:vs_main|no-tint-fs:addSmooth
- builtin-3d-trail|particle-trail:vs_main|no-tint-fs:premultiplied
- builtin-unlit|unlit-vs|unlit-fs
文中的 Demo 是使用内置着色器模板进行创建的。
接着创建着色器程序,Link 上我们创建的顶点着色器和片元着色器。紧接着,设置着色器中所需要的 Uniforms 变量,这里就包括纹理和我们上一步计算得到的视图投影矩阵:

最终,我们的 Framebuffer 会附着上颜色附件、深度附件与模板附件:

需要注意的是对于刚创建完的 FrameBuffer 不能立即使用,因为它还不完整(Complete)。而一个完整的帧缓冲需要满足以下的条件:
- 附加至少一个缓冲(颜色、深度或模板缓冲)。
- 至少有一个 GL_COLOR_ATTACHMENT。
- 所有的附件都必须是完整的(保留了内存)。
- 若开启 Multisampling,则每个缓冲都应该有相同的样本数(sample)。
因此需要使用 glCheckFramebufferStatus
对缓冲区的完整性做出检查:
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// ...
// notify native: getInstance()->glErrorCallback(GL_ERROR, errMsg);
return;
}
3.9 Blend & Test

接着依次进入执行 混合(Blend)、深度测试(Depth Test)、模版测试(Stencil Test)。
首先是 Blend,顾名思义讲两个颜色进行混合。下图展示了混合方程的计算方式:

OpenGL 中常用的混合函数如下图所示:

下面是一个简单的例子,使用着色器来创建红色蒙版的 Blend 效果:


深度测试(Depth Test)在图形渲染中用来决定每个像素是否显示。启用深度测试时,OpenGL会将当前片段的深度值与深度缓冲区的值进行比较。如果通过测试,深度缓冲区将更新为新的深度值,否则该片段会被丢弃。下图展示了 OpenGL 中常用的深度测试函数:

而模板测试(Stencil Test)则用于限制渲染区域。通过模板缓冲区,可以在渲染时创建特殊的区域标记,只有符合模板缓冲区设定条件的片段才会被渲染到屏幕上。模板缓冲区允许实现诸如阴影、镜面效果、轮廓高亮等复杂渲染效果。下图展示了 OpenGL 中常用的模版测试函数:

上述的结果最终都会与 Framebuffer 的 Attachment 机制相关联。Framebuffer 的 Attachment 机制决定了渲染结果如何输出到缓冲区中。Framebuffer 通常会附带多个 buffer,包括颜色缓冲区(GL_COLOR_ATTACHMENT)、深度缓冲区(GL_DEPTH_ATTACHMENT)和模板缓冲区(GL_STENCIL_ATTACHMENT),他们共同决定了最终渲染的结果。

3.10 Commit & Draw Pass

到了管线的最后一步,便是提交(Commit)和绘制(Draw)。
在 Cocos 中每一帧会存储两种状态,一个是当前画面帧的状态(currentState),另一个是我们即将渲染帧的状态(nextState)。

我们需要依次计算 nextState 中的各个部分的 state,之后将 nextState 和 currentState 的状态值做 diff,如果某个环节的状态值不一致,便会触发 commit 操作。以便管线最大程度利用缓存结果。

下面依次介绍下管线中需要管理的状态值:
- Blend States、Depth States、Stencil States
- Cull Mode
- Vertex Buffer
- Program
- Textures
- Uniforms
其中 Program 通常在管线初始化时所有的着色器都会准备好,非极端情况下缓存不会失效,因此上面的图中没有标出这个状态。
Blend States、Depth States、Stencil States 分别存储了我们前文所说的 Blend、Depth Test、Stencil Test 过程中涉及到的 GL 调用的参数和部分结果,这里就不详述了。
接着是 Cull Mode,根据顶点的索引的顺逆时针来用来区分正面与反面,如果状态值和 currentState 不一样,便触发 glCullFace
的调用来进行 commit。

对应顶点缓冲区也是一样有状态值管理,如果变脏了,就需要重新调用 glBindBuffer
进行绑定:

着色器程序也是一样的,如果脏了,就重新调用 glUseProgram
进行设置:

接着便是对 Textures 的检查和提交,这里有两个知识点:
- 纹理的应用:具体涉及到
glActiveTexture
和glBindTexture
。首先使用glActiveTexture
函数来选择当前要激活的纹理单元,这一步决定了接下来绑定的纹理将作用于哪个纹理单元上。然后,通过glBindTexture
函数将具体的纹理对象绑定到特定的纹理目标上。通过这种机制,纹理对象与对应的纹理单元和目标进行关联,从而完成纹理的激活与绑定操作。 - 纹理单元:用于表示显卡可以同时管理的多个纹理。默认情况下,
GL_TEXTURE0
纹理单元总是被激活的状态。此外,OpenGL 规范保证至少支持 16 个纹理单元(即从GL_TEXTURE0
到GL_TEXTURE15
)。纹理单元是按顺序定义的,因此我们可以通过诸如GL_TEXTURE0 + 8
的方式便捷地访问特定编号的纹理单元,以便在复杂的渲染场景中实现多纹理同时使用。

当前面的状态值都准备并提交完毕后,最后需要管理的状态值是 Uniforms,这一步如果有脏区产生,也需要重新提交 Uniforms 变量。比如游戏 Demo,涉及到的 Uniforms 变量有 cc_matViewProj
和 texture
:

最后就是绘制了,其中在每一帧的绘制前都需要调用 glClear
清理 Freambuffer 的状态。下图展示了 gl 指令调用的时序:

由于游戏 Demo 比较简单,绘制只需要准备好纹理和 Uniforms 即可,最后调用 glDrawArrays
或 glDrawElements
将准备好 Framebuffer 绘制上屏:

至此,经历了这一系列的管线处理之后,我们的 Demo 游戏在小游戏容器内完成了上屏。

扩展阅读
- 《GAMES 101》
- 《计算机图形学入门:3D渲染指南》
- 《
LearnOpenGL 》
对网吧的偏见
我对网吧的偏见,源于小学时候。
学校对面有一家网吧,从知道那是网吧开始,到它停止营业,从未进去过,因为时不时就有老师或主任在放学后去网吧逮学生,第二天在广播里通报,再加上家人和老师不断地告知网吧里全是不三不四的社会人,很危险,随时都会被敲诈,现在回想起来,并不无道理。
怕被老师逮到,也怕被坏人欺负,再加上我平时几乎没有机会接触电脑,直到今天,去网吧的次数屈指可数。小学对面的网吧,当时的价格是2元/小时,包宿应该更便宜,但对于每天零花钱只有1元的我来说,也是承担不起的价格;第一次去网吧,得益于同学请客,已经记不清当时坐在电脑面前玩什么游戏,只记得那时候的心情十分忐忑,即便是周六,也担心突然被老师逮到,去网吧上网似乎成了一种罪过;还有一次在同学的带领下,去过一趟所谓的黑网吧,不查身份证,不管你多大,给钱就开机,房间很小,头一次觉得这里确实是一些不三不四的社会人……后来随着学习压力增加,学习时间紧张,读初一的时候家里买了电脑,便再也没有去过网吧,高中毕业之后约过同学去包宿,难受和煎熬,想玩又犯困,以及现在,双休前的最后一个工作日,如果兴致来了,去楼下的网吧玩两个小时,就已经很满足了。
前段时间还在犹豫,想买游戏本打游戏,考虑到自己不仅打得菜,而且平时很少有时间玩,还不如想玩的时候去网吧玩一会来的划算,于是买了 Mac mini M4。附近有不少网吧,以前叫网吧,现在叫网咖的居多,不仅配置和环境更好,价格也更贵了。第一次来南昌,陪女朋友考试,考试期间我就去附近的一家网鱼网咖玩游戏,10元/小时的价格让我惊讶,这还是大厅的价,如果是包厢岂不是更贵?今年寒假,和几个同学开了一间电竞酒店房,有四台电脑,这边四个同学玩游戏,那边四个同学在打牌,累了困了就在床上睡一会,又一次刷新了我对网吧、网咖、电竞酒店的认识和体验。
不怕别人笑话,因为去网吧去得少,我都不清楚上机的过程,如何让老板给我开一台机子,怎么登陆,没带身份证怎么办……也是在高中毕业之后和同学一起去网吧次数多了,渐渐熟悉一点,没带身份证不碍事,打开微信使用 V 上网公众号实名激活就行,接着再让老板充值网费,随便找电脑开机等等。
申公豹说得对,人心中的成见是一座大山,从小对网吧的偏见在我心中也是一座大山,给它打上了不太好的标签,但也影响不了什么,从现在来看,网吧是让我在工作之余放松自我的地方,话又说回来,有条件的话还是尽量在自家房间里玩游戏,环境更好,今天去附近的网吧玩了两个小时,虽然开着空调,但是空气中弥漫着一股奇怪的味道,人多、嘈杂,或许以后还是会选择配置一台 Windows 电脑,每个男人心中都有一个电竞房的梦想。
20250515
今天的惊喜来自和菜头老师在公众号槽边往事写的文章提到了谜底黑胶,《聊点我喜欢的:Wiim 和谜底黑胶》。
之前谜底黑胶也有在别的文章或者视频出镜过,不同的是和菜头老师去“调查”了我,他看到了我的日常,我的黑胶设备,他理解了为什么我会而且我能做出这样一个产品。有一种被懂了的感觉。
今天研究了 Claude + Apple Music MCP 的一些玩法,希望它能给我推荐音乐创建歌单。不过我找的这个 MCP 不太合适,有空要尝试一下另一个。
下午高兴完了就去打麻将了,蛰伏两圈,每圈仅小胡一把。第三圈直接连庄外加四个财神加持,一把翻身。前面两圈让我放平心态,今天就是来陪玩的,于是只专心搞大的,当财神看我可怜分配给我四个白板的时候,我抓住了机会。人生就要抓住机会啊。
【读书记1557】《汪鸿藻楷书金刚经》
自業自得
读作じごうじとく(jigō jitoku),日语里「自作自受」的意思。即自己的业障、自己所得。
自作自受的「标准」在哪里?如果一个当事人遭遇了某些在外人看上是自作自受的剧情,但是他却觉得这是一种难得的人生体验,难道我们要把这个人绑起来打到他屈服,让他承认他就应该「自作自受」?很显然,期待自作自受结局的仍然是「观众」。
我膝盖上有个疤痕,是小时候摔跤之后结痂没两天我就抠破的代价。当时上药的医生用阻吓的方式警告我,抠破结痂就会留下一辈子的疤痕,但是我又手贱,所以总是时不时地扣上两下,直到有一天我躺在床上抠破了结痂,血喷出来的那一刻我具象化了「自作自受」。
因为第二天我的衣服上、床上、墙上都是血迹,免不了一顿臭骂。但是我自己倒是合理化了这个自作自受的结果——结痂扣掉之后,伤口不痒啦!从此之后,所谓的「自作自受」就跟「油漆未干」一样,我不摸一下我怎么知道油漆干没干。
所以基本上自作自受这四个字的恐吓对我来说已经完全失效了,反倒变成了「买个教训」。
从小到大需要面临的「自作自受」情况有很多,比如「现在不好好学习将来没钱吃饭没人管你」「你如果这段时间玩过去了,暑假最后两天赶作业就知道痛苦了」「你太早谈恋爱就会耽误学习,到时候想谈恋爱还没人要」「等你到了我们这个年纪你才知道赚钱有多么辛苦」……
不得不说,这些场景预判都是非常好的剧本设定,即五分钟内将主角拉入剧情的「邀请」,比如一个不听话的小男孩突然在一夜之间长大成人,10岁的灵魂带着40岁的人生去体验他期待的「我长大以后一定会怎样」,或者说是父母苦口婆心想让他理解的「自作自受」。
没错,这个「一夜之间」就是好莱坞电影的类型之一了,例如《辣妈辣妹》《重返十七岁》《夏洛特烦恼》《羞羞的铁拳》——这一类型的电影被称之为「愿望成真」。这一类电影之所以受欢迎(我说的是市场反馈,不是您的个人喜好),是因为它既满足了幻想,也会让观众从一开始就带着强烈的「自作自受」预期。
在《凪》里我埋下了这一期的引子——没人喜欢完美的主角,如果他没有缺陷那就很难与观众形成共鸣。比如我前段时间在客厅看过几次片段剧情的《成家》,一群圣母嬲竟然是为了「善良」在帮助剧情里的每一个角色——哈?
那如果是完美的角色,他的每一个选择、每一个行为都会阴差阳错地带来好运,这个时候观众就会期待「陷阱」的出现,否则他的故事何必要「拍成」电影呢?这便是「愿望成真」这类电影的内核:
把主角(也是观众期待的)最迫切的愿望实现之后,会如何?
- 主角是弱者(不完美的)
- 因为同情,所以观众 希望 弱者的愿望被实现;
- 但同时观众 不希望 弱者永远是「赢家」,所以也期待着「自作自受」的发生;
- 主角是强者(完美的)
- 令人 生厌 的同时,又让观众察觉到主角身上的「可悲」与「闪光点」;
- 所以他必须接受「自作自受」,但他从中学会了那些让观众感到 愉悦 的特质;
不得不说,所谓的「剧本结构」,其实就是通过情绪来控制观众感官的过程(我说的是市场反馈,不是您的个人喜好)。想让人们厌恶一个角色的同时,只要稍加技巧,也可以让这种厌恶中带着一丝怜悯,以至于到最后开始同情和理解「坏人之所以变坏」的合理性。前面举例的那四部电影都是「愿望成真」中的一个子类别「身体交换」,即通过交换性别、年龄或是种族等,最终角色学会感恩。当然,这也是最容易出现「正能量自我感动」的烂片的原因。
从剧本跳脱出来,现实几乎不可能发生「愿望成真」的时候,这种场景更容易发生在「睡前妄想症」的情形当中。
从剧本的结构而言,人们对于「愿望成真」的情感又是极其哲学辩证的——我可以接受他愿望成真,但这个成真的代价就是有一段「自作自受」的剧情,让他重新理解自己得到的一切——这想必是我们在童年接受的教育,他们的底层逻辑竟然是一模一样的。
「我可以给你买这个玩具,但是你先要考到100分;如果给你买也可以,但是你如果下一次考不到100分,我会立马收走这个玩具。」——愿望、条件和诅咒。
这便是「愿望成真」的另外两个剧本内核:
- 条件:得到「魔法」要付出的代价,代价 就是在过程中 追求自我;
- 诅咒:既然有「魔法」也有使用魔法后必然遭受的 对等诅咒;
现实虽然没有「魔法」和它引发的「愿望成真」,但剧本里的内核却同样适用。「愿望成真」是一个极其双标的存在,当别人的愿望成真时,就应该按照剧本的结构去引发那些他即将面对的「自作自受」,但自己的愿望成真又不想付出那些对等的诅咒代价。
代价就是代价,它不会因为一个人的善良、邪恶、好与坏、对与错而发生本质上的改变。它存在于每一个选项的背后,但是这个代价本身是没有好坏之分的。
如果一个当事人遭遇了某些在外人看上是自作自受的剧情,但是他却觉得这是一种难得的人生体验……
这才是人生剧本里的「愿望成真」。
昨天有人在 Telegram 给我留言,问我剧本创作是否可以完全脱离现实生活凭空创造。我还没来得及回答,这是个很庞大的问题,都说「艺术源于生活、但高于生活」,但如果完全记录的是生活中那些狗屁倒灶的事,这样的作品可能就身边几个人看完能会心一笑,市场只会觉得「它跟我有什么关系」。
我也试过创造完全架空的剧本,结果很惨,因为陷入到完全主观的幻想之中,就很容易创造出「旅游导览」式的作品,这正好就是下一期想聊的话题。简单来说,所谓的「旅游导览」就是当创作者创造了一个精美巧妙的世界观后,他其实是很恐惧有人(甚至是主角)去毁掉这里的规则。最后就变成了主角在一个幻想世界束手束脚游历参观的过程——不是谁都能如此潜心地完成《荷马史诗》。
影视作品是反馈现实的「期望」,「梦想成真」就是其中一个类别,但并不是因为有了形式才有了公式,而是因为这个公式就是人性的一部分,只是作品将它们毫无遗漏地暴露,成为了最露骨且直观的表达。
科技爱好者周刊(第 348 期):李飞飞,从移民到 AI 明星
这里记录每周值得分享的科技内容,周五发布。
本杂志开源,欢迎投稿。另有《谁在招人》服务,发布程序员招聘信息。合作请邮件联系(yifeng.ruan@gmail.com)。
封面图
5月1日,宇宙飞船造型的深圳科技馆新馆开馆,上图是设计团队在新馆前合影。(via)
李飞飞,从移民到 AI 明星
大家知道李飞飞吧,AI 的明星教授。
她在斯坦福大学任教,是美国国家工程院等三院院士,担任过斯坦福 AI 实验室主任,以及谷歌云 AI 首席科学家。
她1976年出生于北京,在成都长大,16岁全家移民美国。
我一直好奇,她怎么走上 AI 这条路,从移民变成学术明星?
这几天,我读完她的自传《我看见的世界》(中信出版集团,2024),才发现她的人生很有戏剧性,每当重要关头,都有幸运的事情发生。
(1)高中阶段
她出生于一个普通家庭,中学阶段并无过人之处。
我们家位于成都当时的外环路旁边,小区由三栋一模一样的塔楼组成,我家住在四楼。这个环路是不断扩张的城市边缘,一侧是工厂,另一侧是农田。
我进入了一所吸引全市优秀学生的中学。在那几年里,对女孩的预设和偏见让我越来越不耐烦,这种情绪已经超出了课业的范围。在同龄人中,我已经有"假小子"的称号。
1992年,移民美国后,她家的生活顿时变得困难。一家三口挤在新泽西乡下一间一居室公寓,她睡客厅,床就放在餐桌旁边。
父亲在一家华人商店修理旧相机,后来被辞退,从此失业。母亲做杂货店营业员,后因风湿性心脏病,回家休养。李飞飞下课后,就要去打工,有时在中餐馆端盘子12个小时,每小时2美元。
最后,实在走投无路,她们家决定买下社区的一家干洗店,靠洗衣为生。买下干洗店需要10万美元,全家仅有2万美元储蓄,其余8万美元都是借的。
(2)大学阶段
1997年,李飞飞中学毕业,要申请大学了。
一开始,我的目标大学主要是州立大学和社区大学,而不是常春藤学校。但我一直对一所顶级高校念念不忘,那就是普林斯顿大学。
我们是一个靠从车库市场淘来的旧货才能勉强度日的家庭,连我用的计算器都是坏的,我们怎么可能负担得起常春藤学校的学费呢?
尽管如此,我还是无法抑制内心的冲动,提交了申请。就算只是象征性地申请一下,我也感觉具有特殊意义。
她申请了普林斯顿大学,结果好梦成真,普林斯顿给了全额奖学金。
如果没有全奖,以她家的经济状况,负担不了学费。如果不去普林斯顿大学,她就不太可能走上学术道路了,更不要说后面的成就了。
(3)博士阶段
大学毕业后,李飞飞原想去华尔街工作,解决家庭的经济问题。
母亲鼓励她,继续追求自己的梦想。于是,她选择去加州理工学院读研究生,方向是视觉识别机制。
2004年,李飞飞为了写博士论文,需要图片材料,来训练算法。她找了9000张图片,组成了一个图片集,手工对每张图片进行分类标注,一共分成101类。
这个图片集叫做 Caltech 101,算法经过训练,就能从新图片识别出这101类物品。她因此顺利拿到了博士学位。
(4)助教阶段
博士毕业后,李飞飞先去伊利诺伊大学,后去普林斯顿大学,都是担任计算机科学的助教。
她继续探索视觉识别,想找到一种通用算法,能够识别所有种类的物品,而不是 Caltech 101 那样,只能识别出101类物品。
这意味着她需要一个超大的图片训练集,能够包含了世界上所有物品。这可太难了,所有人都反对这件事。
我们都是年轻的助理教授,所处的院系竞争激烈,在事业起步的那几年里,我们都面临着"要么发表论文,要么完蛋走人"的局面。压力之下,我们必须马不停蹄、保质保量地完成工作,因为我们知道,稍有懈怠就可能与终身教授的职位说再见,一同失去的还有获得稳定生计的最佳机会。
我听到的劝阻之声已经多得够我用一辈子了(可能下辈子也够了).
有上万个类别的数据集有什么用?大部分模型连一两个类别都识别不准!
你知道用这么多图像训练一个模型要花多长时间吗?这个时间可是用"年"来计算的。
别人要怎么下载呢?你这个图像总量比大多数硬盘的存储量还要大。
具体怎么做,你有计划了吗?几百万张图谁来做标注?要花多长时间?怎么验证所有内容的准确性呢?
(5)ImageNet
李飞飞坚持要做,这个通用图片集起名为 ImageNet。那时是2006年。
她想到一个思路,英语词典有一些基本名词,用来解释其他所有物品。只要统计一下,基本名词有多少个,每一个又有多少变体,那就得到了所有物品的基本类别。
统计结果是3万类。因此,李飞飞估计,ImageNet 将有3万个类别,总共包含2000万张图片,每张图片都要有分类和标注,需要从几亿张图片里面筛选出来。
我们发出了邮件,招募愿意帮忙从网上下载和标注图片的本科生,工作时间灵活,每小时10美元。我们招募到一些学生,但是按照这样的进度,完成整个项目需要19年。
这太慢了,项目方法做了改进,用脚本自动去谷歌搜索图片,然后抓取。但是这样也需要人工核对和筛选,只把19年的时间缩短到18年。
幸运的是,亚马逊刚刚发布了众包平台"土耳其机器人"(Amazon Mechanical Turk,AMT)。在这个平台上,你可以出钱,通过互联网,把任务分包给世界各地接活的人。
他们通过这个平台,将 ImageNet 分包出去,投入的人数一下子扩展到几千人,而人均费用只是原来的几十分之一。
2009年6月,ImageNet 的初始版本终于完成了。我们成功达成了目标:收集了1500万张图片,涵盖了2.2万个不同类别。这些图片筛选自近10亿张候选图片,并由来自167个国家的4.8万多名全球贡献者进行了标注。
(6)ILSVRC 算法竞赛
ImageNet 虽然完成了,但在学术界毫无反响,没有太多人关注。
我们遇到了第一个也是最严重的挫折:在当年的"计算机视觉与模式识别大会"上,ImageNet 被降级为"海报展示"。
所谓的"海报展示"是一个学术术语,意味着我们将不能在演讲厅内向听众展示我们的工作,只能在会场的指定区域里摆放一幅印有项目摘要的大幅海报,希望能引起路人的兴趣。
我想过 ImageNet 可能被证明是对的,也可能被证明是错的,对于这两种可能性,我都做好了准备。无论是哪种结果,都会是一个学习的机会。然而,我万万没想到,它被忽视了。
由于 ImageNet 得不到承认,李飞飞想到一个办法,她要每年举行一次算法比赛,看看哪种算法识别 ImageNet 图片集的正确率最高。
这样一来,在计算机视觉领域,ImageNet 就会成为一个比较基准,各种算法都需要用它表示自己的识别能力,大家就不会忽视它了。这个比赛叫做 ILSVRC(ImageNet 大型视觉识别挑战赛,ImageNet Large Scale Visual Recognition Challenge)。
2010年,第一届比赛令人失望,11个团队提交了35个参赛算法。冠军算法是传统的图片向量比较,并无创新之处,正确率也不高。
2011年,第二届比赛更惨,获胜算法还是图片向量比较,正确率只提高了2个百分点。这意味着,没有任何创新和进展。
最糟糕的是,参赛人数也出现急剧下降,参赛算法从35个减少到15个,愿意为此付出努力的人似乎越来越少。
说这种经历"让人羞愧"已经远远不足以描述我们的心情了。为了推动 ImageNet 的发展,我们倾注了多年的心血,搜集的图片数量远远超过以往的任何数据集,还精心策划了一场国际竞赛来探索它的能力,但结果却只是简单地重复了现状。如果说ImageNet 是一场赌注,是时候开始思考我们是不是已经输了。
眼看这个项目就要失败了,几年的心血付之东流。就在这个时候,李飞飞人生最大的惊喜和反转来临了。
2012年,第三届比赛,一个加拿大团队使用被学术界遗忘已久的卷积神经网络,一举将图片识别正确率提高了10%。
接下来的事情,就是被写进教科书的历史了。全世界被神经网络的效果轰动了,AI 研究出现突破,人类进入 AI 时代。
李飞飞彻底翻身,一举成名,从助教变成世界知名的 AI 研究领头人物,人生从此海阔天空。
她的故事令人感叹,如果神经网络算法没有在2012年出现,而是再晚几年,或者更早一点,亚马逊的土耳其机器人众包平台没有在2005年诞生,一切会怎样?
这就是时运吧。科学家的人生和科学发现一样,都是由一些偶然事件推动的。个人奋斗固然重要,但是关键时刻还是离不开幸运。
科技动态
(1)传统的脑电图,需要在头上布满电极(下图),有很多限制,也不舒适。
美国宾州大学的科学家,发明了一种头发电极,细得像头发一样,可以直接粘在皮肤上,淋浴和运动也不会掉下。
这种电极目前还是有线的,但是有计划开发无线版本。
(2)百度地图在导航路面植入广告。
(3)谷歌的 AI 笔记应用 NotebookLM,可能很快就会添加"视频概览"功能。
它已经支持生成音频和 AI 问答,如果再支持生成视频,简直难以想象,是否还需要真人老师。
直接上传课本,它就生成讲课视频了。
(4)安卓官方的桌面模式,泄露了运行照片。下图是它的多窗口模式。
但是 Android 16 可能来不及,发布要等到 Android 17。
手机当作桌面电脑,已经不远了。
(5)百度公布"动物语言转换方法、装置、电子设备及存储介质"专利,使用 AI 识别动物的情感状态,转换为人类能够理解的语言,从而实现动物与人类之间的情感交流和理解。
文章
1、一段让 Chromium 机器人崩溃的代码(英文)
作者介绍了一段 JS 代码,让Chromium 无头浏览器(Puppeteer 和 Playwright)崩溃。它可以用来识别,访问者是不是机器人。
2、Git worktree 简介(英文)
Git 仓库同时只能有一个工作区,如果想同时建立多个工作区,可以使用 git worktree 命令。
3、用 Go 移植 TypeScript 的重要影响(中文)
微软官方要用 Go 语言重写 TypeScript 项目,本文分析这样做的目的和影响。(@imbant 投稿)
4、为什么大模型可以控制手机(中文)
开源项目 droidrun 可以通过大模型,以自然语言操作安卓手机的 APP。本文分析它是如何做到的。(@lezhi12 投稿)
5、创业公司可能无法承受微服务(英文)
本文提出,微服务需要很强的运维能力,并会增加代码复杂性,创业公司不要盲目采用,单体应用更简单。
6、从 Prettier 和 ESLint 迁移到 BiomeJS(英文)
BiomeJS 是用 Rust 语言写的工具,对 JS 代码进行格式化和语法检查,速度极快,可以取代 Prettier 和 ESLint。
7、如何自己托管 Obsidian(英文)
Obsidian 是一个优秀的笔记软件,作者给出详细步骤,自己托管 Obsidian 服务器,从而在任何地方都可以通过浏览器使用。
工具
1、Void
开源的 AI 代码编辑器,Cursor 的替代品,基于 VS Code。
2、Hyvector
在线的矢量图(SVG 文件)编辑工具。
3、Karakeep
一个自搭建的书签 App,提供全文搜索和 AI 自动分类标签,参见介绍文章。
4、PairDrop
局域网传输文件的 Web 应用,代码开源,类似于 ShareDrop 和 LocalSend。
5、zVault
NAS 操作系统 TrueNAS 原本基于 FreeBSD,正在转向 Linux。zVault 是一个社区的分支,由社区推动继续在 FreeBSD 开发。
使用 YAML 格式创建简历,并通过 LaTeX 输出 PDF,方便进行版本管理。(@xiaohanyu 投稿)
7、AllinSSL
开源的 SSL 证书自动化管理平台,集证书申请、管理、部署和监控于一体。(@KincaidYang 投稿)
8、Basecoat
一套基于 Shadcn UI 的组件库,但是不使用 React。
9、Scraperr
网络爬虫的 Web 控制台。
AI 相关
开源的 AI 应用,自动完成数学建模,生成一份完整的论文。(@jihe520 投稿)
基于本地大模型的 Bilibili 弹幕过滤器,对弹幕分类过滤。(@ddddng 投稿)
3、AI 语音克隆
免费的语音克隆工具,3 秒录音克隆人声。(@xiaodaidai0701 投稿)
资源
一个音乐搜索引擎,输入 Spotify、YouTube、Apple、SoundCloud 的音乐链接,它会提供该音乐在其他网站的链接。
地理位置数据库 IPinfo 推出的免费服务,IP 查询地理位置,无需信用卡,API 请求次数不受限制。
英文的 Web Component 入门教程。
图片
1、数字键盘的样式
数字键盘来源于电话。
早期的电话都采用旋转的拨号盘。20世纪50年代,电话可以长途直拨了,拨打长途电话需要输入11个号码,拨号盘就太麻烦了,导致了数字键盘的诞生。
1955年,AT&T 公司的研究人员,做过一个研究,10个数字的小键盘应该怎样排列,效率最高?
他们一共列出了15种排列。
经过研究和比较,用户更喜欢从左到右、从上到下的布局。
具体来说,两排五列水平布局与现在普遍使用的 3x3+1 布局速度相当,差异很小。
AT&T 公司最终为电话选择了 3x3+1 布局,主要原因大概是它比较紧凑。
文摘
一位开发者大学毕业后,加入了一家创业公司。
他逐渐发现,公司内部有很多矛盾,产品决策也有失误。
最终,公司开始走下坡路,他就提交了辞呈,放弃了自己的期权。
离职后,他写了一篇文章,总结了自己得到的教训。
(1)即使创业公司的每个员工都很有动力,但如果创始人并非顶尖人才,那么取得巨大成功的机会很低(但你仍然可以从中学到很多东西)。
(2)创业公司只有两种工作:开发和销售。如果创始人既不做开发,也不做销售,不知道他在做什么,那就相信你的直觉吧。
(3)创业公司的产品还未得到市场验证的情况下,为多个平台构建原生应用,是一种极其低效的行为。如果同时为两个产品在每个平台开发两个原生应用,简直是疯了。
(4)创业公司的路演,大多是浪费时间。产品的验证来自于与用户交流和迭代,而不是打动评委。
(5)没有什么比并肩作战、共同实现梦想更神奇的了。如果你经常见不到创始人,所有的沟通都只能通过远程进行,那可不是好兆头。
(6)如果创业公司没有经过严格的面试,就录用了你,这是一个危险信号。他们到底是基于能力来录用你,还是因为你是第一个同意只收很少的报酬,就为他们工作的工程师?
言论
1、
科学项目日益大型化和制度化,使得个人的好奇心和创新,对于科学的推动正在减弱。科学的进步越来越依靠有效的组织和大量的投入。
-- 《思想家和实干家》
2、
除非你参与过历史遗留项目,否则你不能自称高级工程师。
-- infobip.com
3、
是什么让硅谷的公司如此强大?
不仅仅是它们数十亿美元的资金或数十亿用户,也不仅仅是因为它们拥有惊人计算能力和数据储备,让学术实验室的资源相形见绌。它们之所以强大,是因为成千上万个才华横溢的人在同一个屋檐下共同努力。
-- 《李飞飞自传》
4、
以前的小团队是1名高级开发人员 + 5名初级开发人员,以后是1名高级开发人员 + AI 大模型。
5、
我打赌,以后的工程师必须深入底层,更接近硅片的层面。开发应用程序将不再需要精通技术的人,AI 让每个人都可以开发自己的应用程序。
往年回顾
OpenAI 的图书馆工位(#301)
国产单板机值得推荐(#251)
中国需要成立半导体部(#201)
NFT 是什么,听说能赚钱(#151)
(完)
文档信息
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
- 发表日期: 2025年5月16日
众生牛马
周五,事儿还有很多没忙完,当然本质上也忙不完。这段时间早七点晚九点的忙,一天十四个小时的干活,也算是鞠躬尽瘁了,当然死而后已还谈不上,我还想多活几十年。
这一个月左右的时间,密集的进行各项工作的优化和调整,从二级班子的全员竞聘到全院质量与安全体系的重构,从临床专科的建设到整体人力资源与绩效考核的再造,一切都在忙而不乱的节奏中稳步前行。
当然,整个推进过程中,总有一些磕磕碰碰,不过框架搭好了,方向明确了,接下来就该是撸起袖子加油干了。很多时候,我们需要做到“无碍”,言行自由,心灵轻盈自在。不报有固定的鄙闻漏见,不坚持死板的条条框框,以一颗平和清静之心去待人接物,像水一样随物成形,而不像石头一样冥顽不灵。
当然,我们也需要慈悲之心,心境通透,用广大的慈悲,把天地都纳入自己的禅院,快乐从容的平波万里。回到现实,踏踏实实的从众生牛马做起,日日是好日。
简易教程: C++的智能指针
C++ 智能指针教程
C++ 中的智能指针提供了自动且安全的内存管理。它们通过 RAII(资源获取即初始化)机制,帮助开发者避免内存泄漏和悬空指针的问题,确保对象在生命周期结束时被正确释放。
本教程将介绍 C++ 中三种主要的智能指针:
std::unique_ptr
:独占式所有权std::shared_ptr
:共享式所有权std::weak_ptr
:非拥有式弱引用
1. std::unique_ptr
unique_ptr
拥有独占所有权。一个资源只能被一个 unique_ptr 拥有。
示例:管理简单对象
#include <iostream> #include <memory> int main() { std::unique_ptr<int> p = std::make_unique<int>(42); std::cout << "值: " << *p << "\n"; // 转移所有权 std::unique_ptr<int> q = std::move(p); if (!p) std::cout << "p 现在是空指针\n"; std::cout << "q 指向: " << *q << "\n"; }
示例:构建链表
struct Node { int val; std::unique_ptr<Node> next; Node(int v) : val(v), next(nullptr) {} }; void printList(const std::unique_ptr<Node>& head) { const Node* curr = head.get(); while (curr) { std::cout << curr->val << " "; curr = curr->next.get(); } std::cout << "\n"; } int main() { auto head = std::make_unique<Node>(1); head->next = std::make_unique<Node>(2); head->next->next = std::make_unique<Node>(3); printList(head); }
2. std::shared_ptr
shared_ptr
允许多个指针共享同一个资源的所有权。它通过引用计数管理资源,当引用数为零时自动释放内存。
示例:共享资源
#include <iostream> #include <memory> int main() { std::shared_ptr<int> a = std::make_shared<int>(100); std::shared_ptr<int> b = a; std::cout << "a 的引用计数: " << a.use_count() << "\n"; std::cout << "b 的引用计数: " << b.use_count() << "\n"; std::cout << "*b = " << *b << "\n"; }
示例:共享链表节点
struct Node { int val; std::shared_ptr<Node> next; Node(int v) : val(v), next(nullptr) {} };
3. std::weak_ptr
weak_ptr
是一种弱引用,不拥有资源,仅用于观察 shared_ptr
所管理的对象。它常用于打破循环引用,防止内存泄漏。
示例:打破循环引用
#include <iostream> #include <memory> struct B; struct A { std::shared_ptr<B> b_ptr; ~A() { std::cout << "A 被销毁\n"; } }; struct B { std::weak_ptr<A> a_ptr; ~B() { std::cout << "B 被销毁\n"; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_ptr = a; }
是否存在 make_weak?
不存在 std::make_weak
,因为 weak_ptr
不拥有资源,它必须引用一个已存在的 shared_ptr
。
如何创建 weak_ptr
#include <iostream> #include <memory> int main() { std::shared_ptr<int> sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; if (auto locked = wp.lock()) { std::cout << "值: " << *locked << "\n"; } else { std::cout << "对象已被释放\n"; } }
总结
智能指针 | 所有权 | 线程安全的引用计数 | 典型用途 |
---|---|---|---|
unique_ptr |
独占 | N/A | 高效、安全的单一所有权 |
shared_ptr |
共享 | 是 | 多个所有者共享资源 |
weak_ptr |
无 | N/A | 打破 shared_ptr 的循环引用 |
C/C++编程
- C++中的 const和constexpr 比较
- 简易教程: C++的智能指针
- C++ 编程练习题: 如何合并两个二叉树?
- C++ 编程练习题 - 找出第三大的数
- C++ 编程练习题 - 最多连续的 1
- C++ 编程练习题 - 左子树叶节点之和 (深度优先+广度优先+递归)
- C++ 编程练习题 - 最多水容器 (递归)
- C++的异步编程: std::future, std::async 和 std::promise
- C编程练习题: 翻转整数位
- C++编程练习题: 找出字符串的所有大小小组合
- C/C++ 中的内存管理器(堆与栈)
- C++编程练习题: 对两单向链表求和
英文:Tutorial on C++ Smart Pointers
本文一共 375 个汉字, 你数一下对不对.
相关文章:
- 被动收入之: 微博红包 今年开始重新经营我的微博帐号 drlai 收到两笔微信红包,应该是来自于官方的支持,150元(成功提现到支付宝)。虽然这不能持久,也没多少,但毕竟实现了零的突破,意义重大。 如果流量上来,内容创作者可能会接受到比较多的赞赏,这也是一个比较简单的变现方法。这也能作为一种被动收入,不过如果不是头部网红,可能杯水车薪,但如果你有好几个类似这样的,也能积少成多! 在用户中心,微博用户可以每天登陆手机微博APP打卡,获取点数和少量的红包钱(几分钱),积少成多! 微博做些小任务可获得积分和几分钱。聊胜于无。 微博的主要盈利模式 微博的主要盈利模式主要包括以下几个方面: 广告收入:微博的大部分收入来源于广告,尤其是品牌广告和效果广告。广告形式包括信息流广告(类似于推文广告)、热门话题广告、开屏广告和视频广告。品牌和企业可以利用微博庞大的用户群和社交互动来提升曝光率、推广品牌和产品。 会员服务:微博提供的VIP会员服务,用户可以支付订阅费用来享受更多的特权,比如个性化的主题、特有的表情包、私密权限设置等。这些会员服务主要面向个人用户,提升其社交体验。 直播和打赏:微博提供直播平台,用户可以通过购买虚拟礼物来支持主播,微博会从这些打赏中抽取一定比例的分成。此外,微博与内容创作者分成,通过内容付费、知识付费等形式变现。 增值服务:针对企业和大V(拥有大量粉丝的用户),微博还提供增值服务,如账号认证、粉丝数据分析、精准推送、推广和营销工具等。这些服务帮助企业提升营销效果,同时也增加了微博的收入来源。 电商和导流:微博上有大量的电商导流业务,尤其是和明星、网红的合作推广。微博用户在浏览社交内容时,可以直接跳转到商品购买链接,微博通过这种方式赚取导流佣金。 游戏联运:微博也会与一些游戏公司合作推出联合运营的游戏,微博负责推广和流量引入,用户充值或付费时,微博可以获得一部分的分成。 这些模式相结合,使得微博能够在广告市场、内容创作和电商等多个领域获利。...
- Javascript 中 sleep 函数实现 Javascript 中并没有 built-in 的 sleep 函数支持, 在 async/await/Promise 的支持之前, 我们可以用 busy-waiting 的方式来模拟: 1 2 3...
- 换了个奥迪Q5大灯花了我1000英镑 我那辆奥迪Q5 SUV今年年检没通过,原因是左前车灯坏了,需要更换。车厂告诉我,光是订购零件就要700多英镑,加上人工费,总费用得1000英镑。但没办法,如果不修,车辆年检(MOT)就过不了,车也不能上路。 MOT是英国的机动车强制性安全检测(Ministry of Transport Test)的简称。 近侧前位置灯不工作 drl/位置灯集成(4.2.1(a)(ii)) Nearside Front Position lamp not working drl/position...
- C++ 编程练习题: 如何合并两个二叉树? 题意: 合并两个二叉树, 没有说不可以改变原来的二叉树. 合并的时候把结点求合. C/C++ 中二叉树的定义 在C或者C++中, 二叉树的定义可以很方便的用结构体来表征. 其中左右子树都是递归定义. 1 2 3 4 5 6...
- 步步高学生电脑上 Basic 编程语言 peek 用法示例 步步高学生电脑 是8位FC机的经典之作.它上面的BASIC有三个版本 1.0, 2.0 和 2.1 2.1 版本有个在线帮助,实际上是 help.cmd 1.0 是用 Esc 键退回到 DOS 的,...
- 你给SteemIt中文微信群拖后腿了么? 这年头不缺算法, 就缺数据. 这两天花了很多时间在整API上, 整完之后自己用了一下还觉得真是挺方便的. 今天就突然想看一看自己是否给大家拖后腿了, 于是调用每日中文区微信群排行榜单的API, 刷刷拿着 NodeJs 练手: 1 2 3 4 5 6...
- C++ 编程练习题 – 找出第三大的数 题意: 给出一个数组, 求第三大的数字是多少, 重复的数字并不算在内, 比如 第3大的数字是1 而不是 2. Using std::set set 是集合, 是有序的(从小到大), 集合中不包含重复的元素, 所以我们可以遍历数组并把数字添加到集合中....
- 最简单有效的过滤WordPress垃圾评论的方法 当你的Wordpress博客流量大的时候, 不免会收到很多垃圾评论. 本文介绍一种特别简单而且免费的过滤Wordpress垃圾评论的方法. 这种方法不需要你安装任何插件, 也不需要拥有修改Wordpress主题模板函数的能力, 只需要1分钟就可以搞定. 把这个列表拷贝下来 打开 WordPress 的控制面版, 到设置-讨论 拷贝上面的列表到 “评论审核” 或者 “评论黑名单”...
借助AI快速开源了三个小工具: 写代码越来越像做产品了, AI 真把我宠坏了(Vibe Coding)
程序员的未来?Vibe Coding + AI 一起上!
借助 AI 快速开源了三个小工具
最近,我利用 ChatGPT-4o 和 o4-mini 快速开发并开源了三个小工具。起因其实很简单——每次想转换 YAML/JSON 或进行 Base64 编码时,我总是得去 Google 搜索在线工具。时间一久,我不禁开始思考:既然每次都要用,为什么不自己动手实现一个呢?正好我也在学习 React,把这个过程当作练习岂不是一举两得?
于是我开始“扮演”产品经理,向 ChatGPT 提出需求,它很快就为我搭建好了基础框架。包括项目结构、CI 工具(单元测试、ESLint——后来我换成了 Prettier)、README 文档,甚至是 GitHub Actions 上的自动化测试和部署流程,全部一气呵成。
这几个工具都部署在 GitHub Pages 上,开源、带测试、写了文档,虽然小巧,但功能完整,不需要自建服务器就能稳定运行,部署成本几乎为零。
YAML/JSON 转换工具
用于在 YAML 和 JSON 格式之间进行双向转换,支持美化和压缩,简单直观。
Base64 编码/解码工具
支持文字和文件的编码解码操作,可以将文件内容直接转换为 Base64 字符串,或者反向解析恢复文件。
ROT47 混淆工具
这是一个轻量级的字符串混淆工具。ROT47 的特点是“加密两次即为原文”,类似于异或操作,适用于简单场景下的信息模糊处理。
程序员的门槛正在降低
AI 的发展速度太快了,现在生成代码的质量不仅更高,出错率也更低。即便出错,只需将报错信息交还给 AI,它就能分析并修正。程序员的角色正在转变:与其说是手工写代码,不如说是理解需求、与 AI 高效沟通,并拼装整合它给出的解决方案。
对我来说,项目最初的搭建一直是拖延的主要原因。ChatGPT 正好擅长这一环节——自动生成 boilerplate,让我几乎能“秒启动”一个新项目,显著提升了开发效率。
我已经养成每天使用 AI 的习惯,甚至已经很少再使用 Google 搜索。遇到问题,第一反应是直接问 ChatGPT,不仅快,还能一步到位解决问题。
Vibe Coding:AI 驱动的编程方式
Vibe Coding 这个概念今年特别火,核心思想是“用氛围编码”。也就是说,程序员不再执着于每一行代码的细节,而是通过自然语言描述需求,让 AI 来实现代码的生成、重构与调试。
Andrej Karpathy (2025)
TLDR;Vibe 编码是一种新兴的软件构建实践,它用简单的语言描述你想要的内容 – 然后让 AI 处理代码和测试。
TLDR; Vibe coding is the emerging practice of building software by describing what you want in plain language – and letting AI handle the code and test.
我发现自己越来越倾向于这种方式。遇到 bug,第一时间就将错误日志扔给 ChatGPT 处理。虽然效率很高,但也让我逐渐失去了深入思考和调试的乐趣。
现在还有很多支持 Vibe Coding 的工具,比如 Cursor,或者在 VSCode 中配置 AI Agent。你只需告诉它“我要实现什么”,它就能完成代码编写、框架搭建,甚至进行模块拆解和逻辑优化。而你要做的,仅仅是审查和接受它的建议——这不就是产品经理的工作吗?
初级程序员的挑战与机会
AI 工具的普及正在重塑编程的边界,传统“照着教程敲代码”的模式已经远远落后。未来,初级程序员面临的挑战不再是“学不会写代码”,而是“不会用 AI”。
想要不被淘汰,最重要的是掌握如何高效地与 AI 协作,提升自己的系统思考与设计能力。AI 是强大的助手,但人类的洞察、判断和创造,依然无可替代。
未来属于那些善于利用工具、不断精进自己思考能力的人。也许“写代码”的定义正在改变,但“解决问题”的本质永远不会变。
AI大大提高了生产力,执行力和创造力会更加重要。软件工程师需要懂得怎么用AI来干活拧螺丝(开发/调试/解决问题)。感觉程序员的门槛瞬间变得好低,只要懂得看懂AI生成的代码,懂得基本的软件工程就可以了。
ChatGPT 可以拿来做什么?
- 硅谷娇妻是怎么样的? 让四个大语言模型AI来告诉你
- 教娃编程之: ChatGPT写了一个Python交互程序调用x.ai的Grok大语言模型
- 软件工程师可以通过ChatGPT来帮助审核代码(提高代码质量)
- ChatGPT-4o好幽默:给我画了两张北京女孩穿吊带/迷你裙
- 可以用ChatGPT来轻松转换编程语言
- ChatGPT最擅长的任务之: 写邮件
- ChatGPT 使用 Promise.All 重构/重写代码(并行发送请求)
- ChatGPT-4 使用 Math Wolfram 插件解决数学脑筋急转弯问题
- ChatGPT (3.5和4) 解2023年高考数学题
- 问了ChatGPT比特币2023年年底多少钱(ChatGPT能预测或者估计将来?)
- 让ChatGPT给孩子讲讲什么是公司股票
- 教媳妇: 自定义ChatGPT攻略 | 币价查询GPT
- AI 美女视频 - 抖音网红要失业了?
- 借助AI快速开源了三个小工具: 写代码越来越像做产品了, AI 真把我宠坏了(Vibe Coding)
ChatGPT 通用人工智能
- ChatGPT崛起让网站流量大幅度减少, 搞不到钱了
- 解决单点故障: STEEM区块链ChatGPT机器人的多个读进程
- 系统设计: Steem区块链ChatGPT机器人
- 整合 ChatGPT Prompt AI到 STEEM 区块链上!
- 整合 ChatGPT 到微信公众号机器人
- ChatGPT在数学和逻辑方面表现不佳
- 浅谈ChatGPT对用户生成内容 (UGC)的影响
- 我儿子也会 Prompt Engineering
- 未来10年的下一个技术趋势是什么?
Grok 3
字节/豆包/AI
英文:From Idea to GitHub Pages: Building Tools with AI and Vibe Coding
本文一共 1266 个汉字, 你数一下对不对.
相关文章:
- 特朗普加关税的公式竟然是EXCEL里弄的? 这两天中美关税大战越演越烈,据说,特朗普加关税的计算方式竟然是直接在EXCEL电子表格里弄的,具体如下: 其中 I 是 Import,进口;E 是 Export 出口。 优美又实用的公式家族又添新成员 勾股定理: 欧拉恒等式: 牛顿运动定律: 爱因斯坦质能等价公式: 特朗普的“互惠关税”公式:,其中 I...
- 按揭贷款(房贷,车贷) 每月还贷计算器 去年给银行借了17万英镑 买了20万7500英镑的房子, 25年还清. 前2年是定率 Fix Rate 的合同 (年利率2.49%). 每个月大概是还 700多英镑. 有很多种还贷的计算方式, 定率/每月固定 是比较常用的. 简单来说就是 每个月交的钱是...
- 测测你的幸运 – Linux Fortune-Teller LINUX 下有很好很好玩的命令,之前已经介绍过: figlet, rig, curl. 现在推荐另一个 命令 fortune 是用来随机显示一段(句)话的.fortune 在英文里就是幸运的意思. 这个命令可以不需要 参数 如果没有 可以通过 apt-get...
- 教娃编程有趣的瞬间 vlog视频集合 教娃700天:写在教娃编程700天, what’s next?。有很多有意思的瞬间记录一下,持续更新。 和孩子一起打闹,学习,成长。 教娃编程孩子有趣的瞬间 vlog(Day 641) 娃说我不够 Senior(Day 701) Bro.. I thought you’re like...
- 智能手机 HTC One M9 使用测评 虽然我对手机要求不高, 远远没有像追求VPS服务器一样, 但是怎么算来两年内换了四个手机, 先是三星 S4 用了一年多, 然后 Nokia Lumia 635 Windows Phone, 后来又是 BLU, 半年多前换了...
- 用 SB2000 的 FBASIC 计算圆周率后80位 英文同步 SB2000 的 FBASIC 是浮点 BASIC 的意思, 就是说可以用在计算一些数值,高级计算机.今天我们还测一下它的计算性能,之前大概知道很慢.. 计算圆周率有很多公式,收敛度不一样, 我们挑一个性价比较高的,比较容易实现的..比如这个: 在SB2000上,80位要算5 小时; 40位1个小时; 20位几分钟, 真得很慢....
- 推荐一款 CHROME 插件 – Wappalyzer 这个CHROME浏览器插件很方便的可以让你知道当前标签页里的网页 有用到哪些 技术. 你可以用它来检查是否插件正常工作. 1. CLOUD FLARE CDN 2. ADSENSE 3. GOOGLE ANALYTICS 4. Gravatar...
- 你要找什么样的老婆? 找媳妇的标准 昨天和网友在剑桥面基, 网友奔现, 他从爱尔兰过来, 小我12岁, 就聊到了找对象的标准. TLDR; 找老婆不要(只)看颜值, 而要注重性格, 为人处事和顾家等更重要的品质, 当然性和谐也很重要. 在当今社会, 人们对于找伴侣的标准有所不同. 有些人认为颜值是最重要的, 因为外貌吸引力可以让人在日常生活中感到愉悦, 这是人的本性,...
董某莹与肖某引发舆情事件迎来初步通报
4月引发的网络舆情,5月1日,国家卫健委新闻发言人表示:
近日,我委关注到中日友好医院胸外科肖某被举报所引发的相关舆情。我委已成立调查组,坚持实事求是、客观公正原则,联合有关方面对事件涉及的肖某、董某及有关机构等进行认真调查核查,对发现的违法违规问题将依法依纪严肃处理。
然后一直在昨天都没任何消息,我曾经以为这个事情就这么过去,一切交给时间。是真的没想到在今天晚上,新闻各种推送,来自国家卫健委的通报:
1、对肖某是进行了一些所谓的处罚,吊销医师执业证书,并给予五年以上禁止从事医疗卫生服务的处罚。
2、对于董某莹,仅仅是按程序撤销董某莹的毕业证书、学位证书。北京市卫生健康委依据医师管理法律法规,对董某莹医师资格证书、医师执业证书予以撤销。这是罚酒三杯的节奏啊。
3、其他情况,国家卫生健康委调查组正联合有关部门,对本次事件涉及的相关单位、人员开展深入调查,对违规违纪违法等问题将严肃追责问责。
对于董某莹的处罚,放在很多工薪阶层的普通人来说,被吊销四证意味着人生前途尽毁,已经没有其他可能性,完全不知道该如何重新站起来。而对于如董某莹这类高门贵女来说,被吊销医疗行业的四证,只不过医疗行业让她玩腻了,可以在医疗之外的9999个行业,继续大展身手,丝毫不影响她在其他行业发挥。
当然,今天的通报,仅仅是初步处置进展。对董董某莹、以及董某莹从入学到毕业过程中,违法为她保驾护航的这些人,后续还有哪些处理,我们拭目以待!
只是,我觉得这个事情应该就是到此为止,应该不会有下文了,已经不相信能发出什么让老百姓信任的信息,有也就是罚酒三杯的那种。在一个信任崩塌的地方,亲手毁灭的信任是很难再建立回来的。
杂记-20250515
【乡下父母的人生课】9:只要努力成为自己就好
VXNA 和 Folo
引
除了口口相传,个人博客的曝光途径很少 。十年之约 和 BlogFinder 算是比较经典的平台,但能见度往往也仅限于「博客爱好者」,很难出圈。
最近看博客后台数据时,VXNA 和 Folo 给了我一些小惊喜。
Folo
Folo 弱化了 RSS 概念,转而主推大众易于理解的 「Follow、关注」,让很多新用户了解并尝试。它还简化了成为 RSS 长期用户最难的部分:寻找高质量的内容。其实,读者一旦关注足够多的「有趣博客」,会很容易养成 RSS 阅读习惯。
同一篇文章的浏览数,Folo 甚至偶尔会跟我博客后台的数据齐平。

VXNA
VXNA 则是 V2EX 的一个新功能——博客聚合器。用户可以申请收录,收录后会被定期展示。

VXNA 仅展示文章标题,点击后跳转原网站(这点对博客作者来说很贴心),所以从博客后台能很直观的看到访问数据。
目前,它已经是除了搜索引擎外,我博客最大的流量来源。

结
前段时间,跟朋友聊天,话题转到「写作与表达」。他觉得我「被看见的需求比较淡」,很少写追着热点的内容,大多跟着自己的兴趣走。
但我想说,「被看见,被阅读」对我很重要,只是我从没想过自己写的东西会被很多人阅读。
感谢这些为个人博客增加能见度的平台!