2026-08-10 周一 · 阶段 0 & 1 完成
技术
C 语言
学习内容:变量的作用域和生存期
理论要点
| 变量类型 | 关键字 | 作用域 | 生存期 | 默认初值 |
|---|---|---|---|---|
| 非静态局部变量 | (默认) | 代码块内 | 代码块执行期间 | 随机值 |
| 静态局部变量 | static |
代码块内 | 程序整个执行期间 | 0 |
| 非静态全局变量 | (默认) | 整个项目 | 程序整个执行期间 | 0 |
| 静态全局变量 | static |
当前文件 | 程序整个执行期间 | 0 |
两个原则:作用域尽量小、生存期尽量短。能用局部不用全局,能用非静态不用静态。
多文件编程基础:.h 放声明 + extern 全局变量,.c 放实现,编译时所有 .c 一起编译。
今天写的代码
1. 统计二进制位中 1 的个数(两种实现):
// 方法一:逐位检查
int counter_binary(char *p, int n) {
int count = 0;
for (int i = 0; i < n; i++) {
unsigned char byte = p[i];
for (int k = 7; k >= 0; k--) {
if (byte & (1 << k)) count += 1;
}
}
return count;
}
// 方法二:查表法(每半个字节查一次,O(n) 但常数小)
int counter_binary2(const unsigned char *p, int n) {
int count = 0;
const int popcount[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
for (int i = 0; i < n; i++) {
unsigned char byte = p[i];
count += popcount[byte & 0x0F]; // 低 4 位
count += popcount[byte >> 4]; // 高 4 位
}
return count;
}
查表法用空间换时间,一个字节拆成两个 nibble(半字节),每个查一次 16 元素的表。
2. 冒泡排序、strcpy、strcmp 手写实现
3. sizeof 与指针运算:
char str[] = "memory";
char *p = str + 1;
printf("%lu\n", sizeof(str)); // 7(数组,包含 '\0')
printf("%lu\n", sizeof(p)); // 4(指针,32 位平台)
printf("%lu\n", sizeof(a)); // 8(struct:char 1 + 对齐 3 + int 4)
unsigned char *p1 = (unsigned char *)0x80100000;
printf("%x\n", p1 + 5); // 0x80100005
Rust
学习内容:Rust Day11 笔记——格式化宏 + 阶段 0 热身 + 阶段 1 所有权与借用
格式化宏
Rust 用 {} 替代 C 的 %,核心区分两个 trait:Display({},面向用户)和 Debug({:?},面向开发者)。完整格式说明符:{ [参数] : [填充] [对齐] [符号] [#] [0] [宽度] [.精度] [类型] }。
阶段 0 热身(10 题)✅
温/摄氏度转换、BMI 计算、闰年判断、乘法表、最大公约数/最小公倍数、素数判断、阶乘 & 斐波那契、菱形打印、猜数字——10 道题覆盖基础语法。
阶段 1 所有权与借用 ✅
今天全部走完,逐个概念拆开练习,按理解顺序排列:
| # | 概念 | 一句话 |
|---|---|---|
| 1 | 所有权转移 | 传参后原变量不能再用 |
| 2 | 不可变借用 &T |
借出去还能要回来,可同时多个 |
| 3 | 可变借用 &mut T |
同一时刻只能有一个,且不能和 &T 共存 |
| 4 | &str 切片 |
比 &String 更通用,String 和字面量都能传 |
| 5 | 生命周期 'a |
标注引用有效范围,编译期防止悬垂引用 |
| 6 | take_ownership |
String → Vec<char>,原值被消费 |
| 7 | 返回切片引用 | first_word:返回 &str,不复制数据 |
| 8 | swap |
通过 &mut 交换值,需解引用 *a |
| 9 | &[T] 切片 |
Vec 和数组自动转切片,函数参数首选 |
| 10 | while 删除模式 |
删除不加 i,不删才加 i——替代有 bug 的 for |
| 11 | 迭代器链 | filter + copied + collect,返回新 Vec |
| 12 | Option<(&T, &T)> |
返回多个引用的首尾元素 |
| 13 | 字符串切片 | find(char::is_whitespace) 找位置切分 |
| 14 | 字符串反转 | chars().rev().collect(),一行 |
核心规则: 一个值一个所有者 → 传参会转移 → 不想转移就用 & 借用 → 需要改就用 &mut → 返回引用加生命周期 → 视图用切片。
几个印象深的点:
for i in 0..v.len()+v.remove(i)有 bug:v.len()不变但数组变短,索引越界 + 跳元素while i < v.len()+ 删了不i++:删完后下一个元素自动移到当前位置&str为什么总比&String好:&String只能传String的引用,&str可以传&String、字符串字面量、切片——范围大得多
思考
今天突然意识到一件事:我似乎一直在要求自己的思考必须被输出。
每天写日志,慢慢形成了一种习惯——如果几天没有输出什么思考,就会觉得自己应该产生一点什么,于是强迫自己去思考、去记录。
这有点像薛定谔的猫:在我把思考写下来之前,我似乎无法确认自己今天到底有没有产生值得记录的思考。于是我会主动"打开盒子",强迫它变成一个确定的结果。
但今天特别的地方在于,我第一次比较清楚地知道自己到底在说什么。以前可能只是"感觉不对",却很难准确描述。今天我能把它说出来:
我似乎不是强迫自己思考,而是在强迫思考必须留下可观察、可输出的结果。
也许思考本身不需要每天都有结果。有时候没有什么值得记录的东西,也可以只是没有什么值得记录的东西。
至少今天,我知道了自己在想什么。
明日计划
- C 语言:跟课堂节奏,继续变量/函数章节
- Rust:进入阶段 2(结构体、枚举、错误处理、集合)