C 语言历险记
我们一起去 Code 的海洋历险。
- 当你看到这样的语法时,它是我的内心插曲而已,看起来会有点暴敛乖张,
那不是我,那一定不是我!当然如果影响你的心情,可直接忽略。
有一个人前来 Coding……
最近转专业行吗你们哥俩
哥们你这 Code 多少钱一 KB
两块钱一KB
卧槽你这 Code 是函数多还是对象多啊
你看看现在谁还古法编程,这都是手写的 Code,你嫌它复杂我还嫌它 TLE 呢
给我写一个
行
这个 Code 怎么样
你这 Code 保 AC 吗
我写文章要发的,能给你 WA 的答案
我问你这 Code 保 AC 吗
你是故意找茬是吧
你这 Code 要 AC 我肯定要啊
那它要是不能 AC 怎么办啊
要是不能 AC 我自己 rm 了它满意了吧
十五斤三十块 AC
你这哪能 AC 啊,你这测试环境有问题啊
你 TM 故意找茬是吧,你到底要不要,要不要
UB,另外你说的,这要是不能 AC 你就自己 rm 它
rm -rf ./Demo_Luogu_C
你他妈删我题解是吧
萨日朗!!!
是的,上面的段子是我的一个小插曲,菜菜的我居然写出了 UB(Undefined Behavior,未定义行为),搭配 OJ 的 O2 激进优化,我居然干出了在本地怎么测试都没问题,上 OJ 全炸的光荣事迹。
C 未定义行为
这些行为在 C 语言标准中没有明确定义,因此它们可以表现为任何结果。根据我的经验,这些行为一些可以被 IDE 中附带的 Clang-tidy 发现,一些不行,并且大多数 IDE 运行代码时采用的是 Debug 模式,更加导致这些问题在前期隐蔽而无法被发现。
再得益于 C 语言设计的哲学:完全信任程序员,程序员应该能够写出正确的代码,从而导致了一些预料之外的行为在特定的情况下才能发现。
下面列出的可能还包含一些非 UB 的类型,但它们同样会引发一些奇怪且不会计入 Error 的问题,故并列列出。
一些也被称为 UB 的行为,由于现代编译器会将其直接认作 Error,故不列出。
不初始化变量
任何变量必须遵循声明即赋值的规则。因为使用未初始化的变量,会引发意外行为;对于数组等类型,若声明后不初始化,其无法得到干净的空数组。在该数组未写满的情况下,会出现严重问题。尤其是字符数组,这将导致 strlen 得到错误结果。
声明局部变量,实质是告诉操作系统为该变量预留栈上空间,但操作系统不会主动清除栈上的数据,这将导致一些随机的脏数据混入。
在栈上分配过大的变量
局部变量默认存储在栈上,操作系统对栈上空间有严格限制,超长数组等若直接声明会导致 Segmentation fault,若不得已声明超长数组,必须使用 static 关键字或 malloc/calloc 将其分配到堆上空间。
字符数组存储字符串遗漏结束标识符
\0 是 strlen 等字符串方法识别字符串结束的重要标识,fgets 等写入的是 \n,而并没有写入 \0,若未处理即使用这些方法将导致越界访问 Segmentation fault。
数组越界
解引用空指针
不能确保指针移动
在某些涉及多个指针移动操作时,在极端情况下,这些指针可能无法正确移动。例如当 strtol 无法再读到数字时,可能不会写入第二个参数的指针,若外层再将其解引用结果作为循环判断条件,可能陷入死循环,可考虑增加判断第一个和第二个参数指针是否相等来强制移动指针。
递归时结束条件优先级错误
递归结束条件必须写在函数体的开头,且具有正确的优先级。
与 Rust 生命周期等语法混淆
这个就有点扯淡了。事实上,C 语言没有所谓严格的生命周期管理,给函数传参不会导致其“所有权”被移动,不要和指针混淆。如果要求函数处理超出自身作用域的局部变量,考虑使用指针。
执行违背数学规律的运算
“精打细算”
这一点稍后会讨论到。在 C 语言 O2 优化以后,很多看起来很费事的操作其实并不费时,一些变量也并没有想象中的浪费空间。时间复杂度和内存用量需要从另外的角度衡量,永远不要按照题目给到的所谓的输入格式、数字大小等去设计选取变量,尤其是 OJ 环境对自己来说是一片黑箱的情况。
- 咋也不是嵌入式开发,没必要跟那几 KB 的内存和几毫秒的时间精打细算,真正浪费时间的是那些 DFS,真正浪费空间的是似人数据疯狂草包你的数组。
未显式转换
按照规范(强迫症),Clang-tidy 会警告所有隐式转换。不同的编译器和环境在处理隐式转换时可能有细微差异,这可能导致致命错误。永远遵循:只对同类型数据进行运算操作,若类型不同,先显式转换再运算。
给不具有数字特性的变量应用关系运算
尝试修改字面量、给已初始化的字符数组赋值
若要将字符串赋值给字符数组,考虑使用 strcpy。字面量不能直接修改。
对多条件 if 和 if-else 的误用
多条件 if 和 if-else 是不同的分支结构,只有多条件 if 只包含两个判断时且能覆盖所有可能条件时才与 if-else 等价,不要擅自简化 if-else 结构。
0-base 和 1-base 不明确
- 作为一个学计算机的,你他妈的不用 0-base,你就不是计算机行业的屌丝了,你应该去动物园。(开玩笑)
当我们试图用 1-base 追踪一些变量时(匹配题目要求),要么构造结构体单独存储它,然后使用高尚的 0-base 作为程序内部索引,要么统一用 1-base 语法。不要大量混用它们,甚至将它们作为有具体含义的字面量。
对 malloc 和 calloc 对象直接应用 sizeof
char *buffer = calloc(n, sizeof(char));
if (fgets(buffer, sizeof(buffer), stdin)) {
......
}
为了应用 malloc 和 calloc,我们有时会采用 char * 类型,但是 sizeof 求得的值是该指针的大小,即 8 字节,它会导致 fgets 无法读完整行内容。切记,sizeof 取得的大小是指定变量的类型的大小,char [] 才能取得这个数组真实的大小,对指针应用这个操作会导致意外行为。
思路心得
不要尝试离散化一些问题
当你发现一些问题可以离散化解决时,就说明它一定还能通过状态追踪更新的方式解决。考虑将条件和目标结果颠倒过来遍历,能大幅度缩小时空复杂度。
- 离散化遇到想草包你的 OJ,必然 TLE。
关联题目:P1003
遵循输入-处理-输出的架构
我们有时会考虑手动解析输入,所以输入的事情放在同一 if 块解决,能有效提升代码可读性。
一个函数只负责完成一个任务,不要把很多事情写在同一个函数里,否则难以发现可以被复用的代码。
递归时先考虑结束条件
结束条件明朗了,递归自然也就好写了。
高精度实现必须将数字倒序存储
高精度的本质是模拟人类竖式计算。由小学知识可知,竖式计算时从个位开始向高位对齐,而在数组中容易对齐的方式是倒序存储。
关联题目:P1009
复杂问题先构建中间层
一些本身在数学思路方面具有困难的题目,不能采用动态翻译的策略,必须先构建中间层——即先让大脑专注于构建人类能够理解的思路,最后再让大脑专注于翻译工作。翻译时要注意状态机思维、递归发现思维、最优路径思维、裁剪思维、约束思维、并行执行思维、泛化使用思维、最小步思维等等。当然,一些比较简单的事情动态翻译就好了,就不必浪费时间了。
就我的实践来说,我最不好掌握的就是状态机思维。如何在合适的地方追踪合适的状态,采用什么样的手段追踪,又要确保变量空间不被污染,是一件极富哲学的问题。
所谓哲学,其实就是大脑疯狂if决策以后产生大量的水分,抽干了整个人的元气,空气也呼吸了,水也喝了,饭也吃了,能量消耗了,身体掏空了,把自己搞憔悴了,走到街上别人指着鼻子说:
你看那个傻逼搞二次元的
是这样的啊,包包上挂几个露露的女角色,里面只有一本破笔电和一堆线
是啊,精神他妈都看着不正常了,神经兮兮的
学计算机是这样的
一些不错的前人智慧
斐波那契数列
注意,常见的题目不会直接告诉你它是斐波那契数列。需要通过发现递归规律去探索。无论如何,把它套上斐波那契的数列,甚至不惜构建二位数组,都可以有效降低题解难度。
由于斐波那契数列还算简单,用循环就能解决,不做过多阐述。
卡特兰数(栈的智慧 - 括号问题)
这是我最被震撼的一集,震撼到我愿意花大量的篇幅把原题贴在这里,并把我的思路讲出来。
P1044 栈
题目描述
一个操作数序列(图示为 1 到 3 的情况)栈 A 的深度大于 n。
现在可以进行两种操作,
- 将一个数,从操作数序列的头端移到栈的头端(对应栈的 push 操作)
- 将一个数,从栈的头端移到输出序列的尾端(对应栈的 pop 操作)
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由
1 2 3生成序列2 3 1的过程。
你的程序将对给定的 n,计算并输出由操作数序列 1,2, … ,n 经过操作可能得到的输出序列的总数。
输入格式
输入文件只含一个整数 n
输出格式
输出文件只有一行,即可能输出序列的总数目。
刚看时,你是否摸不着头脑,我也是。
- 大佬求别嘲笑我,让我好为人师一下下,阿里嘎多~
如果我们将注意力放在数字序列本身,那么我们的关注点就错了,这道题既然是栈,则必然考虑到栈的特点。
这道题的栈具有如下特点:
- pop 和 push 操作必然对等,因为 push 多少数字,必然 pop 多少数字,push 和 pop 动作的数量是确定且对等的;
- 具有配对原则,即必须先被 push 进去,被 push 进去以后,在栈内的位置不可改变,同时它必然配对一个合适的时机 pop 出来;
- 后进后出的原则,后被 push 的可先被 pop 出来。
什么东西具有这个特点呢?当然是括号配对,将 push 操作看为 (,pop 操作看为 ),你将能得到以下特点和它匹配:
- 合法的括号必然成对(pop == push);
- 具有配对原则,即必须先有
(,才能有)(必须先 push 进去对应的元素才能 pop 出来),它不一定必须紧跟它的((它不一定必须在 push 之后立即 pop),但必须在合适的地方闭合(但必须在合适的时机 pop); - 后出现的
(可先被最先出现的)匹配(后进先出原则)。
- 震惊我一万年。
好了,Linus 说代码胜过千言,我们上代码:
void dfs(int left, int right) {
if (left == n && right == n) {
// 得到一个合法序列,结束本层深入(剪枝的灵魂所在)
return;
}
if (left < n) {
// 左括号还能放,放一个 (
dfs(left + 1, right);
}
if (right < left) {
// 右括号还能放,放一个 )
dfs(left, right + 1);
}
}
but 这里有一个关键问题,这是 DFS,以暴力求解著称,我们理解起来很方便,但是 OJ 又大又粗的数据会干爆它,直接 TLE,DFS 的时空复杂度很大,怎么办呢?
接下来就要请出我们的分治大人出场了。详见请见后文,这里只阐述大概:
根据语文规则,我们可以这样分治来写出通式:(A)B
A 是内部的一段合法括号,里面有 i 对符合条件的括号;B是外部的一段合法括号,有 n - 1 - i 对符合条件的括号(因为最外层已经有一对了,所以减一)
改变最外层那个分治用的括号,可以再得到另一种情况,所以我们只需要简单地求和即可,也就是说,你可以两层 for 遍历循环递归了,那么:
我的天哪,总数就是(求和) C_i x C_{n - 1 - i}!(恭喜你得到了卡特兰数)
那么我的题解就是:
//
// Created by renahsacme on 2026/4/11.
//
#include <stdio.h>
#include <stdlib.h>
int main() {
char buffer[1000] = {};
long long C[20] = {0};
int n = 0;
if (fgets(buffer, sizeof(buffer), stdin)) n = (int) strtol(buffer, NULL, 10);
C[0] = 1;
for (int i = 1; i <= n; i++) for (int j = 0; j < i; j++) C[i] += C[j] * C[i - 1 - j];
printf("%lld", C[n]);
return 0;
}
妙哉妙哉!
关联问题:P1044
如何快速构造递归
我以函数的几个相关要素分享我的经验:
首先是返回值:递归函数通常注重的是某一层的流程在何时适当终止,同时由于我存储结果所使用的内存一般都在函数的外面,属于被公共调用的数据,所以我一般不构造函数返回值,使用 void;
其次是函数参数:首先必传的是存储结果的指针,直接操作某一数据,比互相传数据简单许多,其次是必传的操作指针,比如用于跟踪当前对结果数组处理进度的这种具有跟踪性质的指针或其他数据,它们是递归的灵魂,然后是一些其他参数,视情况追加。
最后是函数主体:反过来思考,首先考虑递归中的特殊情况。递归中的特殊情况往往就是跳出本层递归的关键。要注意存在多个递归条件时它们的先后顺序,不要随意替换 if-else 和多 if 分支(当然如果你的每个分支都 return 了那可以随便替换)。然后什么操作是每层递归都要做的,把动作框架搞清楚以后再填变量就会轻松很多,最后注意,普通部分一般不会直接 return,会紧接着再调用它自身,要注意调用的时机。
递归有时会大幅度增大时空复杂度,尤其是构造 DFS 时,除非问题比较简单、或者明显是递归题、或者数据都很小,否则不要随便构造递归,容易超时。考虑 DP、动态剪枝等算法。
快速排序(必背)
原型
void (int *a, int l, int r) {
if (l >= r) return;
int i = l, j = r, pivot = (l + r) / 2;
while (i <= j) {
while (a[i] < pivot) i++; // 升序
while (a[j] > pivot) j--;
if (i <= j) {
int tmp = a[i];
a[i] = a[j], a[j] = tmp;
}
}
quick_sort(a, l, j), quick_sort(a, i, r);
}
多条件约束的快速排序
通过单独构建 cmp 函数返回特定表达式实现对指针的跟踪。
struct demo {
int a;
int b;
};
int cmp (struct demo Demo_a, struct demo Demo_b) {
if (Demo_a.a != Demo_a.b) return Demo_a.a > Demo_a.b; // 约束 1:对 a 降序
else return Demo_a.b < Demo_b.b; // 约束 2:对 b 升序
}
void quick_sort(struct demo *Demo, int l, int r) {
if (l >= r) return;
int i = l, j = r;
struct demo pivot = (l + r) / 2;
while (i <= j) {
while (cmp(i, pivot)) i++;
while (cmp(pivot, j)) j--;
if (i <= j) {
struct demo tmp = a[i];
a[i] = a[j], a[j] = tmp;
}
}
quick_sort(a, l, j), quick_sort(a, i, r);
}
理论:严格弱序
快速排序算的上是竞赛/机试中较易快速实现、较易理解的排序算法,且时空复杂度不高,在一些对时间空间都有要求的题目了,算是性价比较高的那一类了。但不管使用哪种排序算法,排序正确的核心是严格弱序(Strict Weak Ordering),简单来说就是:
- 反自反性:任何一个东西不能比自己小;
- 可传递性:如果有
a < b且b < c,那么a < c必然成立; - 不矛盾性:如果有
a < b,则不能有b < a; - 等价类一致性:如果有
a == b,那么a和b对c的比较行为必须一致。
若不满足上述条件,可能出现陷入死循环,不能在正确的时机离开递归等问题,往往造成的是 Segmentation fault 这类诡异错误。
分治的艺术
我为什么称分治为艺术呢?这是一个很有意思的算法。它拥有和递归相同的思维灵魂,甚至是相同的时空复杂度,但它在某些场合却能降低原先算法的复杂度。
分治,核心在于分,需要找到我们重复执行的那些动作,然后将很大的问题分解成一个个小问题,最后逐个击破,合并所有结果。
- 写到这里,我突然想起了初中时我第二次尝试编程(第一次是在 Python IDE,我记得当时我好像以极其稚嫩的声音录制了视频发在 B 站上?但是被高中中二的我因羞耻无比而扫进了垃圾堆,找不到了。。我该庆幸大学的我能包容那个高中中二病的我,我接纳了他。。。什么水仙发言,孩子有点危险了,虽然我现在只是个臭二次元,不过我对自己很满意。。??)时,是在 Apple Swift Playground 的学习编程系列课程,我记得它有一个思想很洗脑,叫做模块化,拆解问题。虽然如今看起来那个例子真的很抽象(比如实现一个右转,需要我构造一个连续左转三次的模块),但是给我留下了深深的烙印。我总是想模块化很多东西,但我写出来的东西被 AI 评价为:看起来解耦了,实际上耦合度还是很高。。。
- 是的,上学期人工智能导论的模型训练作业真的写的我很抽象。搞模型训练的看来都是有写屎山的共识,前些日子我维护了一个另外一个广州大学童鞋的一个训练中文国粹的项目。你还真别说,他那个似乎是直接把他用的算力服务器的环境配置直接克隆下来,一堆冗余写在 requirement 里面,还有硬编码的绝对路径,开源了但好像别人无法复现??不过幸好他用的基层模型在 Hugging Face 上都能找到,我 refactor 训练脚本也确实使用了大量国粹哈(什么奇怪的应景。。。)交了一个 PR——那是我第一个交给别人的 PR,虽然只 refactor 了 train,test 没时间搞,后来又忙于转专业备考,缺乏适宜的动机(我靠我写了这么多,我不是在备考吗,怎么回事。。。),故作罢。
- 这个作业我唯一自豪的地方时,我的项目结构极其地规范,我能保证我亲爱的老师只要按照我的 README 就绝对能够复现它。仅此而已。我从来不觉得我的那个模型效果会有多好,一个是基座模型选的是 Efficient-Net 的小模型,它本来也不是什么很厉害的模型,二是我没有充分的数据集,我只使用了老师给到的很小的数据集,并做了随机增强变换应用到后训练阶段。我本身对医学更没什么兴趣,我为什么或者说有什么充分的动机去收集数据洗数据做这种又脏又累的活呢?质量差的数据加上我洗数据的手法本就不能与大佬相比,平均准确率 0.78,它没有一股脑全部瞎猜,模型没崩我都谢天谢地了。三人小组,只有我在训练模型,然后它们拿着我的数据喂给 AI,生成可怜的所谓论文和 PPT,我觉得这很可悲,在一个不是计算机学习的氛围里发挥我那在计算机学院看来所谓微不足道的计算机才能,我觉得有一种吃屎的感觉,被白嫖的感觉,找不到知音的感觉,对牛弹琴的感觉、被人用奇怪的眼光看着的感觉,那种感觉,比我中二病时被所有人强烈批判那种孤寂感还要令人无奈,那种无奈,断然不是我所谓自持清高,而是我发自内心的热爱被当作一种可被观赏的、可被利用的、而不是我们彼此进步的、心灵交流的、同频共振的那种深入交流,我只是给一群不懂计算机的人展示所谓我的实力——
- 我有一种感觉,我的双眼正在被蒙蔽,我逐渐意识到一件事情,那就是如果我再不寻求突破,再不寻求另一个新的交际圈,不仅在医学的摧残下这点能力会逐渐被遗忘,我甚至会活成在技术上虚伪而又不自知的傻子和笨蛋。
- 我最痛心的三件事,一是我为什么为了来到更好的学校而忽视专业问题,在择校和专业之间,我选择了一条与所有人走的更累的路,叫先择校再专业,叫转专业。被调剂又能怪谁呢?在有限的高考分数里,我想来好学校和我想来我喜欢的专业这两句话,显得如此苍白无力。我消息源闭塞,当初被一句转了一次原则上不能再转这句话唬得团团转。开学的二次遴选,没有我心仪的专业,我选择按兵不动,留在医学的苦海里,等待第二学期的转专业到来。我偶然间认识了一名,暂且称为水蜡烛,的学长(他认识我了吗我不知道,我只知道我们互相关注了 GitHub,我知道他的名字,知道他长什么样,一些互联网成长轨迹,以及高中在哪里,甚至是知道他的学号,,我真下头,ky致歉)我看了他的传奇经历,他在二次遴选时跳到工科的怀抱,最后进入我梦寐以求的网安。我不知道网安会不会是他转专业的终点,但我知道的是,我这一步棋和这位大佬比起来,我下错了:我学习的这些通识课基本不能被网安所承认,首先我在通识起点上就竞争不过别人,网安有什么更多充分的理由接受我呢?网安有什么充分的理由接受一个留级生作为拖油瓶?天才比比皆是,我只是一个拥有相较于大众的更好的计算机水平,而并没有什么天资构成他们接受我的充分动机,扔掉我这一个耗材,在他们的眼里又何尝微不足道。我写不出论文,拿不出奖励,我没有参加竞赛,我被这突如其来的大学生活弄得团团转,留白的那一栏显得我如此无力。我在申请理由那里写的都是:我很有经验,我很热爱,我需要你,我甚至不惜成为你们的学术耗材,我写了五百字,为了删除多余话语甚至都是带着斟酌。我是带着拼命的,卑微的,甚至渴求的态度面对这次转专业。我知道所有人都在赞美高松灯的转专业宽松政策,但在绝对的竞争面前,稍有不慎我即是他人炮灰。我甚至产生了一种畸变的心理,我痛恨医学,我一点也不喜欢它。
- 二是我最痛心的事情是我是一个实用主义、经验主义驱动的人。我什么作品也拿不出来。从前我最喜欢的事情,就是折腾,就是探索。这算另一种意义上的加入网安的动机吧,毕竟我比较喜欢去折腾一些攻防上的事情。论计算机各领域所谓的那些经验,我愿意说我是非常丰富的。但我遗憾在广而不精。进入大学以后,我更加意识到这一点:我看到我的同龄人甚至比我年轻的家伙创作出那些了不起的程序,实现不一样的项目,我有一种强烈的悲伤感——我写了许多帖子,从QQ空间日志写到微信公众号,再写到GitHub杂物仓库最后写到我的静态网页托管上,我侃侃而谈,解决问题,但从来不是创造问题的那个人。这个年纪,我心里一直有一种充分的冲动,我如果不做点什么,只是念完了这几本书,而不是做一些惊为天人的、外面的人看不大懂的、复杂而又精细的、耗费精力的事情,就这样和我这几年大学生活告别,我怎么对得起我拥有的所有人都在渴望的年华呢?我又怎么对得起我所谓的热爱呢?热爱是一个宏伟的命题,我总是想阐述它的动机、它的表现,甚至我充分意识到我的热爱正离我从所未有的近,我即将接触到它,我为什么不能献出我的一切,向它展示我的筹码呢?作为一个人,我经常想着,我在哲学面前,显得如此可笑无知而又渺小,我却用力阐释它并不断接近它,不断地走出符合我期望的那一步,而不是任我失控——我渴望一个完美的自己,我不想让我变得更糟,我爱我自己,我相信,人若不热爱自己,则无资格苛求其他的热爱,更无需期待他人的热爱。
- 三是我没有达到在我心中身为一名中山大学本科生应该付出的努力,应该拥有的成就。在我们高中的班级里,因为成绩分班制度的存在,我们会下意识地认为 985 这个标签是如此平凡。有人会说,物以类聚人以群分,我认为这是相当有失公允的。当我看到当下就业环境,第一学历双非者,就像背上了赛博案底,这辈子都洗不清了,都将在就业的海洋里苦苦挣扎,我认识到:我要珍惜在中大的这一切。成为更好的人的动机是我热爱我自己,而绝不是建立在践踏他人短板、攫取更多利益的自我优越感,我认为这是一种绝对的现实因果主义,毫无理想的低贱行为。他人命运纵然跌宕,我无从干预,又凭什么嘲笑他人?我喜欢一个优秀的自己,这就是我的动机,仅此而已。现实必然发生的物质利益,我愿把握住它,而绝非追逐它。只要我不死,那我就要睁着眼看着这个世界,然后怀揣着我的理想走下去。这是我交给中大也是交给我自己的的第一份答卷,我很满意。然而,我没有成为能够承担得起中大重量二字的人。百年学府的重量,天才辈出的地方,此身平庸似有亵渎之意。我有时躺在床上会思考,如果现在让我写一份简历,我会写什么上去呢?我发现,除了闪亮的中山大学四个字,我什么都没有。强迫症的我又怎能公然允许这样的事情发生?我不是天才,但我或许可以考虑成为一名带着脑子的耗材。谁都想在什么奇奇怪怪的地方留下魔幻的一笔,我只想给自己的故事上留下绚烂的一笔,但这一笔,最好能让世界添几分色彩。
- 我是一个混杂着现实主义和理想主义的人。我相信的一件事就是,绝对现实主义的人,ta就不应该活在世上,绝对理想主义的人,ta活该活不在这世上。我知道这或许有些极端,但这确实是我的人生信条。首先我希望活下去,其次就是希望能活得很好,活成自己喜欢的样子,最后就是为世界做出一些让世界更好的 Changes,我从未奢侈过 Breaking Changes,我只希望我是那个 Contributor。我希望当我被蹂躏时,我知道有一块理想乡正在等着我回去;我希望当我有能力做出改变时,能睁着眼睛看清世界的五味杂陈。当然,我也更愿意让理想主义大过现实主义,哪怕让我磕得头破血流,我觉得一些事情坚持下去,就是现实主义的取胜方法。
这是哪来孤立的一段?算了扔到这里吧。
- 群友批评我训练图像二分模型居然把电脑整的响到吵醒舍友,群里大佬太多,我一时竟然语塞不知从何反驳(我为什么又要反驳?我为什么要用反驳这个词?我又不是跟亲爱的群友们吵架?)(被站在了道德的制高点,当时我想象我自己是那个可怜的串子正在被审视),等我翻出训练脚本的时候,我才发现我有点跳梁小丑那味儿了。好吧,洗数据的疯狂,调参调到在过拟合和疯狂震荡反复横跳,以及永远收敛不了热力图,我在不到上千份的眼底照片里诊断白内障?我自己都看不懂那些片子祈祷我的 AI 能看懂它,在看着被吃满的 8 GB 显存(MD我当初为什么不直接买台式+MBP,我非得买个游戏本?那你问自己喽,因为我有时会想打游戏),你问我为什么风扇转,我只能说我这只是 5070 Laptop,你这得问老黄是怎么砍显卡的。好吧,这本难念的经还是留给我自己念吧,当初写的有些蹩脚了,念出来还是有些羞耻。
- 我的信条是能提 PR 就绝不提 Issue,我觉得在开源社区里面伸手要饭是一件很不礼貌的事情,自己有能力不如我挑个合适的时机实现它,交个 PR 的话心里会有很棒的小骄傲(对的,每次躺床上,我都会打开 GitHub Mobile 品味我 Commit 的那些微不足道的垃圾)。如果我菜的不行了,那我认栽,我提 Issue 也要带着思路去提。对吧,我认为 Issue 是一个羞耻榜:最高尚的人,是给到详细的日志+复现+修复思路(我经常用这套跟 SYSU INC 对线,帮助台的人有时候都不想理我了,笑),一流公民,是给到日志+复现,二流公民是能把问题说清楚,下流公民是自以为是的宣泄,非人类是把 Issue 当成表态区。我相信应该严肃的地方应该有绝对的严肃,绝对的规范。
糟糕的数学漏洞
负基质数的原理
关联题目:P1017
我没写完,我有罪,罪已诏,朕赦免自己。


