C++ 教程
C++ 是 C 语言的超集,由 Bjarne Stroustrup 于 1985 年发布,在 C 的基础上增加了面向对象、泛型、STL 标准库等特性。它以高性能著称,是系统开发、游戏引擎、高频交易等领域的王者。
C++ 的核心特性
| 特性 | 说明 |
|---|---|
| 面向对象 | 类、继承、多态、封装 |
| 泛型编程 | 模板(Template) |
| STL 标准库 | 容器、算法、迭代器 |
| 内存控制 | 指针、引用、智能指针 |
| 高性能 | 接近硬件的执行效率 |
第一个 C++ 程序
#include <iostream>
using namespace std;
int main() {
cout << "Hello, CodeNest!" << endl;
return 0;
}
编译与运行
# g++ 编译
g++ hello.cpp -o hello
./hello
# 或 clang++
clang++ hello.cpp -o hello
C++ 的应用领域
- 操作系统、驱动程序
- 游戏引擎(Unreal、Unity 底层)
- 浏览器内核(Chrome、Firefox)
- 数据库(MySQL、MongoDB)
- 高频交易、嵌入式系统
- AI 框架底层(TensorFlow、PyTorch)
版本演进
| 版本 | 年份 | 亮点 |
|---|---|---|
| C++98 | 1998 | 首个标准 |
| C++11 | 2011 | auto、智能指针、Lambda(划时代) |
| C++14 | 2014 | 泛型 Lambda 等小改进 |
| C++17 | 2017 | if constexpr、filesystem |
| C++20 | 2020 | 概念、协程、范围 |
| C++23 | 2023 | 模块、打印库 |
C++ 是一门"能驾驭底层、也能写抽象"的语言。学习难度较高,但掌握后收益极大。
C++ 简介
C++ 简介:C++ 的历史、与 C 的关系、设计哲学。
C++ 的诞生
- 1979 年 Bjarne Stroustrup 在贝尔实验室开始研发
- 最初叫 "C with Classes"(带类的 C)
- 1983 年正式改名 C++(++ 是 C 的自增运算符)
- 1985 年发布首个商用版本
C++ vs C
| 对比项 | C | C++ |
|---|---|---|
| 编程范式 | 面向过程 | 面向对象 + 泛型 + 过程 |
| 标准库 | C 标准库 | C 库 + STL |
| 内存管理 | malloc/free | new/delete + RAII |
| 类型安全 | 较弱 | 更强(模板、强类型) |
| 适用 | 嵌入式、内核 | 复杂系统、应用 |
C++ 的设计哲学
- 零成本抽象:高级特性不牺牲性能
- 兼容 C:C 代码可直接编译
- 多范式:不强制一种风格
- 信任程序员:给你控制权,也让你负责
C++ 的编程范式
- 面向过程(函数)
- 面向对象(类与对象)
- 泛型编程(模板)
- 函数式(Lambda、算法)
- 元编程(模板元)
为什么学 C++
- 深入理解计算机底层原理
- 高性能应用开发的必备技能
- 游戏、AI、数据库行业刚需
- 面试算法题常用语言
C++ 难在"选择多"(同样任务多种写法)。学习时先掌握一种主流写法,再逐步探索其他范式。
C++ 环境设置
配置 C++ 开发环境:编译器、构建工具、IDE。
安装编译器
# macOS:安装 Xcode Command Line Tools(含 clang++)
xcode-select --install
# Ubuntu/Debian
sudo apt install g++ build-essential
# Windows:安装 MinGW-w64 或使用 Visual Studio
验证编译器
g++ --version
# 或
clang++ --version
编写并编译第一个程序
// hello.cpp
#include <iostream>
int main() {
std::cout << "Hello C++" << std::endl;
return 0;
}
g++ hello.cpp -o hello # 编译
./hello # 运行
常用编译选项
g++ main.cpp -o app # 编译
g++ main.cpp -o app -std=c++17 # 指定标准
g++ main.cpp -o app -O2 # 优化等级
g++ main.cpp -o app -Wall -Wextra # 开启警告
g++ main.cpp -o app -g # 调试信息
g++ a.cpp b.cpp -o app # 多文件编译
IDE 选择
| 工具 | 特点 |
|---|---|
| CLion | JetBrains 出品,C++ 首选(付费) |
| Visual Studio | Windows 最强 |
| VS Code + 插件 | 轻量免费(C/C++ 扩展) |
| Code::Blocks | 免费跨平台 |
构建工具
- Make / CMake:跨平台构建
- Bazel / Ninja:大型项目
- vcpkg / Conan:依赖管理
入门建议:VS Code + g++ 即可;项目变大后用 CMake 管理构建,这是 C++ 业界标准。
C++ 基本语法
掌握 C++ 基本语法:程序结构、语句、标识符。
程序基本结构
#include <iostream> // 预处理器指令:引入头文件
int main() { // 主函数:程序入口
// 语句
std::cout << "Hello" << std::endl;
return 0; // 返回值:0 表示成功
}
语句与分号
int a = 10; // 每条语句以分号结尾
int b = 20;
int sum = a + b;
// 分号遗漏是初学者最常见的编译错误
标识符规则
- 字母、数字、下划线组成
- 不能以数字开头
- 区分大小写
- 不能是关键字(int、class、if 等)
注释
// 单行注释
/*
* 多行注释
*/
/**
* 文档注释(配合 Doxygen)
*/
命名规范
| 场景 | 规范 | 示例 |
|---|---|---|
| 变量/函数 | 小驼峰或下划线 | userName / user_name |
| 类名 | 大驼峰 | StudentManager |
| 常量 | 全大写 | MAX_SIZE |
使用 std:: 命名空间
// 方式一:全限定名(推荐,清晰)
std::cout << "Hello" << std::endl;
// 方式二:using 声明
using std::cout;
using std::endl;
cout << "Hello" << endl;
// 方式三:整个命名空间(不推荐,污染)
using namespace std;
cout << "Hello" << endl;
小项目用 using namespace std; 图方便;大项目推荐 std:: 前缀,避免命名冲突。
C++ 注释
C++ 注释:注释的三种写法与最佳实践。
三种注释
// 单行注释:两个斜杠
/* 多行注释
可以跨行 */
/**
* Doxygen 文档注释
* 可生成 API 文档
*/
Doxygen 注释
/**
* 计算两个整数的和
* @param a 第一个加数
* @param b 第二个加数
* @return a + b 的结果
*/
int add(int a, int b) {
return a + b;
}
注释的最佳实践
- 解释"为什么",而非"是什么"
- 复杂算法写清思路
- TODO 标记待办:
// TODO: 优化此处 - FIXME 标记问题:
// FIXME: 边界情况未处理
生成文档
# 安装 doxygen 后生成文档
doxygen Doxyfile
好的注释是"写给未来的自己和其他开发者"的。命名清晰时少写废话注释;逻辑复杂时多写说明。
C++ 数据类型
掌握 C++ 数据类型:基本类型、大小与范围。
基本数据类型
| 类型 | 大小(64位) | 范围 |
|---|---|---|
bool | 1 字节 | true/false |
char | 1 字节 | -128 ~ 127 |
short | 2 字节 | -32768 ~ 32767 |
int | 4 字节 | ±21 亿 |
long | 4/8 字节 | 取决于平台 |
long long | 8 字节 | 极大 |
float | 4 字节 | 约 7 位精度 |
double | 8 字节 | 约 15 位精度 |
wchar_t | 4 字节 | 宽字符(Unicode) |
修饰符
signed int // 有符号(默认)
unsigned int // 无符号(非负)
short int // 短整型
long int // 长整型
unsigned long long // 组合
类型大小查询
#include <iostream>
using namespace std;
int main() {
cout << "int: " << sizeof(int) << " 字节" << endl;
cout << "double: " << sizeof(double) << " 字节" << endl;
cout << "int 范围: " << INT_MIN << " ~ " << INT_MAX << endl;
return 0;
}
auto 类型推导(C++11+)
auto x = 42; // int
auto y = 3.14; // double
auto name = "C++"; // const char*
auto v = {1, 2, 3}; // initializer_list
类型转换
// 隐式转换
int i = 3.99; // 3(截断)
// 静态转换(推荐)
double d = 3.99;
int n = static_cast<int>(d); // 3
// C 风格(不推荐)
int n2 = (int)d;
选择类型原则:够用就好。计数用 int,金额/精度用 double,大数用 long long,状态用 bool。
C++ 变量类型
掌握 C++ 变量类型:变量声明、初始化与作用域。
变量声明与初始化
// 声明
int age;
double price;
string name;
// 初始化(多种方式)
int a = 42; // 拷贝初始化
int b(42); // 直接初始化
int c{42}; // 列表初始化(推荐,防窄化)
int d = {42}; // 拷贝列表初始化
// 多条声明
int x = 1, y = 2, z = 3;
变量类型
// 局部变量:函数内
void func() {
int local = 10; // 局部变量
}
// 全局变量:所有函数外
int global = 100;
// 静态变量:static
static int counter = 0; // 生命周期到程序结束
// 常量
const double PI = 3.14159; // 不可修改
constexpr int MAX = 100; // 编译期常量
auto 与 decltype
auto num = 42; // int
auto pi = 3.14; // double
decltype(pi) another = 2.5; // double(取类型)
// 返回值类型推导
auto add(int a, int b) -> int { // 尾置返回
return a + b;
}
变量作用域
#include <iostream>
using namespace std;
int global = 100; // 全局作用域
int main() {
int local = 10; // 局部作用域
{
int block = 5; // 块作用域
cout << block << endl;
}
// cout << block; // ❌ 块外不可访问
cout << local << " " << global << endl;
return 0;
}
extern 声明
// file1.cpp
int sharedValue = 42;
// file2.cpp
extern int sharedValue; // 引用其他文件的变量
现代 C++ 推荐 列表初始化 {}:能防止窄化转换(如 double 转 int 会报错),更安全。
C++ 变量作用域
掌握 C++ 变量作用域:局部、全局、块级与命名空间作用域。
作用域类型
| 作用域 | 位置 | 生命周期 |
|---|---|---|
| 全局 | 所有函数外 | 程序运行期间 |
| 局部 | 函数内 | 函数调用期间 |
| 块级 | { } 内 | 代码块执行期间 |
| 命名空间 | namespace 内 | 程序运行期间 |
示例
#include <iostream>
using namespace std;
int globalVar = 1; // 全局变量
namespace Math {
int globalVar = 2; // 命名空间中的变量
}
int main() {
int localVar = 3; // 局部变量
cout << globalVar << endl; // 1(全局)
cout << Math::globalVar << endl; // 2(命名空间)
cout << localVar << endl; // 3
{
int blockVar = 4;
cout << blockVar << endl;
}
// cout << blockVar; // ❌ 块作用域外不可见
return 0;
}
名字遮蔽(Shadowing)
int value = 10; // 全局
int main() {
int value = 20; // 局部遮蔽全局
cout << value << endl; // 20(局部优先)
cout << ::value << endl; // 10(:: 访问全局)
return 0;
}
static 变量
void counter() {
static int count = 0; // 只初始化一次,函数调用间保留
count++;
cout << count << endl;
}
counter(); // 1
counter(); // 2
counter(); // 3
作用域最佳实践
- 变量最小作用域:靠近使用处声明
- 避免全局变量(难维护、线程不安全)
- 避免名字遮蔽
- 循环变量在循环内声明
变量声明越靠近使用处越好,作用域越小越好——这是代码可维护性的重要原则。
C++ 常量
掌握 C++ 常量:const、constexpr、字面量。
字面量常量
42 // int
3.14 // double
3.14f // float
'A' // char
"hello" // const char*(字符串字面量)
true // bool
10000000000LL // long long
const 常量
const double PI = 3.14159; // 运行时常量
const int MAX_ITEMS = 100;
// const 指针
const int* p1 = &x; // 指向常量的指针:不能改 *p1
int* const p2 = &x; // 常量指针:不能改 p2 指向
const int* const p3 = &x; // 都不能改
constexpr(编译期常量,C++11+)
constexpr int MAX = 100; // 编译期确定
constexpr double PI = 3.14159;
constexpr int square(int x) { // constexpr 函数
return x * x;
}
int arr[square(5)]; // 编译期数组大小 ✅
// 对比:
// const:运行时常量
// constexpr:编译期常量(可优化更好)
enum 枚举常量
enum Color { RED, GREEN, BLUE }; // 0,1,2
enum Status { OK = 200, NOT_FOUND = 404 };
// 强类型枚举(推荐,C++11+)
enum class Level { LOW, MEDIUM, HIGH };
Level l = Level::HIGH; // 必须用作用域符
define 宏常量(避免)
// ❌ 旧风格(无类型检查)
#define MAX_SIZE 100
// ✅ 现代风格(类型安全)
constexpr int MAX_SIZE = 100;
现代 C++ 原则:用 const/constexpr 替代 #define,用 enum class 替代裸 enum——类型安全且可调试。
C++ 修饰符类型
掌握 C++ 修饰符类型:signed/unsigned/short/long 等类型修饰符。
类型修饰符
| 修饰符 | 作用 | 示例 |
|---|---|---|
signed | 有符号(可负) | signed int |
unsigned | 无符号(非负,范围加倍) | unsigned int |
short | 缩短 | short int |
long | 加长 | long int、long long |
使用示例
unsigned int u = 42; // 0 ~ 4294967295
signed int s = -42; // 有符号
unsigned long long big = 18446744073709551615ULL;
short small = 100; // 省内存
⚠️ unsigned 的坑
unsigned int a = 1;
int b = -2;
if (a > b) {
cout << "a > b" << endl; // 不打印!
} else {
cout << "a <= b" << endl; // 实际执行这里!
}
// 原因:a 转成 unsigned,-2 变成巨大正数
// 数组大小、容器大小常用 size_t(unsigned)
vector<int> v = {1,2,3};
for (size_t i = 0; i < v.size(); i++) { }
const 修饰符
const int a = 10; // 常量
const string s = "hi"; // 常量字符串
// 成员函数 const:承诺不修改对象
class Box {
public:
int getSize() const { return size; } // const 成员函数
private:
int size = 0;
};
volatile 修饰符(少用)
volatile int flag = 0; // 告诉编译器不要优化该变量
// 硬件寄存器、信号处理器场景使用
unsigned 与 signed 混用比较会产生意外结果。循环计数、数组索引等非负场景用 unsigned/size_t 要格外小心。
C++ 存储类
掌握 C++ 存储类:auto、static、extern、register、thread_local。
存储类总览
| 存储类 | 作用 |
|---|---|
auto | 自动存储(默认,C++11 起表示类型推导) |
static | 静态存储(生命周期=程序) |
extern | 声明外部变量/函数 |
register | 请求寄存器存储(已废弃) |
thread_local | 线程局部存储(C++11+) |
mutable | const 成员函数中可修改 |
static 的三种用法
// 1. 静态局部变量:函数调用间保留
void counter() {
static int n = 0;
n++;
cout << n << endl;
}
// 2. 静态全局/成员:仅本文件/类共享
static int fileScope = 42; // 仅本文件可见
class Config {
public:
static int version; // 静态成员声明
static void print() {} // 静态成员函数
};
int Config::version = 1; // 静态成员定义
// 3. 静态类对象:初始化一次
class Logger {
public:
static Logger& instance() { // 单例模式
static Logger logger; // 首次调用时创建
return logger;
}
};
extern
// a.cpp
int global = 42;
// b.cpp
extern int global; // 使用 a.cpp 的变量
void f() {
cout << global << endl;
}
// 头文件中的 extern 声明(多文件项目)
// shared.h
extern int sharedCounter;
thread_local(C++11+)
thread_local int threadId; // 每个线程独立副本
void worker() {
threadId = ...; // 各线程互不影响
}
mutable
class Cache {
public:
int get(int key) const {
mutableCache[key] = ...; // const 函数中修改 mutable
return mutableCache[key];
}
private:
mutable map<int,int> mutableCache;
};
static 是 C++ 高频关键词:静态局部变量(状态保持)、静态成员(类级数据)、静态函数(文件私有)三种用途要分清。
C++ 运算符
掌握 C++ 运算符:算术、关系、逻辑、位运算、赋值。
算术运算符
int a = 10, b = 3;
a + b // 13
a - b // 7
a * b // 30
a / b // 3(整数除法)
a % b // 1(取余)
++a // 前置自增
a++ // 后置自增
关系与逻辑
a > b a < b a >= b a <= b
a == b a != b
a && b // 逻辑与
a || b // 逻辑或
!a // 逻辑非
位运算符
int x = 6; // 110
x & 3 // 2 (010) 与
x | 3 // 7 (111) 或
x ^ 3 // 5 (101) 异或
~x // 取反
x << 1 // 12 左移(乘2)
x >> 1 // 3 右移(除2)
赋值运算符
int n = 10;
n += 5; // n = 15
n -= 3; // 12
n *= 2; // 24
n /= 4; // 6
n %= 5; // 1
n <<= 1; // 左移赋值
三目运算符
int age = 20;
string s = (age >= 18) ? "成年" : "未成年";
sizeof 与类型转换
sizeof(int) // 4
sizeof(arr) / sizeof(arr[0]) // 数组长度
// 静态转换(推荐)
double d = 3.7;
int n = static_cast<int>(d); // 3
运算符优先级
() 最高 → 一元(++ -- !) → 算术(* / %) → 算术(+ -)
→ 关系(< >) → 相等(== !=) → 逻辑与(&&) → 逻辑或(||)
→ 赋值(=) 最低
优先级记不住没关系——多用括号明确顺序,可读性远比"少写括号"重要。
C++ 循环
掌握 C++ 循环:for、while、do-while 与范围 for。
for 循环
for (int i = 0; i < 5; i++) {
cout << i << " ";
}
// 0 1 2 3 4
// 递减
for (int i = 5; i > 0; i--) {
cout << i << " ";
}
// 5 4 3 2 1
while 循环
int n = 0;
while (n < 5) {
cout << n << " ";
n++;
}
// 0 1 2 3 4
do-while(至少执行一次)
int n = 10;
do {
cout << n << " "; // 即使条件不满足也执行一次
n++;
} while (n < 5);
// 10
范围 for(C++11+,推荐)
vector<int> v = {1, 2, 3, 4};
for (int x : v) {
cout << x << " ";
}
// 引用遍历(可修改 + 高效)
for (int& x : v) {
x *= 2; // 修改原值
}
// const 引用(只读 + 高效)
for (const auto& x : v) {
cout << x << " ";
}
// 遍历字符串
for (char c : "hello") {
if (c == ' ') break;
cout << c;
}
break / continue
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 跳过偶数
if (i > 7) break; // 超过 7 停止
cout << i << " "; // 1 3 5 7
}
嵌套循环
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= i; j++) {
cout << "*";
}
cout << endl;
}
// *
// **
// ***
遍历容器优先范围 for;需要索引时用传统 for;循环条件不明确用 while。
C++ 判断
掌握 C++ 判断语句:if、if-else、switch 与三目。
if 语句
int score = 85;
if (score >= 60) {
cout << "及格" << endl;
}
if-else
if (score >= 60) {
cout << "及格";
} else {
cout << "不及格";
}
if-else if 链
if (score >= 90) cout << "优秀";
else if (score >= 80) cout << "良好";
else if (score >= 60) cout << "及格";
else cout << "不及格";
switch 语句
int day = 3;
switch (day) {
case 1: cout << "周一"; break;
case 2: cout << "周二"; break;
case 3: cout << "周三"; break;
default: cout << "其他";
}
// break 不可省略,否则会"穿透"
三目运算符
int age = 20;
string type = (age >= 18) ? "成年" : "未成年";
// 嵌套三目(可读性差,少用)
int a = 10, b = 20, c = 30;
int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
逻辑组合
if (age >= 18 && age <= 60) { /* 工作年龄 */ }
if (city == "北京" || city == "上海") { /* 一线 */ }
if (!isDeleted) { /* 未删除 */ }
初始化语句(C++17+)
// if 初始化语句:变量作用域限在 if 内
if (auto it = m.find("key"); it != m.end()) {
cout << "找到: " << it->second;
} else {
cout << "未找到";
}
条件多分支用 switch(编译优化好);简单二选一用三目;复杂判断用 if-else if 保证可读性。
C++ 函数
掌握 C++ 函数:定义、参数、返回值、重载。
函数定义与调用
// 返回类型 函数名(参数列表) { 函数体 }
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4); // 调用
cout << result << endl; // 7
return 0;
}
函数声明(原型)
// 先声明,后定义(用于多文件/互相调用)
int multiply(int a, int b); // 声明
int main() {
cout << multiply(3, 4); // 可用
return 0;
}
int multiply(int a, int b) { // 定义
return a * b;
}
参数传递
// 值传递(副本,不影响原值)
void byValue(int x) { x = 100; }
// 引用传递(直接操作原值,推荐)
void byRef(int& x) { x = 100; }
// 指针传递
void byPtr(int* x) { *x = 100; }
int main() {
int n = 0;
byValue(n); // n 仍为 0
byRef(n); // n 变为 100
byPtr(&n); // n 变为 100
return 0;
}
默认参数
void greet(string name, string greeting = "你好") {
cout << greeting << "," << name << endl;
}
greet("张三"); // 你好,张三
greet("李四", "Hello"); // Hello,李四
// 默认参数只能从右往左连续设置
函数重载
int max(int a, int b) { return a > b ? a : b; }
double max(double a, double b) { return a > b ? a : b; }
max(1, 2); // int 版本
max(1.5, 2.5); // double 版本
内联函数
// inline:建议编译器内联展开(小函数)
inline int square(int x) {
return x * x;
}
Lambda(C++11+)
// [捕获](参数) -> 返回类型 { 函数体 }
auto add = [](int a, int b) -> int {
return a + b;
};
cout << add(2, 3) << endl; // 5
// 捕获外部变量
int base = 10;
auto addBase = [base](int x) { return base + x; };
cout << addBase(5) << endl; // 15
传递大型对象(string/vector/自定义类)用 const 引用;需要修改用引用;只有小基本类型才值传递。
C++ 数字
掌握 C++ 数字处理:数值类型、数学函数、随机数。
数字类型
int a = 42; // 整数
double b = 3.14; // 双精度
float c = 2.5f; // 单精度
long long d = 10000000000LL;
unsigned int e = 100;
// 十六进制/八进制/二进制
int hex = 0xFF; // 255
int oct = 017; // 15
int bin = 0b1010; // 10 (C++14+)
数学函数(<cmath>)
#include <cmath>
abs(-10); // 10 绝对值
fabs(-3.5); // 3.5 浮点绝对值
sqrt(16); // 4.0 平方根
pow(2, 10); // 1024.0 幂
ceil(3.2); // 4.0 向上取整
floor(3.8); // 3.0 向下取整
round(3.5); // 4.0 四舍五入
fmod(10, 3); // 1.0 浮点取余
log(2.71828); // 1.0 自然对数
log10(100); // 2.0
sin(M_PI / 2); // 1.0
cos(0); // 1.0
tan(0); // 0.0
随机数(<random>,C++11+)
#include <random>
// 现代方式
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<> dist(1, 100); // 1-100 均匀分布
int randomNum = dist(gen);
// 浮点随机
uniform_real_distribution<double> fdist(0.0, 1.0);
double r = fdist(gen);
// 旧方式(rand,了解即可)
srand(time(nullptr));
int old = rand() % 100 + 1; // 1-100
数值转换
#include <string>
// 字符串 → 数字
int n = stoi("42"); // 42
double d = stod("3.14"); // 3.14
long l = stol("1000000");
// 数字 → 字符串
string s1 = to_string(42); // "42"
string s2 = to_string(3.14); // "3.140000"
精度问题
// 浮点比较不要用 ==
double x = 0.1 + 0.2; // 0.30000000000000004
// 用误差范围比较
const double EPS = 1e-9;
if (fabs(x - 0.3) < EPS) {
cout << "相等" << endl;
}
金融计算考虑定点数或整数分;随机数用 <random> 库(rand 有偏且弱);浮点比较用误差。
C++ 数组
掌握 C++ 数组:一维、二维、数组与指针关系。
一维数组
// 声明与初始化
int scores[5]; // 未初始化(随机值)
int scores[5] = {90, 85, 95}; // 前 3 个初始化,其余 0
int nums[] = {1, 2, 3, 4}; // 自动推导大小 4
int zeros[5] = {}; // 全 0
// 访问
scores[0] = 100; // 修改
cout << scores[0]; // 读取
int size = sizeof(scores) / sizeof(scores[0]); // 元素个数
// 遍历
for (int i = 0; i < size; i++) {
cout << scores[i] << " ";
}
for (int x : scores) { // 范围 for
cout << x << " ";
}
二维数组
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
matrix[1][2]; // 7
// 遍历
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
字符数组(C 风格字符串)
char name[] = "CodeNest"; // 自动加 ' '
char name2[20] = "Hello";
// 需要 <cstring>
#include <cstring>
strlen(name); // 长度(不含 )
strcpy(dest, src); // 复制
strcmp(a, b); // 比较(0 相等)
// 现代 C++ 用 std::string(推荐)
string s = "CodeNest";
数组与指针
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // 数组名即首地址
p[0]; // 1(指针可下标访问)
*(p + 2); // 3(指针算术)
p++; // 指向下一个元素
// 数组作参数退化为指针
void printArr(int arr[], int size) { } // 等价 int* arr
std::array(C++11+,推荐)
#include <array>
array<int, 5> a = {1, 2, 3, 4, 5};
a.size(); // 5(安全)
a.at(0); // 带边界检查
a.front(); // 1
a.back(); // 5
// 大小固定、连续内存,比裸数组安全
固定大小用 std::array,动态大小用 std::vector,裸 C 数组仅用于性能敏感或与 C 交互。
C++ 字符串
掌握 C++ 字符串:std::string 的全面用法。
std::string 基础
#include <string>
string s1; // 空字符串
string s2 = "hello"; // 初始化
string s3("world");
string s4(5, 'a'); // "aaaaa"
// 拼接
string full = s2 + " " + s3; // "hello world"
s2 += "!"; // "hello!"
// 长度
s2.size(); // 6
s2.length(); // 6(等价)
s2.empty(); // 是否为空
访问与遍历
string s = "CodeNest";
s[0]; // 'C'(不检查边界)
s.at(1); // 'o'(越界抛异常)
s.front(); // 'C'
s.back(); // 't'
for (char c : s) {
cout << c;
}
for (size_t i = 0; i < s.size(); i++) {
cout << s[i];
}
查找与截取
string s = "Hello CodeNest";
s.find("Code"); // 6(找不到返回 string::npos)
s.find("X"); // string::npos(很大的数)
s.rfind("l"); // 从后找
s.substr(6); // "CodeNest"
s.substr(0, 5); // "Hello"
if (s.find("Code") != string::npos) {
cout << "找到" << endl;
}
修改与替换
string s = "hello";
s.insert(0, "say "); // "say hello"
s.replace(4, 5, "bye"); // "say bye"
s.erase(0, 4); // "bye"
s.push_back('!'); // "bye!"
s.pop_back(); // "bye"
s.clear(); // 清空
// 大小写(需要 algorithm 或手写)
transform(s.begin(), s.end(), s.begin(), ::toupper);
C 风格字符串转换
// string → const char*
string s = "hello";
const char* cstr = s.c_str();
const char* data = s.data();
// const char* → string
string back = cstr;
string vs char*
| 对比项 | std::string | char* |
|---|---|---|
| 自动管理 | ✅(自动扩容/释放) | ❌(手动管理) |
| 操作 | 丰富(拼接/查找/截取) | 少(cstring 函数) |
| 安全 | 越界检查(at) | 易溢出 |
| 推荐 | ✅ 现代 C++ | C 兼容场景 |
现代 C++ 一律用 std::string;仅与 C 库交互时转 c_str()。
C++ 指针
掌握 C++ 指针:内存地址与间接访问的核心概念。
指针基础
int value = 42;
int* ptr = &value; // & 取地址,ptr 存 value 的地址
cout << ptr; // 地址(如 0x7ffe...)
cout << *ptr; // 42(* 解引用,取指向的值)
*ptr = 100; // 通过指针修改
cout << value; // 100
空指针与野指针
// 空指针:不指向任何对象
int* p = nullptr; // 现代 C++(推荐)
// int* p = NULL; // C 风格
// int* p = 0; // 旧式
if (p != nullptr) {
cout << *p;
}
// 野指针:未初始化的指针(危险!)
// int* bad; // 未初始化,指向未知内存
// *bad = 10; // ❌ 未定义行为!
指针运算
int arr[] = {10, 20, 30};
int* p = arr; // 指向 arr[0]
p++; // 指向 arr[1]
*p; // 20
*(p + 1); // 30
p - arr; // 元素偏移量
指针与数组
int arr[3] = {1, 2, 3};
int* p = arr; // 数组名即首元素地址
// 三种等价访问
arr[1]; // 2
*(arr+1); // 2
p[1]; // 2
指针与函数
// 指针参数:修改调用方的变量
void swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int x = 1, y = 2;
swap(&x, &y); // x=2, y=1
// 函数指针
int add(int a, int b) { return a + b; }
int (*funcPtr)(int, int) = add;
funcPtr(3, 4); // 7
指针 vs 引用
| 对比项 | 指针 | 引用 |
|---|---|---|
| 可空 | ✅(nullptr) | ❌(必须绑定) |
| 可改绑 | ✅ | ❌ |
| 语法 | * 和 & | 自动 |
| 安全 | 需小心 | 更安全 |
能用引用用引用;需要"可空/可重定向"才用指针。C++11+ 用 unique_ptr/shared_ptr 管理动态内存。
C++ 引用
掌握 C++ 引用:变量的别名。
引用基础
int value = 42;
int& ref = value; // ref 是 value 的别名
ref = 100; // 修改 ref 即修改 value
cout << value; // 100
// 引用必须初始化,不能改绑
// int& bad; // ❌ 必须初始化
引用作函数参数(最常用)
// 值传递:拷贝(慢,不修改原值)
void byValue(string s) { s += "!"; }
// 引用传递:直接操作(快,可修改)
void byRef(string& s) { s += "!"; }
// const 引用:快且只读(推荐)
void printStr(const string& s) {
cout << s; // 只读,不拷贝
}
string msg = "hello";
byValue(msg); // msg 不变
byRef(msg); // msg = "hello!"
printStr(msg); // 高效只读
引用返回值
// 返回引用:避免拷贝,可链式调用
class Counter {
int count = 0;
public:
Counter& add() { // 返回自身引用
count++;
return *this;
}
int get() const { return count; }
};
Counter c;
c.add().add().add(); // 链式调用
c.get(); // 3
右值引用(C++11+)
// 左值:有名字、可取地址
int x = 10; // x 是左值
// 右值:临时对象
// 10 是右值
// f() 返回值可能是右值
// 右值引用 &&:移动语义
string getTemp() { return "temp"; }
string a = getTemp(); // 移动构造(C++11 自动)
// 显式移动
string b = "hello";
string c = std::move(b); // b 被移动(内容转移)
引用 vs 指针速查
| 场景 | 推荐 |
|---|---|
| 函数参数(大型对象) | const 引用 |
| 需要修改调用方变量 | 引用 |
| 可空/可选参数 | 指针 |
| 动态内存 | 智能指针 |
引用是 C++ 的高频特性:const 引用传参 + 引用返回是写高效 C++ 的基本功。
C++ 日期 & 时间
掌握 C++ 日期与时间:<ctime> 与 <chrono>。
ctime(C 风格)
#include <ctime>
#include <iostream>
using namespace std;
int main() {
// 当前时间戳(秒)
time_t now = time(nullptr);
cout << now << endl; // 秒数
// 转本地时间
tm* local = localtime(&now);
cout << local->tm_year + 1900 << "年" // 年
<< local->tm_mon + 1 << "月" // 月(0 起)
<< local->tm_mday << "日" << endl;
// 格式化输出
char buf[80];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local);
cout << buf << endl; // 2026-08-11 14:30:00
return 0;
}
chrono(现代 C++,推荐)
#include <chrono>
using namespace std::chrono;
// 当前时间点
auto now = system_clock::now();
// 转时间戳
auto ts = system_clock::to_time_t(now);
// 转成可读时间
std::tm* t = std::localtime(&ts);
// 计时(高频场景)
auto start = high_resolution_clock::now();
// ... 执行代码 ...
auto end = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(end - start);
cout << "耗时: " << duration.count() << " ms" << endl;
时间单位
using namespace std::chrono;
nanoseconds // 纳秒
microseconds // 微秒
milliseconds // 毫秒
seconds // 秒
minutes // 分
hours // 时
// 单位转换
auto ms = duration_cast<milliseconds>(seconds(1));
// 1000ms
计时实用工具
// 简单计时器
class Timer {
steady_clock::time_point start;
public:
Timer() : start(steady_clock::now()) {}
double elapsed_ms() {
auto end = steady_clock::now();
return duration_cast<milliseconds>(end - start).count();
}
};
Timer t;
// 执行代码...
cout << t.elapsed_ms() << " ms" << endl;
计时/时间操作首选 <chrono>(类型安全、精度高、跨平台);<ctime> 用于格式化展示。
C++ 基本的输入输出
掌握 C++ 输入输出:cin、cout、cerr、文件流。
标准输出 cout
#include <iostream>
using namespace std;
cout << "Hello"; // 输出字符串
cout << 42; // 输出数字
cout << "值: " << 3.14; // 链式输出
cout << endl; // 换行(刷新缓冲区)
cout << "a" << "
"; // 换行(更快)
标准输入 cin
int age;
string name;
cout << "输入姓名: ";
cin >> name; // 读取一个词(遇空白停止)
cout << "输入年龄: ";
cin >> age;
// 读取整行(含空格)
string line;
getline(cin, line);
// 注意:cin >> 后接 getline 需先清空缓冲区
cin.ignore(numeric_limits<streamsize>::max(), '
');
输入校验
int num;
cout << "输入数字: ";
if (cin >> num) {
cout << "收到: " << num;
} else {
cout << "输入无效" << endl;
cin.clear(); // 清除错误状态
cin.ignore(10000, '
'); // 清空缓冲
}
cerr / clog
cerr << "错误信息" << endl; // 错误输出(不缓冲)
clog << "日志信息" << endl; // 日志输出(缓冲)
格式化输出
#include <iomanip>
double pi = 3.14159265;
cout << fixed << setprecision(2) << pi; // 3.14
cout << setw(10) << setfill('0') << 42; // 0000000042
cout << hex << 255; // ff(十六进制)
cout << boolalpha << true; // true
文件输入输出
#include <fstream>
// 写文件
ofstream out("data.txt");
out << "第一行" << endl;
out << 42 << endl;
out.close(); // 关闭
// 读文件
ifstream in("data.txt");
string line;
while (getline(in, line)) {
cout << line << endl;
}
in.close();
endl 会刷新缓冲区(慢),大批量输出用 "
";文件读写用 RAII 方式(对象析构自动关闭)。
C++ 结构体(struct)
掌握 C++ 结构体(struct):组合不同类型的数据。
定义与使用
#include <iostream>
using namespace std;
// 结构体定义
struct Student {
string name;
int age;
double score;
};
int main() {
// 初始化
Student s1;
s1.name = "张三";
s1.age = 20;
s1.score = 88.5;
// 聚合初始化
Student s2 = {"李四", 22, 95.0};
Student s3{"王五", 21, 76.5}; // 列表初始化
// 访问
cout << s2.name << " " << s2.score << endl;
return 0;
}
结构体数组
Student students[3] = {
{"张三", 20, 88},
{"李四", 22, 95},
{"王五", 21, 76}
};
// 遍历
for (const auto& s : students) {
cout << s.name << ": " << s.score << endl;
}
结构体与函数
// 传引用(高效)
void printStudent(const Student& s) {
cout << s.name << "," << s.age << "岁";
}
// 返回结构体
Student makeStudent(string name, int age) {
return Student{name, age, 0};
}
// 结构体数组排序
sort(students, students + 3, [](const Student& a, const Student& b) {
return a.score > b.score; // 按分数降序
});
结构体 vs 类
| 对比项 | struct | class |
|---|---|---|
| 默认访问 | public | private |
| 用途 | 简单数据聚合 | 完整面向对象 |
| 方法 | 可有 | 核心 |
结构体中的方法(C++ 可混合)
struct Point {
double x, y;
// 成员函数
double distance() {
return sqrt(x * x + y * y);
}
};
Point p{3, 4};
p.distance(); // 5
简单数据组合用 struct(默认 public);需要封装/继承用 class。C++ 中两者本质相同,仅默认访问权限不同。
C++ vector 容器
掌握 C++ vector 容器:动态数组,最常用的 STL 容器。
创建与基本操作
#include <vector>
using namespace std;
vector<int> v1; // 空 vector
vector<int> v2(5); // 5 个 0
vector<int> v3(5, 10); // 5 个 10
vector<int> v4 = {1, 2, 3}; // 初始化列表
vector<int> v5(v4); // 拷贝
// 添加元素
v4.push_back(4); // 尾部添加
v4.emplace_back(5); // 尾部构造(高效)
// 访问
v4[0]; // 不检查边界
v4.at(1); // 越界抛异常
v4.front(); // 第一个
v4.back(); // 最后一个
// 大小
v4.size(); // 元素个数
v4.empty(); // 是否为空
v4.capacity(); // 容量
修改与删除
vector<int> v = {1, 2, 3, 4, 5};
v.pop_back(); // 删除尾部 → {1,2,3,4}
v.insert(v.begin() + 1, 99); // 插入 → {1,99,2,3,4}
v.erase(v.begin() + 2); // 删除 → {1,99,3,4}
v.clear(); // 清空
v.resize(10); // 调整大小
// 删除所有 3
v.erase(remove(v.begin(), v.end(), 3), v.end());
遍历
vector<int> v = {1, 2, 3};
// 范围 for(推荐)
for (int x : v) { cout << x; }
// 引用遍历(修改)
for (int& x : v) { x *= 2; }
// 索引遍历
for (size_t i = 0; i < v.size(); i++) { }
// 迭代器
for (auto it = v.begin(); it != v.end(); ++it) { }
算法配合
#include <algorithm>
sort(v.begin(), v.end()); // 排序
reverse(v.begin(), v.end()); // 反转
find(v.begin(), v.end(), 3); // 查找
count(v.begin(), v.end(), 3); // 计数
max_element(v.begin(), v.end()); // 最大值位置
accumulate(v.begin(), v.end(), 0); // 求和
// 二维 vector(矩阵)
vector<vector<int>> matrix(3, vector<int>(4, 0));
vector vs 数组
| 对比项 | vector | 数组 |
|---|---|---|
| 大小 | 动态 | 固定 |
| 自动管理 | ✅ | ❌ |
| 性能 | 略低(极少) | 最高 |
| 推荐 | ✅ 默认选择 | 性能敏感 |
vector 是 C++ 最常用的容器:动态扩容、内存连续(缓存友好)、与算法库完美配合。
C++ 数据结构
掌握 C++ 数据结构:常见数据结构的 STL 实现。
数据结构总览
| 结构 | STL 容器 | 特点 |
|---|---|---|
| 动态数组 | vector | 随机访问 O(1) |
| 链表 | list / forward_list | 插入删除 O(1) |
| 栈 | stack | LIFO |
| 队列 | queue | FIFO |
| 优先队列 | priority_queue | 自动排序取最大 |
| 哈希表 | unordered_map/set | 查找 O(1) |
| 平衡树 | map/set | 有序,O(log n) |
栈 stack
#include <stack>
stack<int> st;
st.push(1); // 入栈
st.push(2);
st.top(); // 2(栈顶)
st.pop(); // 出栈
st.empty(); // 是否空
st.size(); // 大小
队列 queue
#include <queue>
queue<int> q;
q.push(1); // 入队
q.push(2);
q.front(); // 1(队头)
q.back(); // 2(队尾)
q.pop(); // 出队
优先队列(堆)
#include <queue>
// 最大堆(默认)
priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(5);
pq.top(); // 5(最大)
// 最小堆
priority_queue<int, vector<int>, greater<int>> minPq;
minPq.top(); // 最小
map(有序键值)
#include <map>
map<string, int> scores;
scores["张三"] = 90;
scores["李四"] = 85;
scores["张三"]; // 90
scores.count("张三"); // 1(是否存在)
scores.find("李四"); // 迭代器
scores.erase("李四"); // 删除
// 遍历(按键升序)
for (const auto& [k, v] : scores) {
cout << k << ": " << v << endl;
}
unordered_map(哈希,更快)
#include <unordered_map>
unordered_map<string, int> um;
um["apple"] = 5;
um["banana"] = 3;
um["apple"]; // 5(O(1) 查找)
// 需要有序遍历时用 map,只要快速查找用 unordered_map
数据结构选择
- 要排序/范围查询 → map / set
- 只要快速查找 → unordered_map / unordered_set
- 取最大/最小 → priority_queue
- FIFO → queue;LIFO → stack
- 动态数组 → vector(默认)
STL 容器是 C++ 面试和实战的基石:会选容器 + 会用算法,事半功倍。
C++ 类 & 对象
掌握 C++ 类与对象:面向对象编程的核心。
定义类
#include <iostream>
using namespace std;
class Student {
private: // 私有:外部不可直接访问
string name;
int age;
public: // 公有:对外接口
// 构造方法
Student(string n, int a) : name(n), age(a) {}
// 成员函数
void introduce() {
cout << "我是 " << name << "," << age << "岁" << endl;
}
// getter/setter
string getName() { return name; }
void setName(string n) { name = n; }
};
int main() {
Student s("张三", 20); // 创建对象(调用构造)
s.introduce(); // 我是 张三,20岁
s.setName("李四");
cout << s.getName(); // 李四
return 0;
}
构造与析构
class Resource {
public:
// 构造:初始化
Resource() {
cout << "构造" << endl;
data = new int[100]; // 申请资源
}
// 析构:清理(RAII)
~Resource() {
cout << "析构" << endl;
delete[] data; // 释放资源
}
private:
int* data;
};
初始化列表
class Point {
double x, y;
public:
// 初始化列表(推荐,比赋值高效)
Point(double a, double b) : x(a), y(b) {}
};
this 指针
class Box {
int size;
public:
void setSize(int size) {
this->size = size; // this->size 是成员
}
};
访问控制
| 关键字 | 访问范围 |
|---|---|
public | 任何地方 |
protected | 类内 + 子类 |
private | 仅类内 |
static 成员
class Counter {
public:
static int total; // 静态成员声明
Counter() { total++; }
};
int Counter::total = 0; // 静态成员定义(类外)
Counter a, b, c;
Counter::total; // 3(所有对象共享)
C++ 类的核心思想:封装数据 + RAII 管理资源(构造申请、析构释放),这是 C++ 区别于其他语言的关键。
C++ 继承
掌握 C++ 继承:代码复用与多态的基础。
基本继承
#include <iostream>
using namespace std;
// 基类
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {}
void eat() {
cout << name << " 吃东西" << endl;
}
};
// 派生类:public 继承
class Dog : public Animal {
public:
Dog(string n) : Animal(n) {} // 调用基类构造
void bark() {
cout << name << " 汪汪叫" << endl;
}
};
int main() {
Dog dog("旺财");
dog.eat(); // 继承的方法
dog.bark(); // 自己的方法
return 0;
}
继承方式
| 继承方式 | 基类 public | 基类 protected | 基类 private |
|---|---|---|---|
| public 继承 | public | protected | 不可见 |
| protected 继承 | protected | protected | 不可见 |
| private 继承 | private | private | 不可见 |
方法覆盖(多态前提)
class Animal {
public:
// virtual:虚函数,支持多态
virtual void speak() {
cout << "动物叫" << endl;
}
};
class Cat : public Animal {
public:
void speak() override { // override:覆盖
cout << "喵喵" << endl;
}
};
// 多态调用
Animal* a = new Cat();
a->speak(); // 喵喵(虚函数动态绑定)
// 纯虚函数(抽象类)
class Shape {
public:
virtual double area() = 0; // = 0:纯虚函数
virtual ~Shape() {}
};
构造与析构顺序
// 构造:先基类后派生
// 析构:先派生后基类
class Base {
public:
Base() { cout << "Base 构造" << endl; }
virtual ~Base() { cout << "Base 析构" << endl; }
// 注意:基类析构必须是 virtual!否则 delete 派生对象不会调用派生析构
};
class Derived : public Base {
public:
Derived() { cout << "Derived 构造" << endl; }
~Derived() { cout << "Derived 析构" << endl; }
};
继承类型
| 类型 | 说明 |
|---|---|
| 单继承 | 一个子类一个父类(最常见) |
| 多继承 | 一个子类多个父类(谨慎使用) |
| 菱形继承 | 多继承+共享祖先(用 virtual 继承解决) |
C++ 继承要点:基类析构要 virtual、覆盖方法加 override、有虚函数就有虚析构——这三条能避免 90% 的坑。
C++ 重载运算符和重载函数
掌握 C++ 重载运算符与重载函数。
函数重载
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
add(1, 2); // int 版本
add(1.5, 2.5); // double 版本
add(1, 2, 3); // 3 参数版本
运算符重载
#include <iostream>
using namespace std;
class Vector {
double x, y;
public:
Vector(double a, double b) : x(a), y(b) {}
// 成员函数重载 +
Vector operator+(const Vector& other) {
return Vector(x + other.x, y + other.y);
}
// 成员函数重载 ==
bool operator==(const Vector& other) {
return x == other.x && y == other.y;
}
// 友元函数重载 <<(输出)
friend ostream& operator<<(ostream& os, const Vector& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
int main() {
Vector v1(1, 2), v2(3, 4);
Vector sum = v1 + v2; // 调用 operator+
cout << sum << endl; // (4, 6)
return 0;
}
常用可重载运算符
| 运算符 | 典型用途 |
|---|---|
+ - * / | 数学对象(复数、向量) |
== != < > | 比较(排序需要 <) |
<< >> | 流输入输出 |
[] | 下标访问 |
() | 函数对象(仿函数) |
++ -- | 迭代器 |
重载注意
- 不能发明新运算符
- 不改变优先级和结合性
- 至少一个操作数是自定义类型
- 保持语义一致(+ 就是加法)
重载 < 即可用 sort/map/set;重载 << 方便调试输出。但要克制——过度重载反而难读。
C++ 多态
掌握 C++ 多态:虚函数与动态绑定。
多态的基础
#include <iostream>
using namespace std;
class Shape {
public:
// 虚函数:运行时决定调用哪个版本
virtual double area() {
return 0;
}
// 虚析构(重要!)
virtual ~Shape() {}
};
class Circle : public Shape {
double r;
public:
Circle(double radius) : r(radius) {}
double area() override { // 覆盖
return 3.14159 * r * r;
}
};
class Rect : public Shape {
double w, h;
public:
Rect(double a, double b) : w(a), h(b) {}
double area() override {
return w * h;
}
};
int main() {
// 基类指针指向派生类对象
Shape* shapes[] = {new Circle(5), new Rect(4, 6)};
for (Shape* s : shapes) {
cout << s->area() << endl; // 多态:各调各的
}
// 78.5398
// 24
for (Shape* s : shapes) delete s; // 虚析构确保正确清理
return 0;
}
纯虚函数与抽象类
// 抽象类:含纯虚函数,不能实例化
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
virtual ~Animal() {}
};
class Dog : public Animal {
public:
void speak() override { cout << "汪汪"; }
};
// Animal a; // ❌ 抽象类不能实例化
Animal* a = new Dog();
a->speak(); // 汪汪
override 与 final
class Base {
public:
virtual void f() {}
};
class Derived : public Base {
public:
void f() override {} // override:明确覆盖(编译器检查)
// void g() override {} // ❌ 基类无 g(),报错
};
// final:禁止继续覆盖
class Final : public Base {
public:
void f() override final {} // 子类不能再覆盖
};
虚函数表(vtable)
有虚函数的类会有虚函数表:调用虚函数时通过 vtable 间接跳转(动态绑定),多一次间接寻址,但换来多态能力。构造函数不能是虚函数。
多态的好处
- 统一接口,不同实现
- 代码扩展性(新增类不改调用代码)
- 实现设计模式的基础
多态三件套:虚函数 + 基类指针/引用 + 派生类覆盖。基类析构务必 virtual,否则内存泄漏。
C++ 数据抽象
掌握 C++ 数据抽象:只暴露接口,隐藏实现细节。
什么是数据抽象
把"做什么"(接口)和"怎么做"(实现)分离:用户只关心接口,不关心内部实现。
示例:栈的抽象
#include <iostream>
#include <vector>
using namespace std;
// 对外只暴露 push/pop/top/isEmpty
class Stack {
private:
vector<int> data; // 实现细节(隐藏)
public:
void push(int x) { data.push_back(x); }
int pop() {
int top = data.back();
data.pop_back();
return top;
}
int top() const { return data.back(); }
bool isEmpty() const { return data.empty(); }
};
int main() {
Stack s;
s.push(1);
s.push(2);
cout << s.pop() << endl; // 2
// 用户不需要知道内部是 vector 还是数组
return 0;
}
抽象的好处
| 好处 | 说明 |
|---|---|
| 简化使用 | 接口清晰,无需理解内部 |
| 便于修改 | 内部实现可换,接口不变 |
| 安全性 | 防止误操作内部状态 |
| 可测试 | 通过接口测试 |
抽象 vs 封装
| 对比项 | 数据抽象 | 封装 |
|---|---|---|
| 侧重点 | 接口与实现分离 | 数据隐藏 |
| 手段 | public 接口 + private 实现 | private 成员 |
| 关系 | 封装是抽象的实现手段 | — |
抽象层次设计得好,调用方"只看接口不读源码"就能用——这也是接口文档重要的原因。
C++ 数据封装
掌握 C++ 数据封装:信息隐藏与访问控制。
封装的核心
- private 隐藏数据
- public 提供受控访问(getter/setter)
- 保护数据完整性
封装示例
#include <iostream>
using namespace std;
class BankAccount {
private:
string accountNo;
double balance; // 私有:外部不可直接改
public:
BankAccount(string no, double b) : accountNo(no), balance(b) {}
// 受控读取
double getBalance() const { return balance; }
// 受控写入(带校验)
bool deposit(double amount) {
if (amount <= 0) return false; // 校验
balance += amount;
return true;
}
bool withdraw(double amount) {
if (amount > balance) return false; // 余额不足
balance -= amount;
return true;
}
};
int main() {
BankAccount acc("12345", 1000);
// acc.balance = -100; // ❌ 不能直接改
acc.deposit(500); // ✅ 通过方法
cout << acc.getBalance() << endl; // 1500
return 0;
}
三种访问级别
| 关键字 | 本类 | 子类 | 外部 |
|---|---|---|---|
public | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ❌ |
private | ✅ | ❌ | ❌ |
封装最佳实践
- 成员变量默认 private
- 提供 getter/setter 时加校验
- 不变数据可以只读(getter,无 setter)
- 公开接口要稳定,内部可随意改
封装让对象"自治":数据 + 操作数据的规则都封装在类里,外部只能通过接口安全交互。
C++ 接口(抽象类)
掌握 C++ 接口(抽象类):用纯虚类定义契约。
什么是接口
C++ 没有 interface 关键字,用纯虚类(全部方法为纯虚函数)模拟接口:只定义"能做什么",不实现。
接口示例
#include <iostream>
using namespace std;
// 接口:支付能力
class IPayment {
public:
virtual void pay(double amount) = 0; // 纯虚
virtual ~IPayment() {} // 虚析构
};
// 实现接口
class WechatPay : public IPayment {
public:
void pay(double amount) override {
cout << "微信支付 " << amount << " 元" << endl;
}
};
class Alipay : public IPayment {
public:
void pay(double amount) override {
cout << "支付宝支付 " << amount << " 元" << endl;
}
};
// 面向接口编程
void checkout(IPayment& payment, double amount) {
payment.pay(amount); // 传入什么实现就怎么支付
}
int main() {
WechatPay w;
Alipay a;
checkout(w, 100); // 微信支付
checkout(a, 200); // 支付宝
return 0;
}
多接口实现
// 一个类实现多个接口
class IDrawable {
public:
virtual void draw() = 0;
virtual ~IDrawable() {}
};
class ISerializable {
public:
virtual string serialize() = 0;
virtual ~ISerializable() {}
};
class Shape : public IDrawable, public ISerializable {
public:
void draw() override { /* 画 */ }
string serialize() override { return "shape"; }
};
接口的意义
- 解耦:调用方依赖接口而非实现
- 可替换:换实现不影响调用代码
- 可测试:测试时可注入 mock 实现
- 多态:同一接口多种行为
接口命名习惯:I 前缀(IPayment)或 able 后缀(Drawable)。接口是 C++ 架构设计的基石。
C++ 文件和流
掌握 C++ 文件和流:fstream 读写文件。
文件流类
| 类 | 用途 |
|---|---|
ifstream | 读文件 |
ofstream | 写文件 |
fstream | 读写文件 |
写文件
#include <fstream>
using namespace std;
// 方式一:构造时打开
ofstream out("data.txt");
if (!out) {
cerr << "打开失败" << endl;
return;
}
out << "Hello" << endl;
out << 42 << endl;
out.close(); // 显式关闭
// 追加模式
ofstream append("data.txt", ios::app);
append << "追加内容" << endl;
append.close();
读文件
#include <fstream>
#include <string>
using namespace std;
ifstream in("data.txt");
string line;
// 逐行读取
while (getline(in, line)) {
cout << line << endl;
}
// 逐词读取
string word;
while (in >> word) {
cout << word << " ";
}
in.close();
二进制文件
// 写二进制
ofstream out("data.bin", ios::binary);
int num = 42;
out.write(reinterpret_cast<char*>(&num), sizeof(num));
out.close();
// 读二进制
ifstream in("data.bin", ios::binary);
int val;
in.read(reinterpret_cast<char*>(&val), sizeof(val));
in.close();
随机访问
fstream file("data.txt", ios::in | ios::out);
file.seekg(0, ios::end); // 移到文件末尾(读位置)
streampos size = file.tellg();// 获取文件大小
file.seekg(0, ios::beg); // 回到开头
file.seekg(5); // 移到第 5 字节
状态检查
if (in.good()) // 正常
if (in.fail()) // 失败(格式错)
if (in.eof()) // 到达文件尾
if (in.bad()) // 严重错误
文本文件用 << / getline;二进制用 read/write。RAII:文件流对象析构自动关闭。
C++ 异常处理
掌握 C++ 异常处理:try、catch、throw。
基本用法
#include <iostream>
#include <stdexcept>
using namespace std;
double divide(double a, double b) {
if (b == 0) {
throw runtime_error("除数不能为 0"); // 抛出异常
}
return a / b;
}
int main() {
try {
double result = divide(10, 0);
cout << result;
} catch (const exception& e) {
cout << "捕获异常: " << e.what() << endl;
}
return 0;
}
标准异常类
#include <stdexcept>
invalid_argument("参数无效")
out_of_range("越界")
length_error("长度错误")
overflow_error("溢出")
runtime_error("运行时错误")
logic_error("逻辑错误")
// 多 catch(从上到下匹配)
try {
// ...
} catch (const invalid_argument& e) {
// 具体异常先捕获
} catch (const exception& e) {
// 通用异常
} catch (...) {
// 捕获所有(包括非标准异常)
}
自定义异常
class ApiException : public runtime_error {
int statusCode;
public:
ApiException(int code, const string& msg)
: runtime_error(msg), statusCode(code) {}
int getCode() const { return statusCode; }
};
void fetchData() {
throw ApiException(404, "资源不存在");
}
try {
fetchData();
} catch (const ApiException& e) {
cout << "HTTP " << e.getCode() << ": " << e.what();
}
异常安全与 RAII
// 利用 RAII 自动清理(异常发生时也安全)
void func() {
vector<int> v(100); // 异常时自动释放
// 传统 new/delete 需要手动清理,异常可能泄漏
}
// 标准库容器和智能指针都是异常安全的
异常使用建议
- 只用异常处理"异常情况"(不是正常流程)
- 按引用捕获(const exception&)避免切片
- 构造函数失败抛异常(不能返回错误码)
- 避免在析构函数抛异常(会导致 terminate)
现代 C++ 哲学:异常 + RAII 替代手动错误检查。但性能敏感热路径(如高频循环)可能用错误码。
C++ 动态内存
掌握 C++ 动态内存:new/delete 与智能指针。
new / delete
// 分配单个对象
int* p = new int(42); // 分配并初始化
delete p; // 释放
// 分配数组
int* arr = new int[10];
delete[] arr; // 数组用 delete[]
// 分配对象
Student* s = new Student("张三", 20);
s->introduce();
delete s;
⚠️ 内存管理风险
// 内存泄漏:忘记 delete
void leak() {
int* p = new int(42);
// 没有 delete → 泄漏!
}
// 双重释放:delete 两次
int* p = new int(1);
delete p;
delete p; // ❌ 未定义行为!
// 悬垂指针:delete 后继续用
int* p = new int(1);
delete p;
*p = 10; // ❌ 访问已释放内存!
// 解决方案:智能指针(现代 C++ 推荐)
unique_ptr(独占所有权)
#include <memory>
// 独占:同一时间只有一个 owner
unique_ptr<int> p = make_unique<int>(42);
*p; // 42
// 不能拷贝,只能移动
// unique_ptr<int> q = p; // ❌
unique_ptr<int> q = move(p); // ✅ 转移所有权
// 离开作用域自动释放(RAII)
void f() {
auto s = make_unique<Student>("张三", 20);
s->introduce();
// 自动 delete
}
shared_ptr(共享所有权)
// 多个指针共享同一对象(引用计数)
shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<int> p2 = p1; // ✅ 可拷贝
p1.use_count(); // 2(引用计数)
// 最后一个 shared_ptr 销毁时释放对象
p1.reset();
p2.reset(); // 此时对象被释放
weak_ptr(弱引用,防循环)
// 不增加引用计数,观察但不拥有
weak_ptr<int> wp;
{
auto sp = make_shared<int>(42);
wp = sp; // 不增加计数
// sp 销毁后对象释放
}
if (auto sp = wp.lock()) { // lock 获取 shared_ptr
cout << *sp;
} else {
cout << "对象已释放";
}
智能指针选择
| 场景 | 选择 |
|---|---|
| 独占资源 | unique_ptr(默认首选) |
| 多方共享 | shared_ptr |
| 观察/防循环 | weak_ptr |
| 裸指针 | 仅在不拥有资源时用 |
现代 C++ 内存管理:优先栈对象,其次 unique_ptr,少用 new/delete。裸 new/delete 是 90% 内存 bug 的来源。
C++ 命名空间
掌握 C++ 命名空间:组织代码、避免命名冲突。
基本命名空间
#include <iostream>
using namespace std;
namespace Math {
const double PI = 3.14159;
int square(int x) {
return x * x;
}
}
namespace String {
int length(const char* s) {
return strlen(s);
}
}
int main() {
cout << Math::PI << endl; // 作用域符访问
cout << Math::square(5) << endl; // 25
cout << String::length("hi"); // 2
return 0;
}
嵌套与别名
// 嵌套
namespace Outer {
namespace Inner {
int value = 42;
}
}
Outer::Inner::value;
// C++17 嵌套简写
namespace Outer::Inner2 {
int v = 1;
}
// 命名空间别名
namespace M = Math;
M::square(3); // 9
using 声明与指令
// using 声明:只引入某个名字
using Math::square;
square(4); // 直接用
// using 指令:引入整个命名空间
using namespace Math;
PI; // 可用(但可能冲突)
// 建议:小作用域内使用
void f() {
using namespace std; // 函数内 using
cout << "hi";
}
匿名命名空间
// 匿名命名空间:相当于 static(仅本文件可见)
namespace {
int filePrivateVar = 10; // 文件私有
void helper() { /* 仅本文件可用 */ }
}
std 命名空间
// C++ 标准库都在 std 中
std::cout
std::vector
std::string
std::make_unique
// 建议:用 std:: 前缀而非 using namespace std
//(避免与全局命名冲突)
大型项目规范:自己的代码放独立命名空间(如 com::codenest::utils),避免与第三方库冲突。
C++ 模板
掌握 C++ 模板:泛型编程的核心。
函数模板
#include <iostream>
using namespace std;
// 函数模板:T 是类型参数
template <typename T>
T maxValue(T a, T b) {
return a > b ? a : b;
}
int main() {
cout << maxValue(3, 7) << endl; // 7(int)
cout << maxValue(2.5, 1.5) << endl; // 2.5(double)
cout << maxValue('a', 'z') << endl; // z(char)
return 0;
}
类模板
template <typename T>
class Box {
private:
T value;
public:
Box(T v) : value(v) {}
T get() const { return value; }
void set(T v) { value = v; }
};
Box<int> intBox(42);
Box<string> strBox("hello");
cout << intBox.get() << " " << strBox.get();
多个类型参数
template <typename T, typename U>
pair<T, U> makePair(T a, U b) {
return {a, b};
}
auto p = makePair(1, "one"); // pair<int, const char*>
模板特化
// 通用版本
template <typename T>
string typeName() { return "未知"; }
// 特化:int 版本
template <>
string typeName<int>() { return "int"; }
// 部分特化(类模板)
template <typename T>
class Box<T*> { // 指针特化
// 特殊处理指针
};
非类型模板参数
template <typename T, int N>
class FixedArray {
T data[N]; // 编译期确定大小
public:
int size() const { return N; }
};
FixedArray<int, 10> arr;
arr.size(); // 10
STL 就是模板
vector<int> // vector 是类模板
list<string>
map<string, int>
sort(v.begin(), v.end()) // sort 是函数模板
模板是 C++"零成本抽象"的体现:编译期生成具体类型代码,无运行时开销。STL 是学习模板的最佳范例。
C++ 预处理器
掌握 C++ 预处理器:编译前处理的指令。
预处理器指令
| 指令 | 作用 |
|---|---|
#include | 包含头文件 |
#define | 定义宏 |
#ifdef / #ifndef | 条件编译 |
#if / #else / #endif | 条件编译 |
#pragma | 编译器指令 |
#undef | 取消宏定义 |
头文件保护(最重要)
// myclass.h
#ifndef MYCLASS_H // 如果没有定义
#define MYCLASS_H // 定义(防止重复包含)
class MyClass {
// ...
};
#endif // 结束
宏定义
// 对象宏
#define MAX_SIZE 100
#define PI 3.14159
// 函数宏(慎用,有副作用风险)
#define SQUARE(x) ((x) * (x))
// 现代 C++ 用 constexpr 替代
constexpr int MAX_SIZE = 100;
条件编译
#define DEBUG
#ifdef DEBUG
cout << "调试信息" << endl;
#endif
#ifndef RELEASE
cout << "开发模式" << endl;
#endif
// 平台判断
#ifdef _WIN32
// Windows 代码
#elif defined(__APPLE__)
// macOS 代码
#elif defined(__linux__)
// Linux 代码
#endif
预定义宏
__FILE__ // 当前文件名
__LINE__ // 当前行号
__DATE__ // 编译日期
__TIME__ // 编译时间
__cplusplus // C++ 版本
// 调试打印
#define LOG(msg) cout << __FILE__ << ":" << __LINE__ << " " << msg << endl;
LOG("开始执行");
头文件保护(#ifndef)必不可少;宏尽量少用,用 constexpr/inline 替代。编译时可用 -D DEBUG 定义宏。
C++ 信号处理
掌握 C++ 信号处理:<csignal> 处理程序信号。
常见信号
#include <csignal>
SIGINT // Ctrl+C 中断
SIGTERM // 终止请求
SIGSEGV // 段错误(非法内存访问)
SIGABRT // 异常终止
SIGFPE // 除零等算术错误
注册信号处理器
#include <iostream>
#include <csignal>
using namespace std;
volatile sig_atomic_t g_flag = 0;
void signalHandler(int signum) {
cout << "收到信号: " << signum << endl;
g_flag = 1; // 设置标志
// 清理资源...
exit(signum);
}
int main() {
// 注册 SIGINT(Ctrl+C)处理器
signal(SIGINT, signalHandler);
cout << "按 Ctrl+C 退出..." << endl;
while (true) {
// 主循环
}
return 0;
}
raise 发送信号
#include <csignal>
// 向当前进程发送信号
raise(SIGINT);
// 向指定进程发送
// kill(pid, SIGTERM); // POSIX
注意
- 信号处理器中只能调用"异步安全"的函数
- 避免在处理器中做复杂操作(malloc、cout 等可能不安全)
- 用 volatile sig_atomic_t 做标志通信
- 现代 C++ 更推荐信号量/原子变量管理
生产代码中,优雅关闭(SIGTERM/SIGINT 时保存状态)是常见需求;但处理器逻辑务必简单。
C++ 多线程
掌握 C++ 多线程:<thread> 与并发编程。
创建线程
#include <iostream>
#include <thread>
using namespace std;
void worker(int id) {
cout << "线程 " << id << " 运行中" << endl;
}
int main() {
thread t1(worker, 1); // 创建并启动线程
thread t2(worker, 2);
t1.join(); // 等待线程结束
t2.join();
cout << "主线程继续" << endl;
return 0;
}
join 与 detach
thread t(worker, 1);
t.join(); // 阻塞等待线程完成
// t.detach(); // 分离:后台运行(不等待)
// 判断是否可 join
if (t.joinable()) {
t.join();
}
数据竞争问题
#include <mutex>
int counter = 0;
mutex mtx; // 互斥锁
void increment() {
for (int i = 0; i < 100000; i++) {
lock_guard<mutex> lock(mtx); // RAII 锁
counter++; // 临界区
} // 自动解锁
}
原子操作
#include <atomic>
atomic<int> counter{0}; // 原子变量
void increment() {
for (int i = 0; i < 100000; i++) {
counter++; // 原子操作,无需锁
}
}
// 无锁且安全,性能好
条件变量
#include <condition_variable>
mutex mtx;
condition_variable cv;
bool ready = false;
void waiter() {
unique_lock<mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件
cout << "条件满足,继续执行" << endl;
}
void notifier() {
{
lock_guard<mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知一个等待线程
}
future / async(推荐)
#include <future>
// 异步执行并获取结果
int compute(int x) { return x * x; }
future<int> result = async(launch::async, compute, 6);
cout << result.get() << endl; // 36(阻塞等待结果)
多线程注意事项
- 共享数据必须同步(锁/原子)
- 避免死锁(锁顺序一致、用 lock_guard)
- 优先 async/future(比手写线程简单)
- 线程数不要超过硬件并发数
C++ 并发三件套:thread + mutex/atomic + future。能用 async 就别手写线程,能原子就别用锁。
C++ Web 编程
掌握 C++ Web 编程:C++ 与 Web 开发的结合方式。
C++ 在 Web 中的角色
| 角色 | 说明 |
|---|---|
| Web 框架 | Crow、Drogon、cpprestsdk |
| 底层服务 | Nginx 模块、高并发网关 |
| HTTP 客户端 | libcurl、cpprestsdk |
| WebSocket | websocketpp |
| JSON 处理 | nlohmann/json、RapidJSON |
HTTP 客户端(libcurl)
#include <curl/curl.h>
#include <iostream>
#include <string>
using namespace std;
size_t writeCallback(void* data, size_t size, size_t nmemb, string* out) {
out->append(static_cast<char*>(data), size * nmemb);
return size * nmemb;
}
int main() {
CURL* curl = curl_easy_init();
if (!curl) return 1;
string response;
curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/users");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK) {
cout << response << endl;
}
curl_easy_cleanup(curl);
return 0;
}
JSON 处理(nlohmann/json)
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// 解析
json data = json::parse("{"name":"张三","age":25}");
string name = data["name"];
int age = data["age"];
// 构造
json obj;
obj["name"] = "李四";
obj["age"] = 30;
obj["tags"] = {"cpp", "web"};
string str = obj.dump(); // 序列化
Web 框架(Drogon 示例)
#include <drogon/drogon.h>
using namespace drogon;
int main() {
app().registerHandler("/hello",
[](const HttpRequestPtr&, std::function<void(const HttpResponsePtr&)>&& callback) {
auto resp = HttpResponse::newHttpResponse();
resp->setBody("Hello from C++!");
callback(resp);
},
{Get});
app().addListener("0.0.0.0", 8080).run();
return 0;
}
多数 Web 场景用 Python/Node/Go 更高效;C++ 用于性能瓶颈模块(网关、推荐引擎、图像处理服务)。
C++ STL 教程
掌握 C++ STL 教程:标准模板库全景。
STL 组成
| 组件 | 说明 | 示例 |
|---|---|---|
| 容器 | 存储数据的结构 | vector、list、map、set |
| 算法 | 通用操作 | sort、find、copy |
| 迭代器 | 访问容器的接口 | begin()、end() |
| 函数对象 | 可调用对象 | greater、less |
| 适配器 | 接口转换 | stack、queue |
容器分类
序列容器:vector / list / deque / array / forward_list
关联容器:set / map / multiset / multimap
无序容器:unordered_set / unordered_map
容器适配器:stack / queue / priority_queue
算法示例
#include <algorithm>
#include <numeric>
vector<int> v = {5, 2, 8, 1, 9};
sort(v.begin(), v.end()); // 排序 [1,2,5,8,9]
reverse(v.begin(), v.end()); // 反转
auto it = find(v.begin(), v.end(), 5); // 查找
int cnt = count(v.begin(), v.end(), 2); // 计数
int sum = accumulate(v.begin(), v.end(), 0); // 求和
auto mx = max_element(v.begin(), v.end()); // 最大值
// 复制
vector<int> copy;
copy.resize(v.size());
copy(v.begin(), v.end(), copy.begin());
// Lambda 算法
sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 降序
STL 的优势
- 通用:一套代码适配所有容器
- 高效:经过工业级优化
- 标准:跨平台一致行为
- 类型安全:模板编译期检查
STL 是 C++ 的"标准零件库":先学容器,再学算法,最后理解迭代器如何连接两者。
C++ 导入标准库
掌握 C++ 导入标准库:include 与 import(C++20 模块)。
传统 #include
#include <iostream> // 尖括号:标准库
#include "myheader.h" // 引号:本地文件
// 常用标准库头文件
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <memory>
C++20 模块(import)
// 现代模块化方式(C++20+)
import std; // 导入标准库模块
int main() {
std::vector<int> v = {1, 2, 3};
return 0;
}
// 模块优势:编译更快、封装更好、无头文件
// 编译器支持:MSVC / GCC 14+ / Clang 17+
头文件最佳实践
- 头文件加防重复包含保护
- 头文件尽量少 include(前向声明)
- 头文件只放声明,实现放 .cpp
- 用 include guards 或 #pragma once
// 前向声明(减少依赖)
class MyClass; // 只声明,不 include
void usePointer(MyClass* p); // 指针可以前向声明
// 需要完整定义时再 include
理解 #include 的"文本替换"本质:预处理时把头文件内容粘贴进来。C++20 模块是未来方向,但 #include 仍为主流。
C++ 标准库
掌握 C++ 标准库:常用头文件与功能速查。
核心头文件
| 头文件 | 内容 |
|---|---|
<iostream> | 输入输出(cin/cout) |
<string> | std::string |
<vector> | 动态数组 |
<map> / <unordered_map> | 键值容器 |
<algorithm> | 通用算法(sort/find) |
<memory> | 智能指针 |
<thread> / <mutex> | 多线程 |
<fstream> | 文件流 |
<cmath> | 数学函数 |
<chrono> | 时间 |
std::string 常用
string s = "Hello";
s.size(); s.substr(0, 2); s.find("l");
s += "!"; s.replace(0, 1, "J");
智能指针
auto u = make_unique<int>(42); // 独占
auto s = make_shared<int>(42); // 共享
weak_ptr<int> w = s; // 弱引用
算法速查
sort / reverse / find / count
min / max / min_element / max_element
accumulate / copy / remove / replace
标准库就是"轮子仓库":写代码前先想想标准库有没有现成的,避免重复造轮子。
C++ 有用的资源
C++ 有用的资源:学习资料与工具推荐。
官方文档
| 资源 | 链接/说明 |
|---|---|
| cppreference | cppreference.com(最权威) |
| isocpp | isocpp.org(标准与 FAQ) |
| cppinsights | cppinsights.io(模板展开可视化) |
书籍推荐
- C++ Primer:入门经典(第 5 版)
- Effective Modern C++:最佳实践
- Effective C++:55 条建议
- The C++ Programming Language:作者原著
在线练习
- LeetCode:算法题(C++ 提交)
- Codeforces:竞赛
- Compiler Explorer(godbolt.org):在线编译
工具链
- 编译器:GCC、Clang、MSVC
- 构建:CMake(事实标准)
- 依赖:vcpkg、Conan
- 分析:Valgrind(内存)、perf(性能)
- 格式化:clang-format、clang-tidy
学习路线建议:语法基础 → STL → 面向对象 → 模板/现代特性 → 项目实战。配合 cppreference 随时查证。
C++ 实例
C++ 实例:常见编程实例练习。
1. 判断素数
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
2. 斐波那契
long long fib(int n) {
if (n <= 1) return n;
long long a = 0, b = 1;
for (int i = 2; i <= n; i++) {
long long t = a + b;
a = b;
b = t;
}
return b;
}
3. 字符串反转
string reverse(string s) {
reverse(s.begin(), s.end()); // STL 一行
return s;
}
4. 统计字符频率
map<char, int> countChars(const string& s) {
map<char, int> freq;
for (char c : s) freq[c]++;
return freq;
}
5. vector 排序去重
vector<int> uniqueSort(vector<int> v) {
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
return v;
}
6. 二分查找
int binarySearch(vector<int>& arr, int target) {
int left = 0, right = arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
7. 两数之和(哈希)
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> seen;
for (int i = 0; i < nums.size(); i++) {
int need = target - nums[i];
if (seen.count(need)) return {seen[need], i};
seen[nums[i]] = i;
}
return {};
}
动手敲这些实例,体会 STL 容器与算法的配合——它们是算法面试的高频题型。
C++ 测验
本章测验检验你对 C++ 的掌握。先独立作答,再看解析。
选择题
1. C++ 程序执行的入口函数是?
- A.
start() - B.
main()✅ - C.
run() - D.
init()
2. 哪个是引用传递参数的写法?
- A.
void f(int x) - B.
void f(int* x) - C.
void f(int& x)✅ - D.
void f(const x)
3. 多态依赖的关键字是?
- A.
static - B.
virtual✅ - C.
extern - D.
register
4. 动态数组容器是?
- A.
array - B.
vector✅ - C.
list - D.
map
5. 释放 new[] 分配的内存用?
- A.
delete - B.
delete[]✅ - C.
free - D.
clear
6. 独占所有权的智能指针是?
- A.
shared_ptr - B.
unique_ptr✅ - C.
weak_ptr - D.
auto_ptr
判断题
7. C++ 中 struct 与 class 默认访问权限相同。
❌ 错误。struct 默认 public,class 默认 private。
8. 基类析构函数应为 virtual。
✅ 正确。否则 delete 派生对象不会调用派生类析构,造成泄漏。
简答题
9. 写出 vector 排序的代码。
#include <algorithm>
sort(v.begin(), v.end());
10. 什么是 RAII?
RAII(Resource Acquisition Is Initialization):资源在构造时获取,析构时释放(如 lock_guard、unique_ptr)。异常安全、自动清理。
计分
| 答对 | 水平 |
|---|---|
| 9-10 | 优秀,可投入实战 |
| 6-8 | 良好,查漏补缺 |
| <6 | 建议重学基础章节 |
C++ 要"多用多写":编译错误是最好的老师,多看编译器报错信息能快速进步。
C++ <iostream>
掌握 C++ <iostream>:标准输入输出流。
核心对象
| 对象 | 用途 |
|---|---|
std::cin | 标准输入(键盘) |
std::cout | 标准输出(屏幕) |
std::cerr | 错误输出(不缓冲) |
std::clog | 日志输出(缓冲) |
输出
#include <iostream>
using namespace std;
cout << "Hello" << endl; // 换行并刷新
cout << 42 << " " << 3.14; // 多值输出
cout << "值: " << x << "\n"; // 快速换行
// 格式化
#include <iomanip>
cout << fixed << setprecision(2) << 3.14159; // 3.14
cout << setw(5) << setfill('0') << 42; // 00042
输入
int age;
string name;
cin >> age; // 读数字
cin >> name; // 读一个词
getline(cin, name); // 读整行
// 循环读取直到 EOF
int n;
while (cin >> n) {
cout << n * 2 << endl;
}
流状态
if (cin) // 流正常
if (cin.fail()) // 读取失败
if (cin.eof()) // 文件末尾
// 清除错误并继续
cin.clear();
cin.ignore(10000, '
');
endl 会刷新缓冲区(慢),大量输出用 "\n"。调试用 cerr(不缓冲,实时显示)。
C++ <fstream>
掌握 C++ <fstream>:文件流。
文件流类
#include <fstream>
using namespace std;
ifstream in; // 读
ofstream out; // 写
fstream f; // 读写
写文件
ofstream out("data.txt"); // 打开(覆盖)
if (!out) { cerr << "打开失败"; return; }
out << "第一行" << endl;
out << 42 << " " << 3.14 << endl;
out.close(); // 关闭
// 追加模式
ofstream app("log.txt", ios::app);
app << "新日志" << endl;
读文件
ifstream in("data.txt");
string line;
while (getline(in, line)) {
cout << line << endl; // 逐行
}
int a, b;
while (in >> a >> b) { // 按格式读
cout << a + b << endl;
}
二进制
ofstream out("bin.dat", ios::binary);
int n = 100;
out.write(reinterpret_cast<char*>(&n), sizeof(n));
out.close();
ifstream in("bin.dat", ios::binary);
in.read(reinterpret_cast<char*>(&n), sizeof(n));
随机访问
fstream f("data.txt", ios::in | ios::out);
f.seekg(0, ios::end); // 移到末尾
streampos size = f.tellg(); // 文件大小
f.seekg(0, ios::beg); // 回到开头
文件流对象离开作用域自动关闭(RAII);但显式 close() 更清晰,能及时释放文件句柄。
C++ <sstream>
掌握 C++ <sstream>:字符串流。
stringstream 基础
#include <sstream>
using namespace std;
// 拼接
stringstream ss;
ss << "Hello" << " " << 42 << " " << 3.14;
string result = ss.str(); // "Hello 42 3.14"
// 清空复用
ss.str(""); // 重置内容
ss.clear(); // 清除状态
解析(字符串 → 数字)
string data = "123 456 789";
stringstream ss(data);
int a, b, c;
ss >> a >> b >> c; // 依次提取
// a=123, b=456, c=789
// 按分隔符解析
string line = "1,2,3,4";
stringstream ls(line);
string token;
while (getline(ls, token, ',')) { // 逗号分割
cout << token << " "; // 1 2 3 4
}
数字转字符串
// 方式一:to_string
string s1 = to_string(42); // "42"
// 方式二:stringstream(可控制格式)
stringstream ss;
ss << fixed << setprecision(2) << 3.14159;
string s2 = ss.str(); // "3.14"
应用:解析 CSV
vector<vector<string>> parseCSV(const string& content) {
vector<vector<string>> rows;
stringstream ss(content);
string row;
while (getline(ss, row, '
')) {
vector<string> cols;
stringstream rs(row);
string col;
while (getline(rs, col, ',')) {
cols.push_back(col);
}
rows.push_back(cols);
}
return rows;
}
stringstream 是"字符串与数字互转、按分隔符解析"的利器,比手写字符串处理简洁得多。
C++ <iomanip>
掌握 C++ <iomanip>:输入输出格式化。
常用操作符
| 操作符 | 作用 |
|---|---|
setw(n) | 设置宽度 |
setfill(c) | 填充字符 |
setprecision(n) | 小数位数 |
fixed | 固定小数表示 |
scientific | 科学计数法 |
left / right | 左/右对齐 |
hex / oct / dec | 进制 |
boolalpha | true/false 输出 |
showpos | 显示正号 |
示例
#include <iostream>
#include <iomanip>
using namespace std;
double pi = 3.14159265;
// 精度
cout << setprecision(3) << pi; // 3.14
cout << fixed << setprecision(2) << pi; // 3.14
cout << scientific << pi; // 3.141593e+00
// 宽度与填充
cout << setw(10) << 42 << endl; // " 42"
cout << setw(10) << setfill('0') << 42; // "0000000042"
cout << left << setw(10) << "Name" << "|" << endl;
// 进制
cout << hex << 255 << endl; // ff
cout << oct << 8 << endl; // 10
cout << dec << 10 << endl; // 10
// 布尔
cout << boolalpha << true; // true
表格对齐输出
cout << left
<< setw(15) << "姓名"
<< setw(10) << "年龄"
<< setw(10) << "分数" << endl;
cout << left
<< setw(15) << "张三"
<< setw(10) << 20
<< setw(10) << 88.5 << endl;
setw 只对下一次输出有效;setfill/fixed/setprecision 持续有效(直到改变)。
C++<array>
掌握 C++ <array>:固定大小数组容器。
基本用法
#include <array>
using namespace std;
array<int, 5> a = {1, 2, 3, 4, 5}; // 固定大小 5
a.size(); // 5
a[0]; // 1(不检查边界)
a.at(0); // 1(越界抛异常)
a.front(); // 1
a.back(); // 5
a.fill(0); // 全部填 0
a.empty(); // 是否为空(固定大小总是 false)
遍历
for (int x : a) { cout << x; }
for (auto it = a.begin(); it != a.end(); ++it) { }
for (size_t i = 0; i < a.size(); i++) { cout << a[i]; }
array vs 裸数组 vs vector
| 对比项 | std::array | 裸数组 | vector |
|---|---|---|---|
| 大小 | 固定 | 固定 | 动态 |
| 安全 | at() 检查 | 无 | at() 检查 |
| STL 兼容 | ✅ | ❌ | ✅ |
| 推荐 | ✅ 固定大小 | C 兼容 | 动态大小 |
算法配合
#include <algorithm>
array<int, 4> a = {3, 1, 4, 2};
sort(a.begin(), a.end()); // 排序
reverse(a.begin(), a.end()); // 反转
// 与裸数组互转
int* raw = a.data(); // 获取底层指针
固定大小且需要 STL 功能时用 std::array;它没有裸数组的"退化为指针"问题,更安全。
C++ <vector>
掌握 C++ <vector>:动态数组容器(STL 最常用)。
创建
#include <vector>
using namespace std;
vector<int> v1; // 空
vector<int> v2(5); // 5 个 0
vector<int> v3(5, 10); // 5 个 10
vector<int> v4 = {1, 2, 3}; // 列表初始化
vector<int> v5(v4.begin(), v4.end()); // 范围拷贝
操作
vector<int> v = {1, 2, 3};
v.push_back(4); // 尾部添加
v.emplace_back(5); // 尾部构造添加
v.pop_back(); // 尾部删除
v.insert(v.begin(), 0); // 头部插入
v.erase(v.begin()); // 头部删除
v.clear(); // 清空
v.size(); // 元素个数
v.capacity(); // 容量(已分配)
v.empty(); // 是否空
v.reserve(100); // 预分配容量(优化)
v[0]; v.at(0); v.front(); v.back();
扩容机制
// 容量不足时自动扩容(通常翻倍)
// 频繁添加时预先 reserve 避免反复扩容
vector<int> v;
v.reserve(1000); // 预分配 1000 容量
for (int i = 0; i < 1000; i++) {
v.push_back(i); // 不会触发扩容
}
二维 vector
// 3 行 4 列,全 0
vector<vector<int>> matrix(3, vector<int>(4, 0));
matrix[1][2] = 9;
for (const auto& row : matrix) {
for (int x : row) cout << x << " ";
cout << endl;
}
算法配合
#include <algorithm>
#include <numeric>
sort(v.begin(), v.end());
reverse(v.begin(), v.end());
find(v.begin(), v.end(), 3);
accumulate(v.begin(), v.end(), 0); // 求和
vector 是 C++ 的"默认容器":内存连续、随机访问快、自动扩容。95% 的动态数组场景用它。
C++ <list>
掌握 C++ <list>:双向链表容器。
基本操作
#include <list>
using namespace std;
list<int> l = {1, 2, 3};
l.push_back(4); // 尾部添加
l.push_front(0); // 头部添加
l.pop_back(); // 尾部删除
l.pop_front(); // 头部删除
l.size(); l.empty();
l.front(); l.back();
l.insert(l.begin(), 99); // 指定位置插入
l.erase(l.begin()); // 指定位置删除
l.clear();
list 特有操作
list<int> a = {1, 2, 3};
list<int> b = {4, 5};
a.splice(a.end(), b); // 拼接(O(1),转移节点)
a.sort(); // 排序(list 自己的 sort)
a.reverse(); // 反转
a.unique(); // 去重(相邻相同)
a.remove(2); // 删除所有值=2
a.merge(b); // 合并(需都已排序)
list vs vector
| 对比项 | list | vector |
|---|---|---|
| 内存 | 分散节点 | 连续 |
| 随机访问 | O(n) 慢 | O(1) 快 |
| 头部插入 | O(1) 快 | O(n) 慢 |
| 缓存友好 | 差 | 好 |
| 适用 | 频繁中段增删 | 随机访问为主 |
注意:list 无随机访问
// list 不支持 l[3](无 operator[])
// 需要遍历到目标位置
auto it = l.begin();
advance(it, 3); // 前进 3 步
list 的节点分散导致缓存不友好,性能常不如 vector。默认用 vector;只有大量"中间插入/删除"才考虑 list。
C++ <forward_list>
掌握 C++ <forward_list>:单向链表(C++11+)。
基本操作
#include <forward_list>
using namespace std;
forward_list<int> fl = {1, 2, 3};
fl.push_front(0); // 头部添加(只能头部!)
fl.pop_front(); // 头部删除
fl.front(); // 第一个元素
fl.insert_after(fl.begin(), 99); // 在指定位置后插入
fl.erase_after(fl.begin()); // 删除指定位置后的元素
// 没有 size()!需要遍历统计
int n = distance(fl.begin(), fl.end());
forward_list vs list
| 对比项 | forward_list | list |
|---|---|---|
| 方向 | 单向 | 双向 |
| 内存 | 更小(少一个指针) | 较大 |
| 反向遍历 | ❌ | ✅ |
| size() | ❌ | ✅ O(1) |
| 适用 | 内存敏感的单向链表 | 通用双向链表 |
遍历
for (int x : fl) { cout << x; }
// 前驱访问(单向链表的特色)
auto prev = fl.before_begin(); // 第一个元素之前的位置
auto cur = fl.begin();
while (cur != fl.end()) {
if (*cur == 99) {
fl.erase_after(prev); // 通过前驱删除
break;
}
prev = cur;
++cur;
}
forward_list 是最省内存的链表,但功能最少。日常开发 list 或 vector 更常用;它主要出现在内存极度敏感的场景。
C++ <deque>
掌握 C++ <deque>:双端队列容器。
基本操作
#include <deque>
using namespace std;
deque<int> d = {1, 2, 3};
d.push_back(4); // 尾部添加
d.push_front(0); // 头部添加
d.pop_back(); // 尾部删除
d.pop_front(); // 头部删除
d[0]; // 随机访问(O(1))
d.at(0);
d.front(); d.back();
d.size(); d.empty();
d.insert(d.begin() + 1, 99);
d.erase(d.begin() + 1);
deque vs vector vs list
| 对比项 | deque | vector | list |
|---|---|---|---|
| 头尾操作 | O(1) 都快 | 头慢尾快 | 都快 |
| 随机访问 | O(1) | O(1) | O(n) |
| 内存 | 分块连续 | 连续 | 分散 |
| 适用 | 头尾都要增删 | 尾部为主 | 中段增删 |
遍历
for (int x : d) { cout << x; }
for (size_t i = 0; i < d.size(); i++) { cout << d[i]; }
for (auto it = d.begin(); it != d.end(); ++it) { }
典型应用
// 滑动窗口(头尾都操作)
deque<int> window;
// 双端队列实现单调队列(滑动窗口最值)
while (!d.empty() && d.back() < nums[i]) {
d.pop_back(); // 保持单调递减
}
d.push_back(i);
需要"头尾都能高效增删"时用 deque(如滑动窗口、双端操作)。它比 vector 的头部插入高效得多。
C++ <stack>
掌握 C++ <stack>:栈容器适配器(LIFO)。
基本操作
#include <stack>
using namespace std;
stack<int> st;
st.push(1); // 入栈
st.push(2);
st.push(3);
st.top(); // 3(栈顶,不移除)
st.pop(); // 出栈(移除栈顶)
st.size(); // 元素个数
st.empty(); // 是否为空
经典应用
// 括号匹配
bool isValid(string s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else {
if (st.empty()) return false;
char top = st.top();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
st.pop();
}
}
return st.empty();
}
// 逆波兰表达式求值、函数调用栈模拟
// 浏览器的前进后退(两个栈)
底层容器
// 默认 deque,可指定底层
stack<int> s1; // deque 实现
stack<int, vector<int>> s2; // vector 实现
stack<int, list<int>> s3; // list 实现
注意
- 不能遍历 stack(只能访问栈顶)
- pop() 不返回值(先 top 再 pop)
- 访问空栈的 top 是未定义行为
栈的思想:后进先出。括号匹配、递归转循环、撤销操作都是栈的典型应用。
C++ <queue>
掌握 C++ <queue>:队列容器适配器(FIFO)。
基本操作
#include <queue>
using namespace std;
queue<int> q;
q.push(1); // 入队(尾部)
q.push(2);
q.push(3);
q.front(); // 1(队头)
q.back(); // 3(队尾)
q.pop(); // 出队(移除队头)
q.size(); // 大小
q.empty(); // 是否为空
经典应用:BFS
// 广度优先搜索
void bfs(vector<vector<int>>& graph, int start) {
queue<int> q;
vector<bool> visited(graph.size(), false);
q.push(start);
visited[start] = true;
while (!q.empty()) {
int node = q.front();
q.pop();
cout << node << " ";
for (int next : graph[node]) {
if (!visited[next]) {
visited[next] = true;
q.push(next);
}
}
}
}
其他应用
- 消息队列(生产者-消费者)
- 任务调度(FIFO)
- 树的层序遍历
- 打印缓冲
底层容器
queue<int> q1; // 默认 deque
queue<int, list<int>> q2; // list 实现
队列"先进先出":BFS、排队处理、流式任务都是它的主场。记住 front/pop 配合使用(pop 不返回值)。
C++ <priority_queue>
掌握 C++ <priority_queue>:优先队列(堆)。
基本操作
#include <queue>
using namespace std;
// 最大堆(默认):top 是最大值
priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(5);
pq.top(); // 5(最大值)
pq.pop(); // 移除最大值
pq.size(); pq.empty();
最小堆
// 最小堆:top 是最小值
priority_queue<int, vector<int>, greater<int>> minPq;
minPq.push(3);
minPq.push(1);
minPq.top(); // 1
自定义比较
struct Task {
int priority;
string name;
};
// 自定义优先级:priority 大的先出
struct TaskCmp {
bool operator()(const Task& a, const Task& b) {
return a.priority < b.priority; // 大顶堆
}
};
priority_queue<Task, vector<Task>, TaskCmp> taskQueue;
经典应用
// 前 K 大元素
vector<int> topK(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap;
for (int n : nums) {
minHeap.push(n);
if (minHeap.size() > k) minHeap.pop(); // 保持 K 个
}
vector<int> result;
while (!minHeap.empty()) {
result.push_back(minHeap.top());
minHeap.pop();
}
return result;
}
- Top K 问题
- 合并 K 个有序链表
- 任务调度(按优先级)
- Dijkstra 最短路
priority_queue 默认最大堆;要最小堆加 greater<int>;自定义类型重载 < 或提供比较器。
C++ <set>
掌握 C++ <set>:有序集合(红黑树实现)。
基本操作
#include <set>
using namespace std;
set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(1); // 重复被忽略
s.size(); // 3
s.empty();
s.count(2); // 1(存在性判断)
s.find(2); // 迭代器(end() 表示未找到)
s.erase(2); // 删除
s.clear();
有序遍历
// 自动升序
for (int x : s) { cout << x << " "; } // 1 2 3
// 降序
for (auto it = s.rbegin(); it != s.rend(); ++it) { }
区间操作
// lower_bound:第一个 >= val 的位置
auto it = s.lower_bound(2);
// upper_bound:第一个 > val 的位置
auto it2 = s.upper_bound(2);
// 范围元素个数
int cnt = distance(s.lower_bound(2), s.upper_bound(5));
set vs unordered_set
| 对比项 | set(有序) | unordered_set(哈希) |
|---|---|---|
| 查找 | O(log n) | O(1) 平均 |
| 顺序 | 有序 | 无序 |
| 实现 | 红黑树 | 哈希表 |
| 适用 | 需要有序/范围 | 只要去重查找 |
应用
// 去重
vector<int> v = {1, 2, 2, 3, 3, 3};
set<int> uniqueSet(v.begin(), v.end());
// 1 2 3
// 有序去重后转回 vector
vector<int> result(uniqueSet.begin(), uniqueSet.end());
set 自动排序 + 去重;multiset 允许重复;需要 O(1) 查找且不在乎顺序用 unordered_set。
C++ <unordered_set>
掌握 C++ <unordered_set>:哈希集合(C++11+)。
基本操作
#include <unordered_set>
using namespace std;
unordered_set<int> us;
us.insert(3);
us.insert(1);
us.insert(3); // 重复忽略
us.count(3); // 1(O(1) 查找)
us.find(1); // 迭代器
us.erase(1);
us.size(); us.empty();
us.clear();
无序遍历
// 顺序不确定(哈希)
for (int x : us) { cout << x << " "; }
// 遍历同时删除
for (auto it = us.begin(); it != us.end();) {
if (*it % 2 == 0) it = us.erase(it); // C++11 返回下一个
else ++it;
}
性能
// 平均 O(1) 查找/插入/删除
// 最坏 O(n)(哈希冲突)
// 内存比 set 大(哈希表开销)
// 自定义哈希(自定义类型)
struct Person {
string name;
bool operator==(const Person& o) const {
return name == o.name;
}
};
struct PersonHash {
size_t operator()(const Person& p) const {
return hash<string>()(p.name);
}
};
unordered_set<Person, PersonHash> people;
应用场景
- 去重(不在乎顺序)
- 快速存在性检查(O(1))
- 集合运算(交集/并集/差集)
只做"去重 + 快速查找"用 unordered_set(O(1));需要有序输出或范围查询用 set(O(log n))。
C++ <map>
掌握 C++ <map>:有序键值对容器(红黑树)。
基本操作
#include <map>
using namespace std;
map<string, int> scores;
// 插入/更新
scores["张三"] = 90; // 插入
scores["李四"] = 85;
scores["张三"] = 95; // 更新
// 或 insert(已存在不覆盖)
scores.insert({"王五", 70});
// 查找
scores.count("张三"); // 1
auto it = scores.find("李四");
if (it != scores.end()) {
cout << it->second; // 85
}
// 删除
scores.erase("王五");
scores.size(); scores.empty();
有序遍历(按键升序)
for (const auto& [key, value] : scores) { // C++17 结构化绑定
cout << key << ": " << value << endl;
}
// 传统迭代器
for (auto it = scores.begin(); it != scores.end(); ++it) {
cout << it->first << "=" << it->second;
}
经典应用:计数
// 统计词频
map<string, int> freq;
for (const string& word : words) {
freq[word]++; // 不存在则初始化为 0 再 ++
}
// 输出按词频排序的结果
vector<pair<string,int>> vec(freq.begin(), freq.end());
sort(vec.begin(), vec.end(), [](auto& a, auto& b) {
return a.second > b.second; // 按频率降序
});
map vs unordered_map
| 对比项 | map | unordered_map |
|---|---|---|
| 查找 | O(log n) | O(1) 平均 |
| 有序 | ✅ 按键排序 | ❌ 无序 |
| 内存 | 较小 | 较大 |
| 适用 | 要顺序/范围查询 | 只要快速查找 |
区间查询
// [lower, upper) 之间的键
auto lo = scores.lower_bound("A");
auto hi = scores.upper_bound("M");
for (auto it = lo; it != hi; ++it) {
cout << it->first << " ";
}
map 按键自动排序;operator[] 访问不存在的键会自动插入(注意副作用),用 count/find 先判断。
C++ <unordered_map>
掌握 C++ <unordered_map>:哈希映射(C++11+)。
基本操作
#include <unordered_map>
using namespace std;
unordered_map<string, int> um;
um["apple"] = 5; // 插入
um["banana"] = 3;
um["apple"] = 8; // 更新
um.count("apple"); // 1(O(1) 存在性)
auto it = um.find("banana");
if (it != um.end()) {
cout << it->second;
}
um.erase("apple");
um.size(); um.empty();
遍历(无序)
for (const auto& [k, v] : um) {
cout << k << "=" << v << endl;
}
// 遍历删除
for (auto it = um.begin(); it != um.end();) {
if (it->second < 5) it = um.erase(it);
else ++it;
}
经典应用:两数之和
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> seen; // 值 → 索引
for (int i = 0; i < nums.size(); i++) {
int need = target - nums[i];
if (seen.count(need)) {
return {seen[need], i};
}
seen[nums[i]] = i;
}
return {};
}
应用场景
- 高频键值查找(O(1))
- 缓存(LRU 的查找部分)
- 去重 + 计数
- 图(邻接表)
性能注意
// 预分配 bucket 减少 rehash
unordered_map<string, int> um;
um.reserve(10000);
// 自定义哈希(同 unordered_set 类似)
需要 O(1) 键值查找选 unordered_map;需要按键有序遍历选 map。多数"字典"场景 unordered_map 是默认。
C++ <bitset>
掌握 C++ <bitset>:位集容器。
基本用法
#include <bitset>
using namespace std;
bitset<8> b1; // 8 位,全 0
bitset<8> b2(42); // 用整数初始化:00101010
bitset<8> b3("10101010"); // 用二进制字符串
// 访问与设置
b1[0] = 1; // 设置第 0 位
b1.set(1); // 设置第 1 位
b1.set(); // 全部置 1
b1.reset(); // 全部置 0
b1.flip(); // 全部取反
b1.test(0); // 检查第 0 位
b1.any(); // 是否有 1
b1.all(); // 是否全 1
b1.none(); // 是否全 0
b1.count(); // 1 的个数
b1.size(); // 位数
运算
bitset<8> a("1100");
bitset<8> b("1010");
a & b; // 与
a | b; // 或
a ^ b; // 异或
~a; // 取反
// 转换
string s = a.to_string(); // "00001100"
unsigned long v = a.to_ulong(); // 12
经典应用
// 状态标记(多个开关)
bitset<8> flags;
flags.set(0); // 标志 0 开
flags.set(3); // 标志 3 开
if (flags.test(0)) { /* 处理 */ }
// 集合表示(集合运算用位运算)
// 求两个集合交集是否非空
bitset<100> setA, setB;
if ((setA & setB).any()) { /* 有交集 */ }
// 布隆过滤器思想(位数组标记)
bitset 固定大小(编译期确定)、内存紧凑、位运算极快,适合状态标记、集合运算、位图场景。
C++ <algorithm>
掌握 C++ <algorithm>:通用算法库(STL 核心)。
排序
#include <algorithm>
using namespace std;
vector<int> v = {5, 2, 8, 1, 9};
sort(v.begin(), v.end()); // 升序
sort(v.begin(), v.end(), greater<int>()); // 降序
sort(v.begin(), v.end(),
[](int a, int b) { return a > b; }); // Lambda 降序
stable_sort(v.begin(), v.end()); // 稳定排序
partial_sort(v.begin(), v.begin()+3, v.end()); // 前 3 最小
查找
auto it = find(v.begin(), v.end(), 8); // 线性查找
if (it != v.end()) { cout << *it; }
bool found = binary_search(v.begin(), v.end(), 8); // 二分(需有序)
auto lo = lower_bound(v.begin(), v.end(), 5); // 第一个 >= 5
auto hi = upper_bound(v.begin(), v.end(), 5); // 第一个 > 5
修改
reverse(v.begin(), v.end()); // 反转
rotate(v.begin(), v.begin()+2, v.end()); // 旋转
fill(v.begin(), v.end(), 0); // 填充
replace(v.begin(), v.end(), 8, 88); // 替换
remove(v.begin(), v.end(), 5); // 移除(配合 erase)
// 移除 + 删除(erase-remove 惯用法)
v.erase(remove(v.begin(), v.end(), 5), v.end());
查询与聚合
int cnt = count(v.begin(), v.end(), 3); // 计数
auto mx = max_element(v.begin(), v.end()); // 最大值
auto mn = min_element(v.begin(), v.end()); // 最小值
bool allPos = all_of(v.begin(), v.end(), [](int x){ return x > 0; });
bool anyEven = any_of(v.begin(), v.end(), [](int x){ return x % 2 == 0; });
bool noneZero = none_of(v.begin(), v.end(), [](int x){ return x == 0; });
// 相邻重复
auto it = adjacent_find(v.begin(), v.end());
// 去重(相邻)
v.erase(unique(v.begin(), v.end()), v.end());
复制与变换
vector<int> dest(v.size());
copy(v.begin(), v.end(), dest.begin());
// 映射变换
vector<int> squared(v.size());
transform(v.begin(), v.end(), squared.begin(),
[](int x) { return x * x; });
// 合并两个有序序列
vector<int> merged;
merge(v1.begin(), v1.end(), v2.begin(), v2.end(),
back_inserter(merged));
<algorithm> 是 STL 的灵魂:90% 的容器操作都有现成算法。先查算法再手写,是高效 C++ 的秘诀。
C++ <iterator>
掌握 C++ <iterator>:迭代器库。
迭代器类型
| 类型 | 能力 | 容器 |
|---|---|---|
| 输入迭代器 | 只读单向 | istream |
| 输出迭代器 | 只写单向 | ostream |
| 前向迭代器 | 读写单向 | forward_list |
| 双向迭代器 | 读写双向 | list、set、map |
| 随机访问 | 任意跳跃 | vector、deque、array |
迭代器操作
#include <iterator>
using namespace std;
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin(); // 指向第一个
auto end = v.end(); // 指向最后一个之后
it++; // 前进
it--; // 后退(双向)
it + 2; // 跳 2 步(随机访问)
it - v.begin(); // 距离(索引)
*it; // 解引用取值
it->member; // 成员访问
// 通用工具
advance(it, 3); // 前进 3 步(任意迭代器)
auto dist = distance(v.begin(), v.end()); // 距离
iter_swap(it, it+1); // 交换两个迭代器指向的值
// 反向迭代器
for (auto rit = v.rbegin(); rit != v.rend(); ++rit) { }
插入迭代器
#include <iterator>
// back_inserter:自动 push_back
vector<int> dest;
copy(v.begin(), v.end(), back_inserter(dest));
// 与算法配合
transform(v.begin(), v.end(),
back_inserter(squared),
[](int x){ return x * x; });
// front_inserter / inserter
list<int> l;
copy(v.begin(), v.end(), front_inserter(l)); // 头部插入
流迭代器
// 从输入流读入
istream_iterator<int> in(cin), eof;
vector<int> nums(in, eof); // 读全部数字
// 输出到流
ostream_iterator<int> out(cout, " ");
copy(v.begin(), v.end(), out); // 1 2 3 4 5
迭代器是"容器与算法之间的桥梁":算法只依赖迭代器接口,所以一套算法适用于所有容器。
C++ <functional>
掌握 C++ <functional>:函数对象与绑定器。
函数对象(仿函数)
#include <functional>
using namespace std;
// 内置函数对象
plus<int>()(1, 2); // 3
minus<int>()(5, 2); // 3
multiplies<int>()(3, 4); // 12
greater<int>()(5, 3); // true
less<int>()(3, 5); // true
// 配合算法
sort(v.begin(), v.end(), greater<int>()); // 降序
sort(v.begin(), v.end(), less<int>()); // 升序
自定义函数对象
struct Square {
int operator()(int x) const { // 重载 ()
return x * x;
}
};
Square sq;
sq(5); // 25(像函数一样调用)
// 有状态函数对象
struct Counter {
int count = 0;
int operator()() {
return ++count;
}
};
Counter c;
c(); c(); c(); // 1 2 3
std::function(通用函数包装器)
function<int(int, int)> f;
f = [](int a, int b) { return a + b; }; // Lambda
f(1, 2); // 3
f = std::plus<int>(); // 函数对象
f(3, 4); // 7
f = [](int a, int b) { return a * b; }; // 可重新赋值
// 函数指针
int add(int a, int b) { return a + b; }
f = add;
f(5, 6); // 11
// 存储回调
function<void(const string&)> callback;
callback = [](const string& s) { cout << s; };
bind(绑定参数)
using namespace std::placeholders;
// 绑定部分参数
auto add5 = bind(plus<int>(), _1, 5); // 第一个参数 + 5
add5(10); // 15
// 参数重排
auto subReversed = bind(minus<int>(), _2, _1);
subReversed(10, 3); // 3 - 10 = -7
std::ref / std::cref
// 按引用传递(否则拷贝)
int x = 42;
auto f = [y = std::ref(x)]() { y.get() = 100; };
f();
// x == 100
std::function 是"类型安全的函数指针":存 Lambda、函数指针、函数对象皆可,回调设计的利器。
C++ <numeric>
掌握 C++ <numeric>:数值算法库。
数值算法
#include <numeric>
using namespace std;
vector<int> v = {1, 2, 3, 4, 5};
// 求和
int sum = accumulate(v.begin(), v.end(), 0); // 15
int sum10 = accumulate(v.begin(), v.end(), 10); // 25(初始值 10)
// 乘积(用乘法函数对象)
int product = accumulate(v.begin(), v.end(), 1,
multiplies<int>()); // 120
// 点积(内积)
vector<int> u = {1, 2, 3};
vector<int> w = {4, 5, 6};
int dot = inner_product(u.begin(), u.end(), w.begin(), 0); // 32
// 相邻差
vector<int> diffs;
adjacent_difference(v.begin(), v.end(), back_inserter(diffs));
// [1, 1, 1, 1, 1]
// 部分和(前缀和)
vector<int> prefix;
partial_sum(v.begin(), v.end(), back_inserter(prefix));
// [1, 3, 6, 10, 15]
// 填充(iota:从 start 递增)
vector<int> seq(5);
iota(seq.begin(), seq.end(), 1); // [1,2,3,4,5]
前缀和的经典应用
// 区间和查询:[l, r) 的和 = prefix[r] - prefix[l]
vector<int> prefix;
partial_sum(v.begin(), v.end(), back_inserter(prefix));
int rangeSum(int l, int r) {
// prefix 前缀和(prefix[i] = v[0..i-1] 和)
return prefix[r] - prefix[l];
}
accumulate(求和)、iota(填充递增)、partial_sum(前缀和)是刷题和数值处理的常用工具。
C++ <complex>
掌握 C++ <complex>:复数运算。
基本用法
#include <complex>
using namespace std;
complex<double> c1(3, 4); // 3 + 4i
complex<double> c2(1, -2); // 1 - 2i
c1.real(); // 3(实部)
c1.imag(); // 4(虚部)
abs(c1); // 5(模)
arg(c1); // 辐角(弧度)
norm(c1); // 25(模的平方)
conj(c1); // 3 - 4i(共轭)
// 运算
c1 + c2; c1 - c2; c1 * c2; c1 / c2;
创建与转换
// 从极坐标
complex<double> p = polar(5.0, 0.927); // 模 5,辐角 0.927
// 虚数单位 i
complex<double> i(0, 1);
complex<double> result = i * i; // -1 + 0i
// 类型
complex<float> cf(1, 2);
complex<long double> cd(1, 2);
应用
// 复数运算用于信号处理、FFT
// 电路分析(阻抗)
// 量子力学计算
// 使用复数做向量旋转(乘以 i 旋转 90 度)
complex<double> v(1, 0); // 向量 (1,0)
complex<double> rotated = v * i; // (0,1) 旋转 90°
需要复数运算(信号处理、FFT、数学计算)时,<complex> 提供完整支持且性能良好。
C++ <valarray>
掌握 C++ <valarray>:数值数组运算。
基本用法
#include <valarray>
using namespace std;
valarray<double> v1 = {1.0, 2.0, 3.0, 4.0};
valarray<double> v2 = {10.0, 20.0, 30.0, 40.0};
// 元素级运算
v1 + v2; // {11, 22, 33, 44}
v1 * 2; // {2, 4, 6, 8}
v1 * v2; // 逐元素乘
v1.sum(); // 10(和)
v1.max(); // 4
v1.min(); // 1
v1.size(); // 4
// 数学函数(元素级)
sqrt(v1);
abs(v1);
切片与掩码
valarray<int> v = {1, 2, 3, 4, 5, 6};
// 切片:从 0 开始,步长 2,取 3 个
valarray<int> slice = v[slice(0, 3, 2)]; // {1, 3, 5}
// 掩码筛选
valarray<bool> mask = (v > 3); // {f,f,f,t,t,t}
valarray<int> filtered = v[mask]; // {4, 5, 6}
valarray vs vector
| 对比项 | valarray | vector |
|---|---|---|
| 运算 | 元素级数学运算 | 需手写循环/算法 |
| 切片 | 内置 | 无 |
| 用途 | 数值计算 | 通用容器 |
| 使用频率 | 较低 | 极高 |
valarray 适合数值密集运算(向量化);通用场景用 vector。数值计算库(如 Eigen)更强大,但 valarray 是标准库内置。
C++ <cmath>
掌握 C++ <cmath>:数学函数库。
常用数学函数
#include <cmath>
using namespace std;
// 基础
abs(-10); // 10 整数绝对值
fabs(-3.5); // 3.5 浮点绝对值
sqrt(16); // 4.0 平方根
cbrt(27); // 3.0 立方根
pow(2, 10); // 1024.0 幂
// 取整
ceil(3.2); // 4.0 向上
floor(3.8); // 3.0 向下
round(3.5); // 4.0 四舍五入
trunc(3.9); // 3.0 截断
fmod(10, 3); // 1.0 浮点取余
// 对数与指数
log(2.71828); // 1.0 自然对数
log10(100); // 2.0 常用对数
log2(8); // 3.0 以 2 为底
exp(1); // 2.71828 e 的幂
exp2(3); // 8.0
// 三角函数(弧度)
sin(M_PI / 2); // 1.0
cos(0); // 1.0
tan(M_PI / 4); // 1.0
asin(1); // 反三角
// 双曲函数
sinh(0); cosh(0); tanh(0);
// 最值
fmax(3.5, 4.5); // 4.5
fmin(3.5, 4.5); // 3.5
常量
M_PI // π = 3.14159...
M_E // e = 2.71828...
M_SQRT2 // √2
// 需定义 _USE_MATH_DEFINES(MSVC)或使用 C++20 的 std::numbers
C++20 std::numbers
#include <numbers>
using namespace std;
numbers::pi; // π
numbers::e; // e
numbers::sqrt2; // √2
注意
- 角度与弧度:三角函数用弧度(角度 × π/180)
- 整数运算先转 double:
sqrt(static_cast<double>(n)) - 负数 sqrt/负数对数返回 NaN
算法题常用:sqrt、pow、abs、min/max、ceil/floor;几何题用 atan2(y, x) 求角度。
C++ <string>
掌握 C++ <string>:字符串类的完整 API。
构造与基本操作
#include <string>
using namespace std;
string s1; // 空
string s2 = "hello"; // C 风格转
string s3("world");
string s4(5, 'a'); // "aaaaa"
string s5 = s2 + " " + s3; // 拼接
s2.size(); s2.length(); // 5
s2.empty();
s2.clear(); // 清空
查找
string s = "Hello CodeNest";
s.find("Code"); // 6
s.rfind("l"); // 从后找
s.find_first_of("e"); // 找字符
s.find_last_of("e");
// 找不到返回 npos
if (s.find("X") == string::npos) {
cout << "未找到";
}
截取与修改
string s = "Hello CodeNest";
s.substr(6); // "CodeNest"
s.substr(0, 5); // "Hello"
s.insert(5, ","); // "Hello, CodeNest"
s.erase(5, 1); // 删除 5 位置 1 个字符
s.replace(6, 4, "C++"); // "Hello C++Nest"
// 转换
string up;
for (char c : s) up += toupper(c); // 大写
// C++ 无内置大小写函数(用 transform)
transform(s.begin(), s.end(), s.begin(), ::tolower);
比较
string a = "apple", b = "banana";
a < b; // true(字典序)
a == "apple"; // true
a.compare(b); // 负数/0/正数
数字转换
// 字符串 → 数字
int n = stoi("42");
double d = stod("3.14");
long l = stol("1000");
size_t sz = stoull("99");
// 数字 → 字符串
string s = to_string(3.14159); // "3.141590"
string t = to_string(42); // "42"
与 C 字符串互转
// string → const char*
const char* cstr = s.c_str();
// const char* → string
string back = cstr;
string 的 find 返回 size_t(npos = -1 的大数);频繁拼接用 += 或预先 reserve 优化。
C++ <regex>
掌握 C++ <regex>:正则表达式库(C++11+)。
基本用法
#include <regex>
using namespace std;
string text = "手机号 13812345678 和 13987654321";
// regex_match:整体匹配
regex emailPattern(R"(\w+@\w+\.\w+)");
bool isEmail = regex_match("a@b.com", emailPattern);
// regex_search:查找子串
smatch match;
if (regex_search(text, match, regex(R"(1[3-9]\d{9})"))) {
cout << match[0]; // 13812345678(第一个匹配)
}
// regex_iterator:遍历所有匹配
for (sregex_iterator it(text.begin(), text.end(),
regex(R"(1[3-9]\d{9})")), end;
it != end; ++it) {
cout << (*it)[0] << " "; // 13812345678 13987654321
}
替换与分割
// 替换
string result = regex_replace("a1b2c3", regex(R"(\d)"), "#");
// "a#b#c#"
// 分组替换
string phone = regex_replace("138-1234-5678",
regex(R"((\d{3})-(\d{4})-(\d{4}))"),
R"($1 $2 $3)");
// "138 1234 5678"
// 分割
vector<string> parts;
for (auto it = sregex_token_iterator("a,b;c",
regex(R"([,;])"), -1), end;
it != end; ++it) {
parts.push_back(*it);
}
// {"a", "b", "c"}
分组提取
string date = "日期 2026-08-11";
regex pattern(R"((\d{4})-(\d{2})-(\d{2}))");
smatch m;
if (regex_search(date, m, pattern)) {
m[1]; // 2026
m[2]; // 08
m[3]; // 11
}
常用模式
// 原始字符串 R"(...)" 避免转义地狱
R"(1[3-9]\d{9})" // 手机号
R"(\w+@\w+\.\w+)" // 邮箱
R"((\d{1,3}\.){3}\d{1,3})" // IP
R"(\d{4}-\d{2}-\d{2})" // 日期
用原始字符串 R"(...)" 写正则,避免反斜杠转义;性能敏感场景避免正则,用手写解析。
C++ <ctime>
掌握 C++ <ctime>:C 风格时间库。
基本用法
#include <ctime>
using namespace std;
// 当前时间戳(秒,自 1970-01-01)
time_t now = time(nullptr);
// 转本地时间结构
tm* local = localtime(&now);
local->tm_year + 1900; // 年
local->tm_mon + 1; // 月(0-11)
local->tm_mday; // 日
local->tm_hour; // 时
local->tm_min; // 分
local->tm_sec; // 秒
local->tm_wday; // 星期(0=周日)
// UTC 时间
tm* gmt = gmtime(&now);
// 格式化
char buf[80];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local);
cout << buf; // 2026-08-11 14:30:00
时间转换
// 字符串 → 时间
tm t = {};
strptime("2026-08-11", "%Y-%m-%d", &t); // POSIX
time_t ts = mktime(&t); // tm → time_t
// time_t → tm
tm* t2 = localtime(&ts);
// 计算时间差
time_t start = time(nullptr);
// ... 执行代码 ...
time_t end = time(nullptr);
double seconds = difftime(end, start); // 秒差
与 chrono 对比
| 对比项 | ctime | chrono |
|---|---|---|
| 精度 | 秒 | 纳秒 |
| 类型安全 | 弱 | 强 |
| 格式化 | strftime 方便 | 需转 time_t |
| 推荐 | 格式化场景 | 计时/计算 |
需要格式化时间字符串用 ctime 的 strftime;需要高精度计时/计算用 chrono。
C++ <chrono>
掌握 C++ <chrono>:现代时间库(C++11+)。
时钟与时间点
#include <chrono>
using namespace std::chrono;
// 三种时钟
system_clock::now(); // 系统时间(可转日历)
steady_clock::now(); // 单调时钟(只用于计时)
high_resolution_clock::now(); // 最高精度
// 时间点类型
time_point<system_clock> tp = system_clock::now();
// 转 time_t
time_t t = system_clock::to_time_t(tp);
时长 Duration
seconds s(5); // 5 秒
milliseconds ms(100); // 100 毫秒
minutes m(2); // 2 分钟
// 转换
auto ms2 = duration_cast<milliseconds>(seconds(1)); // 1000ms
auto sec = duration_cast<seconds>(milliseconds(1500)); // 1s
// 字面量(C++14+)
using namespace std::chrono_literals;
auto t1 = 5s; // 5 秒
auto t2 = 100ms; // 100 毫秒
auto t3 = 2min; // 2 分钟
计时器(最常用)
auto start = steady_clock::now();
// ... 执行要计时的代码 ...
auto end = steady_clock::now();
auto elapsed = duration_cast<milliseconds>(end - start);
cout << "耗时: " << elapsed.count() << " ms" << endl;
时间点运算
auto now = system_clock::now();
auto later = now + minutes(30); // 30 分钟后
auto earlier = now - hours(1); // 1 小时前
// 时间差
auto diff = later - now; // 30 分钟
auto mins = duration_cast<minutes>(diff).count(); // 30
计时用 steady_clock(单调);要日历时间用 system_clock;duration_cast 做单位转换。这是 C++ 性能测试的标准姿势。
C++ <thread>
掌握 C++ <thread>:线程库(C++11+)。
创建线程
#include <thread>
using namespace std;
void worker(int id) {
cout << "线程 " << id << " 开始" << endl;
}
int main() {
thread t1(worker, 1);
thread t2(worker, 2);
// join:等待完成(必须 join 或 detach!)
t1.join();
t2.join();
return 0;
}
join 与 detach
thread t(worker, 1);
t.join(); // 阻塞等待
// t.detach(); // 分离后台运行(不等待)
// 析构时若既未 join 也未 detach 会 std::terminate!
if (t.joinable()) t.join(); // 安全检查
Lambda 线程
thread t([](int x) {
cout << "计算: " << x * x << endl;
}, 5);
t.join();
多线程与引用
// 传引用需用 std::ref(否则拷贝)
void modify(int& x) { x = 100; }
int value = 0;
thread t(modify, ref(value));
t.join();
// value == 100
硬件并发数
unsigned n = thread::hardware_concurrency();
cout << "硬件线程数: " << n;
// 线程池常用:min(目标线程数, hardware_concurrency())
注意
- 共享数据要加锁(mutex)
- 线程必须 join 或 detach
- 传递引用用 std::ref
- 创建线程有开销,批量任务用线程池
简单并行用 std::async;需要精细控制用 thread + mutex;批量任务建线程池。别裸开大量线程。
C++ <mutex>
掌握 C++ <mutex>:互斥锁(线程同步)。
基本用法
#include <mutex>
#include <thread>
using namespace std;
int counter = 0;
mutex mtx; // 互斥锁
void increment() {
for (int i = 0; i < 100000; i++) {
mtx.lock();
counter++; // 临界区
mtx.unlock();
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join(); t2.join();
cout << counter; // 200000(正确)
return 0;
}
lock_guard(推荐)
// RAII:构造加锁,析构自动解锁(异常安全)
void increment() {
for (int i = 0; i < 100000; i++) {
lock_guard<mutex> lock(mtx); // 加锁
counter++; // 临界区
} // 自动解锁
}
unique_lock(灵活)
// 比 lock_guard 灵活:可手动解锁、延迟加锁
void task() {
unique_lock<mutex> lock(mtx, defer_lock); // 延迟加锁
// 做其他事...
lock.lock(); // 手动加锁
// 临界区
lock.unlock(); // 可提前解锁
// 继续其他事
}
死锁与防护
// 死锁:两个线程互相等对方的锁
// 解决:锁顺序一致,或一次性锁多个
mutex m1, m2;
// 用 lock 一次性锁定多个(避免死锁)
void safe() {
lock(m1, m2);
lock_guard<mutex> l1(m1, adopt_lock);
lock_guard<mutex> l2(m2, adopt_lock);
// 临界区
}
原子操作替代
#include <atomic>
atomic<int> counter{0}; // 无锁,更快
void increment() {
for (int i = 0; i < 100000; i++) {
counter++; // 原子自增
}
}
选择
| 场景 | 方案 |
|---|---|
| 简单临界区 | lock_guard |
| 需要提前解锁 | unique_lock |
| 读多写少 | shared_mutex(C++17) |
| 单个变量 | atomic(最快) |
优先 atomic(无锁);需要保护复杂临界区用 lock_guard;能减少锁粒度就减少(锁住越小越好)。
C++ <condition_variable>
掌握 C++ <condition_variable>:条件变量(线程通信)。
生产者-消费者
#include <condition_variable>
#include <mutex>
#include <queue>
using namespace std;
mutex mtx;
condition_variable cv;
queue<int> dataQueue;
// 生产者
void producer() {
for (int i = 0; i < 10; i++) {
{
lock_guard<mutex> lock(mtx);
dataQueue.push(i);
} // 解锁后通知
cv.notify_one(); // 通知一个等待线程
}
}
// 消费者
void consumer() {
while (true) {
unique_lock<mutex> lock(mtx);
// 等待条件(带谓词防止虚假唤醒)
cv.wait(lock, []{ return !dataQueue.empty(); });
int val = dataQueue.front();
dataQueue.pop();
lock.unlock();
cout << "消费: " << val << endl;
}
}
wait 的两种形式
// 1. 无条件等待
cv.wait(lock); // 可能虚假唤醒!
// 2. 带谓词(推荐)
cv.wait(lock, []{ return ready; });
// 等价于:
while (!ready) cv.wait(lock); // 循环检查
notify_one vs notify_all
cv.notify_one(); // 唤醒一个等待线程
cv.notify_all(); // 唤醒所有等待线程
// 单消费者用 notify_one
// 多消费者/广播用 notify_all
wait_for(超时等待)
// 最多等 1 秒
if (cv.wait_for(lock, seconds(1),
[]{ return ready; })) {
cout << "条件满足";
} else {
cout << "超时";
}
注意事项
- wait 必须配合 unique_lock(不能 lock_guard)
- 务必用带谓词的 wait(防虚假唤醒)
- 改变共享状态后要 notify
- notify 在解锁后调用更高效
条件变量解决"等待某个条件成立"的线程同步:生产者-消费者、任务队列、线程池都依赖它。
C++ <future>
掌握 C++ <future>:异步任务与结果获取。
async 基本用法
#include <future>
#include <thread>
using namespace std;
int compute(int x) {
this_thread::sleep_for(seconds(1)); // 模拟耗时
return x * x;
}
int main() {
// 异步启动任务
future<int> result = async(launch::async, compute, 6);
// 做其他事...
cout << "主线程继续..." << endl;
// 获取结果(阻塞等待)
int value = result.get();
cout << "结果: " << value << endl; // 36
return 0;
}
launch 策略
// launch::async:新线程执行
// launch::deferred:惰性(get 时才同步执行)
// launch::async | launch::deferred:由实现决定(默认)
future<int> f1 = async(launch::async, compute, 6);
future<int> f2 = async(launch::deferred, compute, 7);
packaged_task
// 包装可调用对象为异步任务
packaged_task<int(int)> task(compute);
future<int> result = task.get_future();
thread t(move(task), 8); // 在线程中执行
t.join();
cout << result.get(); // 64
promise / future 配对
// 线程间传值
void setValue(promise<int>& prom) {
this_thread::sleep_for(seconds(1));
prom.set_value(42); // 设置结果
}
promise<int> prom;
future<int> fut = prom.get_future();
thread t(setValue, ref(prom));
t.join();
cout << fut.get(); // 42(等待 promise 设置值)
并发执行多个任务
// 同时启动多个异步任务
auto f1 = async(launch::async, compute, 3);
auto f2 = async(launch::async, compute, 4);
auto f3 = async(launch::async, compute, 5);
// 汇总结果(并行计算)
int total = f1.get() + f2.get() + f3.get(); // 50
异常传播
// 异步任务中的异常在 get() 时抛出
future<int> f = async(launch::async, []() -> int {
throw runtime_error("任务失败");
return 0;
});
try {
f.get();
} catch (const exception& e) {
cout << "捕获: " << e.what();
}
future/async 是"简单并行"的首选:不用手管线程,异常和结果自动传递。比裸 thread 安全得多。
C++ <atomic>
掌握 C++ <atomic>:原子类型(无锁线程安全)。
基本用法
#include <atomic>
using namespace std;
atomic<int> counter{0}; // 原子整数
counter++; // 原子自增
counter--; // 原子自减
counter.fetch_add(5); // 原子加 5
counter.fetch_sub(2); // 原子减 2
counter.load(); // 读取(原子)
counter.store(100); // 写入(原子)
counter.exchange(50); // 交换并返回旧值
// 比较交换
int expected = 100;
counter.compare_exchange_strong(expected, 200);
// 若 counter == 100 则设为 200,否则 expected = counter
支持的原子类型
atomic<int> atomic<long>
atomic<bool> atomic<double>
atomic<int*> atomic<size_t>
atomic_flag // 最简原子布尔(仅 test_and_set/clear)
// 自定义类型需是 trivially copyable
atomic<shared_ptr<T>> // C++20 支持
内存序(memory_order)
// 默认 seq_cst(顺序一致,最安全)
counter.store(1, memory_order_seq_cst);
// 常用:
// memory_order_relaxed:只保证原子性(不保证顺序)
// memory_order_acquire/release:配对保证可见性
// 默认用 seq_cst,性能敏感再优化
典型应用
// 计数器(多线程自增)
atomic<int> hits{0};
// hits++ 无需加锁
// 标志位
atomic<bool> stop{false};
// 线程检查 stop.load(),主线程 stop.store(true)
// 自旋锁
class SpinLock {
atomic<bool> flag{false};
public:
void lock() {
while (flag.exchange(true)) { /* 自旋 */ }
}
void unlock() { flag.store(false); }
};
atomic vs mutex
| 对比项 | atomic | mutex |
|---|---|---|
| 性能 | 快(无锁) | 慢(有系统调用) |
| 适用 | 单个变量 | 复杂临界区 |
| 简单 | 简单 | 需管理锁 |
单个共享变量用 atomic(计数器、标志位);多个变量的一致性保护用 mutex。atomic 不是"万能锁"。
C++ <type_traits>
掌握 C++ <type_traits>:类型特性(编译期类型查询)。
类型判断
#include <type_traits>
using namespace std;
is_integral<int>::value; // true(整数类型)
is_floating_point<double>::value; // true(浮点)
is_pointer<int*>::value; // true(指针)
is_reference<int&>::value; // true(引用)
is_const<const int>::value; // true(常量)
is_same<int, int>::value; // true(相同类型)
is_base_of<Base, Derived>::value; // 继承关系
// C++17 简写(_v 后缀)
is_integral_v<int> // true
类型修改
remove_const<const int>::type; // int
add_const<int>::type; // const int
remove_reference<int&>::type; // int
remove_pointer<int*>::type; // int
decay<const int&>::type; // int(退化为值类型)
conditional<true, int, double>::type; // int(条件类型)
enable_if(SFINAE)
// 模板条件启用
template <typename T>
enable_if_t<is_integral_v<T>, T> // 仅整数类型可用
twice(T x) {
return x * 2;
}
twice(5); // ✅ int
// twice(5.5); // ❌ 编译错误(double 不满足)
if constexpr(C++17)
// 编译期条件分支
template <typename T>
void process(T value) {
if constexpr (is_integral_v<T>) {
cout << "整数: " << value << endl;
} else if constexpr (is_floating_point_v<T>) {
cout << "浮点: " << value << endl;
} else {
cout << "其他类型" << endl;
}
}
// 注意:if constexpr 分支在编译期选择,不生成代码
应用
- 泛型代码中按类型分支处理
- SFINAE 约束模板参数
- 类型安全的序列化/转换
- 库开发中的类型检查
type_traits 是模板元编程的基础;C++17 的 if constexpr 让按类型分支简单直观。
C++ <typeinfo>
掌握 C++ <typeinfo>:运行时类型信息(RTTI)。
typeid 运算符
#include <typeinfo>
#include <iostream>
using namespace std;
int x = 42;
const type_info& t = typeid(x);
cout << t.name(); // 实现定义(如 "i")
// 类型比较
if (typeid(x) == typeid(int)) {
cout << "是 int" << endl;
}
// 多态对象的动态类型
class Animal { virtual ~Animal() {} };
class Dog : public Animal {};
Animal* a = new Dog();
typeid(*a).name(); // Dog(运行时类型)
typeid(a).name(); // Animal*(静态类型,指针自身)
dynamic_cast
// 安全向下转型(需多态)
Animal* animal = new Dog();
// 成功返回 Dog*,失败返回 nullptr
if (Dog* dog = dynamic_cast<Dog*>(animal)) {
dog->bark();
} else {
cout << "不是 Dog" << endl;
}
// 引用版本(失败抛 bad_cast)
Dog& dogRef = dynamic_cast<Dog&>(*animal);
RTTI 注意事项
- typeid/dynamic_cast 依赖 RTTI(默认开启)
- 编译可禁用(-fno-rtti),但 dynamic_cast 不可用
- 有运行时开销(虚表查询)
- 优先用虚函数而非 dynamic_cast(更好的设计)
// 设计建议:用虚函数替代类型判断
class Animal {
public:
virtual void speak() = 0;
virtual ~Animal() {}
};
// 而不是 if (typeid(*a) == typeid(Dog))
typeid 查类型、dynamic_cast 安全转型;但好的多态设计应少用 RTTI——用虚函数表达行为差异。
C++ <exception>
掌握 C++ <exception>:异常基类与处理机制。
标准异常层次
#include <exception>
using namespace std;
// 层次结构
exception
├── logic_error
│ ├── invalid_argument
│ ├── domain_error
│ ├── length_error
│ └── out_of_range
├── runtime_error
│ ├── range_error
│ ├── overflow_error
│ └── underflow_error
└── bad_alloc(内存分配失败)
bad_cast、bad_typeid
基本处理
try {
vector<int> v(1000000000000); // bad_alloc
} catch (const bad_alloc& e) {
cout << "内存不足: " << e.what();
} catch (const exception& e) {
cout << "异常: " << e.what();
} catch (...) {
cout << "未知异常";
}
自定义异常
// 继承 exception
class MyError : public exception {
string msg;
public:
explicit MyError(const string& m) : msg(m) {}
// 覆盖 what(),返回错误信息
const char* what() const noexcept override {
return msg.c_str();
}
};
throw MyError("业务错误");
try {
// ...
} catch (const MyError& e) {
cout << e.what();
}
异常安全级别
| 级别 | 说明 |
|---|---|
| 基本保证 | 异常后对象有效但状态不确定 |
| 强保证 | 异常后状态回滚(提交/回滚模式) |
| 无抛出 | 保证不抛异常(析构、swap) |
注意事项
- 析构函数不要抛异常(会 terminate)
- 按引用捕获(避免切片)
- 构造失败用异常(无法用返回值)
- noexcept 标记不抛异常的函数
异常处理三原则:按引用捕获、析构不抛、RAII 保证清理。自定义异常继承 std::exception 并重写 what()。
C++ <stdexcept>
掌握 C++ <stdexcept>:标准异常类。
常用标准异常
#include <stdexcept>
using namespace std;
// 逻辑错误(程序 bug)
invalid_argument("参数无效");
out_of_range("索引越界");
length_error("长度错误");
domain_error("定义域错误");
// 运行时错误(环境/数据问题)
runtime_error("运行时错误");
range_error("范围错误");
overflow_error("数值溢出");
underflow_error("数值下溢");
使用示例
int at(vector<int>& v, size_t i) {
if (i >= v.size()) {
throw out_of_range("索引 " + to_string(i) + " 越界");
}
return v[i];
}
int divide(int a, int b) {
if (b == 0) {
throw invalid_argument("除数不能为 0");
}
return a / b;
}
double sqrtSafe(double x) {
if (x < 0) {
throw domain_error("不能对负数开方");
}
return sqrt(x);
}
捕获顺序
try {
// ...
} catch (const invalid_argument& e) {
// 具体类型先捕获
} catch (const logic_error& e) {
// 父类
} catch (const exception& e) {
// 基类兜底
}
异常 vs 错误码
| 对比项 | 异常 | 错误码 |
|---|---|---|
| 传播 | 自动向上 | 手动传递 |
| 忽略风险 | 不可忽略(强制处理) | 易被忽略 |
| 性能 | 有开销 | 快 |
| 适用 | 业务逻辑错误 | 性能热路径 |
标准异常类开箱即用:参数校验用 invalid_argument,索引检查用 out_of_range,数值问题用 overflow_error。
C++ <cstdio>
掌握 C++ <cstdio>:C 风格标准输入输出。
printf / scanf
#include <cstdio>
using namespace std;
// 格式化输出
printf("Hello %s, age %d, score %.1f
",
"张三", 25, 88.5);
// 格式符
%d // 整数
%f // 浮点(%.2f 两位小数)
%c // 字符
%s // 字符串
%x // 十六进制
%p // 指针
%zu // size_t
// 输入
int age;
printf("输入年龄: ");
scanf("%d", &age); // 注意取地址 &
char name[50];
scanf("%49s", name); // 字符串(限长防溢出)
字符串格式化
char buf[100];
snprintf(buf, sizeof(buf), "值: %d, 名: %s", 42, "test");
// 安全:限长
// 现代替代:std::format(C++20)
#include <format>
string s = format("值: {}, 名: {}", 42, "test");
文件操作
FILE* fp = fopen("data.txt", "w");
if (fp) {
fprintf(fp, "写入 %d
", 100);
fclose(fp);
}
fp = fopen("data.txt", "r");
char line[256];
while (fgets(line, sizeof(line), fp)) {
printf("%s", line);
}
fclose(fp);
printf vs cout
| 对比项 | printf | cout |
|---|---|---|
| 类型安全 | 弱(格式符易错) | 强 |
| 扩展性 | 差(自定义类型难) | 重载 << 即可 |
| 性能 | 快 | 略慢(可加速) |
| 推荐 | C 兼容/性能 | ✅ 现代 C++ |
新代码用 cout/format;需要与 C 库交互或极致性能用 printf。scanf 务必注意缓冲区溢出。
C++ <cstdint>
掌握 C++ <cstdint>:定宽整数类型。
定宽整数
#include <cstdint>
using namespace std;
int8_t // 8 位有符号(-128~127)
uint8_t // 8 位无符号(0~255)
int16_t // 16 位
uint16_t
int32_t // 32 位(跨平台保证 4 字节)
uint32_t
int64_t // 64 位
uint64_t
// 最小宽度
int_least8_t // 至少 8 位
int_fast32_t // 最快 32 位
// 最大整数
intmax_t // 最大有符号
uintmax_t
为什么用定宽类型
// int 大小依赖平台(32 位系统 4 字节,部分 16 位系统 2 字节)
// 跨平台/网络协议/文件格式需要确定字节数
int32_t port = 8080; // 任何平台都是 4 字节
uint64_t timestamp; // 任何平台都是 8 字节
// 网络字节序转换配合使用
htonl(port); ntohl(port);
常量与打印
INT32_MAX; // 2147483647
INT32_MIN;
UINT64_MAX; // 18446744073709551615
SIZE_MAX; // size_t 最大值
// 打印
printf("%" PRId64 "
", int64_value); // 可移植打印
cout << int64_value << endl; // cout 更简单
应用场景
- 网络协议(固定字段宽度)
- 文件格式(二进制布局)
- 嵌入式/跨平台代码
- 位运算与掩码
需要"确定大小"的整数(协议、文件、跨平台)用 cstdint 的定宽类型;普通计数用 int 即可。
C++ <memory>
掌握 C++ <memory>:智能指针与内存管理。
unique_ptr(独占)
#include <memory>
using namespace std;
// make_unique(推荐,异常安全)
unique_ptr<int> p = make_unique<int>(42);
unique_ptr<string> s = make_unique<string>("hello");
*p; // 42
p->size(); // 成员访问(若为对象)
p.get(); // 裸指针
p.reset(); // 释放并置空
p.reset(new int(100)); // 替换
// 独占:不能拷贝
// unique_ptr<int> q = p; // ❌
unique_ptr<int> q = move(p); // ✅ 转移所有权
shared_ptr(共享)
auto p1 = make_shared<int>(42);
auto p2 = p1; // ✅ 可拷贝(引用计数 +1)
p1.use_count(); // 2
p1.unique(); // false(非独占)
p1.reset(); // 计数 -1
p2.reset(); // 计数 0 → 对象释放
weak_ptr(弱引用)
// 观察但不拥有,解决循环引用
weak_ptr<int> wp;
{
auto sp = make_shared<int>(42);
wp = sp;
// sp 作用域结束,对象释放
}
if (auto sp = wp.lock()) { // 尝试获取
cout << *sp; // 对象还在
} else {
cout << "已释放";
}
循环引用问题
struct Node {
shared_ptr<Node> next; // 循环引用泄漏!
// 应改为:
weak_ptr<Node> next; // ✅ 不增加计数
};
// A -> B,B -> A 会互相持有导致永不释放
// 用 weak_ptr 打破循环
自定义删除器
// 管理非 new 资源(如文件句柄)
unique_ptr<FILE, decltype(&fclose)> file(fopen("a.txt", "r"), fclose);
// 离开作用域自动 fclose
选择指南
| 场景 | 选择 |
|---|---|
| 独占资源 | unique_ptr |
| 多方共享 | shared_ptr |
| 观察/防循环 | weak_ptr |
| 不需要所有权 | 裸指针/引用 |
现代 C++ 黄金法则:优先 make_unique/make_shared,禁止裸 new。智能指针是内存安全的第一道防线。
C++ <new>
掌握 C++ <new>:动态内存分配。
new / delete
#include <new>
using namespace std;
// 分配单个
int* p = new int; // 未初始化
int* p2 = new int(42); // 初始化为 42
int* p3 = new int{42}; // 列表初始化
// 分配数组
int* arr = new int[10];
int* arr2 = new int[10]{1, 2, 3}; // 部分初始化
// 释放
delete p;
delete[] arr; // 数组必须 delete[]
// 二维
int** matrix = new int*[3];
for (int i = 0; i < 3; i++) matrix[i] = new int[4];
new 失败处理
// 默认:失败抛 bad_alloc
try {
int* p = new int[1000000000];
} catch (const bad_alloc& e) {
cout << "内存不足: " << e.what();
}
// nothrow:失败返回 nullptr
int* p = new (nothrow) int[1000000000];
if (!p) {
cout << "分配失败";
}
placement new(定位构造)
// 在指定内存上构造对象
char buffer[sizeof(int)];
int* p = new (buffer) int(42); // 在 buffer 上构造
// 显式调用析构
p->~int();
// 用于内存池、缓存优化
⚠️ 常见错误
// 内存泄漏:忘记 delete
// 悬垂指针:delete 后使用
// 双重释放:delete 两次
// new[] / delete 不匹配
// 数组越界写
// 解决方案:智能指针 + RAII
auto p = make_unique<int[]>(10); // 数组版
裸 new/delete 是 bug 温床。现代 C++:栈对象优先,智能指针其次,new 仅用于特殊场景。
C++ <utility>
掌握 C++ <utility>:通用工具(pair、move、swap)。
std::pair
#include <utility>
using namespace std;
pair<string, int> p("张三", 25);
p.first; // "张三"
p.second; // 25
// 快捷构造
auto p2 = make_pair("李四", 30);
// 结构化绑定(C++17)
auto [name, age] = p2;
// name="李四", age=30
// 比较(先 first 后 second)
p < p2;
std::move
// 转为右值引用,启用移动语义
string a = "hello";
string b = move(a); // a 的内容被"移动"给 b
// a 处于"有效但未指定"状态(通常为空)
// 移动 vs 拷贝:移动更高效(不复制数据)
// 用途:
// 1. 移动构造/赋值
// 2. 传入需要右值的函数(如 unique_ptr 转移)
std::swap
int x = 1, y = 2;
swap(x, y); // x=2, y=1
vector<int> v1(100000), v2(100000);
swap(v1, v2); // O(1) 交换指针(高效)
std::exchange / std::forward
// exchange:赋值并返回旧值
int a = 10;
int old = exchange(a, 20); // old=10, a=20
// forward:完美转发(模板中)
template <typename T>
void wrapper(T&& arg) {
inner(forward<T>(arg)); // 保持左值/右值属性
}
std::cmp 比较函数(C++20)
#include <utility>
// cmp_equal / cmp_less / cmp_greater
cmp_less(5u, -1); // false(安全比较,防符号坑)
pair 用于"两个值"的返回;move 触发移动语义(高效转移);swap 交换容器 O(1)。都是高频小工具。
C++ <random>
掌握 C++ <random>:随机数生成库(C++11+)。
现代随机数流程
#include <random>
using namespace std;
// 1. 随机种子(硬件熵源)
random_device rd;
// 2. 伪随机引擎
mt19937 gen(rd()); // 梅森旋转(质量好)
// 3. 分布器
uniform_int_distribution<> dice(1, 6); // 掷骰子
// 4. 生成
int roll = dice(gen); // 1-6
常用分布
uniform_int_distribution<> uni(1, 100); // 均匀整数
uniform_real_distribution<double> ur(0.0, 1.0); // 均匀实数
normal_distribution<double> norm(0.0, 1.0); // 正态分布
bernoulli_distribution coin(0.5); // 伯努利(掷硬币)
discrete_distribution<> weights({1, 2, 3}); // 加权选择
poisson_distribution<> poisson(4.0); // 泊松分布
引擎对比
| 引擎 | 特点 |
|---|---|
| mt19937 | 梅森旋转,质量好(推荐) |
| mt19937_64 | 64 位版 |
| minstd_rand | 线性同余,快但质量一般 |
| ranlux24 | 高质量 |
随机选择元素
vector<string> names = {"A", "B", "C", "D"};
uniform_int_distribution<size_t> pick(0, names.size() - 1);
string chosen = names[pick(gen)];
// 洗牌
shuffle(names.begin(), names.end(), gen);
rand() 的局限
// 旧 API(了解即可,不推荐)
srand(time(nullptr));
int x = rand() % 100 + 1; // 有偏、质量差、不可复现
// 现代:随机引擎 + 分布器
// 分布均匀、可复现(种子固定)、类型安全
随机数标准姿势:random_device 种子 + mt19937 引擎 + 分布器。需要可复现测试时固定种子(如 mt19937 gen(42))。
C++ <locale>
掌握 C++ <locale>:本地化与字符集。
locale 基础
#include <locale>
using namespace std;
// 获取全局 locale
locale loc = locale::global(locale(""));
// 常见 locale 名称
"zh_CN.UTF-8" // 中文
"en_US.UTF-8" // 美式英文
"C" // 默认(C 风格)
// 切换全局 locale
locale::global(locale("zh_CN.UTF-8"));
数字/货币格式化
// 使用 locale 的 facet
locale loc("en_US.UTF-8");
// 数字分组(千分位)
cout.imbue(loc);
cout << 1234567.89 << endl; // 1,234,567.89
// 货币格式化
const money_put<char>& mp = use_facet<money_put<char>>(loc);
字符分类(locale 相关)
// 注意:isupper 等受 locale 影响
locale loc("zh_CN.UTF-8");
isupper('A', loc); // true
isdigit('5', loc); // true
// 但中文等宽字符处理要小心(C 风格函数不支持)
宽字符与编码
// wchar_t 处理 Unicode(配合 wstring)
wstring ws = L"你好";
wcout << ws << endl;
// 现代推荐:用 UTF-8 的 std::string + 编码转换库
// C++20 char8_t / std::filesystem 原生支持 UTF-8
locale 用于数字/货币/排序的本地化;现代 C++ 处理文本推荐 UTF-8 std::string,locale 主要用于格式化输出。
C++ <codecvt>
掌握 C++ <codecvt>:字符编码转换(已弃用,了解即可)。
codecvt 背景
// C++11 提供 UTF-8/16/32 转换
// C++17 标记为弃用(deprecated)
// C++23 移除(建议用第三方库)
#include <codecvt>
// 已弃用,不建议新代码使用
转换示例(旧代码参考)
// UTF-8 → UTF-16
wstring_convert<codecvt_utf8_utf16<char16_t>, char16_t> conv;
u16string utf16 = conv.from_bytes("你好");
// UTF-16 → UTF-8
string utf8 = conv.to_bytes(utf16);
现代替代方案
// 1. C++20:char8_t + std::filesystem
u8"UTF-8 字符串"; // const char8_t*
// 2. 第三方库(推荐)
// ICU(国际组件库)
// utf8cpp(轻量 UTF-8 处理)
// 开源的 u8 转换函数
// 3. 简单场景手写转换
// UTF-8 解析:按首字节判断字符长度
实用建议
- 内部统一用 UTF-8 std::string
- 与外部交互(文件/网络)按需转换
- 需要完整 Unicode 处理用 ICU
- 避免 wchar_t 滥用(跨平台大小不同)
codecvt 已弃用。现代 C++ 文本处理策略:UTF-8 为唯一内部编码,必要时用 utf8cpp 或 ICU 转换。
C++ <cassert>
掌握 C++ <cassert>:断言调试。
assert 基本用法
#include <cassert>
using namespace std;
int divide(int a, int b) {
assert(b != 0); // 条件为假则终止程序
return a / b;
}
// assert 失败输出:Assertion `b != 0' failed.
// 并终止程序(abort)
启用/禁用断言
// 定义 NDEBUG 则 assert 不生效
// 编译时:-DNDEBUG
// 调试版:启用断言(默认)
g++ main.cpp -o app
// 发布版:禁用断言
g++ main.cpp -o app -DNDEBUG
assert 使用场景
// 前置条件
void push(vector<int>& v, int x) {
assert(v.size() < 1000000); // 大小限制
v.push_back(x);
}
// 后置条件
int abs(int x) {
int r = x >= 0 ? x : -x;
assert(r >= 0); // 结果检查
return r;
}
// 不变量
class Stack {
// ...
void checkInvariant() {
assert(size <= capacity);
}
};
assert vs 异常
| 对比项 | assert | 异常 |
|---|---|---|
| 用途 | 调试期逻辑检查 | 运行时错误处理 |
| NDEBUG | 发布版消失 | 始终生效 |
| 失败行为 | 终止程序 | 可捕获处理 |
| 适用 | 程序员错误(bug) | 可恢复/环境错误 |
assert 用于"不可能发生"的假设检查(调试利器);用户输入校验用 if + 异常/错误码。发布版记得 -DNDEBUG 关闭。
C++ <cwchar>
掌握 C++ <cwchar>:宽字符函数(C 风格)。
宽字符基础
#include <cwchar>
using namespace std;
// 宽字符(通常 4 字节,可表示 Unicode)
wchar_t wc = L'中';
wstring ws = L"你好世界";
// 宽字符串操作
wcslen(ws.c_str()); // 长度
wcscmp(a.c_str(), b.c_str()); // 比较
wcscpy(dest, src); // 复制
wcscat(dest, src); // 拼接
// 宽字符 I/O
wcout << L"宽输出" << endl;
wcin >> wc;
宽字符格式化
wchar_t buf[100];
swprintf(buf, 100, L"数字: %d", 42);
// 宽字符转换
wcsrtombs / mbsrtowcs // 宽窄互转(受 locale 影响)
wchar_t 的跨平台问题
| 平台 | wchar_t 大小 |
|---|---|
| Windows | 2 字节(UTF-16) |
| Linux/macOS | 4 字节(UTF-32) |
// 大小不同 → 代码不可移植!
// 现代 C++ 建议:
// - 内部统一 UTF-8 std::string
// - 跨平台 Unicode 用 char16_t/char32_t 或第三方库
// - 避免直接使用 wchar_t 做跨平台开发
wchar_t 跨平台大小不一致,现代 C++ 推荐 UTF-8 std::string;需要宽字符时用 char16_t/char32_t(定宽)。
C++ <climits>
掌握 C++ <climits>:整数类型极限值。
整数极限常量
#include <climits>
using namespace std;
CHAR_BIT // 每字节位数(通常 8)
CHAR_MAX // char 最大值
CHAR_MIN
INT_MAX // int 最大值:2147483647
INT_MIN // int 最小值:-2147483648
UINT_MAX // unsigned int 最大值
LONG_MAX / LONG_MIN
LLONG_MAX / LLONG_MIN // long long
SHRT_MAX / SHRT_MIN // short
使用场景
// 初始化"最大/最小"哨兵值
int minVal = INT_MAX;
for (int x : nums) {
minVal = min(minVal, x);
}
// 溢出检查
int addSafe(int a, int b) {
if (a > INT_MAX - b) {
throw overflow_error("整数溢出");
}
return a + b;
}
// 与类型实际大小对比
static_assert(sizeof(int) == 4, "int 应为 4 字节");
溢出陷阱
// 有符号溢出是未定义行为!
int x = INT_MAX;
x + 1; // ❌ 未定义行为(可能变成负数)
// 无符号溢出:回绕(定义行为,但通常不是想要的)
unsigned y = UINT_MAX;
y + 1; // 0(回绕)
// 用 long long 或先检查避免溢出
INT_MAX/MIN 常用于哨兵值和溢出检查;涉及大数计算用 long long,可能溢出时先判断。
C++ <cfloat>
掌握 C++ <cfloat>:浮点数极限与精度。
浮点极限常量
#include <cfloat>
using namespace std;
FLT_MAX // float 最大值:~3.4e38
FLT_MIN // float 最小正数:~1.2e-38
DBL_MAX // double 最大值:~1.8e308
DBL_MIN // double 最小正数:~2.2e-308
FLT_DIG // float 十进制有效数字:6
DBL_DIG // double 十进制有效数字:15
FLT_EPSILON // 1.0 与下一个 float 的差:~1.2e-7
DBL_EPSILON // ~2.2e-16
浮点比较(重要)
// ❌ 不要直接用 == 比较浮点
double a = 0.1 + 0.2; // 0.30000000000000004
if (a == 0.3) { } // false!
// ✅ 用误差范围比较
bool approxEqual(double x, double y, double eps = 1e-9) {
return fabs(x - y) < eps;
}
approxEqual(0.1 + 0.2, 0.3); // true
精度注意事项
// float 只有约 7 位有效数字
float f = 3.14159265358979f;
// 存储为 3.1415927(精度丢失)
// 金额计算避免浮点(用整数分或 BigDecimal)
// 大数小数相减精度差
double x = 1e16;
x + 1 == x; // true!(1 被吃掉)
// 避免接近 0 的数作分母
特殊值
#include <cmath>
isfinite(x); // 是否有限
isnan(x); // 是否 NaN
isinf(x); // 是否无穷
// NaN 判断不能用 ==
double n = 0.0 / 0.0; // NaN
n == n; // false!(NaN != NaN)
isnan(n); // true
浮点三坑:不能 == 比较、精度有限、NaN 不自等。金额用整数/定点数,比较用 epsilon。
C++ <cstdlib>
掌握 C++ <cstdlib>:C 标准工具库。
内存管理
#include <cstdlib>
using namespace std;
// C 风格内存(与 C 库交互时用)
void* p = malloc(100);
free(p);
// C++ 中优先 new/delete 或智能指针
数值转换
int n = atoi("42"); // 字符串 → int
long l = atol("1000");
double d = atof("3.14");
// 更安全的 strtol(可检测错误)
char* end;
long val = strtol("123abc", &end, 10); // val=123, end 指向 "abc"
// 现代 C++:stoi / stod 更安全
随机数(旧 API)
srand(time(nullptr)); // 种子
int r = rand(); // 0 ~ RAND_MAX
int dice = rand() % 6 + 1; // 1-6
// 现代 C++ 用 <random> 引擎
进程控制
exit(0); // 立即退出
abort(); // 异常终止
system("ls"); // 执行系统命令(慎用!)
// atexit 注册退出回调
atexit([](){ cout << "程序退出"; });
排序与查找(C 风格)
int arr[] = {5, 2, 8, 1, 9};
int n = 5;
// 排序
qsort(arr, n, sizeof(int),
[](const void* a, const void* b) {
return *(int*)a - *(int*)b;
});
// 二分查找(需有序)
int* found = (int*)bsearch(&key, arr, n, sizeof(int),
[](const void* a, const void* b) {
return *(int*)a - *(int*)b;
});
// C++ 优先 std::sort / binary_search
cstdlib 的多数功能有 C++ 现代替代(stoi、sort、random)。它主要用于 C 库兼容和进程控制(exit/system)。
C++ <numbers>
掌握 C++ <numbers>:数学常量(C++20+)。
数学常量
#include <numbers>
using namespace std;
numbers::pi; // π = 3.1415926535...
numbers::pi_v<float>; // 指定类型
numbers::e; // 自然常数 e
numbers::sqrt2; // √2
numbers::ln2; // ln 2
numbers::ln10; // ln 10
numbers::log2e; // log2(e)
numbers::phi; // 黄金比例 φ
// 类型指定
numbers::pi_v<double>
numbers::e_v<long double>
与传统宏对比
// 旧方式:M_PI(非标准,需 _USE_MATH_DEFINES)
// #define _USE_MATH_DEFINES
// #include <cmath>
// M_PI
// 新方式:标准、类型安全、支持任意浮点类型
numbers::pi; // double
numbers::pi_v<float>; // float
numbers::pi_v<long double>; // long double
使用示例
// 圆面积
double circleArea(double r) {
return numbers::pi * r * r;
}
// 角度转弧度
double toRadians(double degrees) {
return degrees * numbers::pi / 180.0;
}
// 黄金比例相关
double golden = numbers::phi; // 1.618...
C++20 的 <numbers> 是数学常量的标准来源(替代非标准 M_PI),类型安全且支持任意浮点类型。
C++ OpenCV
掌握 C++ OpenCV:计算机视觉库集成。
OpenCV 简介
OpenCV(Open Source Computer Vision Library)是最流行的开源计算机视觉库,C++ 是其首选语言,支持图像处理、视频分析、机器学习等。
安装
# Ubuntu
sudo apt install libopencv-dev
# macOS
brew install opencv
# Windows/跨平台:vcpkg
vcpkg install opencv4
读取与显示图像
#include <opencv2/opencv.hpp>
using namespace cv;
int main() {
// 读取图像
Mat img = imread("photo.jpg");
if (img.empty()) {
std::cerr << "无法读取图像" << std::endl;
return -1;
}
// 显示
imshow("窗口", img);
waitKey(0); // 等待按键
return 0;
}
图像处理
// 灰度化
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
// 模糊
Mat blurred;
GaussianBlur(img, blurred, Size(5, 5), 0);
// 边缘检测(Canny)
Mat edges;
Canny(gray, edges, 100, 200);
// 缩放
Mat resized;
resize(img, resized, Size(320, 240));
// 旋转
Mat rotated;
rotate(img, rotated, ROTATE_90_CLOCKWISE);
保存图像
imwrite("output.jpg", edges); // 保存结果
编译链接
# 手动编译
g++ main.cpp -o app $(pkg-config --cflags --libs opencv4)
# CMake
# find_package(OpenCV REQUIRED)
# target_link_libraries(app ${OpenCV_LIBS})
进阶功能
- 人脸检测(Haar/深度学习模型)
- 视频处理(VideoCapture)
- 特征匹配(SIFT/ORB)
- 目标跟踪、图像拼接
- 深度学习推理(DNN 模块)
OpenCV 的 C++ API 性能最佳(比 Python 快),适合生产级视觉应用;Mat 是核心数据结构(自动内存管理)。