C++进阶编程技术:PPP2E核心概念深度解析
软件设计的理念不是构造一个可以做任何事情的程序,而是构造很多类,这些类可以准确反映我们的思想,可以组合在一起工作,允许我们来构造漂亮的应用程序,并且具有最小的工作量(相对于任务的复杂度而言)、足够高的性能以及保证产生正确的结果等优点。
—— Bjarne Stroustrup
参考文献:《Programming: Principles and Practice Using C++》(PPP2E)
目录
- 1. 迭代器:STL的统一访问接口
- 2. STL容器体系与选择策略
- 3. Lambda表达式:函数式编程的C++实现
- 4. 关联容器:键值映射的数据结构
- 5. 虚函数与多态性:面向对象设计的核心
- 6. 纯虚函数与接口设计
- 7. C++设计理念与历史演进
1. 迭代器:STL的统一访问接口
1.1 理论基础与设计哲学
迭代器作为STL的核心抽象概念,体现了C++设计中接口与实现分离的重要思想。迭代器本质上是一种设计模式的实现,它在存储细节和使用方法之间构建了一座桥梁。
1.1.1 迭代器的本质定义
迭代器(Iterator)是一种行为型设计模式,提供了遍历聚合对象元素的统一方法,而无需暴露其内部表示。在C++中,迭代器是泛化的指针,它模拟指针的行为但不局限于指针的实现。
核心特征:
- 统一接口:为不同容器提供一致的访问方式
- 抽象封装:隐藏容器内部实现细节
- 类型安全:编译期类型检查
- 性能优化:针对不同容器的优化实现
1.1.2 容器与迭代器的协作关系
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client │ ─→ │ Iterator │ ─→ │ Container │
│ (用户代码) │ │ (统一接口) │ │ (存储实现) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
协作机制:
- 提供访问机制:容器负责数据存储,迭代器提供统一的访问机制
- 封装底层实现:用户无需了解容器内部结构即可遍历元素
- 统一访问接口:所有容器都提供
begin()
和end()
方法
1.2 迭代器类型体系
1.2.1 迭代器分类
迭代器类型 | 支持操作 | 典型容器 | 应用场景 |
---|---|---|---|
Input Iterator | ++ , * , == , != | istream_iterator | 单次读取 |
Output Iterator | ++ , * | ostream_iterator | 单次写入 |
Forward Iterator | Input + 多次读取 | forward_list | 单向遍历 |
Bidirectional Iterator | Forward + -- | list , set , map | 双向遍历 |
Random Access Iterator | Bidirectional + [] , + , - | vector , deque | 随机访问 |
1.2.2 实际应用示例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// 创建整数向量
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用迭代器进行遍历和修改
std::cout << "原始向量: ";
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用迭代器修改元素
std::cout << "修改后向量: ";
for (auto it = vec.begin(); it != vec.end(); ++it) {
*it *= 2; // 每个元素乘以2
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用算法和迭代器
auto result = std::find(vec.begin(), vec.end(), 6);
if (result != vec.end()) {
std::cout << "找到元素: " << *result << std::endl;
}
return 0;
}
1.3 现代C++中的迭代器进化
1.3.1 Range-based for循环
// C++11及以后的简化写法
std::vector<int> vec = {1, 2, 3, 4, 5};
// 传统迭代器写法
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
// 现代简化写法
for (const auto& element : vec) {
std::cout << element << " ";
}
1.3.2 泛型编程模板
template<typename Iterator>
void print_range(Iterator first, Iterator last) {
for (auto it = first; it != last; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
// 使用示例
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<double> lst = {1.1, 2.2, 3.3};
print_range(vec.begin(), vec.end());
print_range(lst.begin(), lst.end());
2. STL容器体系与选择策略
2.1 容器分类与特性分析
2.1.1 序列容器性能对比
容器类型 | 随机访问 | 插入删除 | 内存布局 | 适用场景 |
---|---|---|---|---|
vector | O(1) | O(n) | 连续 | 频繁随机访问,尾部插入 |
deque | O(1) | O(1)首尾 | 分段连续 | 双端操作 |
list | O(n) | O(1) | 非连续 | 频繁中间插入删除 |
forward_list | O(n) | O(1) | 非连续 | 内存受限的单向链表 |
array | O(1) | 不支持 | 连续 | 固定大小,编译期确定 |
2.1.2 容器选择决策树
// 容器选择指导原则
class ContainerSelector {
public:
enum class AccessPattern {
RandomAccess, // 需要随机访问
Sequential, // 顺序访问
BidirectionalIterator // 双向迭代
};
enum class ModificationPattern {
BackInsertion, // 主要在尾部插入
FrontInsertion, // 主要在头部插入
MiddleInsertion, // 频繁中间插入
Stable // 很少修改
};
template<typename T>
static std::string recommend_container(AccessPattern access,
ModificationPattern modification) {
if (access == AccessPattern::RandomAccess) {
if (modification == ModificationPattern::BackInsertion) {
return "std::vector<T>";
} else if (modification == ModificationPattern::FrontInsertion) {
return "std::deque<T>";
}
} else if (modification == ModificationPattern::MiddleInsertion) {
return "std::list<T>";
}
return "std::vector<T>"; // 默认推荐
}
};
2.2 vector深度分析
2.2.1 内存管理机制
#include <iostream>
#include <vector>
void analyze_vector_growth() {
std::vector<int> vec;
std::cout << "Vector增长分析:" << std::endl;
for (int i = 0; i < 20; ++i) {
vec.push_back(i);
std::cout << "Size: " << vec.size()
<< ", Capacity: " << vec.capacity()
<< ", Growth Factor: "
<< (i > 0 ? static_cast<double>(vec.capacity()) / (vec.size()-1) : 1.0)
<< std::endl;
}
}
2.2.2 性能优化技巧
class VectorOptimization {
public:
// 预分配内存避免重新分配
static void reserve_optimization() {
std::vector<int> vec;
vec.reserve(1000); // 预分配空间
for (int i = 0; i < 1000; ++i) {
vec.push_back(i); // 不会触发重新分配
}
}
// 使用emplace_back减少拷贝
static void emplace_optimization() {
std::vector<std::pair<int, std::string>> vec;
// 低效:创建临时对象再拷贝
vec.push_back(std::make_pair(1, "hello"));
// 高效:直接在容器中构造
vec.emplace_back(2, "world");
}
// 缩减多余内存
static void shrink_to_fit_usage() {
std::vector<int> vec(1000);
vec.resize(10); // 只使用10个元素
vec.shrink_to_fit(); // 释放多余内存
}
};
3. Lambda表达式:函数式编程的C++实现
3.1 Lambda表达式语法深度解析
3.1.1 完整语法结构
[capture list](parameter list) mutable exception -> return type {
// function body
}
语法要素详解:
- capture list:捕获外部变量的方式
- parameter list:参数列表(可选)
- mutable:允许修改按值捕获的变量(可选)
- exception:异常规范(可选)
- return type:返回类型(可选,支持自动推导)
- function body:函数体
3.1.2 捕获方式详细分析
#include <iostream>
#include <functional>
void lambda_capture_analysis() {
int x = 10, y = 20;
// 1. 按值捕获
auto by_value = [x, y](int z) {
return x + y + z; // x, y的副本
};
// 2. 按引用捕获
auto by_reference = [&x, &y](int z) {
x += z; // 修改原始变量
return x + y;
};
// 3. 混合捕获
auto mixed = [x, &y](int z) {
y += z; // 修改y的原始值
return x + y; // x是副本
};
// 4. 全部按值捕获
auto capture_all_by_value = [=](int z) {
return x + y + z;
};
// 5. 全部按引用捕获
auto capture_all_by_reference = [&](int z) {
x += z;
return x + y;
};
// 6. 初始化捕获(C++14)
auto init_capture = [counter = 0](int increment) mutable {
counter += increment;
return counter;
};
}
3.2 Lambda与STL算法的完美结合
3.2.1 实际应用场景
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
class LambdaSTLApplications {
public:
static void algorithm_examples() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 1. 查找满足条件的元素
auto even_it = std::find_if(numbers.begin(), numbers.end(),
[](int n) { return n % 2 == 0; });
// 2. 统计满足条件的元素
auto even_count = std::count_if(numbers.begin(), numbers.end(),
[](int n) { return n % 2 == 0; });
// 3. 变换元素
std::vector<int> squares;
std::transform(numbers.begin(), numbers.end(),
std::back_inserter(squares),
[](int n) { return n * n; });
// 4. 累积计算
int sum = std::accumulate(numbers.begin(), numbers.end(), 0,
[](int acc, int n) { return acc + n; });
// 5. 自定义排序
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) { return a > b; }); // 降序
}
// 高阶函数示例
template<typename Predicate>
static auto make_counter(Predicate pred) {
return [pred, count = 0](auto value) mutable {
if (pred(value)) ++count;
return count;
};
}
};
3.3 Lambda性能分析与优化
3.3.1 编译期优化
#include <chrono>
#include <vector>
#include <algorithm>
class LambdaPerformance {
public:
// Lambda vs 函数指针性能对比
static void performance_comparison() {
std::vector<int> data(1000000);
std::iota(data.begin(), data.end(), 1);
// Lambda表达式(内联优化)
auto start = std::chrono::high_resolution_clock::now();
std::transform(data.begin(), data.end(), data.begin(),
[](int x) { return x * 2; });
auto lambda_time = std::chrono::high_resolution_clock::now() - start;
// 函数指针(可能无法内联)
start = std::chrono::high_resolution_clock::now();
std::transform(data.begin(), data.end(), data.begin(), double_value);
auto function_time = std::chrono::high_resolution_clock::now() - start;
std::cout << "Lambda time: "
<< std::chrono::duration_cast<std::chrono::microseconds>(lambda_time).count()
<< " microseconds" << std::endl;
std::cout << "Function time: "
<< std::chrono::duration_cast<std::chrono::microseconds>(function_time).count()
<< " microseconds" << std::endl;
}
private:
static int double_value(int x) { return x * 2; }
};
4. 关联容器:键值映射的数据结构
4.1 关联容器分类与实现原理
4.1.1 基于树的有序容器
#include <map>
#include <set>
#include <iostream>
class OrderedAssociativeContainers {
public:
static void tree_based_analysis() {
// std::map - 键值对映射,基于红黑树
std::map<std::string, int> ages;
ages["Alice"] = 25;
ages["Bob"] = 30;
ages["Charlie"] = 28;
// 自动排序(按键)
for (const auto& [name, age] : ages) {
std::cout << name << ": " << age << std::endl;
}
// std::set - 唯一元素集合
std::set<int> unique_numbers = {3, 1, 4, 1, 5, 9, 2, 6};
// 自动去重并排序: {1, 2, 3, 4, 5, 6, 9}
// 时间复杂度分析
// 插入、删除、查找:O(log n)
// 遍历:O(n)
// 空间复杂度:O(n)
}
};
4.1.2 基于哈希的无序容器
#include <unordered_map>
#include <unordered_set>
class UnorderedAssociativeContainers {
public:
static void hash_based_analysis() {
// std::unordered_map - 基于哈希表
std::unordered_map<std::string, int> hash_ages;
hash_ages["Alice"] = 25;
hash_ages["Bob"] = 30;
// 平均时间复杂度
// 插入、删除、查找:O(1)
// 最坏情况:O(n)(哈希冲突)
// 自定义哈希函数
struct CustomHash {
std::size_t operator()(const std::string& key) const {
return std::hash<std::string>{}(key) ^ 0x12345678;
}
};
std::unordered_map<std::string, int, CustomHash> custom_map;
// 性能调优
hash_ages.reserve(1000); // 预分配bucket
hash_ages.max_load_factor(0.75); // 设置负载因子
}
};
4.2 容器选择策略
4.2.1 性能对比表
操作 | map/set | unordered_map/set |
---|---|---|
插入 | O(log n) | O(1) 平均 |
查找 | O(log n) | O(1) 平均 |
删除 | O(log n) | O(1) 平均 |
遍历 | 有序 O(n) | 无序 O(n) |
内存开销 | 较低 | 较高(哈希表) |
5. 虚函数与多态性:面向对象设计的核心
5.1 多态性的理论基础
5.1.1 运行时多态机制
多态性通过**虚函数表(Virtual Function Table, vtable)**实现:
#include <iostream>
#include <memory>
// 基类设计
class Shape {
public:
// 虚析构函数确保正确的多态销毁
virtual ~Shape() = default;
// 纯虚函数定义接口
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual void draw() const = 0;
// 非虚函数提供通用行为
void print_info() const {
std::cout << "Area: " << area() << ", Perimeter: " << perimeter() << std::endl;
}
};
// 具体实现类
class Circle : public Shape {
private:
double radius_;
public:
explicit Circle(double radius) : radius_(radius) {}
double area() const override {
return 3.14159 * radius_ * radius_;
}
double perimeter() const override {
return 2 * 3.14159 * radius_;
}
void draw() const override {
std::cout << "Drawing a circle with radius " << radius_ << std::endl;
}
};
class Rectangle : public Shape {
private:
double width_, height_;
public:
Rectangle(double width, double height) : width_(width), height_(height) {}
double area() const override {
return width_ * height_;
}
double perimeter() const override {
return 2 * (width_ + height_);
}
void draw() const override {
std::cout << "Drawing a rectangle " << width_ << "x" << height_ << std::endl;
}
};
5.2 虚函数机制深度分析
5.2.1 内存布局与性能影响
#include <iostream>
class VTableAnalysis {
public:
static void analyze_vtable_overhead() {
// 无虚函数的类
class SimpleClass {
int data;
public:
void simple_method() {}
};
// 有虚函数的类
class VirtualClass {
int data;
public:
virtual void virtual_method() {}
};
std::cout << "SimpleClass size: " << sizeof(SimpleClass) << " bytes" << std::endl;
std::cout << "VirtualClass size: " << sizeof(VirtualClass) << " bytes" << std::endl;
// 通常VirtualClass会多出一个指针的大小(8字节在64位系统)
}
// 虚函数调用性能测试
static void performance_test() {
const int iterations = 10000000;
Circle circle(5.0);
Shape* shape_ptr = &circle;
// 直接调用(编译期确定)
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
volatile double area = circle.area(); // 防止优化
}
auto direct_time = std::chrono::high_resolution_clock::now() - start;
// 虚函数调用(运行期确定)
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
volatile double area = shape_ptr->area(); // 虚函数调用
}
auto virtual_time = std::chrono::high_resolution_clock::now() - start;
std::cout << "Direct call time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(direct_time).count()
<< "ms" << std::endl;
std::cout << "Virtual call time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(virtual_time).count()
<< "ms" << std::endl;
}
};
5.3 现代C++中的多态设计
5.3.1 智能指针与多态
#include <memory>
#include <vector>
class ModernPolymorphism {
public:
static void smart_pointer_polymorphism() {
// 使用智能指针管理多态对象
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));
// 多态遍历
for (const auto& shape : shapes) {
shape->draw();
shape->print_info();
}
// 自动管理内存,无需手动delete
}
// 工厂模式与多态
static std::unique_ptr<Shape> create_shape(const std::string& type,
const std::vector<double>& params) {
if (type == "circle" && params.size() == 1) {
return std::make_unique<Circle>(params[0]);
} else if (type == "rectangle" && params.size() == 2) {
return std::make_unique<Rectangle>(params[0], params[1]);
}
return nullptr;
}
};
6. 纯虚函数与接口设计
6.1 抽象基类设计原则
6.1.1 接口分离原则
// 遵循接口分离原则的设计
class Drawable {
public:
virtual ~Drawable() = default;
virtual void draw() const = 0;
};
class Movable {
public:
virtual ~Movable() = default;
virtual void move(double dx, double dy) = 0;
virtual void set_position(double x, double y) = 0;
};
class Resizable {
public:
virtual ~Resizable() = default;
virtual void resize(double factor) = 0;
virtual void set_size(double width, double height) = 0;
};
// 具体类实现多个接口
class GameObject : public Drawable, public Movable, public Resizable {
private:
double x_, y_, width_, height_;
public:
GameObject(double x, double y, double w, double h)
: x_(x), y_(y), width_(w), height_(h) {}
// Drawable接口实现
void draw() const override {
std::cout << "Drawing object at (" << x_ << ", " << y_
<< ") size " << width_ << "x" << height_ << std::endl;
}
// Movable接口实现
void move(double dx, double dy) override {
x_ += dx;
y_ += dy;
}
void set_position(double x, double y) override {
x_ = x;
y_ = y;
}
// Resizable接口实现
void resize(double factor) override {
width_ *= factor;
height_ *= factor;
}
void set_size(double width, double height) override {
width_ = width;
height_ = height;
}
};
6.2 模板与虚函数的权衡
6.2.1 编译期多态 vs 运行期多态
#include <iostream>
#include <vector>
#include <chrono>
// 编译期多态(模板)
template<typename T>
void compile_time_polymorphism(const T& object) {
object.draw(); // 编译期确定调用
}
// 运行期多态(虚函数)
void runtime_polymorphism(const Drawable& object) {
object.draw(); // 运行期通过vtable确定调用
}
class PolymorphismComparison {
public:
static void performance_analysis() {
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
const int iterations = 1000000;
// 编译期多态性能测试
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
compile_time_polymorphism(circle);
compile_time_polymorphism(rectangle);
}
auto template_time = std::chrono::high_resolution_clock::now() - start;
// 运行期多态性能测试
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
runtime_polymorphism(circle);
runtime_polymorphism(rectangle);
}
auto virtual_time = std::chrono::high_resolution_clock::now() - start;
std::cout << "Template polymorphism: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(template_time).count()
<< "ms" << std::endl;
std::cout << "Virtual polymorphism: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(virtual_time).count()
<< "ms" << std::endl;
}
};
7. C++设计理念与历史演进
7.1 设计哲学的深度思考
7.1.1 Bjarne Stroustrup的设计原则
“C++的设计目标是提供一种既能进行系统编程又能进行应用程序编程的语言,它应该是高效的、灵活的,并且能够与C兼容。”
核心设计原则:
-
零开销抽象(Zero-overhead Abstraction)
- 你不使用的功能不会产生开销
- 你使用的功能效率最高
-
多范式编程支持
- 过程式编程(来自C)
- 面向对象编程(类、继承、多态)
- 泛型编程(模板)
- 函数式编程(Lambda表达式)
-
静态类型安全
- 编译期类型检查
- 模板元编程
- 概念(Concepts, C++20)
7.1.2 历史演进与技术积淀
// C++演进的关键里程碑
class CppEvolution {
public:
static void evolution_timeline() {
std::cout << "C++发展历程:" << std::endl;
std::cout << "1979-1983: C with Classes" << std::endl;
std::cout << "1985: C++1.0 - 首次商业发布" << std::endl;
std::cout << "1998: C++98 - 第一个ISO标准" << std::endl;
std::cout << "2003: C++03 - 缺陷修复" << std::endl;
std::cout << "2011: C++11 - 现代C++的开始" << std::endl;
std::cout << "2014: C++14 - 增量改进" << std::endl;
std::cout << "2017: C++17 - 重要特性添加" << std::endl;
std::cout << "2020: C++20 - 概念、模块、协程" << std::endl;
std::cout << "2023: C++23 - 持续演进" << std::endl;
}
// 展示不同时代的编程风格
static void programming_style_evolution() {
// C风格(1970s-1980s)
void c_style() {
int* arr = (int*)malloc(10 * sizeof(int));
// ... 使用数组
free(arr);
}
// 早期C++风格(1990s)
void early_cpp_style() {
int* arr = new int[10];
// ... 使用数组
delete[] arr;
}
// 现代C++风格(2010s+)
void modern_cpp_style() {
std::vector<int> arr(10);
// 自动内存管理,RAII原则
}
}
};
7.2 关键人物与学术传承
7.2.1 技术谱系
重要人物:
- Bjarne Stroustrup: C++之父,贝尔实验室
- David Wheeler: 计算机科学先驱,Stroustrup的导师
- Alexander Stepanov: STL设计者,泛型编程理论奠基人
学术传承的重要性: 正如Stroustrup所说,真正的技术创新需要深厚的理论基础和长期的实践积累。C++的成功不是偶然的,而是建立在深厚的计算机科学理论基础之上的。
7.2.2 设计理念的时代意义
// 体现C++设计理念的代码示例
template<typename Container, typename Predicate>
auto filter_and_transform(const Container& input, Predicate pred)
-> std::vector<typename Container::value_type> {
std::vector<typename Container::value_type> result;
std::copy_if(input.begin(), input.end(),
std::back_inserter(result), pred);
return result;
}
// 使用示例:展示多范式编程
void demonstrate_paradigms() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 函数式编程 + 泛型编程
auto even_numbers = filter_and_transform(numbers,
[](int n) { return n % 2 == 0; });
// 面向对象编程
std::unique_ptr<Shape> shape = std::make_unique<Circle>(5.0);
shape->draw();
// 过程式编程
for (const auto& num : even_numbers) {
std::cout << num << " ";
}
}
7.3 现代C++的发展方向
7.3.1 零开销抽象的实现
// 展示零开销抽象的威力
#include <algorithm>
#include <vector>
#include <numeric>
class ZeroOverheadDemo {
public:
// 高级抽象但零开销的代码
static double compute_statistics(const std::vector<double>& data) {
// 使用STL算法,编译器会优化为高效的循环
auto sum = std::accumulate(data.begin(), data.end(), 0.0);
auto mean = sum / data.size();
auto variance = std::accumulate(data.begin(), data.end(), 0.0,
[mean](double acc, double x) {
return acc + (x - mean) * (x - mean);
}) / data.size();
return std::sqrt(variance);
}
// 等价的C风格代码(但可读性较差)
static double compute_statistics_c_style(const double* data, size_t size) {
double sum = 0.0;
for (size_t i = 0; i < size; ++i) {
sum += data[i];
}
double mean = sum / size;
double variance_sum = 0.0;
for (size_t i = 0; i < size; ++i) {
double diff = data[i] - mean;
variance_sum += diff * diff;
}
return sqrt(variance_sum / size);
}
};
总结与展望
C++作为一门经过几十年发展的系统级编程语言,其设计理念和技术特性体现了计算机科学的深厚积淀。从PPP2E的学习中,我们可以看到:
核心价值
- 抽象与效率的平衡:C++提供了高级抽象,但不牺牲性能
- 多范式编程支持:适应不同的编程需求和思维模式
- 渐进式学习曲线:可以从简单开始,逐步掌握高级特性
技术演进
正如Stroustrup所言,真正有意义的技术是经过实践检验、时间沉淀的。C++的每一个特性都有其深刻的设计考量,理解这些设计思想比单纯掌握语法更为重要。
“过早的优化是万恶之源,但当我们确实需要优化时,理解系统的本质原理是关键。” —— Donald Knuth
在学习和使用C++的过程中,我们应该:
- 理解设计原理,而不仅仅是记忆语法
- 重视实践验证,而不是追求表面技巧
- 培养系统思维,理解软件设计的本质规律
最初写于:2024年3月28日
深度重构:2024年12月22日