一、学习顺序建议
建议先学 **C 语言**,再学 **C++**。C 是 C++ 的基础,掌握 C 的底层思维后,学 C++ 会更顺畅。
二、C 语言学习路线
入门阶段(2-4 周)
1、基础语法:变量、数据类型、运算符、控制流(`if/else`、`for`、`while`、`switch`)
- 学习内容:数据类型、变量、运算符、
if/else、for、while、switch、函数基础 - 这部分相对简单,重点注意 C 语言中的隐式类型转换陷阱:
cpp
#include <stdio.h>
int main() {
// 陷阱1:整数除法
int a = 7, b = 2;
float result = a / b; // result = 3.0,不是 3.5!
float correct = (float)a / b; // result = 3.5,需要显式转换
// 陷阱2:unsigned 与 signed 比较
unsigned int x = 1;
int y = -1;
if (y < x) {
printf("预期走这里\n");
} else {
printf("实际走这里!因为 y 被隐式转为极大的 unsigned 值\n");
}
// y(-1) 转为 unsigned 后变成 4294967295,比 1 大
// 陷阱3:char 的范围
char c = 128; // char 通常是 signed,范围 -128~127
printf("%d\n", c); // 输出 -128(溢出)
return 0;
}
2、函数:函数定义、参数传递(值传递)、递归
3、数组与字符串:一维/二维数组、`char` 数组、常用字符串函数
4、指针(核心重点):指针与地址、指针运算、指针与数组、指针与函数、多级指针
- 指针是 C 语言的灵魂,也是绝大多数初学者的最大障碍。
- 难点 1:指针的本质 --- 存储地址的变量
cpp
#include <stdio.h>
int main() {
int num = 42;
int *p = # // p 存储了 num 的地址
// 理解这张内存图:
// 变量名 地址(假设) 值
// num 0x1000 42
// p 0x1008 0x1000(即 num 的地址)
printf("num 的值: %d\n", num); // 42
printf("num 的地址: %p\n", &num); // 0x1000
printf("p 的值: %p\n", p); // 0x1000(和上面相同)
printf("p 指向的值: %d\n", *p); // 42(解引用,取出地址处的值)
printf("p 自己的地址: %p\n", &p); // 0x1008
*p = 100; // 通过指针修改 num 的值
printf("num 现在的值: %d\n", num); // 100
return 0;
}
- 难点 2:指针与数组的关系
cpp
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 数组名就是首元素地址,等价于 &arr[0]
// 以下三种访问方式完全等价:
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]); // 下标访问
printf("*(p+%d) = %d\n", i, *(p + i)); // 指针算术
printf("*(arr+%d) = %d\n", i, *(arr + i)); // 数组名也能做指针算术
}
// 但数组名 ≠ 指针变量!
// arr = p; // 编译错误!数组名是常量,不可修改
// sizeof(arr) = 20(5个int),sizeof(p) = 8(一个指针的大小)
return 0;
}
- 难点 3:指针与函数(值传递 vs 地址传递)
cpp
#include <stdio.h>
// 错误示范:值传递,无法修改外部变量
void swap_wrong(int a, int b) {
int temp = a;
a = b;
b = temp;
// a 和 b 只是副本,函数结束后就销毁了
}
// 正确做法:传指针
void swap_right(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
// 通过指针直接修改原始变量
}
int main() {
int x = 10, y = 20;
swap_wrong(x, y);
printf("swap_wrong 后: x=%d, y=%d\n", x, y); // x=10, y=20(没变!)
swap_right(&x, &y);
printf("swap_right 后: x=%d, y=%d\n", x, y); // x=20, y=10(成功交换)
return 0;
}
- 难点 4:多级指针
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 经典场景:在函数内部为外部指针分配内存
// 需要传"指针的指针",因为要修改的是指针本身
void allocate_string(char **pp, const char *text) {
*pp = (char *)malloc(strlen(text) + 1);
strcpy(*pp, text);
}
int main() {
char *str = NULL;
// 如果传 str(值传递),函数内修改的是副本,str 仍为 NULL
// 必须传 &str(指针的地址),才能修改 str 本身
allocate_string(&str, "Hello, C!");
printf("%s\n", str); // Hello, C!
free(str);
return 0;
}
- 难点 5:函数指针
cpp
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
// 函数指针作为参数 → 实现"回调"机制
int compute(int x, int y, int (*operation)(int, int)) {
return operation(x, y);
}
int main() {
// 声明函数指针
int (*func_ptr)(int, int);
func_ptr = add;
printf("add: %d\n", func_ptr(3, 4)); // 7
func_ptr = mul;
printf("mul: %d\n", func_ptr(3, 4)); // 12
// 用回调实现灵活计算
printf("compute add: %d\n", compute(10, 5, add)); // 15
printf("compute sub: %d\n", compute(10, 5, sub)); // 5
// 函数指针数组 → 简化分支逻辑
int (*ops[])(int, int) = {add, sub, mul};
const char *names[] = {"加", "减", "乘"};
for (int i = 0; i < 3; i++) {
printf("%s: %d\n", names[i], ops[i](6, 3));
}
return 0;
}
5、结构体与联合体:`struct`、`union`、`typedef`
- 难点 6:结构体 + 指针 + 动态内存(综合)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 用结构体 + 指针实现一个简单的链表
typedef struct Node {
int data;
struct Node *next; // 指向下一个节点的指针
} Node;
// 头插法创建节点
Node *insert_head(Node *head, int value) {
Node *new_node = (Node *)malloc(sizeof(Node));
if (!new_node) {
fprintf(stderr, "内存分配失败\n");
exit(1);
}
new_node->data = value;
new_node->next = head;
return new_node;
}
void print_list(Node *head) {
Node *curr = head;
while (curr) {
printf("%d -> ", curr->data);
curr = curr->next;
}
printf("NULL\n");
}
void free_list(Node *head) {
while (head) {
Node *temp = head;
head = head->next;
free(temp); // 必须先保存 next,再释放当前节点
}
}
int main() {
Node *list = NULL;
list = insert_head(list, 30);
list = insert_head(list, 20);
list = insert_head(list, 10);
print_list(list); // 10 -> 20 -> 30 -> NULL
free_list(list); // 释放所有内存,避免内存泄漏
return 0;
}
6、内存管理:`malloc`/`calloc`/`realloc`/`free`,堆与栈的区别
- 难点 7:常见内存错误
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 错误1: 使用未初始化的指针(野指针)
// int *p;
// *p = 10; // 未定义行为!p 指向随机地址
// 错误2: 释放后继续使用(悬空指针)
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);
// *p = 100; // 未定义行为!内存已释放
p = NULL; // 好习惯:释放后置 NULL
// 错误3: 内存泄漏
// for (int i = 0; i < 1000000; i++) {
// int *leak = (int *)malloc(1024);
// // 忘记 free,每次循环泄漏 1KB
// }
// 错误4: 重复释放
int *q = (int *)malloc(sizeof(int));
free(q);
// free(q); // 双重释放,导致崩溃
q = NULL; // 置 NULL 后,free(NULL) 是安全的
// 错误5: 数组越界
int *arr = (int *)malloc(3 * sizeof(int));
arr[0] = 1; arr[1] = 2; arr[2] = 3;
// arr[3] = 4; // 越界写入!可能覆盖其他数据
free(arr);
return 0;
}
7、文件 I/O:`fopen`/`fclose`/`fread`/`fwrite`/`fprintf`/`fscanf`
综合小项目:做一个"学生成绩管理系统"(文件存储 + 链表 + 结构体),把前三周学的全部串起来。
推荐资源
| 类型 | 资源 |
|---|---|
| 书籍 | 《C Primer Plus》(入门首选)、《C程序设计语言》(K&R,经典) |
| 视频 | 浙大翁恺《C语言程序设计》(中国大学MOOC) |
| 练习 | LeetCode、洛谷(基础题) |
三、C++ 学习路线
入门阶段(3-4 周)
1、C++ 对 C 的增强:引用、`const`、命名空间、`new`/`delete`、函数重载、默认参数
- 难点 8:引用 vs 指针
cpp
#include <iostream>
using namespace std;
// 引用:变量的别名,语法更简洁,更安全
void swap_ref(int &a, int &b) { // & 在类型后面是引用
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10;
// 引用 vs 指针的核心区别
int &ref = x; // 引用:必须初始化,之后不可改绑
int *ptr = &x; // 指针:可以不初始化,可以改指向
ref = 20; // 直接用,无需解引用
*ptr = 30; // 指针需要 * 解引用
// 引用不可为空,指针可以为 NULL
// int &bad_ref; // 编译错误!引用必须初始化
int *null_ptr = nullptr; // 合法
// 引用不可改绑
int y = 100;
ref = y; // 这不是改绑!这是把 y 的值赋给 x
ptr = &y; // 这才是改指向
cout << "x = " << x << endl; // x = 100
// 函数中使用引用,比指针简洁
int a = 1, b = 2;
swap_ref(a, b);
cout << "a=" << a << ", b=" << b << endl; // a=2, b=1
return 0;
}
2、面向对象编程(OOP):类与对象、构造/析构函数、`this` 指针、访问控制
- 难点 9:构造函数、析构函数、拷贝控制
cpp
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
char *data_;
size_t len_;
public:
// 构造函数
MyString(const char *str = "") {
len_ = strlen(str);
data_ = new char[len_ + 1];
strcpy(data_, str);
cout << "构造: \"" << data_ << "\"" << endl;
}
// 拷贝构造函数(深拷贝)
// 如果不写,编译器默认做"浅拷贝"(只复制指针,不复制内容)
// 两个对象指向同一块内存 → 析构时重复释放 → 崩溃!
MyString(const MyString &other) {
len_ = other.len_;
data_ = new char[len_ + 1]; // 分配新内存
strcpy(data_, other.data_); // 复制内容
cout << "拷贝构造: \"" << data_ << "\"" << endl;
}
// 拷贝赋值运算符
MyString &operator=(const MyString &other) {
if (this == &other) return *this; // 防止自赋值
delete[] data_; // 释放旧内存
len_ = other.len_;
data_ = new char[len_ + 1];
strcpy(data_, other.data_);
cout << "拷贝赋值: \"" << data_ << "\"" << endl;
return *this;
}
// 析构函数
~MyString() {
cout << "析构: \"" << data_ << "\"" << endl;
delete[] data_; // 释放动态内存
}
void print() const { cout << data_ << endl; }
};
int main() {
MyString s1("Hello"); // 构造
MyString s2 = s1; // 拷贝构造(不是赋值!)
MyString s3("World"); // 构造
s3 = s1; // 拷贝赋值
s1.print(); // Hello
s2.print(); // Hello
s3.print(); // Hello
// 函数结束,s3、s2、s1 按逆序析构(栈的后进先出)
return 0;
}
规则:如果类管理了动态资源(如 new),必须同时实现析构函数、拷贝构造、拷贝赋值(Rule of Three)。
3、继承与多态:继承、虚函数、纯虚函数、抽象类、虚析构函数
- 难点 10:虚函数与多态
cpp
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Shape {
public:
// 没有 virtual:编译时绑定(静态绑定),根据指针类型调用
// 有 virtual:运行时绑定(动态绑定),根据实际对象类型调用
virtual double area() const = 0; // 纯虚函数 → Shape 是抽象类
virtual string name() const = 0;
virtual ~Shape() = default; // 虚析构:通过基类指针删除时,正确调用派生类析构
};
class Circle : public Shape {
double radius_;
public:
Circle(double r) : radius_(r) {}
double area() const override { return 3.14159 * radius_ * radius_; }
string name() const override { return "圆"; }
};
class Rectangle : public Shape {
double w_, h_;
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
double area() const override { return w_ * h_; }
string name() const override { return "矩形"; }
};
// 多态的威力:用基类指针/引用统一处理不同子类
void print_info(const Shape &s) {
cout << s.name() << " 的面积 = " << s.area() << endl;
}
int main() {
Circle c(5.0);
Rectangle r(3.0, 4.0);
print_info(c); // 圆 的面积 = 78.5398
print_info(r); // 矩形 的面积 = 12
// 用基类指针管理不同子类对象
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Circle>(10));
shapes.push_back(make_unique<Rectangle>(5, 6));
shapes.push_back(make_unique<Circle>(2.5));
cout << "\n所有图形:" << endl;
for (const auto &s : shapes) {
cout << " " << s->name() << ": " << s->area() << endl;
}
return 0;
}
- 为什么需要虚析构函数?
cpp
class Base {
public:
~Base() { cout << "~Base" << endl; } // 非虚析构
// virtual ~Base() { cout << "~Base" << endl; } // 应该用这个
};
class Derived : public Base {
int *data_;
public:
Derived() : data_(new int[100]) {}
~Derived() {
delete[] data_;
cout << "~Derived" << endl;
}
};
int main() {
Base *p = new Derived();
delete p;
// 非虚析构:只调用 ~Base,~Derived 不执行 → data_ 内存泄漏!
// 虚析构:先调用 ~Derived,再调用 ~Base → 正确释放
}
4、运算符重载:常见运算符重载、友元函数
5、STL 标准模板库:`vector`、`string`、`map`、`set`、`queue`、`stack`、迭代器、常用算法(`sort`、`find`、`accumulate`)
- 难点 11:迭代器失效
cpp
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
// 错误:边遍历边删除,迭代器失效
// for (auto it = v.begin(); it != v.end(); ++it) {
// if (*it % 2 == 0) {
// v.erase(it); // erase 后 it 失效!后续 ++it 是未定义行为
// }
// }
// 正确做法:erase 返回下一个有效迭代器
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // 删除后,it 指向下一个元素
} else {
++it;
}
}
for (int x : v) cout << x << " "; // 1 3 5 7
cout << endl;
// 另一种方法:erase-remove idiom(更高效)
vector<int> v2 = {1, 2, 3, 4, 5, 6, 7, 8};
v2.erase(
remove_if(v2.begin(), v2.end(), [](int x) { return x % 2 == 0; }),
v2.end()
);
for (int x : v2) cout << x << " "; // 1 3 5 7
return 0;
}
进阶阶段(4-8 周)
1、模板编程:函数模板、类模板、模板特化
- 难点 12:函数模板与类模板
cpp
#include <iostream>
#include <string>
using namespace std;
// 函数模板:编译器根据调用自动生成对应类型的函数
template <typename T>
T my_max(T a, T b) {
return (a > b) ? a : b;
}
// 模板特化:为特定类型提供专门实现
template <>
const char *my_max<const char *>(const char *a, const char *b) {
return (strcmp(a, b) > 0) ? a : b; // 字符串要用 strcmp,不能直接比较指针
}
// 类模板:通用的栈
template <typename T, int MaxSize = 100>
class Stack {
T data_[MaxSize];
int top_ = -1;
public:
void push(const T &val) {
if (top_ >= MaxSize - 1) throw runtime_error("栈满");
data_[++top_] = val;
}
T pop() {
if (top_ < 0) throw runtime_error("栈空");
return data_[top_--];
}
bool empty() const { return top_ < 0; }
};
int main() {
cout << my_max(3, 7) << endl; // 7(T = int)
cout << my_max(3.14, 2.71) << endl; // 3.14(T = double)
cout << my_max<const char*>("abc", "xyz") << endl; // xyz(特化版本)
Stack<int, 50> int_stack;
Stack<string> str_stack; // MaxSize 使用默认值 100
int_stack.push(10);
int_stack.push(20);
cout << int_stack.pop() << endl; // 20
str_stack.push("Hello");
str_stack.push("World");
cout << str_stack.pop() << endl; // World
return 0;
}
2、智能指针:`unique_ptr`、`shared_ptr`、`weak_ptr`,RAII 思想
- 难点 13:智能指针与 RAII
cpp
#include <iostream>
#include <memory>
using namespace std;
class Resource {
string name_;
public:
Resource(const string &n) : name_(n) { cout << name_ << " 被创建" << endl; }
~Resource() { cout << name_ << " 被销毁" << endl; }
void use() { cout << "使用 " << name_ << endl; }
};
int main() {
// unique_ptr:独占所有权,不可复制,可以移动
{
auto p1 = make_unique<Resource>("资源A");
p1->use();
// auto p2 = p1; // 编译错误!不能复制
auto p2 = move(p1); // 移动所有权,p1 变为空
// p1->use(); // 运行时崩溃!p1 已经是 nullptr
p2->use();
} // 离开作用域,p2 自动析构,"资源A 被销毁"
cout << "---" << endl;
// shared_ptr:共享所有权,引用计数
{
shared_ptr<Resource> sp1 = make_shared<Resource>("资源B");
cout << "引用计数: " << sp1.use_count() << endl; // 1
{
shared_ptr<Resource> sp2 = sp1; // 共享
cout << "引用计数: " << sp1.use_count() << endl; // 2
sp2->use();
} // sp2 析构,引用计数减为 1,资源还在
cout << "引用计数: " << sp1.use_count() << endl; // 1
} // sp1 析构,引用计数减为 0,"资源B 被销毁"
cout << "---" << endl;
// weak_ptr:不增加引用计数,解决循环引用
// 循环引用问题示例:
struct Node {
shared_ptr<Node> next;
// shared_ptr<Node> prev; // 如果双向都用 shared_ptr → 循环引用 → 内存泄漏
weak_ptr<Node> prev; // 用 weak_ptr 打破循环
~Node() { cout << "Node 销毁" << endl; }
};
{
auto n1 = make_shared<Node>();
auto n2 = make_shared<Node>();
n1->next = n2;
n2->prev = n1; // weak_ptr 不增加 n1 的引用计数
} // 正常释放,两个 "Node 销毁" 都会打印
return 0;
}
3、移动语义与右值引用:`std::move`、移动构造、完美转发
- 难点 14:移动语义与右值引用
cpp
#include <iostream>
#include <cstring>
#include <utility>
using namespace std;
class Buffer {
char *data_;
size_t size_;
public:
// 普通构造
Buffer(size_t size) : size_(size), data_(new char[size]) {
memset(data_, 0, size);
cout << "构造 " << size << " 字节" << endl;
}
// 拷贝构造:深拷贝,开销大
Buffer(const Buffer &other) : size_(other.size_), data_(new char[other.size_]) {
memcpy(data_, other.data_, size_);
cout << "拷贝构造 " << size_ << " 字节(昂贵!)" << endl;
}
// 移动构造:直接"偷"资源,零开销
Buffer(Buffer &&other) noexcept : data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // 让原对象放弃资源
other.size_ = 0;
cout << "移动构造 " << size_ << " 字节(几乎免费!)" << endl;
}
~Buffer() {
delete[] data_;
cout << "析构" << endl;
}
};
Buffer create_buffer() {
Buffer buf(1024 * 1024); // 1MB
return buf; // 返回时触发移动构造(或 NRVO 优化直接省略)
}
int main() {
Buffer b1(1024);
Buffer b2 = b1; // 拷贝构造(深拷贝 1024 字节)
Buffer b3 = move(b1); // 移动构造(只交换指针,极快)
// 注意:move 之后 b1 处于"有效但未指定"状态,不要再使用它
Buffer b4 = create_buffer(); // 函数返回值 → 移动构造或 NRVO
return 0;
}
关键理解:左值(lvalue)有名字、可取地址;右值(rvalue)是临时的、将要销毁的。std::move 把左值"标记"为右值,允许移动构造/赋值来"窃取"其资源。
4、Lambda 表达式:捕获列表、`std::function`
- 难点 15:Lambda 表达式
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main() {
// Lambda 基本语法:[捕获列表](参数) -> 返回类型 { 函数体 }
auto greet = [](const string &name) {
cout << "Hello, " << name << "!" << endl;
};
greet("World");
// 捕获列表详解
int x = 10, y = 20;
auto by_value = [x, y]() { return x + y; }; // 值捕获(副本)
auto by_ref = [&x, &y]() { x++; y++; }; // 引用捕获(可修改原变量)
auto all_val = [=]() { return x + y; }; // 捕获所有外部变量(值)
auto all_ref = [&]() { x += 10; }; // 捕获所有外部变量(引用)
by_ref();
cout << "x=" << x << ", y=" << y << endl; // x=11, y=21
// 实际应用:STL 算法 + Lambda
vector<int> nums = {5, 2, 8, 1, 9, 3, 7};
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 降序排序
});
int threshold = 5;
auto count = count_if(nums.begin(), nums.end(), [threshold](int n) {
return n > threshold;
});
cout << "大于 " << threshold << " 的有 " << count << " 个" << endl;
// Lambda 做闭包(状态保持)
auto counter = [n = 0]() mutable { return ++n; };
cout << counter() << endl; // 1
cout << counter() << endl; // 2
cout << counter() << endl; // 3
return 0;
}
5、多线程编程:`std::thread`、`mutex`、`condition_variable`、`atomic`、`async`/`future`
- 难点 16:多线程基础
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <atomic>
using namespace std;
mutex mtx;
int unsafe_counter = 0;
int safe_counter = 0;
atomic<int> atomic_counter(0); // 原子操作,无需加锁
void increment_unsafe(int times) {
for (int i = 0; i < times; i++)
unsafe_counter++; // 数据竞争!多线程同时读写
}
void increment_safe(int times) {
for (int i = 0; i < times; i++) {
lock_guard<mutex> lock(mtx); // RAII 风格加锁,作用域结束自动解锁
safe_counter++;
}
}
void increment_atomic(int times) {
for (int i = 0; i < times; i++)
atomic_counter++; // 原子操作,线程安全且无需锁
}
int main() {
const int TIMES = 100000;
const int THREADS = 10;
// 不安全版本
vector<thread> threads;
for (int i = 0; i < THREADS; i++)
threads.emplace_back(increment_unsafe, TIMES);
for (auto &t : threads) t.join();
cout << "不安全: " << unsafe_counter
<< "(期望 " << TIMES * THREADS << ")" << endl;
// 结果通常小于期望值,因为数据竞争
// mutex 版本
threads.clear();
for (int i = 0; i < THREADS; i++)
threads.emplace_back(increment_safe, TIMES);
for (auto &t : threads) t.join();
cout << "mutex: " << safe_counter << endl; // 正确:1000000
// atomic 版本
threads.clear();
for (int i = 0; i < THREADS; i++)
threads.emplace_back(increment_atomic, TIMES);
for (auto &t : threads) t.join();
cout << "atomic: " << atomic_counter << endl; // 正确:1000000
return 0;
}
6、现代 C++ 特性(C++11/14/17/20):`auto`、结构化绑定、`constexpr`、`std::optional`、`concepts`、`ranges`
推荐资源
| 类型 | 资源 |
|---|---|
| 书籍(入门) | 《C++ Primer》(第5版,最权威的入门书) |
| 书籍(进阶) | 《Effective C++》、《Effective Modern C++》(Scott Meyers) |
| 书籍(深入) | 《深度探索C++对象模型》、《C++ Templates: The Complete Guide》 |
| 视频 | 侯捷 C++ 系列课程(极为推荐) |
四、高级进阶方向
根据你的兴趣选择方向深入:
| 方向 | 关键知识 | 推荐资源 |
|---|---|---|
| 系统编程 | Linux 系统调用、进程/线程、网络编程(socket) | 《UNIX环境高级编程》(APUE)、《Linux高性能服务器编程》 |
| 算法竞赛 | 数据结构、图论、动态规划、数论 | 《算法导论》、Codeforces、LeetCode |
| 游戏/图形 | OpenGL/Vulkan、游戏引擎架构 | 《Game Engine Architecture》 |
| 嵌入式开发 | 单片机、RTOS、硬件接口 | STM32 开发板实践 |
| 高性能计算 | SIMD、缓存优化、并发编程 | 《C++ Concurrency in Action》 |
五、高效学习的关键原则
1、动手为王 --- 每学一个知识点,立刻写代码验证。只看不写等于没学。
2、项目驱动 --- 尽早做小项目:
C 语言:学生管理系统、简易通讯录、迷宫游戏
C++:简易 JSON 解析器、HTTP 服务器、内存池、简易数据库
3、读优秀源码 --- 比如 Redis(C)、muduo 网络库(C++)、leveldb(C++),能极大提升水平
4、 善用调试工具 --- 学会用 GDB/LLDB 调试、Valgrind 检测内存泄漏、AddressSanitizer
5、理解底层 --- 了解编译链接过程、内存布局、函数调用栈,会让你对 C/C++ 有更深的理解
6、坚持刷题 --- 每天 1-2 道算法题,用 C/C++ 实现,既练语法又练思维
六、建议的时间规划
| 阶段 | 时间 | 目标 |
|---|---|---|
| C 语言入门 | 2-4 周 | 掌握指针、内存管理,能写 500 行以上的程序 |
| C++ 入门 | 3-4 周 | 掌握 OOP 和 STL,能用 C++ 解算法题 |
| C++ 进阶 | 1-2 月 | 掌握模板、智能指针、现代 C++ 特性 |
| 项目实战 | 持续 | 完成 2-3 个有深度的项目 |
| 源码阅读 | 持续 | 阅读开源项目,学习工程实践 |
核心建议:不要追求"快速学完",而是追求"扎实理解"。C/C++ 的核心价值在于对底层的掌控力,这需要时间沉淀。指针、内存管理、对象模型这些难点,宁可多花时间弄透,也不要囫囵吞枣。
推荐项目路线(由易到难)
| 序号 | 项目 | 涉及知识点 | 预计时间 |
|---|---|---|---|
| 1 | 简易 Shell | 进程管理、fork/exec、管道 |
1 周 |
| 2 | 内存池分配器 | 内存管理、模板、性能优化 | 1 周 |
| 3 | JSON 解析器 | 递归下降解析、variant/map、字符串处理 |
1-2 周 |
| 4 | 简易 HTTP 服务器 | Socket 编程、多线程/IO 多路复用、HTTP 协议 | 2-3 周 |
| 5 | KV 存储引擎 | B+ 树/LSM-Tree、文件 I/O、序列化 | 3-4 周 |
每日学习模板(建议每天 2-4 小时)
| 时间段 | 内容 | 比例 |
|---|---|---|
| 前 30 分钟 | 复习昨天的笔记和代码 | 15% |
| 中间 1-2 小时 | 学新知识点 + 立即写代码验证 | 50% |
| 后 30 分钟 | 刷 1-2 道 LeetCode(用 C/C++) | 20% |
| 最后 15 分钟 | 整理笔记、记录疑问 | 15% |
总结:最常见的难点排序
按难度和重要性排序,这些是必须攻克的关卡:
- 指针与内存管理(C 的核心,不理解指针等于没学 C)
- 深拷贝 vs 浅拷贝(Rule of Three / Rule of Five)
- 虚函数与多态的实现原理(虚函数表 vtable)
- 移动语义与右值引用(现代 C++ 性能的关键)
- 模板与泛型编程(STL 的基础)
- 多线程与并发安全(数据竞争、死锁)
- 智能指针与 RAII(现代 C++ 资源管理的核心思想)
每个难点我都在上面给出了代码示例。建议把每个示例都手动敲一遍、编译运行、修改参数观察变化,而不是只看。理解来自动手实践。