本教程面向大学一年级学生,按"C++ 基础 → 面向对象 → STL 与模板 → 现代 C++ → 工程与综合项目"的路线展开。我们将以现代 C++(以 C++17/20 为主)的视角组织内容,突出类与对象、STL、智能指针、Lambda,让你从零走向能交付完整 C++ 项目。
C++ 是一门"难学难精"的语言——它既保留了对硬件的直接控制,又加入了面向对象与泛型。本章先帮你建立全局认知。
C++ 由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计,最初叫"C with Classes"。它是 C 的超集:
一句话总结:C++ 是性能敏感系统的"主力语言",学完它你能理解从操作系统到游戏引擎的底层逻辑。
C++ 的每一次标准更新都带来"更现代、更安全、更高效"的写法。建议直接学现代 C++,而不是先学传统 C++ 再迁移。
auto、Lambda、范围 for、智能指针、std::optional 等让代码更简洁、更安全。本教程默认使用 C++17 风格。与 C 相比,C++ 使用 iostream + std::cout 输出,理解命名空间与流的概念。
#include <iostream>
int main()
{
std::cout << "Hello, World!" << std::endl;
return 0;
}#include <iostream>:包含标准输入输出流头文件;std::cout:std 命名空间下的"标准输出流"对象;<<:流插入运算符,把右侧数据"插入"到流里;std::endl:输出换行并刷新缓冲区(比 "\n" 稍慢);:::作用域运算符,访问 std 中的 cout。// 单行注释(C99/C++ 都支持)
/*
多行注释
*/
#include <iostream>
// 方式一:每次写 std:: (推荐,更清晰)
int main() {
std::cout << "A" << std::endl;
return 0;
}
// 方式二:使用 using 声明简化(大作业慎用,避免命名冲突)
using std::cout; // 只引入 cout
using std::endl;
// 方式三:using namespace std; (教学代码常用,工业代码慎用)using namespace std;,但工业代码与面试代码推荐显式写 std:: —— 避免与第三方库的同名符号冲突。# 1. 用编辑器写 hello.cpp
$ vim hello.cpp
# 2. 用 g++ 编译:-o 指定输出文件名
$ g++ -std=c++17 hello.cpp -o hello
# 3. 运行
$ ./hello
Hello, World!
# 常用选项
$ g++ -Wall -Wextra -O2 -std=c++17 main.cpp -o main # 警告 + 优化
$ g++ -g main.cpp -o main # 调试模式
$ g++ main.cpp -o main # 默认 C++ 标准(通常 C++14)搭好工具链是开始的第一步 —— 编译器、IDE、构建系统三件套。
| 编译器 | 平台 | 特点 |
|---|---|---|
| g++ (GCC) | Linux/macOS/Windows | GNU 出品,最常用的开源 C++ 编译器,本课程默认使用 |
| clang++ | macOS/Linux/Windows | Apple 主力,错误提示友好,编译快 |
| MSVC (cl.exe) | Windows | Visual Studio 自带,Windows 平台原生 |
| Intel oneAPI | 跨平台 | Intel CPU 优化最佳 |
g++ main.cpp -o main && ./main;当项目不止一个源文件时,CMake 能帮你自动管理编译流程 —— 是 C++ 工程化的"行业标准"。
# CMakeLists.txt —— CMake 项目配置文件
cmake_minimum_required(VERSION 3.15) # 最低版本要求
project(hello_cpp LANGUAGES CXX) # 项目名 + 语言
set(CMAKE_CXX_STANDARD 17) # 使用 C++17 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(hello main.cpp) # 生成可执行文件 hello$ mkdir build && cd build
$ cmake .. # 生成 Makefile / 构建系统
$ cmake --build . # 编译
$ ./hello # 运行本节聚焦 C++ 相对 C 的关键语法差异 —— 命名空间、流式 I/O、类型推断。
C++ 关键字除了继承自 C 的全部,还增加了面向对象、模板与现代特性关键字:
| 类别 | 关键字(部分) |
|---|---|
| 类型 | bool char int double void auto decltype |
| 面向对象 | class struct public private protected virtual override final |
| 模板 | template typename |
| 控制流 | if else switch for while do break continue return goto |
| 异常 | try catch throw noexcept |
| 存储 | static const constexpr mutable explicit inline volatile |
| 命名空间 | namespace using |
| 类型转换 | static_cast dynamic_cast const_cast reinterpret_cast |
| 智能指针 | nullptr |
| 现代 | enum class decltype(auto) requires concept co_await co_return module import |
#include <iostream>
#include <string>
int main() {
// 传统写法:明确类型
int age = 18;
double score = 95.5;
std::string name = "Alice";
// C++11 起:auto 让编译器推断类型
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"; // const char* (注意不是 string)
auto z = name + "!"; // std::string
// 常量:const / constexpr
const int MAX = 100;
constexpr double PI = 3.14159; // 编译期常量
std::cout << name << ", age=" << age << ", PI=" << PI << std::endl;
}#include <iostream>
#include <string>
int main() {
int age;
std::string name;
std::cout << "请输入姓名和年龄:";
std::cin >> name >> age; // 自动跳过空白,不需要 &
std::cout << "你好," << name << "!今年 "
<< age << " 岁。" << std::endl;
// 读一行(含空格)
std::string line;
std::getline(std::cin, line);
}cin >> x 自动传引用、不需要写 &;getline 用于读含空格的整行。C++ 在 C 的基础上加入 bool、引用类型,以及更安全的 static_cast 系列转换。
| 类型 | 含义 | 典型大小 |
|---|---|---|
| bool | 布尔 | 1 字节(true / false) |
| char | 字符 | 1 |
| wchar_t / char32_t | 宽字符 | 2 / 4 |
| short / int / long / long long | 整型 | 2 / 4 / 4~8 / 8 |
| float / double / long double | 浮点 | 4 / 8 / 12 或 16 |
C++ 摒弃了 C 风格的强制转换 (int)x,推荐使用更安全、意图明确的命名转换:
#include <iostream>
int main() {
int a = 10;
double b = static_cast<double>(a) / 3; // 编译期检查,常用
const int x = 42;
int* p = const_cast<int*>(&x); // 去掉 const(慎用)
// dynamic_cast 用于多态指针/引用的下行转换(见继承章节)
// reinterpret_cast 是底层位重解释(极少使用)
std::cout << b << std::endl; // 3.333...
}| 转换方式 | 用途 |
|---|---|
| static_cast | 编译期检查的常规类型转换(最常用) |
| dynamic_cast | 多态类型的下行转换(运行时检查,需 RTTI) |
| const_cast | 添加或去除 const / volatile |
| reinterpret_cast | 位级别的重新解释(危险,少用) |
C++ 在 C 的运算符之外,新增了几个常用运算符:
:::访问命名空间、类的成员;-> / .:对象的指针与直接访问;?:、逗号 ,、sizeof:同 C;typeid(RTTI);与 C 基本相同;C++11 起新增范围 for遍历,让循环更简洁。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 拷贝每个元素到 x(只读遍历)
for (int x : numbers) {
std::cout << x << " ";
}
std::cout << std::endl;
// 引用遍历(修改元素)
for (auto& x : numbers) {
x = x * x; // 平方
}
// const 引用:避免拷贝
for (const auto& x : numbers) {
std::cout << x << " ";
}
}建议:遍历容器时优先用范围 for + const auto& —— 比传统下标循环更安全、更通用。
if (score >= 90) grade = 'A';
else if (score >= 80) grade = 'B';
else if (score >= 60) grade = 'C';
else grade = 'D';
switch (op) {
case '+': result = a + b; break;
case '-': result = a - b; break;
default: std::cerr << "未知操作符";
}C++ 在 C 函数基础上加入默认参数、函数重载、引用参数、内联函数 —— 让函数更强大、更易用。
void greet(const std::string& name = "World",
int times = 1) {
for (int i = 0; i < times; i++)
std::cout << "Hello, " << name << "!\n";
}
int main() {
greet(); // Hello, World! (用默认值)
greet("Alice"); // Hello, Alice!
greet("Bob", 3); // 打印 3 次
}同一作用域内,同名但参数列表不同的函数构成重载。编译器根据实参类型选择调用哪个。
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
std::string add(const std::string& a,
const std::string& b) { return a + b; }
int main() {
std::cout << add(1, 2) << "\n"; // 3
std::cout << add(1.5, 2.5) << "\n"; // 4
std::cout << add("Hello, ", "C++") << "\n"; // Hello, C++
}// 1. 值传递:拷贝一份,不影响原值
void by_value(int x) { x = 999; }
// 2. 引用传递:直接操作原变量
void by_ref(int& x) { x = 999; }
// 3. const 引用:只读,避免拷贝(推荐用于大对象)
void print(const std::string& s) { std::cout << s; }
// 4. 指针传递:也可修改,但调用方需传地址
void by_ptr(int* x) { if (x) *x = 999; }void f(T&) 或 void f(const T&));裸指针更多留给"所有权"语义(如 new/malloc)。// inline:建议编译器把函数体"内联"展开(避免函数调用开销)
inline int square(int x) { return x * x; }
// 递归:阶乘(注意终止条件)
long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}C++ 在 C 风格数组之上,提供了std::string 与 std::vector —— 推荐优先使用。
#include <string>
#include <iostream>
int main() {
std::string s = "Hello";
s += ", World!"; // 拼接(+ 可用)
std::cout << s << " 长度=" << s.size() << std::endl;
// 子串、查找、替换
std::string sub = s.substr(0, 5); // "Hello"
auto pos = s.find("World"); // 7 或 npos
s.replace(7, 5, "C++"); // 替换 World → C++
// 遍历
for (char c : s) std::cout << c; // 按字符遍历
}#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3};
v.push_back(4); // 末尾追加
v.pop_back(); // 末尾删除
v.insert(v.begin() + 1, 99); // 在下标 1 处插入
std::cout << "size=" << v.size() << ", empty="
<< v.empty() << std::endl;
// 二维 vector:矩阵
std::vector<std::vector<int>> mat({{1,2},{3,4}});
}记住:size()、empty()、push_back、pop_back、clear、resize 是最高频操作。
#include <array>
#include <string>
#include <cstring> // C 字符串函数(strlen 等)
std::array<int, 5> a = {1,2,3,4,5}; // 固定长度数组,有 size()、at()
// string ↔ C 风格字符串
std::string s = "hi";
const char* c = s.c_str(); // string → C-string
std::string s2 = c; // C-string → string指针继承自 C;引用是 C++ 新增的"安全别名" —— 在 OOP 中频繁使用。
| 特性 | 指针 (T*) | 引用 (T&) |
|---|---|---|
| 是否可以为空 | 可以(nullptr) | 不可以(必须绑定到对象) |
| 能否重新绑定 | 可以(p = &x) | 不可以(终身绑定到初始对象) |
| 访问方式 | *p 解引用 | 直接使用,与原变量同义 |
| 是否需要取地址 | 需要 & | 不需要 |
| 使用建议 | 表示"可能为空/可变的资源" | 表示"必然存在、不变的别名" |
int a = 10;
int& ref = a; // 引用:ref 是 a 的别名
int* p = &a; // 指针:p 存储 a 的地址
ref = 20; // a 现在是 20
*p = 30; // a 现在是 30nullptr C++11现代 C++ 用 nullptr 取代 C 的 NULL,类型更安全:
int* p = nullptr; // 推荐
// int* p = NULL; // 不推荐(C 风格宏)
if (p == nullptr) { /* 安全检查 */ }
// 或更简洁(C++17)
if (!p) { /* ... */ }// 1. 避免大对象拷贝
void print(const std::vector<int>& v) { /* 只读 */ }
// 2. 绑定到临时对象(右值)
std::string const& r = "literal" + "suffix";
// 3. const 引用延长临时对象生命周期
const int& x = 42; // OK,普通引用不行面向对象(OOP)让代码从"操作裸数据"升级到"操作有行为的数据" —— 是 C++ 区别于 C 的关键。
OOP 三大特性:封装(数据 + 行为打包,隐藏细节) · 继承(子类复用父类) · 多态(同一接口,不同实现)。
#include <string>
class Student {
public: // 公开:外部可访问
Student(); // 默认构造函数
Student(const std::string& name, int age);
~Student(); // 析构函数
void study() const;
void print() const;
std::string getName() const { return name_; } // 内联实现
int getAge() const { return age_; }
private: // 私有:外部不可访问(封装)
std::string name_;
int age_;
};#include <iostream>
#include "Student.h"
Student::Student() : name_("Unknown"), age_(0) {} // 初始化列表
Student::Student(const std::string& n, int a)
: name_(n), age_(a) {}
Student::~Student() { std::cout << name_ << " 毕业\n"; }
void Student::study() const {
std::cout << name_ << " 正在学习\n";
}
void Student::print() const {
std::cout << "姓名:" << name_ << ",年龄 " << age_ << std::endl;
}int main() {
Student s1; // 默认构造
Student s2("Alice", 20); // 带参构造
Student* p = new Student("Bob", 19); // 堆上构造
s2.study();
s2.print();
delete p;
}private,对外暴露 public 的 getter/setter(必要时加约束)。class Box {
public:
// this 指向当前对象的指针(类型 Box* const)
Box& setW(int w) { this->w_ = w; return *this; }
// const 成员函数:承诺不修改成员(可被 const 对象调用)
int volume() const { return w_ * h_ * d_; }
private:
int w_ = 1, h_ = 1, d_ = 1;
};const,否则 const 对象无法调用它们。让类形成"父子层级" —— 子类复用父类代码,多态让同一接口表现不同行为。
class Person { // 基类(父类)
public:
void sayHi() { std::cout << "Hi!\n"; }
};
class Student : public Person { // 派生类(子类)
public:
void study() { std::cout << "学习\n"; }
};
Student s;
s.sayHi(); // 继承自 Person
s.study(); // 自己定义| 继承方式 | 基类 public 成员 | 基类 protected 成员 |
|---|---|---|
| public 继承 | 仍为 public | 仍为 protected |
| protected 继承 | 变为 protected | 仍为 protected |
| private 继承 | 变为 private | 变为 private |
public 继承 —— 它表达"is-a"语义。其他方式罕用。通过基类指针/引用调用虚函数,实际执行的是派生类的版本 —— 这就是多态。
#include <iostream>
class Animal {
public:
virtual void speak() const { // 虚函数
std::cout << "...\n";
}
virtual ~Animal() = default; // 虚析构(必须!)
};
class Dog : public Animal {
public:
void speak() const override { // override:编译期校验
std::cout << "汪汪!\n";
}
};
class Cat : public Animal {
public:
void speak() const override {
std::cout << "喵喵!\n";
}
};
int main() {
Animal* p = new Dog;
p->speak(); // 汪汪!(运行时绑定到 Dog::speak)
delete p;
}class Shape { // 抽象类
public:
virtual double area() const = 0; // 纯虚函数
virtual ~Shape() = default;
};
class Circle : public Shape {
double r_;
public:
Circle(double r) : r_(r) {}
double area() const override { return 3.14159 * r_ * r_; }
};
// Shape s; // ❌ 不能实例化抽象类
Shape* p = new Circle(2); // ✔ 通过派生类指针使用virtual ~Base()。否则通过基类指针 delete 派生类对象时,派生类的析构函数不会被调用,资源会泄漏。通过 operator+、operator<< 等让自定义类支持熟悉的运算符。
#include <iostream>
struct Vec2 {
double x, y;
// 成员函数版:+=
Vec2& operator+=(const Vec2& rhs) {
x += rhs.x; y += rhs.y; return *this;
}
};
// 非成员函数版:+ 与 <<
Vec2 operator+(Vec2 a, const Vec2& b) {
return {a.x + b.x, a.y + b.y};
}
std::ostream& operator<<(std::ostream& os, const Vec2& v) {
return os << "(" << v.x << ", " << v.y << ")";
}
int main() {
Vec2 a{1,2}, b{3,4};
std::cout << (a + b) << std::endl; // (4, 6)
}=、()、[]、-> 只能作为成员函数重载;二元对称运算符(如 +、<<)通常作为非成员函数。"一次编写,多种数据类型使用" —— STL 的基石。
#include <iostream>
// 函数模板:T 是类型参数
template <typename T>
T add(T a, T b) { return a + b; }
// 类模板:通用的"盒子"
template <typename T>
class Box {
T value_;
public:
Box(T v) : value_(v) {}
T get() const { return value_; }
};
int main() {
std::cout << add(3, 5) << "\n"; // 8(int)
std::cout << add(1.5, 2.5) << "\n"; // 4(double)
Box<int> bi(42);
Box<std::string> bs("hello");
}Concepts 让模板参数有"明确的约束" —— 编译错误信息更友好:
#include <concepts>
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template <Addable T>
T add(T a, T b) { return a + b; }STL = 容器(存数据)+ 算法(处理数据)+ 迭代器(连接两者)+ 函数对象 + 适配器。
| 容器 | 特点 | 典型用途 |
|---|---|---|
| vector | 动态数组,随机访问 O(1),末尾增删 O(1) | 默认首选序列容器 |
| deque | 双端队列,两端增删 O(1) | 队列、滑动窗口 |
| list | 双向链表,任意位置增删 O(1),无随机访问 | 频繁中间插入/删除 |
| array | 固定大小数组,无额外开销 | 替代 C 风格数组 |
| forward_list | 单向链表 | 节省内存 |
| stack | LIFO 栈(基于 deque) | DFS、表达式求值 |
| queue | FIFO 队列 | BFS、任务调度 |
| priority_queue | 堆(默认最大堆) | Top-K、Dijkstra |
| set / multiset | 有序集合(红黑树) | 去重 + 排序 |
| map / multimap | 有序键值对 | 字典、索引 |
| unordered_set | 哈希集合,O(1) 平均 | 快速查找 |
| unordered_map ★ | 哈希键值对,O(1) 平均 | 最常用 map |
#include <unordered_map>
#include <iostream>
#include <string>
int main() {
std::unordered_map<std::string, int> scores;
// 增
scores["Alice"] = 95;
scores["Bob"] = 87;
scores.insert({"Carol", 92});
// 查
auto it = scores.find("Alice");
if (it != scores.end())
std::cout << "Alice = " << it->second << std::endl;
// 遍历(C++17 结构化绑定)
for (const auto& [name, score] : scores)
std::cout << name << ": " << score << std::endl;
// 删
scores.erase("Bob");
}STL 提供 ~100 个通用算法,配合迭代器对任意容器工作。
#include <algorithm>
#include <vector>
#include <numeric> // accumulate
#include <iostream>
int main() {
std::vector<int> v = {5, 2, 8, 1, 9, 3};
std::sort(v.begin(), v.end()); // 升序
std::reverse(v.begin(), v.end()); // 反转
auto it = std::find(v.begin(), v.end(), 8); // 查找
int cnt = std::count(v.begin(), v.end(), 5); // 计数
int sum = std::accumulate(v.begin(), v.end(), 0);
bool found = std::binary_search(v.begin(), v.end(), 8);
// 自定义比较器:降序
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
}记住:STL 算法默认作用于"半开区间" [begin, end),传迭代器对,不依赖具体容器类型。
C++11 引入的匿名函数对象 —— 让 STL 算法、自定义回调变得极其简洁。
[捕获列表] (参数) -> 返回类型 { 函数体 }
// 最简形式
auto hello = []() { std::cout << "Hi\n"; };
// 带参数和返回值
auto add = [](int a, int b) -> int { return a + b; };
// 与 STL 算法配合
std::sort(v.begin(), v.end(),
[](int a, int b) { return a > b; }); // 降序int x = 10, y = 20;
[] // 不捕获任何外部变量
[x, y] // 值捕获(拷贝)
[&x, &y] // 引用捕获(可修改)
[=] // 值捕获所有用到的外部变量
[&] // 引用捕获所有用到的外部变量
[=, &x] // 默认值捕获,但 x 用引用
[this] // 捕获当前类的 this 指针
// 例:计数器
int cnt = 0;
auto inc = [&]() { return ++cnt; };
inc(); inc(); // cnt 现在是 2现代 C++ 的核心安全机制 —— 让"忘记 delete"成为历史。
| 智能指针 | 所有权 | 典型场景 |
|---|---|---|
| unique_ptr<T> | 独占所有权(不可复制,可移动) | 默认首选,替代裸 new/delete |
| shared_ptr<T> | 共享所有权(引用计数) | 资源被多处共享、生命周期不确定 |
| weak_ptr<T> | 不增加引用计数,可查 shared_ptr 是否存活 | 解决循环引用 |
#include <memory>
// 1) unique_ptr:独占(推荐首选)
auto p1 = std::make_unique<int>(42);
// std::unique_ptr p2 = p1; // ❌ 不可复制
auto p2 = std::move(p1); // ✔ 可移动
// 2) shared_ptr:共享
auto sp1 = std::make_shared<std::string>("hi");
auto sp2 = sp1; // 引用计数=2
// 3) weak_ptr:观察但不拥有
std::weak_ptr<std::string> wp = sp1;
if (auto locked = wp.lock()) { // 提升为 shared_ptr
// 使用 *locked
}Resource Acquisition Is Initialization:资源在构造函数获取、在析构函数释放。让对象生命周期 = 资源生命周期。
class File {
FILE* fp_;
public:
File(const char* path) : fp_(fopen(path, "r")) {}
~File() { if (fp_) fclose(fp_); } // 自动关闭
// 禁止拷贝或转移所有权
File(const File&) = delete;
File& operator=(const File&) = delete;
};
void read() {
File f("data.txt"); // 打开;离开作用域自动关闭
// 即使抛异常,析构也会执行
}C++ 用流式对象读写文件 —— 比 C 的 fopen 更类型安全。
#include <fstream>
#include <string>
int main() {
// 写文本
std::ofstream out("out.txt");
if (!out) return 1;
out << "Hello, " << 42 << std::endl;
// 读文本(按行)
std::ifstream in("out.txt");
std::string line;
while (std::getline(in, line))
std::cout << line << "\n";
// 二进制:reinterpret_cast + read/write
int arr[3] = {1, 2, 3};
std::ofstream bin("a.bin", std::ios::binary);
bin.write(reinterpret_cast<const char*>(arr), sizeof arr);
}#include <stdexcept>
double divide(double a, double b) {
if (b == 0)
throw std::runtime_error("除数不能为零");
return a / b;
}
int main() {
try {
std::cout << divide(10, 0);
} catch (const std::runtime_error& e) {
std::cerr << "错误:" << e.what() << std::endl;
} catch (std::exception& e) {
std::cerr << "其它异常:" << e.what() << std::endl;
} catch (...) {
std::cerr << "未知异常\n";
}
}noexcept。STL 不仅提供容器,还让我们能学到"如何用 C++ 实现数据结构"。
#include <iostream>
template <typename T>
struct Node {
T data;
Node* next;
Node(T d, Node* n = nullptr) : data(d), next(n) {}
};
template <typename T>
class LinkedList {
Node<T>* head_ = nullptr;
public:
~LinkedList() { while (head_) { auto t = head_; head_ = head_->next; delete t; } }
void push_front(T v) { head_ = new Node<T>(v, head_); }
void print() const {
for (auto p = head_; p; p = p->next)
std::cout << p->data << " -> ";
std::cout << "nullptr\n";
}
};C++ 是练习算法的最佳语言 —— STL 让算法实现变得简洁。
#include <vector>
#include <algorithm>
std::vector<int> v = {1, 3, 5, 7, 9};
bool ok = std::binary_search(v.begin(), v.end(), 7);
auto lo = std::lower_bound(v.begin(), v.end(), 5); // 第一个 ≥5
auto hi = std::upper_bound(v.begin(), v.end(), 5); // 第一个 >5void quickSort(std::vector<int>& a, int lo, int hi) {
if (lo >= hi) return;
int pivot = a[(lo + hi) / 2];
int i = lo, j = hi;
while (i <= j) {
while (a[i] < pivot) i++;
while (a[j] > pivot) j--;
if (i <= j) std::swap(a[i++], a[j--]);
}
quickSort(a, lo, j);
quickSort(a, i, hi);
}实战建议:手写快速排序理解原理;实战用 std::sort(经过精心优化的 introsort)。
从"脚本式编程"升级到"工程化开发" —— 学会组织一个真正的 C++ 项目。
my_project/
├── CMakeLists.txt
├── include/
│ └── Student.h
├── src/
│ ├── Student.cpp
│ └── main.cpp
├── build/ // 编译产物
└── README.md#pragma once
#include <string>
class Student {
public:
Student(const std::string& name, int age);
std::string getName() const;
private:
std::string name_;
int age_;
};#include "Student.h"
Student::Student(const std::string& n, int a)
: name_(n), age_(a) {}
std::string Student::getName() const { return name_; }#include <iostream>
#include "Student.h"
int main() {
Student s("Alice", 20);
std::cout << s.getName();
}cmake_minimum_required(VERSION 3.15)
project(my_project LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(include) # 头文件搜索路径
add_executable(app
src/main.cpp
src/Student.cpp
)强烈建议把每个 C++ 项目都用 Git 管理 —— 提交粒度建议"一个功能/一次提交"。
$ git init
$ git add .
$ git commit -m "feat: 完成 Student 类的基本实现"
$ git log --oneline # 查看历史写出能跑的代码只是开始 —— 能调试才叫工程能力。
| 阶段 | 典型错误 |
|---|---|
| 编译错误 | 语法错误、类型不匹配、未声明标识符 |
| 链接错误 | undefined reference to 'xxx' |
| 运行错误 | 段错误(Segfault)、栈溢出、迭代器失效 |
| 逻辑错误 | 程序能跑但结果不对 |
#pragma once 或 include guard。PascalCase,函数/变量 camelCase,常量 kCamel 或 UPPER_CASE;name_)方便区分;以下项目按难度递进,建议至少完成前 3 个。
支持加减乘除与括号;用类封装;可选用函数指针或 std::function 实现调度。
用 Student 类保存一名学生,vector<Student> 保存多名;支持增删改查、按成绩排序、文件持久化。
每条联系人含姓名、电话、邮箱;用 unordered_map 实现 O(1) 查找;支持模糊搜索;CSV 持久化。
含 Book 与 Borrower 类;用继承与多态表达不同种类图书(教材/小说/工具书);支持借还状态机。
基类 Account;派生 SavingsAccount、CheckingAccount、CreditAccount;多态调用利息计算;交易记录到文件。
支持新建/打开/编辑/保存/查找替换;用 string 做 buffer,vector<string> 做多行。
多文件工程:Student / Course / Enrollment 三个类,vector/map 组合;文件持久化 + 异常处理 + CMake 构建 + Git 版本管理。
写代码时随手翻一翻 —— 比每次去搜更快。
alignasalignofautoboolbreakcasecatchcharclassconstconstexprcontinuedecltypedefaultdeletedodoubleelseenumexplicitexportexternfalsefinalfloatforfriendgotoifinlineintlongmutablenamespacenewnoexceptnullptroperatoroverrideprivateprotectedpublicregisterreinterpret_castreturnshortsignedsizeofstaticstatic_assertstatic_caststructswitchtemplatethisthread_localthrowtruetrytypeidtypenameunionunsignedusingvirtualvoidvolatilewchar_twhile
| 需求 | 推荐容器 |
|---|---|
| 通用序列 | std::vector<T> |
| 头尾都频繁增删 | std::deque<T> |
| 中间频繁插入/删除 | std::list<T> |
| 栈 | std::stack<T> |
| 队列 | std::queue<T> |
| 优先队列 / 堆 | std::priority_queue<T> |
| 有序去重集合 | std::set<T> |
| 哈希集合 | std::unordered_set<T> |
| 有序键值对 | std::map<K, V> |
| 哈希键值对 | std::unordered_map<K, V> |
| 固定大小数组 | std::array<T, N> |
| 头文件 | 内容 |
|---|---|
| <iostream> | cout / cin / cerr / endl |
| <string> | std::string |
| <vector> / <list> / <deque> / <array> | 序列容器 |
| <map> / <set> / <unordered_map> / <unordered_set> | 关联容器 |
| <algorithm> / <numeric> | STL 算法 |
| <functional> | std::function / std::bind / 哈希 |
| <memory> | 智能指针、allocator |
| <fstream> | 文件流 |
| <sstream> | 字符串流 |
| <stdexcept> | 标准异常类 |
| <tuple> | std::tuple / std::pair |
| <optional> C++17 | std::optional<T> |
| <variant> C++17 | std::variant |
| <filesystem> C++17 | 文件操作 |
| <chrono> | 时间 |
| <thread> / <mutex> | 多线程(C++11) |
| <ranges> C++20 | std::ranges |
| <concepts> C++20 | Concepts |
| <coroutine> C++20 | 协程 |
// 1) RAII 文件句柄
std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("a.txt", "r"), &fclose);
// 2) 读整行
std::getline(std::cin, line);
// 3) split 字符串
std::istringstream iss(s); std::string tok;
while (iss >> tok) { /* ... */ }
// 4) 类型擦除的函数包装
std::function<int(int,int)> f = [](int a, int b){ return a + b; };
// 5) 时间测量
auto t0 = std::chrono::steady_clock::now();
// ... do work ...
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0).count();vector 边遍历边 push_back —— 可能导致迭代器失效virtual —— 通过基类指针 delete 派生对象时资源泄漏auto p = new T; 而不用 make_uniqueconst T&#pragma once 或 include guard.cpp 中 include 对应 .h