编译工具
GCC(GNU Compiler Collection)、Clang(LLVM的C++编译器)和MSVC(Microsoft Visual C++)
C++ 程序从源代码到可执行文件需要经过编译和链接两个阶段。不同的编译器有各自的命令行选项和特性。
编译
编译阶段将 .cpp 源文件转换为目标文件(.o 或 .obj)。
常用编译器命令对比:
# GCC / Clang
g++ -c main.cpp -o main.o # 只编译,不链接
clang++ -c main.cpp -o main.o # Clang 用法与 GCC 类似
# MSVC (Visual Studio)
cl /c main.cpp # 生成 main.obj
常用编译选项:
# 优化级别
-O0 # 无优化(调试模式)
-O2 # 常规优化(发布模式)
-O3 # 激进优化
-Os # 优化代码大小
-g # 生成调试信息
# C++ 标准版本
-std=c++11
-std=c++14
-std=c++17
-std=c++20
# 警告选项
-Wall # 启用所有常见警告
-Wextra # 启用额外警告
-Werror # 将警告视为错误
-pedantic # 严格遵循标准
重要: 建议在开发阶段使用
-Wall -Wextra -Werror,可以尽早发现潜在问题。
多文件编译示例:
假设有以下项目结构:
project/
├── main.cpp
├── utils.cpp
└── utils.h
// utils.h
#ifndef UTILS_H
#define UTILS_H
int add(int a, int b);
void print_result(int result);
#endif
// utils.cpp
#include "utils.h"
#include <iostream>
int add(int a, int b) {
return a + b;
}
void print_result(int result) {
std::cout << "Result: " << result << std::endl;
}
// main.cpp
#include "utils.h"
int main() {
int sum = add(3, 5);
print_result(sum);
return 0;
}
编译命令:
# 分别编译每个源文件
g++ -c -std=c++17 -Wall main.cpp -o main.o
g++ -c -std=c++17 -Wall utils.cpp -o utils.o
# 链接生成可执行文件
g++ main.o utils.o -o my_program
易错点: 忘记在编译时包含头文件路径。如果头文件不在当前目录,需要使用
-I选项指定路径:g++ -I./include -c main.cpp -o main.o
Volatile关键字的使用
volatile 关键字告诉编译器,该变量的值可能随时被外部因素(如硬件、其他线程、信号处理程序)改变,禁止编译器对该变量进行优化。
使用场景:
- 硬件寄存器访问
// 假设 0x1000 是硬件寄存器的内存地址
volatile uint32_t* const timer_register =
reinterpret_cast<volatile uint32_t*>(0x1000);
void wait_for_timer() {
// 读取硬件寄存器,每次都必须从内存读取,不能用缓存值
while (*timer_register != 0) {
// 等待定时器完成
}
}
重要: 如果没有
volatile,编译器可能优化掉循环中的重复读取,导致程序死循环或错过硬件状态变化。
- 多线程中的标志位(简单的同步机制)
#include <atomic>
#include <thread>
#include <iostream>
// 不推荐:普通 volatile 不能保证原子性和内存序
volatile bool stop_flag = false;
void worker_thread() {
while (!stop_flag) {
// 执行任务
}
std::cout << "Worker stopped" << std::endl;
}
// 推荐:C++11 以后使用 std::atomic
std::atomic<bool> atomic_stop_flag{false};
void better_worker_thread() {
while (!atomic_stop_flag.load()) {
// 执行任务
}
}
易错点:
volatile不是线程同步原语!它不能保证操作的原子性,也不能防止指令重排序。多线程同步应该使用std::atomic或互斥锁。
- 信号处理程序中的变量
#include <csignal>
#include <cstdlib>
#include <unistd.h>
volatile sig_atomic_t signal_received = 0;
void signal_handler(int sig) {
signal_received = 1; // 必须是 volatile,因为可能在任意时刻被修改
}
int main() {
std::signal(SIGINT, signal_handler);
while (!signal_received) {
// 主循环
sleep(1);
}
return 0;
}
重要: 信号处理程序中只能使用
volatile sig_atomic_t类型的变量,这是标准保证的原子类型。
总结:什么时候用 volatile?
| 场景 | 是否使用 volatile | 推荐替代方案 |
|---|---|---|
| 硬件寄存器访问 | ✅ 必须使用 | - |
| 信号处理程序变量 | ✅ 必须使用 | sig_atomic_t |
| 多线程共享变量 | ❌ 不要使用 | std::atomic |
| 普通变量优化控制 | ❌ 一般不需要 | 重新设计代码 |
链接
链接阶段将多个目标文件和库文件合并成最终的可执行文件或库。
链接类型:
# 静态链接(将库代码复制到可执行文件中)
g++ main.o -static -o my_program_static
# 动态链接(运行时加载共享库)
g++ main.o -o my_program_dynamic
重要: 静态链接的可执行文件更大,但部署简单;动态链接的文件小,但需要确保运行环境有对应的共享库。
链接库文件:
# 链接数学库(libm.a 或 libm.so)
g++ main.o -lm -o my_program
# 链接线程库
g++ main.o -lpthread -o my_program
# 指定库搜索路径
g++ main.o -L./libs -lmylib -o my_program
# 指定运行时库路径(Linux)
g++ main.o -Wl,-rpath,/path/to/libs -lmylib -o my_program
易错点: 链接顺序很重要!被依赖的库要放在依赖它的库的后面: 正确:main 依赖 libA,libA 依赖 libB
g++ main.o -lA -lB -o my_program错误:可能导致未定义引用错误
g++ main.o -lB -lA -o my_program
静态库与动态库的创建和使用:
# 创建静态库(.a 文件)
ar rcs libmylib.a file1.o file2.o
# 创建动态库(.so 文件,Linux)
g++ -shared -fPIC file1.cpp file2.cpp -o libmylib.so
# 创建动态库(.dll 文件,Windows)
cl /LD file1.cpp file2.cpp /Femylib.dll
常见链接错误及解决:
// undefined_reference.cpp
void external_function(); // 声明但未定义
int main() {
external_function(); // 链接错误:undefined reference
return 0;
}
# 错误示例:
$ g++ undefined_reference.cpp -o test
undefined_reference.cpp:(.text+0x5): undefined reference to `external_function()'
collect2: error: ld returned 1 exit status
# 解决方法:提供包含 external_function 定义的源文件或库
$ g++ undefined_reference.cpp external_lib.cpp -o test
易错点: 头文件中声明了函数,但链接时找不到定义。可能原因:
- 忘记编译包含定义的源文件
- 函数定义被
static修饰,只在当前文件可见- C++ 编译器编译了 C 代码,导致名称修饰(mangling)不匹配
解决 C/C++ 混编的名称修饰问题:
// 在 C++ 代码中使用 C 库
#ifdef __cplusplus
extern "C" {
#endif
// C 函数的声明
void c_function1();
void c_function2();
#ifdef __cplusplus
}
#endif
CMake(跨平台构建工具)
CMake 是一个跨平台的构建系统生成器,它根据 CMakeLists.txt 文件生成特定平台的构建文件(如 Makefile、Visual Studio 项目、Ninja 构建文件等)。
基本项目结构:
my_project/
├── CMakeLists.txt # 根 CMake 配置文件
├── src/
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── utils.cpp
├── include/
│ └── utils.h
└── tests/
└── test_main.cpp
最简 CMakeLists.txt:
# 指定 CMake 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 定义项目名称和版本
project(MyProject VERSION 1.0 LANGUAGES CXX)
# 指定 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 添加可执行文件
add_executable(my_app src/main.cpp src/utils.cpp)
# 指定头文件搜索路径
target_include_directories(my_app PRIVATE include)
# 添加编译选项
target_compile_options(my_app PRIVATE -Wall -Wextra -Wpedantic)
构建步骤:
# 1. 创建构建目录(推荐 out-of-source 构建)
mkdir build && cd build
# 2. 生成构建文件
cmake ..
# 3. 编译项目
cmake --build .
# 或者使用原生构建工具(如 make)
make -j$(nproc) # Linux,使用所有 CPU 核心
重要: 始终使用 out-of-source 构建(在单独目录中构建),这样可以保持源代码目录整洁,方便清理构建产物。
多目标项目示例:
cmake_minimum_required(VERSION 3.10)
project(Calculator VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# === 创建静态库 ===
add_library(mathlib STATIC
src/add.cpp
src/subtract.cpp
src/multiply.cpp
src/divide.cpp
)
target_include_directories(mathlib PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# === 创建可执行文件 ===
add_executable(calc src/main.cpp)
# 链接库
target_link_libraries(calc PRIVATE mathlib)
# === 创建测试可执行文件 ===
enable_testing()
add_executable(calc_test tests/test_calc.cpp)
target_link_libraries(calc_test PRIVATE mathlib)
add_test(NAME CalcTest COMMAND calc_test)
易错点:
target_include_directories的可见性选项使用错误:
PRIVATE:仅当前目标使用INTERFACE:仅依赖该目标的目标使用PUBLIC:当前目标和依赖该目标的目标都使用
条件编译和选项:
cmake_minimum_required(VERSION 3.10)
project(AdvancedProject VERSION 1.0)
# 定义选项(可在命令行设置)
option(BUILD_TESTS "Build test programs" ON)
option(ENABLE_OPENMP "Enable OpenMP support" OFF)
set(CMAKE_CXX_STANDARD 17)
add_executable(app src/main.cpp)
# 根据选项条件编译
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
message(STATUS "Tests will be built")
else()
message(STATUS "Tests will NOT be built")
endif()
# 查找和使用外部包
if(ENABLE_OPENMP)
find_package(OpenMP REQUIRED)
if(OpenMP_CXX_FOUND)
target_link_libraries(app PUBLIC OpenMP::OpenMP_CXX)
target_compile_definitions(app PRIVATE USE_OPENMP)
endif()
endif()
使用选项构建:
cmake -DBUILD_TESTS=OFF -DENABLE_OPENMP=ON ..
查找和使用第三方库:
# 查找包(需要系统已安装)
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
find_package(Threads REQUIRED)
add_executable(my_app src/main.cpp)
# 链接找到的库
target_link_libraries(my_app
PRIVATE
Boost::filesystem
Boost::system
Threads::Threads
)
重要:
find_package有两种模式:
- Module 模式:查找
Find<Package>.cmake文件- Config 模式:查找
<Package>Config.cmake文件(现代 CMake 推荐)
现代 CMake 最佳实践:
cmake_minimum_required(VERSION 3.15)
project(ModernProject LANGUAGES CXX)
# 使用 target-based 命令(现代 CMake 核心)
add_library(mylib)
target_sources(mylib PRIVATE
src/mylib.cpp
src/helper.cpp
)
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_compile_features(mylib PUBLIC cxx_std_17)
target_compile_options(mylib PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /permissive->
)
# 为不同构建类型设置选项
target_compile_definitions(mylib PRIVATE
$<$<CONFIG:Debug>:DEBUG_BUILD>
$<$<CONFIG:Release>:NDEBUG>
)
易错点: 避免使用全局命令如
include_directories()、add_definitions(),它们会影响所有后续目标,导致不可预测的依赖关系。始终使用target_xxx命令。
完整的跨平台 CMake 示例:
cmake_minimum_required(VERSION 3.14)
project(CrossPlatformApp VERSION 1.0.0 LANGUAGES CXX)
# C++ 标准设置
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # 不使用编译器扩展
# 输出目录设置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# === 源文件 ===
set(SOURCES
src/main.cpp
src/core/application.cpp
src/utils/logger.cpp
)
set(HEADERS
include/core/application.h
include/utils/logger.h
)
# === 创建可执行文件 ===
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
# 包含目录
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include
)
# 编译选项 - 编译器特定
target_compile_options(${PROJECT_NAME} PRIVATE
# GCC 和 Clang
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:
-Wall
-Wextra
-Wshadow
-Wnon-virtual-dtor
-Wpedantic
>
# MSVC
$<$<CXX_COMPILER_ID:MSVC>:
/W4
/WX
/permissive-
/wd4996 # 禁用特定警告
>
)
# 根据构建类型设置选项
target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<CONFIG:Debug>:DEBUG _DEBUG>
$<$<CONFIG:Release>:NDEBUG RELEASE>
)
# 平台特定设置
if(WIN32)
target_compile_definitions(${PROJECT_NAME} PRIVATE PLATFORM_WINDOWS)
# Windows 特定库
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32)
elseif(APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE PLATFORM_MACOS)
# macOS 特定框架
find_library(COCOA_LIBRARY Cocoa)
target_link_libraries(${PROJECT_NAME} PRIVATE ${COCOA_LIBRARY})
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE PLATFORM_LINUX)
# Linux 特定库
target_link_libraries(${PROJECT_NAME} PRIVATE pthread dl)
endif()
# === 安装规则 ===
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(DIRECTORY include/ DESTINATION include)
构建命令:
# Linux / macOS
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel
# Windows (Visual Studio)
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release --parallel
# Windows (MinGW)
cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel
重要: 在 Windows 上使用 Visual Studio 生成器时,
-DCMAKE_BUILD_TYPE在配置阶段无效,需要在构建时通过--config指定。
基础语法
变量和数据类型
C++ 是一种静态类型语言,每个变量在使用前必须先声明其类型。变量声明的基本语法如下:
type variable_name = initial_value;
基本数据类型及其范围:
#include <iostream>
#include <limits>
int main() {
// 整型
int age = 25; // 通常 4 字节 (-2^31 到 2^31-1)
short small = 100; // 通常 2 字节 (-32768 到 32767)
long big = 1000000L; // 通常 4 或 8 字节
long long huge = 10000000000LL; // 通常 8 字节
// 无符号整型(只能表示非负数,正数范围扩大一倍)
unsigned int positive = 4000000000U;
// 浮点型
float pi = 3.14159f; // 通常 4 字节,精度约 6-7 位
double precise = 3.141592653589793; // 通常 8 字节,精度约 15-17 位
// 字符型
char grade = 'A'; // 1 字节,存储 ASCII 码
wchar_t wide = L'中'; // 宽字符,存储 Unicode
// 布尔型
bool isValid = true; // true 或 false
// 空类型
void* ptr = nullptr; // 无类型指针
// 自动类型推导(C++11)
auto x = 10; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
// 查看类型大小和范围
std::cout << "int 大小: " << sizeof(int) << " 字节\n";
std::cout << "int 最大值: " << std::numeric_limits<int>::max() << "\n";
std::cout << "int 最小值: " << std::numeric_limits<int>::min() << "\n";
return 0;
}
重要: 使用
auto进行类型推导时,必须同时初始化变量,否则编译器无法推导类型。
易错点: 整型除法会截断小数部分!
5 / 2结果是2,不是2.5。要得到浮点结果,至少一个操作数必须是浮点类型:5.0 / 2或5 / 2.0。
变量命名规则:
// ✅ 合法的变量名
int age;
int _count;
int studentName; // 驼峰命名法
int student_name; // 下划线命名法
int MAX_SIZE; // 常量常用全大写
// ❌ 非法的变量名
// int 2name; // 不能以数字开头
// int class; // 不能使用关键字
// int my-name; // 不能包含连字符
算术与控制语句
算术
基本算术类型
C++ 提供了丰富的算术运算符:
#include <iostream>
#include <cmath>
int main() {
int a = 17, b = 5;
// 基本算术运算符
std::cout << "a + b = " << (a + b) << "\n"; // 22 (加法)
std::cout << "a - b = " << (a - b) << "\n"; // 12 (减法)
std::cout << "a * b = " << (a * b) << "\n"; // 85 (乘法)
std::cout << "a / b = " << (a / b) << "\n"; // 3 (整数除法,截断小数)
std::cout << "a % b = " << (a % b) << "\n"; // 2 (取模/求余)
// 浮点数除法
double x = 17.0, y = 5.0;
std::cout << "x / y = " << (x / y) << "\n"; // 3.4
// 自增自减运算符
int c = 5;
std::cout << "c++ = " << c++ << "\n"; // 输出 5,然后 c 变为 6
std::cout << "++c = " << ++c << "\n"; // c 先变为 7,然后输出 7
std::cout << "c-- = " << c-- << "\n"; // 输出 7,然后 c 变为 6
std::cout << "--c = " << --c << "\n"; // c 先变为 5,然后输出 5
// 复合赋值运算符
int d = 10;
d += 5; // 等价于 d = d + 5,d = 15
d -= 3; // 等价于 d = d - 3,d = 12
d *= 2; // 等价于 d = d * 2,d = 24
d /= 4; // 等价于 d = d / 4,d = 6
d %= 4; // 等价于 d = d % 4,d = 2
// 数学函数 (需要 #include <cmath>)
std::cout << "sqrt(16) = " << sqrt(16) << "\n"; // 4
std::cout << "pow(2, 3) = " << pow(2, 3) << "\n"; // 8
std::cout << "abs(-5) = " << abs(-5) << "\n"; // 5
std::cout << "floor(3.7) = " << floor(3.7) << "\n"; // 3
std::cout << "ceil(3.2) = " << ceil(3.2) << "\n"; // 4
std::cout << "round(3.5) = " << round(3.5) << "\n"; // 4
return 0;
}
整数
C++ 中的整数类型有不同的存储大小和表示范围:
#include <iostream>
#include <cstdint> // 固定宽度整数类型
int main() {
// 固定宽度整数类型(推荐在需要精确控制大小时使用)
int8_t i8 = 127; // 8 位有符号整数 (-128 到 127)
uint8_t u8 = 255; // 8 位无符号整数 (0 到 255)
int16_t i16 = 32767; // 16 位有符号整数
uint16_t u16 = 65535; // 16 位无符号整数
int32_t i32 = 2147483647; // 32 位有符号整数
uint32_t u32 = 4294967295U; // 32 位无符号整数
int64_t i64 = 9223372036854775807LL; // 64 位有符号整数
uint64_t u64 = 18446744073709551615ULL; // 64 位无符号整数
// 整数后缀
int a = 42; // 默认 int
long b = 42L; // long
long long c = 42LL; // long long
unsigned d = 42U; // unsigned int
unsigned long e = 42UL; // unsigned long
// 不同进制表示
int dec = 42; // 十进制
int oct = 052; // 八进制(以 0 开头)
int hex = 0x2A; // 十六进制(以 0x 开头)
int bin = 0b101010; // 二进制(C++14,以 0b 开头)
std::cout << "十进制 42 = " << dec << "\n";
std::cout << "八进制 052 = " << oct << "\n";
std::cout << "十六进制 0x2A = " << hex << "\n";
std::cout << "二进制 0b101010 = " << bin << "\n";
// 整数溢出
int8_t small = 127;
// small++; // 溢出!结果变成 -128 (undefined behavior for signed)
return 0;
}
重要: 有符号整数溢出是未定义行为(Undefined Behavior),程序可能崩溃、产生错误结果或表现异常。无符号整数溢出是明确定义的(回绕)。
浮点数及其运算
#include <iostream>
#include <iomanip> // 用于格式化输出
#include <cmath>
#include <limits>
int main() {
// 浮点数精度问题
float f = 0.1f;
double d = 0.1;
std::cout << std::setprecision(20);
std::cout << "float 0.1 = " << f << "\n";
std::cout << "double 0.1 = " << d << "\n";
// 浮点数比较:永远不要直接用 ==
double a = 0.1 + 0.2;
double b = 0.3;
// ❌ 错误方式
if (a == b) {
std::cout << "相等(但可能不执行)\n";
}
// ✅ 正确方式:使用 epsilon 比较
const double EPSILON = 1e-9;
if (fabs(a - b) < EPSILON) {
std::cout << "近似相等\n";
}
// C++11 起推荐的方式
if (std::abs(a - b) < std::numeric_limits<double>::epsilon() * 100) {
std::cout << "近似相等(使用 numeric_limits)\n";
}
// 特殊浮点值
double inf = 1.0 / 0.0; // 正无穷
double nan = 0.0 / 0.0; // NaN (Not a Number)
double neg_inf = -1.0 / 0.0; // 负无穷
std::cout << "1.0/0.0 = " << inf << "\n";
std::cout << "0.0/0.0 = " << nan << "\n";
std::cout << "-1.0/0.0 = " << neg_inf << "\n";
// 检查 NaN
if (std::isnan(nan)) {
std::cout << "是 NaN\n";
}
// 科学计数法
double sci = 1.5e3; // 1500
double tiny = 1.5e-3; // 0.0015
return 0;
}
易错点: 浮点数
0.1 + 0.2 != 0.3在许多编程语言中都成立,这是二进制浮点表示的限制。比较浮点数时始终使用 epsilon 比较法。
布尔类型
#include <iostream>
int main() {
bool flag = true; // 或 false
// 布尔值在算术运算中会被转换为整数:true -> 1, false -> 0
int sum = true + true; // sum = 2
// 非零值在布尔上下文中为 true,零为 false
bool b1 = 42; // true
bool b2 = 0; // false
bool b3 = -1; // true(负数也是 true)
bool b4 = 0.001; // true
// 逻辑运算符
bool a = true, b = false;
std::cout << "a && b = " << (a && b) << "\n"; // 逻辑与 (false)
std::cout << "a || b = " << (a || b) << "\n"; // 逻辑或 (true)
std::cout << "!a = " << (!a) << "\n"; // 逻辑非 (false)
// 短路求值
int x = 0;
if (false && (++x > 0)) {
// ++x 不会执行,因为第一个操作数为 false
}
std::cout << "x = " << x << "\n"; // x 仍为 0
return 0;
}
类型转换
#include <iostream>
int main() {
// 1. 隐式类型转换(自动)
int i = 10;
double d = i; // int -> double,安全
double pi = 3.14;
int truncated = pi; // double -> int,小数部分被截断
std::cout << "truncated = " << truncated << "\n"; // 3
// 2. 显式类型转换(C 风格)- 不推荐
double x = 3.7;
int y = (int)x; // C 风格强制转换
// 3. 显式类型转换(C++ 风格)- 推荐
// static_cast:编译时检查,用于相关类型的转换
int a = static_cast<int>(3.14); // 3
double b = static_cast<double>(5) / 2; // 2.5
// const_cast:添加或移除 const 属性
const int val = 10;
int* ptr = const_cast<int*>(&val); // 危险!仅在确定对象可修改时使用
// reinterpret_cast:低级类型转换,不检查类型兼容性
int num = 65;
char* charPtr = reinterpret_cast<char*>(&num);
// dynamic_cast:用于类层次结构中的安全向下转换(需要虚函数)
// Base* base = new Derived();
// Derived* derived = dynamic_cast<Derived*>(base);
// 4. 列表初始化(C++11,窄化转换会报错)
int m = 3.14; // 警告,允许
// int n = {3.14}; // 错误!窄化转换
int n = {3}; // OK
// 类型提升规则(算术运算时)
// int + double -> double
// float + double -> double
// int + long -> long
// unsigned + signed -> unsigned(可能产生意外结果!)
unsigned int u = 10;
int s = -5;
if (s < u) {
std::cout << "-5 < 10\n"; // 可能不执行!
} else {
std::cout << "由于 unsigned 提升,-5 被认为大于 10\n";
}
return 0;
}
重要: 混合使用有符号和无符号整数时要特别小心!当表达式中同时存在
signed和unsigned时,signed会被提升为unsigned,导致负数变成很大的正数。
条件语句(if和switch)
#include <iostream>
int main() {
int score = 85;
// if-else if-else 结构
if (score >= 90) {
std::cout << "A\n";
} else if (score >= 80) {
std::cout << "B\n";
} else if (score >= 70) {
std::cout << "C\n";
} else if (score >= 60) {
std::cout << "D\n";
} else {
std::cout << "F\n";
}
// 条件运算符(三元运算符)
int a = 5, b = 3;
int max = (a > b) ? a : b; // max = 5
// 嵌套三元运算符(可读性较差,谨慎使用)
int num = 0;
std::string result = (num > 0) ? "正数" : (num < 0) ? "负数" : "零";
// switch 语句(仅适用于整型、字符型、枚举)
char grade = 'B';
switch (grade) {
case 'A':
std::cout << "优秀\n";
break; // 不要忘记 break!
case 'B':
std::cout << "良好\n";
break;
case 'C':
std::cout << "中等\n";
break;
case 'D':
std::cout << "及格\n";
break;
case 'F':
std::cout << "不及格\n";
break;
default:
std::cout << "无效成绩\n";
break;
}
// switch 多个 case 共享代码
int month = 3;
switch (month) {
case 3: case 4: case 5:
std::cout << "春季\n";
break;
case 6: case 7: case 8:
std::cout << "夏季\n";
break;
case 9: case 10: case 11:
std::cout << "秋季\n";
break;
case 12: case 1: case 2:
std::cout << "冬季\n";
break;
default:
std::cout << "无效月份\n";
}
// C++17 if with initializer
// if (auto it = map.find(key); it != map.end()) {
// // 使用 it
// }
return 0;
}
易错点:
switch语句中忘记写break会导致”贯穿(fall-through)“,执行多个 case 的代码。这在 C++17 可以用[[fallthrough]]属性显式标记。
循环语句
#include <iostream>
#include <vector>
int main() {
// 1. for 循环
for (int i = 0; i < 5; ++i) {
std::cout << i << " ";
}
std::cout << "\n";
// 多个初始化表达式(C++ 允许逗号表达式)
for (int i = 0, j = 10; i < j; ++i, --j) {
std::cout << "i=" << i << ", j=" << j << "\n";
}
// 2. while 循环(先检查条件)
int count = 0;
while (count < 5) {
std::cout << count << " ";
++count;
}
std::cout << "\n";
// 3. do-while 循环(至少执行一次)
int n = 0;
do {
std::cout << "执行一次,即使条件为假\n";
} while (n > 0);
// 4. 范围 for 循环(C++11,遍历容器)
std::vector<int> nums = {1, 2, 3, 4, 5};
// 只读访问(拷贝)
for (int num : nums) {
std::cout << num << " ";
num = 0; // 不影响原容器
}
std::cout << "\n";
// 修改元素(引用)
for (int& num : nums) {
num *= 2; // 每个元素乘以 2
}
// 避免拷贝(const 引用)
for (const int& num : nums) {
std::cout << num << " "; // 2 4 6 8 10
}
std::cout << "\n";
// auto 配合范围 for
for (const auto& elem : nums) {
std::cout << elem << " ";
}
std::cout << "\n";
// 5. 循环控制语句
for (int i = 0; i < 10; ++i) {
if (i == 3) continue; // 跳过当前迭代
if (i == 7) break; // 跳出循环
std::cout << i << " "; // 输出: 0 1 2 4 5 6
}
std::cout << "\n";
// 6. 嵌套循环和标签
outer: // 标签
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (i == 1 && j == 1) {
break; // 只跳出内层循环
}
std::cout << "(" << i << "," << j << ") ";
}
}
std::cout << "\n";
// 7. 无限循环
// for (;;) { /* ... */ }
// while (true) { /* ... */ }
return 0;
}
重要: 在循环中使用
auto&还是const auto&取决于是否需要修改元素。如果不修改,使用const auto&可以避免不必要的拷贝。
数组
#include <iostream>
#include <array> // C++11 std::array
#include <vector> // 动态数组
int main() {
// 1. 静态数组(C 风格)
int arr1[5]; // 未初始化,元素值不确定
int arr2[5] = {}; // 全部初始化为 0
int arr3[5] = {1, 2, 3}; // 剩余元素初始化为 0
int arr4[] = {1, 2, 3, 4, 5}; // 编译器自动推断大小为 5
// 访问元素
arr1[0] = 10;
std::cout << "arr1[0] = " << arr1[0] << "\n";
// 数组大小
int size = sizeof(arr4) / sizeof(arr4[0]); // 5
// 遍历数组
for (int i = 0; i < size; ++i) {
std::cout << arr4[i] << " ";
}
std::cout << "\n";
// 2. 多维数组
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << matrix[i][j] << " ";
}
std::cout << "\n";
}
// 3. C++11 std::array(推荐)
std::array<int, 5> stdArr = {1, 2, 3, 4, 5};
std::cout << "Size: " << stdArr.size() << "\n";
std::cout << "First: " << stdArr.front() << "\n";
std::cout << "Last: " << stdArr.back() << "\n";
// 访问元素(带边界检查)
std::cout << "stdArr[0] = " << stdArr[0] << "\n"; // 不检查
std::cout << "stdArr.at(0) = " << stdArr.at(0) << "\n"; // 检查,越界抛异常
// 遍历
for (const auto& elem : stdArr) {
std::cout << elem << " ";
}
std::cout << "\n";
// 4. 动态数组(std::vector)
std::vector<int> vec;
// 添加元素
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
// 预分配空间
vec.reserve(100); // 容量设为 100,避免频繁重新分配
// 插入
vec.insert(vec.begin(), 0); // 在开头插入 0
// 删除
vec.pop_back(); // 删除末尾
vec.erase(vec.begin()); // 删除指定位置
vec.clear(); // 清空
// 初始化方式
std::vector<int> v1(5); // 5 个 0
std::vector<int> v2(5, 10); // 5 个 10
std::vector<int> v3 = {1,2,3}; // 列表初始化
std::vector<int> v4{1,2,3}; // 同上
// 获取底层数组指针
int* rawPtr = vec.data();
return 0;
}
重要: C 风格数组在作为函数参数传递时会**退化(decay)**为指针,丢失大小信息。如果需要保留大小,使用引用或
std::array/std::vector。
易错点: 数组下标越界是 C++ 中最常见的错误之一,且不会自动报错。使用
std::vector::at()或调试工具检测越界。
字符串
#include <iostream>
#include <string> // C++ string 类
#include <cstring> // C 风格字符串函数
#include <sstream> // 字符串流
int main() {
// 1. C 风格字符串(字符数组)
char str1[] = "Hello"; // 自动包含 '\0'
char str2[20] = "World";
char str3[20];
// C 风格字符串操作
strcpy(str3, str1); // 复制
strcat(str3, " "); // 连接
strcat(str3, str2);
size_t len = strlen(str1); // 长度(不包含 '\0')
int cmp = strcmp(str1, str2); // 比较
std::cout << "C string: " << str3 << "\n";
std::cout << "Length: " << len << "\n";
// 2. C++ string 类(强烈推荐)
std::string s1 = "Hello";
std::string s2 = "World";
std::string s3 = s1 + " " + s2; // 方便地连接
// 基本操作
std::cout << "Length: " << s3.length() << "\n";
std::cout << "Size: " << s3.size() << "\n";
std::cout << "Empty: " << s3.empty() << "\n";
std::cout << "First char: " << s3[0] << "\n";
std::cout << "Last char: " << s3.back() << "\n";
// 子串
std::string sub = s3.substr(0, 5); // "Hello",从位置 0 开始,长度为 5
// 查找
size_t pos = s3.find("World"); // 返回位置,找不到返回 string::npos
if (pos != std::string::npos) {
std::cout << "Found at: " << pos << "\n";
}
// 插入和删除
s3.insert(5, ","); // "Hello, World"
s3.erase(5, 1); // 删除位置 5 开始的 1 个字符
s3.replace(6, 5, "C++"); // 替换 "World" 为 "C++"
// 比较
if (s1 == "Hello") {
std::cout << "相等\n";
}
// 转换
std::string numStr = "42";
int num = std::stoi(numStr); // string to int
double d = std::stod("3.14"); // string to double
std::string fromNum = std::to_string(42); // int to string
// 迭代
for (char c : s3) {
std::cout << c;
}
std::cout << "\n";
// C++11 raw string literal(原始字符串,不转义)
std::string path = R"(C:\Program Files\App\file.txt)";
std::string json = R"({"name": "John", "age": 30})";
std::cout << "Path: " << path << "\n";
// 多行 raw string
std::string multiline = R"(
Line 1
Line 2
Line 3
)";
// 3. 字符串流(用于格式化)
std::ostringstream oss;
oss << "Name: " << "John" << ", Age: " << 25;
std::string result = oss.str();
std::cout << result << "\n";
// 解析字符串
std::string data = "100 200 300";
std::istringstream iss(data);
int a, b, c;
iss >> a >> b >> c;
std::cout << a << " " << b << " " << c << "\n";
return 0;
}
重要: 优先使用
std::string而非 C 风格字符串。std::string自动管理内存,提供了丰富的操作方法,且更安全。
输入与输出
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
int main() {
// 1. 标准输入输出
std::cout << "标准输出\n";
std::cerr << "标准错误(无缓冲)\n";
std::clog << "标准日志(有缓冲)\n";
// 2. 格式化输出
double pi = 3.14159265359;
std::cout << "默认: " << pi << "\n";
std::cout << "精度3: " << std::setprecision(3) << pi << "\n";
std::cout << std::fixed; // 固定小数位
std::cout << "固定6位: " << std::setprecision(6) << pi << "\n";
std::cout.unsetf(std::ios::fixed); // 取消固定格式
// 宽度和对齐
std::cout << std::setw(10) << std::left << "Name"
<< std::setw(10) << "Score" << "\n";
std::cout << std::setw(10) << std::left << "Alice"
<< std::setw(10) << 95 << "\n";
// 填充字符
std::cout << std::setfill('0') << std::setw(5) << 42 << "\n";
std::cout << std::setfill(' '); // 恢复默认
// 进制
int num = 255;
std::cout << "十进制: " << num << "\n";
std::cout << "八进制: " << std::oct << num << "\n";
std::cout << "十六进制: " << std::hex << num << "\n";
std::cout << "大写十六进制: " << std::uppercase << num << "\n";
std::cout << std::dec << std::nouppercase; // 恢复
// 布尔值输出
std::cout << std::boolalpha << true << " " << false << "\n"; // "true false"
std::cout << std::noboolalpha; // 恢复 1/0
// 3. 键盘输入
std::string name;
int age;
std::cout << "Enter name: ";
std::cin >> name; // 读到空白字符停止
std::cout << "Enter age: ";
std::cin >> age;
std::cin.ignore(); // 忽略换行符
std::string line;
std::cout << "Enter a line: ";
std::getline(std::cin, line); // 读取整行
// 4. 文件输入输出
// 写入文件
std::ofstream outFile("data.txt");
if (outFile.is_open()) {
outFile << "Hello, File!\n";
outFile << "Line 2\n";
outFile.close();
}
// 读取文件
std::ifstream inFile("data.txt");
if (inFile.is_open()) {
std::string fileLine;
while (std::getline(inFile, fileLine)) {
std::cout << fileLine << "\n";
}
inFile.close();
}
// 追加模式
std::ofstream appendFile("data.txt", std::ios::app);
appendFile << "Appended line\n";
appendFile.close();
// 二进制文件
int data[] = {1, 2, 3, 4, 5};
std::ofstream binOut("data.bin", std::ios::binary);
binOut.write(reinterpret_cast<char*>(data), sizeof(data));
binOut.close();
// 5. 检查输入状态
int value;
std::cout << "Enter an integer: ";
if (!(std::cin >> value)) {
std::cin.clear(); // 清除错误状态
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清空缓冲区
std::cout << "Invalid input!\n";
}
return 0;
}
易错点: 使用
std::cin >> variable后如果要使用std::getline(),必须先调用std::cin.ignore()清除残留的换行符,否则getline会读到空行。
Goto
#include <iostream>
int main() {
// goto 语句:无条件跳转到指定标签
// 在现代 C++ 中很少使用,通常可以用循环和函数替代
int i = 0;
loop_start: // 标签
if (i >= 5) {
goto loop_end;
}
std::cout << i << " ";
++i;
goto loop_start;
loop_end:
std::cout << "\nLoop finished\n";
// 更常见的用途:错误处理和资源清理
int* ptr1 = nullptr;
int* ptr2 = nullptr;
int* ptr3 = nullptr;
ptr1 = new int(1);
if (!ptr1) goto cleanup;
ptr2 = new int(2);
if (!ptr2) goto cleanup;
ptr3 = new int(3);
if (!ptr3) goto cleanup;
// 正常使用资源
std::cout << *ptr1 << " " << *ptr2 << " " << *ptr3 << "\n";
cleanup:
delete ptr1;
delete ptr2;
delete ptr3;
// 现代 C++ 更好的做法:使用 RAII 和智能指针
// 避免使用 goto
return 0;
}
重要: 尽量避免使用
goto。它会使代码难以阅读和维护。现代 C++ 中,goto的唯一合理用途是在 C 风格的错误处理中跳转到统一的清理代码,但更好的做法是使用 RAII、智能指针和异常处理。
函数和参数传递
函数定义和调用
#include <iostream>
#include <vector>
// 函数声明(原型)
int add(int a, int b);
void greet(const std::string& name);
// 函数定义
int add(int a, int b) {
return a + b;
}
void greet(const std::string& name) {
std::cout << "Hello, " << name << "!\n";
}
// 重载函数:同名不同参数
int multiply(int a, int b) {
return a * b;
}
double multiply(double a, double b) {
return a * b;
}
// 返回多个值(使用引用参数)
void divide(int dividend, int divisor, int& quotient, int& remainder) {
quotient = dividend / divisor;
remainder = dividend % divisor;
}
// 返回多个值(使用结构体)
struct Result {
int quotient;
int remainder;
};
Result divide2(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
// 返回多个值(C++17 结构化绑定)
std::pair<int, int> divide3(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
// 递归函数
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 尾递归(某些编译器可优化)
int factorial_tail(int n, int acc = 1) {
if (n <= 1) return acc;
return factorial_tail(n - 1, n * acc);
}
// 函数模板
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Lambda 表达式(匿名函数)
auto lambda_add = [](int a, int b) -> int {
return a + b;
};
int main() {
// 基本调用
int sum = add(3, 5);
greet("Alice");
// 函数重载
std::cout << multiply(3, 4) << "\n"; // int 版本
std::cout << multiply(3.5, 2.0) << "\n"; // double 版本
// 多返回值
int q, r;
divide(17, 5, q, r);
std::cout << "17 / 5 = " << q << " 余 " << r << "\n";
Result res = divide2(17, 5);
std::cout << "17 / 5 = " << res.quotient << " 余 " << res.remainder << "\n";
// C++17 结构化绑定
auto [quot, rem] = divide3(17, 5);
std::cout << "17 / 5 = " << quot << " 余 " << rem << "\n";
// 递归
std::cout << "5! = " << factorial(5) << "\n";
// 模板函数
std::cout << maximum(3, 5) << "\n"; // 自动推导为 int
std::cout << maximum(3.5, 2.5) << "\n"; // 自动推导为 double
// Lambda
std::cout << lambda_add(2, 3) << "\n";
// 立即执行的 lambda
int result = [](int x) { return x * x; }(5);
std::cout << "5^2 = " << result << "\n";
return 0;
}
重要: 函数声明(原型)可以让编译器在函数定义之前就知道函数的签名,这是组织大型代码和多文件项目的基础。
参数传递方式:值传递、引用传递和指针传递
#include <iostream>
#include <string>
// 1. 值传递:创建参数的副本,不影响原变量
void byValue(int x) {
x = 100; // 只修改副本
}
// 2. 引用传递:使用原变量的别名,可以修改原变量
void byReference(int& x) {
x = 100; // 修改原变量
}
// 3. 指针传递:传递地址,可以修改原变量
void byPointer(int* x) {
if (x != nullptr) {
*x = 100; // 解引用修改原变量
}
}
// const 引用:避免拷贝,但不允许修改(推荐用于大型对象)
void printLargeObject(const std::string& str) {
// str += "!"; // 错误!不能修改 const 引用
std::cout << str << "\n";
}
// 对象作为值传递(会调用拷贝构造函数)
class MyClass {
public:
int value;
MyClass(int v) : value(v) {
std::cout << "Constructor\n";
}
MyClass(const MyClass& other) : value(other.value) {
std::cout << "Copy Constructor\n";
}
};
void takeByValue(MyClass obj) {
std::cout << "By value: " << obj.value << "\n";
}
void takeByReference(const MyClass& obj) {
std::cout << "By reference: " << obj.value << "\n";
}
// 输出参数(通过引用返回多个值)
void getMinMax(const int arr[], int size, int& minVal, int& maxVal) {
if (size <= 0) return;
minVal = maxVal = arr[0];
for (int i = 1; i < size; ++i) {
if (arr[i] < minVal) minVal = arr[i];
if (arr[i] > maxVal) maxVal = arr[i];
}
}
// 数组作为参数(退化为指针)
void processArray(int arr[], int size) { // 等同于 int* arr
for (int i = 0; i < size; ++i) {
arr[i] *= 2;
}
}
// 使用引用保留数组大小信息
void processArrayRef(int (&arr)[5]) { // 必须传递大小为 5 的数组
for (int i = 0; i < 5; ++i) {
arr[i] *= 2;
}
}
// C++11 完美转发(转发引用)
template<typename T>
void forward(T&& arg) { // 万能引用
// std::forward<T>(arg) 保持值的类别
}
int main() {
int num = 50;
byValue(num);
std::cout << "After byValue: " << num << "\n"; // 仍为 50
byReference(num);
std::cout << "After byReference: " << num << "\n"; // 变为 100
num = 50;
byPointer(&num);
std::cout << "After byPointer: " << num << "\n"; // 变为 100
// 对比对象传递
MyClass obj(42);
std::cout << "--- Pass by value ---\n";
takeByValue(obj); // 调用拷贝构造函数
std::cout << "--- Pass by reference ---\n";
takeByReference(obj); // 无拷贝
// 数组
int arr[] = {1, 2, 3, 4, 5};
processArray(arr, 5); // 修改原数组
int minVal, maxVal;
getMinMax(arr, 5, minVal, maxVal);
std::cout << "Min: " << minVal << ", Max: " << maxVal << "\n";
return 0;
}
重要: 对于内置类型(int, double 等),值传递和引用传递性能差异不大。对于大型对象,使用
const T&避免昂贵的拷贝操作。
易错点: 数组作为函数参数时会退化为指针,丢失大小信息。务必同时传递大小参数,或使用引用语法
T (&arr)[N]或std::array/std::vector。
内联函数
#include <iostream>
// 普通函数:调用时有函数调用的开销(压栈、跳转、返回)
int max(int a, int b) {
return (a > b) ? a : b;
}
// 内联函数:建议编译器将函数体直接插入调用处,减少调用开销
// inline 只是建议,编译器可能拒绝
inline int maxInline(int a, int b) {
return (a > b) ? a : b;
}
// 在类定义中直接定义的函数自动成为内联函数
class Calculator {
public:
// 隐式内联
int add(int a, int b) {
return a + b;
}
// 显式声明内联
inline int subtract(int a, int b);
};
inline int Calculator::subtract(int a, int b) {
return a - b;
}
// constexpr 函数(C++11):编译期可计算的函数,更强版本的"内联"
constexpr int square(int x) {
return x * x;
}
// 编译期计算
constexpr int result = square(5); // 编译时计算为 25
// constexpr 函数的限制(C++11/14)
// C++14 放宽了限制,允许变量声明和循环
constexpr int factorial(int n) {
int result = 1; // C++14 起允许
for (int i = 1; i <= n; ++i) { // C++14 起允许循环
result *= i;
}
return result;
}
// C++17 if constexpr:编译期条件
// template<typename T>
// auto getValue(T t) {
// if constexpr (std::is_pointer_v<T>) {
// return *t;
// } else {
// return t;
// }
// }
int main() {
int a = 3, b = 5;
// 内联函数调用
int m = maxInline(a, b);
// 编译期计算
int arr[square(5)]; // 数组大小必须是编译期常量
constexpr int fact5 = factorial(5);
std::cout << "5! = " << fact5 << "\n";
return 0;
}
重要:
inline关键字在现代 C++ 中的主要作用不是建议内联优化,而是允许函数在多个翻译单元中定义(通常放在头文件中)。实际的函数内联决策由编译器根据优化级别决定。
默认参数
#include <iostream>
#include <string>
// 默认参数从右向左指定
void printInfo(const std::string& name,
int age = 0,
const std::string& country = "Unknown") {
std::cout << "Name: " << name
<< ", Age: " << age
<< ", Country: " << country << "\n";
}
// ❌ 错误:默认参数不能左边有值而右边没有
// void bad(int a = 1, int b, int c = 3);
// ✅ 正确:从右向左依次设置默认值
void good(int a, int b = 2, int c = 3);
// 函数声明和定义分离时,默认参数只能在声明中指定
void setup(int width, int height, int depth = 1);
void setup(int width, int height, int depth) {
std::cout << width << "x" << height << "x" << depth << "\n";
}
// 默认参数可以是表达式
int getDefaultId() {
return 999;
}
void registerUser(const std::string& name, int id = getDefaultId()) {
std::cout << "User: " << name << ", ID: " << id << "\n";
}
// C++ 函数重载与默认参数的结合
class Rectangle {
public:
// 使用默认参数的版本
void draw(int x = 0, int y = 0, int width = 100, int height = 100);
// 重载版本
void draw(const std::string& style);
};
void Rectangle::draw(int x, int y, int width, int height) {
std::cout << "Draw rect at (" << x << "," << y << ") "
<< "size " << width << "x" << height << "\n";
}
void Rectangle::draw(const std::string& style) {
std::cout << "Draw rect with style: " << style << "\n";
}
int main() {
// 使用不同数量的参数
printInfo("Alice"); // 使用所有默认值
printInfo("Bob", 25); // country 使用默认值
printInfo("Charlie", 30, "USA"); // 不使用默认值
// 指定部分参数
setup(1920, 1080); // depth = 1
setup(800, 600, 32); // 指定所有参数
// 默认参数表达式
registerUser("David");
registerUser("Eve", 123);
// 类成员函数
Rectangle rect;
rect.draw(); // 全部默认
rect.draw(10, 20); // 部分指定
rect.draw("dashed"); // 调用重载版本
return 0;
}
易错点: 默认参数在函数声明和定义分离时,只能在声明中指定,否则会导致编译错误。此外,避免默认参数和函数重载同时使用,容易产生歧义调用。
指针和引用
指针的定义和使用
#include <iostream>
int main() {
int num = 42;
// 1. 指针的定义
int* ptr = # // ptr 存储 num 的地址
// 2. 解引用:获取指针指向的值
std::cout << "Value: " << *ptr << "\n"; // 42
std::cout << "Address: " << ptr << "\n"; // 地址(十六进制)
std::cout << "Num address: " << &num << "\n"; // 同上
// 3. 通过指针修改值
*ptr = 100;
std::cout << "New value: " << num << "\n"; // 100
// 4. 空指针
int* nullPtr = nullptr; // C++11 推荐
// int* oldNull = NULL; // C 风格,不推荐
// int* worstNull = 0; // 不推荐使用 0
if (nullPtr == nullptr) {
std::cout << "Pointer is null\n";
}
// 5. 野指针(未初始化的指针,危险!)
// int* wildPtr; // ❌ 未初始化
// *wildPtr = 10; // ❌ 可能导致程序崩溃
// 6. 指针的指针
int** ptrToPtr = &ptr;
std::cout << "Value via ptrToPtr: " << **ptrToPtr << "\n"; // 100
// 7. 常量指针 vs 指针常量
int a = 1, b = 2;
// 指向常量的指针:可以改指向,不能改值
const int* p1 = &a;
// *p1 = 10; // 错误!不能通过 p1 修改值
p1 = &b; // OK,可以指向其他变量
// 常量指针:不能改指向,可以改值
int* const p2 = &a;
*p2 = 10; // OK
// p2 = &b; // 错误!不能改指向
// 指向常量的常量指针:都不能改
const int* const p3 = &a;
// *p3 = 10; // 错误
// p3 = &b; // 错误
// 8. 指针运算
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // 指向数组首元素
std::cout << *p << "\n"; // 10
std::cout << *(p + 1) << "\n"; // 20(p 指向下一个 int)
std::cout << *(p + 2) << "\n"; // 30
// 指针减法(计算元素间隔)
int* end = &arr[4];
std::cout << "Elements between: " << (end - p) << "\n"; // 4
// 9. void 指针(通用指针)
void* generic = #
// 必须先转换为具体类型才能解引用
int* specific = static_cast<int*>(generic);
std::cout << *specific << "\n";
return 0;
}
重要:
const int*和int* const的区别是 C++ 中的经典面试题。记忆口诀:“const在*左边,值不能变;const在*右边,指向不能变”。
函数指针
#include <iostream>
// 普通函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
// 函数指针作为参数
typedef int (*Operation)(int, int); // 使用 typedef
using Operation2 = int (*)(int, int); // C++11 using 别名
int calculate(int a, int b, Operation op) {
return op(a, b);
}
// 函数返回函数指针
Operation getOperation(char op) {
switch (op) {
case '+': return add;
case '-': return subtract;
case '*': return multiply;
default: return nullptr;
}
}
// 更复杂的例子:回调函数
void processArray(int arr[], int size, void (*callback)(int)) {
for (int i = 0; i < size; ++i) {
callback(arr[i]);
}
}
void printInt(int x) {
std::cout << x << " ";
}
void printSquare(int x) {
std::cout << x * x << " ";
}
// 成员函数指针
class Calculator {
public:
int value = 0;
void add(int x) { value += x; }
void subtract(int x) { value -= x; }
};
// 使用 std::function(C++11,更灵活)
#include <functional>
int main() {
// 1. 定义函数指针
int (*funcPtr)(int, int) = add;
// 使用函数指针调用
std::cout << funcPtr(3, 4) << "\n"; // 7
// 2. 指向不同函数
funcPtr = subtract;
std::cout << funcPtr(7, 4) << "\n"; // 3
// 3. 函数指针数组
int (*opArray[])(int, int) = {add, subtract, multiply};
std::cout << opArray[0](2, 3) << "\n"; // 5 (add)
std::cout << opArray[2](2, 3) << "\n"; // 6 (multiply)
// 4. 作为参数传递
std::cout << calculate(5, 3, add) << "\n"; // 8
std::cout << calculate(5, 3, multiply) << "\n"; // 15
// 5. 使用 typedef/using 简化
Operation op = add;
std::cout << op(10, 20) << "\n"; // 30
// 6. 回调函数
int arr[] = {1, 2, 3, 4, 5};
processArray(arr, 5, printInt); // 1 2 3 4 5
std::cout << "\n";
processArray(arr, 5, printSquare); // 1 4 9 16 25
std::cout << "\n";
// 7. 成员函数指针
void (Calculator::*memberPtr)(int) = &Calculator::add;
Calculator calc;
(calc.*memberPtr)(10); // 调用 add(10)
std::cout << "Value: " << calc.value << "\n"; // 10
memberPtr = &Calculator::subtract;
(calc.*memberPtr)(3); // 调用 subtract(3)
std::cout << "Value: " << calc.value << "\n"; // 7
// 8. std::function(更现代的方式)
std::function<int(int, int)> f = add;
std::cout << f(2, 3) << "\n"; // 5
// 可以存储 lambda
f = [](int a, int b) { return a * b; };
std::cout << f(4, 5) << "\n"; // 20
return 0;
}
重要: 现代 C++ 推荐使用
std::function和 lambda 表达式替代原始函数指针,它们更灵活,可以捕获变量,支持更多类型的可调用对象。
内存管理简介
C++ 允许程序员直接管理内存,这提供了灵活性但也带来了责任。
Alloc和Free
#include <iostream>
#include <cstdlib> // malloc, free
int main() {
// C 风格内存管理:malloc / free
// 分配单个 int
int* p1 = (int*)malloc(sizeof(int));
if (p1 == nullptr) {
std::cerr << "Memory allocation failed\n";
return 1;
}
*p1 = 42;
std::cout << *p1 << "\n";
free(p1); // 释放内存
// 分配数组
int* arr = (int*)malloc(5 * sizeof(int));
if (arr != nullptr) {
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
free(arr);
}
// calloc:分配并初始化为 0
int* zeroed = (int*)calloc(5, sizeof(int)); // 5 个 int,全部初始化为 0
free(zeroed);
// realloc:重新分配大小
int* resized = (int*)malloc(3 * sizeof(int));
resized = (int*)realloc(resized, 10 * sizeof(int)); // 扩展到 10 个 int
free(resized);
return 0;
}
重要: 在 C++ 中,不推荐使用
malloc/free,因为它们不会调用构造函数和析构函数。使用new/delete或智能指针。
New和Delete
#include <iostream>
#include <string>
class MyClass {
public:
int value;
MyClass(int v) : value(v) {
std::cout << "Constructor: " << value << "\n";
}
~MyClass() {
std::cout << "Destructor: " << value << "\n";
}
};
int main() {
// 1. 分配单个对象
int* p1 = new int; // 未初始化
int* p2 = new int(); // 值初始化(0)
int* p3 = new int(42); // 初始化为 42
std::cout << *p1 << " " << *p2 << " " << *p3 << "\n";
delete p1;
delete p2;
delete p3;
// 2. 分配数组
int* arr1 = new int[5]; // 5 个未初始化的 int
int* arr2 = new int[5](); // 5 个初始化为 0 的 int
int* arr3 = new int[5]{1,2,3}; // {1,2,3,0,0}
delete[] arr1; // 数组必须用 delete[]
delete[] arr2;
delete[] arr3;
// 3. 分配对象(会调用构造函数)
MyClass* obj = new MyClass(100);
delete obj; // 会调用析构函数
// 4. 分配对象数组
MyClass* objArr = new MyClass[3]{1, 2, 3};
delete[] objArr; // 会调用每个对象的析构函数
// 5. 分配多维数组
int** matrix = new int*[3]; // 3 行
for (int i = 0; i < 3; ++i) {
matrix[i] = new int[4]; // 每行 4 列
}
// 使用...
matrix[1][2] = 42;
// 释放多维数组
for (int i = 0; i < 3; ++i) {
delete[] matrix[i];
}
delete[] matrix;
// 6. 异常安全分配(C++11 nothrow)
int* safe = new (std::nothrow) int[1000000000000]; // 可能失败
if (safe == nullptr) {
std::cerr << "Allocation failed\n";
} else {
delete[] safe;
}
// 7. 定位 new(在已分配的内存上构造对象)
char buffer[sizeof(MyClass)];
MyClass* placed = new (buffer) MyClass(200); // placement new
placed->~MyClass(); // 必须手动调用析构函数
return 0;
}
重要:
new和delete必须配对使用,new[]和delete[]必须配对使用。混用会导致未定义行为(通常是程序崩溃或内存泄漏)。
易错点:
delete后没有将指针设为nullptr,形成悬空指针- 多次
delete同一指针(双重释放)delete不是new分配的内存- 内存泄漏:分配了内存但没有释放
左值和右值
#include <iostream>
#include <string>
#include <vector>
// 左值引用
void process(int& x) {
std::cout << "Lvalue: " << x << "\n";
}
// 右值引用(C++11)
void process(int&& x) {
std::cout << "Rvalue: " << x << "\n";
}
// 万能引用模板
template<typename T>
void universal(T&& x) { // 万能引用
std::cout << "Universal\n";
}
int getValue() {
return 42;
}
int main() {
int a = 10; // a 是左值,10 是右值
// 左值:有名字,可取地址,可赋值(大部分情况下)
// 右值:临时值,不能取地址,不能赋值
int& ref = a; // OK:左值引用绑定左值
// int& ref2 = 10; // 错误:不能将左值引用绑定到右值
const int& cref = 10; // OK:const 左值引用可以绑定右值
int&& rref = 10; // OK:右值引用绑定右值
// int&& rref2 = a; // 错误:不能将右值引用绑定到左值
// std::move:将左值转为右值引用
int&& rref3 = std::move(a); // OK,但 a 现在处于"被移动"状态
// 函数重载选择
process(a); // 调用左值版本
process(10); // 调用右值版本
process(getValue()); // 调用右值版本
// 移动语义示例
std::vector<std::string> v1;
v1.push_back("Hello");
v1.push_back("World");
std::vector<std::string> v2 = v1; // 拷贝构造(深拷贝)
std::vector<std::string> v3 = std::move(v1); // 移动构造(转移所有权)
// v1 现在处于有效但未指定状态,不应再使用
std::cout << "v2 size: " << v2.size() << "\n"; // 2
std::cout << "v3 size: " << v3.size() << "\n"; // 2
std::cout << "v1 size: " << v1.size() << "\n"; // 0(被移动后)
return 0;
}
重要: 右值引用(
&&)和移动语义是 C++11 的重要特性,允许资源的高效转移而非昂贵的拷贝。使用std::move可以将左值转换为右值引用,触发移动操作。
智能指针
智能指针是 RAII(资源获取即初始化)原则的应用,自动管理内存生命周期。
Unique Pointer
#include <iostream>
#include <memory>
#include <vector>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void use() { std::cout << "Using resource\n"; }
};
int main() {
// 1. 创建 unique_ptr
std::unique_ptr<int> p1(new int(42));
auto p2 = std::make_unique<int>(100); // C++14 推荐方式
// 2. 使用
std::cout << *p1 << "\n"; // 解引用
// 3. unique_ptr 独占所有权,不能复制
// std::unique_ptr<int> p3 = p1; // 错误!不能拷贝
// 但可以转移所有权(移动语义)
std::unique_ptr<int> p3 = std::move(p1);
// p1 现在是 nullptr
if (p1 == nullptr) {
std::cout << "p1 is null\n";
}
// 4. 自定义删除器
auto deleter = [](int* p) {
std::cout << "Custom delete: " << *p << "\n";
delete p;
};
std::unique_ptr<int, decltype(deleter)> p4(new int(50), deleter);
// 5. 管理动态数组
std::unique_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});
std::cout << arr[2] << "\n"; // 3
// C++14 make_unique 数组
auto arr2 = std::make_unique<int[]>(10); // 10 个 int,值初始化
// 6. 容器中的 unique_ptr
std::vector<std::unique_ptr<Resource>> resources;
resources.push_back(std::make_unique<Resource>());
resources.push_back(std::make_unique<Resource>());
// 自动释放所有资源
resources.clear();
// 7. 释放所有权
std::unique_ptr<Resource> res = std::make_unique<Resource>();
Resource* raw = res.release(); // 释放所有权,返回原始指针
delete raw; // 手动释放
// 8. 重置
res.reset(new Resource()); // 释放旧对象,接管新对象
res.reset(); // 释放对象,设为 nullptr
return 0;
} // 所有 unique_ptr 自动释放
重要:
std::unique_ptr是零开销抽象,运行时性能与原始指针相同。它明确表示”独占所有权”的语义,应该作为默认的智能指针选择。
Shared Pointer
#include <iostream>
#include <memory>
#include <vector>
class Node;
// 注意:如果可能形成循环引用,考虑使用 weak_ptr
class Node {
public:
int value;
std::shared_ptr<Node> next;
// std::weak_ptr<Node> parent; // 打破循环引用
Node(int v) : value(v) {
std::cout << "Node " << value << " created\n";
}
~Node() {
std::cout << "Node " << value << " destroyed\n";
}
};
int main() {
// 1. 创建 shared_ptr
std::shared_ptr<int> p1(new int(42));
auto p2 = std::make_shared<int>(100); // 推荐方式,更高效
// 2. 引用计数
std::cout << "p1 count: " << p1.use_count() << "\n"; // 1
{
std::shared_ptr<int> p3 = p1; // 共享所有权
std::cout << "p1 count: " << p1.use_count() << "\n"; // 2
std::cout << "p3 count: " << p3.use_count() << "\n"; // 2
} // p3 销毁,引用计数 -1
std::cout << "p1 count: " << p1.use_count() << "\n"; // 1
// 3. 可以拷贝
std::shared_ptr<int> p4 = p1;
std::cout << "p1 count: " << p1.use_count() << "\n"; // 2
// 4. 自定义删除器
auto fileDeleter = [](FILE* file) {
if (file) {
std::cout << "Closing file\n";
fclose(file);
}
};
// 5. 从 this 创建 shared_ptr(需要继承 enable_shared_from_this)
struct SharedFromThis : std::enable_shared_from_this<SharedFromThis> {
std::shared_ptr<SharedFromThis> getShared() {
return shared_from_this();
}
};
auto obj = std::make_shared<SharedFromThis>();
auto another = obj->getShared(); // 正确的做法
std::cout << "Count: " << obj.use_count() << "\n"; // 2
// 6. 循环引用问题
{
auto node1 = std::make_shared<Node>(1);
auto node2 = std::make_shared<Node>(2);
node1->next = node2;
node2->next = node1; // 循环引用!两个节点都不会被销毁
std::cout << "node1 count: " << node1.use_count() << "\n"; // 2
std::cout << "node2 count: " << node2.use_count() << "\n"; // 2
} // 内存泄漏!
// 7. shared_ptr 和原始指针
int* raw = new int(10);
{
std::shared_ptr<int> sp1(raw);
// std::shared_ptr<int> sp2(raw); // 错误!重复管理同一内存
}
// delete raw; // 错误!已经释放
// 8. 数组支持(C++17)
// std::shared_ptr<int[]> arr(new int[5]{1,2,3,4,5});
return 0;
}
重要:
std::shared_ptr使用引用计数管理共享所有权,但它有额外的内存开销(控制块)。避免循环引用,必要时使用std::weak_ptr。
Weak Pointer
#include <iostream>
#include <memory>
class Person;
class Team {
public:
std::string name;
std::weak_ptr<Person> leader; // 使用 weak_ptr 避免循环引用
Team(const std::string& n) : name(n) {
std::cout << "Team " << name << " created\n";
}
~Team() {
std::cout << "Team " << name << " destroyed\n";
}
};
class Person {
public:
std::string name;
std::shared_ptr<Team> team;
Person(const std::string& n) : name(n) {
std::cout << "Person " << name << " created\n";
}
~Person() {
std::cout << "Person " << name << " destroyed\n";
}
};
int main() {
// 1. weak_ptr 不增加引用计数
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
std::cout << "shared_ptr count: " << sp.use_count() << "\n"; // 1
std::cout << "weak_ptr count: " << wp.use_count() << "\n"; // 1
// 2. 使用前先检查是否有效
if (auto locked = wp.lock()) { // lock() 返回 shared_ptr
std::cout << "Value: " << *locked << "\n";
std::cout << "shared_ptr count: " << sp.use_count() << "\n"; // 2
} // locked 销毁,计数 -1
// 3. 检查是否过期
std::cout << "Expired: " << wp.expired() << "\n"; // 0 (false)
sp.reset(); // 释放资源
std::cout << "Expired: " << wp.expired() << "\n"; // 1 (true)
// lock() 返回空的 shared_ptr
if (auto locked = wp.lock()) {
std::cout << "Won't execute\n";
} else {
std::cout << "Resource already released\n";
}
// 4. 实际应用:缓存观察者模式
{
auto team = std::make_shared<Team>("Engineering");
auto person = std::make_shared<Person>("Alice");
person->team = team;
team->leader = person; // weak_ptr,不增加引用计数
std::cout << "Person count: " << person.use_count() << "\n"; // 1
std::cout << "Team count: " << team.use_count() << "\n"; // 2
// 使用 weak_ptr
if (auto leader = team->leader.lock()) {
std::cout << "Team leader: " << leader->name << "\n";
}
} // 都能正确销毁
return 0;
}
重要:
std::weak_ptr用于”观察”但不拥有对象的场景,典型应用是打破循环引用和实现缓存。
头文件与命名空间
include <> or ""
// main.cpp
// 1. #include <...> - 用于标准库和第三方库头文件
// 编译器在系统包含路径中搜索
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// 2. #include "..." - 用于项目自身的头文件
// 编译器先在当前目录搜索,再到系统路径
#include "myheader.h"
#include "utils/math_utils.h"
// 3. 现代 C++ 模块(C++20)- 替代头文件
// import std.core; // 导入标准库模块
int main() {
// 使用标准库
std::vector<int> vec = {1, 2, 3};
return 0;
}
头文件保护示例:
// math_utils.h
#ifndef MATH_UTILS_H // 如果没有定义这个宏
#define MATH_UTILS_H // 定义它
namespace math {
int add(int a, int b);
int subtract(int a, int b);
}
#endif // MATH_UTILS_H
重要:
- 使用
< >包含系统头文件和标准库- 使用
" "包含项目自己的头文件- 避免在头文件中使用
using namespace,这会污染包含该头文件的所有文件的命名空间
using
#include <iostream>
#include <vector>
#include <string>
// 1. using 声明:引入特定名称
using std::cout;
using std::endl;
// 2. using 指令:引入整个命名空间(不推荐在头文件中使用)
// using namespace std;
// 3. 命名空间别名
namespace fs = std::filesystem; // C++17
namespace mu = my::utility::math;
// 4. 类型别名(C++11 using)
using IntVec = std::vector<int>;
using StringPair = std::pair<std::string, std::string>;
// 对比 typedef
typedef std::vector<int> IntVecOld; // 旧方式
// 5. 函数指针别名
typedef int (*FuncPtr)(int, int); // typedef 方式
using FuncPtr2 = int (*)(int, int); // using 方式(更清晰)
// 6. 模板别名(C++11,typedef 无法做到)
template<typename T>
using Vec = std::vector<T>;
Vec<int> v1; // std::vector<int>
Vec<double> v2; // std::vector<double>
// 7. 在函数内部使用 using
void demo() {
using namespace std; // 只在这个函数内有效
cout << "Hello" << endl;
}
// 8. 匿名命名空间(只在当前文件可见,替代 static)
namespace {
int internalVar = 42; // 只在当前翻译单元可见
void internalFunc() {}
}
// 9. 嵌套命名空间(C++17 简化语法)
namespace A::B::C { // C++17
void func() {}
}
// 等价于
namespace A {
namespace B {
namespace C {
// void func() {}
}
}
}
// 10. inline 命名空间(C++11)
namespace lib {
inline namespace v2 { // 默认使用 v2
void api() { std::cout << "v2 API\n"; }
}
namespace v1 {
void api() { std::cout << "v1 API\n"; }
}
}
int main() {
// 使用 using 声明
cout << "Hello" << endl;
// 使用类型别名
IntVec numbers = {1, 2, 3};
for (const auto& n : numbers) {
cout << n << " ";
}
cout << endl;
// 调用 inline 命名空间
lib::api(); // 调用 v2::api()
lib::v1::api(); // 调用 v1::api()
return 0;
}
重要: 在头文件中永远不要使用
using namespace!这会导致命名空间污染,可能引发难以调试的名字冲突问题。
Pragma once和include guards
// ============ 方式 1:传统 Include Guards(标准兼容)============
// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容
class MyClass {
public:
void doSomething();
};
#endif // MYHEADER_H
// ============ 方式 2:#pragma once(简洁,大部分编译器支持)============
// myheader.h
#pragma once
// 头文件内容
class MyClass {
public:
void doSomething();
};
// ============ 方式 3:两者结合(最佳实践)============
// myheader.h
#pragma once
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容
#endif // MYHEADER_H
对比:
| 特性 | Include Guards | #pragma once |
|---|---|---|
| 标准性 | C/C++ 标准 | 编译器扩展(但广泛支持) |
| 简洁性 | 较冗长 | 简洁 |
| 冲突风险 | 宏名可能冲突 | 无冲突风险 |
| 编译速度 | 需要打开文件检查 | 更快(编译器可优化) |
重要: 现代 C++ 项目推荐使用
#pragma once,因为它更简洁且编译速度略快。但对于需要最大兼容性的库,使用传统的 include guards。
预处理与宏定义
// ============ 1. 简单宏定义 ============
#define PI 3.14159
#define MAX_SIZE 100
#define GREETING "Hello, World!"
// ============ 2. 带参数的宏(函数宏)============
#define SQUARE(x) ((x) * (x)) // 注意括号!
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define DEBUG_PRINT(msg) std::cout << "[DEBUG] " << msg << std::endl
// 多行宏(使用反斜杠续行)
#define SWAP(a, b) do { \
typeof(a) temp = a; \
a = b; \
b = temp; \
} while(0)
// ============ 3. 条件编译 ============
#define DEBUG
#ifdef DEBUG
#define LOG(msg) std::cout << "[DEBUG] " << msg << std::endl
#else
#define LOG(msg)
#endif
// 更复杂的条件
#if defined(DEBUG) && defined(VERBOSE)
#define DETAIL_LOG(msg) std::cout << msg << std::endl
#elif defined(DEBUG)
#define DETAIL_LOG(msg)
#else
#define DETAIL_LOG(msg)
#endif
// 根据平台选择代码
#ifdef _WIN32
#define PLATFORM "Windows"
#include <windows.h>
#elif defined(__linux__)
#define PLATFORM "Linux"
#include <unistd.h>
#elif defined(__APPLE__)
#define PLATFORM "macOS"
#endif
// ============ 4. 预定义宏 ============
void showPredefinedMacros() {
std::cout << "File: " << __FILE__ << "\n"; // 当前文件名
std::cout << "Line: " << __LINE__ << "\n"; // 当前行号
std::cout << "Date: " << __DATE__ << "\n"; // 编译日期
std::cout << "Time: " << __TIME__ << "\n"; // 编译时间
std::cout << "Function: " << __func__ << "\n"; // 当前函数名(C++11)
std::cout << "C++ version: " << __cplusplus << "\n"; // C++ 版本
}
// ============ 5. 宏在调试中的应用 ============
#define ASSERT(cond) \
do { \
if (!(cond)) { \
std::cerr << "Assertion failed: " << #cond \
<< " at " << __FILE__ << ":" << __LINE__ << "\n"; \
std::abort(); \
} \
} while(0)
// # 操作符:将参数转为字符串
#define TO_STRING(x) #x
// ## 操作符:连接
#define CONCAT(a, b) a##b
// ============ 6. 可变参数宏(C++11)============
#define ERROR(fmt, ...) printf("[ERROR] " fmt "\n", ##__VA_ARGS__)
// 更现代的用法
#define LOG_FORMAT(level, fmt, ...) \
printf("[" level "] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
// ============ 7. 取消宏定义 ============
#undef PI // 之后 PI 不再可用
// ============ 8. 实际代码示例 ============
#include <iostream>
#include <cassert>
// 使用 constexpr 替代宏(推荐)
constexpr double PI_CONST = 3.14159;
constexpr int MAX_SIZE_CONST = 100;
// 使用模板函数替代宏函数(类型安全,可调试)
template<typename T>
constexpr T square(T x) {
return x * x;
}
template<typename T>
constexpr T max_val(T a, T b) {
return (a > b) ? a : b;
}
// 断言宏(实际项目风格)
#ifdef NDEBUG
#define MY_ASSERT(cond) ((void)0)
#else
#define MY_ASSERT(cond) \
((cond) ? (void)0 : \
(std::cerr << "Assertion failed: " #cond \
<< " at " << __FILE__ << ":" << __LINE__ << "\n", \
std::abort()))
#endif
int main() {
// 宏使用
std::cout << "PI = " << PI << "\n";
std::cout << "Square of 5 = " << SQUARE(5) << "\n";
std::cout << "Max of 3 and 7 = " << MAX(3, 7) << "\n";
// 现代替代
std::cout << "Const PI = " << PI_CONST << "\n";
std::cout << "Template square = " << square(5) << "\n";
// 调试用宏
LOG("Starting program");
// 预定义宏
showPredefinedMacros();
// 字符串化
std::cout << TO_STRING(Hello World) << "\n"; // "Hello World"
// 连接
int xy = 10;
std::cout << CONCAT(x, y) << "\n"; // xy,即 10
// 断言
MY_ASSERT(2 + 2 == 4);
// MY_ASSERT(2 + 2 == 5); // 会触发断言失败
return 0;
}
重要: 现代 C++ 中,优先使用
const/constexpr、内联函数和模板替代宏。宏没有类型检查、不受命名空间限制、难以调试,且可能有副作用。
易错点: 宏定义中的参数必须加括号,否则可能出现意外的运算顺序问题。例如
#define SQUARE(x) x * x,SQUARE(1+2)会展开为1+2*1+2 = 5而非9。
OOP(面向对象编程)
结构体
struct
struct vs class 的区别 ⭐
| 特性 | struct | class |
|---|---|---|
| 默认访问权限 | public | private |
| 继承默认权限 | public | private |
| 模板参数 | 可以用作模板参数 | 可以用作模板参数 |
| 使用场景 | 简单的数据聚合 | 复杂的封装和继承 |
// struct - 默认 public
struct Point {
int x; // 默认 public
int y;
void print() { // 默认 public
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};
// class - 默认 private
class Point2 {
int x; // 默认 private
int y;
public:
void set(int a, int b) {
x = a;
y = b;
}
};
int main() {
Point p{1, 2}; // C++11 列表初始化
p.x = 10; // OK: struct 成员默认 public
p.print(); // 输出: (10, 2)
Point2 p2;
// p2.x = 10; // Error: class 成员默认 private
p2.set(1, 2); // OK: 通过 public 方法访问
}
⚠️ 常见错误: 在 class 中忘记添加 public: 标签,导致无法访问成员。
结构体的使用
// C++20 结构化绑定
struct Student {
std::string name;
int age;
double score;
};
int main() {
// 多种初始化方式
Student s1{"Alice", 20, 90.5}; // C++11 列表初始化
Student s2 = {"Bob", 21, 85.0}; // 传统方式
// C++17 结构化绑定
auto [name, age, score] = s1;
std::cout << name << " is " << age << " years old." << std::endl;
}
union
联合体的作用和内存布局
联合体所有成员共享同一块内存空间,大小等于最大成员的大小。
// 检查系统字节序
union EndianCheck {
uint32_t i;
uint8_t c[4];
};
// 节省内存的数据存储
union Data {
int i;
float f;
char str[4];
};
// 带类型标记的联合体 (C++11 匿名联合体)
struct Variant {
enum Type { INT, FLOAT, STRING } type;
union { // 匿名联合体
int i;
float f;
char str[32];
};
};
int main() {
EndianCheck e;
e.i = 0x01020304;
if (e.c[0] == 0x04) {
std::cout << "Little Endian" << std::endl;
} else {
std::cout << "Big Endian" << std::endl;
}
// 使用匿名联合体
Variant v;
v.type = Variant::INT;
v.i = 42;
}
⚠️ 注意: 联合体只能同时存储一个成员的值,修改一个成员会影响其他成员。
enum
传统 enum
// 传统 enum - 存在命名空间污染问题
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
// 可以指定底层类型和值
enum Status : unsigned char {
OK = 0,
WARNING = 1,
ERROR = 2
};
int main() {
Color c = RED; // RED 在全局命名空间
// ⚠️ 隐式转换为 int
int x = RED; // OK,但可能不是预期行为
// ⚠️ 不同 enum 可以比较
// if (RED == GREEN) // 编译通过,但无意义
}
enum class (C++11) ⭐⭐
// 强类型枚举,解决传统 enum 的问题
enum class Color {
Red,
Green,
Blue
};
// 指定底层类型
enum class Status : std::uint8_t {
OK = 0,
Warning = 1,
Error = 2
};
int main() {
Color c = Color::Red; // 必须使用作用域解析符
// ⚠️ 不会隐式转换为 int
// int x = Color::Red; // Error!
int x = static_cast<int>(Color::Red); // OK: 需要显式转换
// ⚠️ 不同类型 enum 不能比较
// if (Color::Red == Status::OK) // Error!
}
typedef
typedef 和 using (C++11)
// 传统 typedef
typedef unsigned int uint;
typedef std::vector<int> IntVector;
typedef void (*FuncPtr)(int, int); // 函数指针类型
// C++11 using - 更清晰的语法,支持模板别名
typedef unsigned int uint;
using uint = unsigned int;
using IntVector = std::vector<int>;
using FuncPtr = void(*)(int, int);
// using 特有功能:模板别名
template<typename T>
typedef std::vector<T> Vec; // Error: typedef 不支持模板
template<typename T>
using Vec = std::vector<T>; // OK: using 支持模板别名
// 使用示例
template<typename T>
using StringMap = std::map<std::string, T>;
int main() {
Vec<int> numbers = {1, 2, 3};
StringMap<int> scores{{"Alice", 90}, {"Bob", 85}};
}
类和对象
this指针
this 指针的作用
class Person {
private:
std::string name;
int age;
public:
// 1. 区分成员变量和参数名
void setName(std::string name) {
this->name = name; // this->name 是成员变量,name 是参数
}
// 2. 返回当前对象的引用(实现链式调用)
Person& setAge(int age) {
this->age = age;
return *this; // 返回当前对象的引用
}
// 3. 检查自赋值
Person& operator=(const Person& other) {
if (this == &other) { // 检查是否是同一个对象
return *this;
}
name = other.name;
age = other.age;
return *this;
}
};
int main() {
Person p;
p.setName("Alice")
.setAge(20); // 链式调用
}
什么时候会用到
| 场景 | 示例 |
|---|---|
| 参数名与成员名冲突 | this->name = name |
| 链式调用 | return *this |
| 自赋值检查 | if (this == &other) |
| 传递当前对象 | other.func(*this) |
| CRTP 模式 | static_cast<Derived*>(this) |
const, static和mutable成员 ⭐⭐
const 成员函数
class Date {
private:
int year, month, day;
mutable int cache; // mutable 允许在 const 函数中修改
mutable bool cached;
public:
// const 成员函数:承诺不修改对象状态
int getYear() const {
// year = 2024; // Error: 不能在 const 函数中修改成员
return year;
}
// 计算日期的缓存值
int getDayOfWeek() const {
if (!cached) {
cache = calculateDayOfWeek(); // OK: cache 是 mutable
cached = true;
}
return cache;
}
// ⚠️ const 和非 const 重载
std::string& getData() {
return data_; // 返回可修改引用
}
const std::string& getData() const {
return data_; // 返回 const 引用
}
private:
std::string data_;
int calculateDayOfWeek() const { return 0; }
};
int main() {
Date d1;
const Date d2;
d1.getYear(); // OK: 非 const 对象可以调用 const 函数
d2.getYear(); // OK: const 对象只能调用 const 函数
// d2.setYear(2024); // Error: const 对象不能调用非 const 函数
}
static 成员变量和函数
class Counter {
private:
static int count; // 声明静态成员变量
static const int MAX = 100; // const static 可以在类内初始化
static constexpr double PI = 3.14159; // C++11
int id;
public:
Counter() {
id = ++count;
}
~Counter() {
--count;
}
// 静态成员函数:属于类,不属于对象
static int getCount() {
// return id; // Error: 静态函数不能访问非静态成员
return count; // OK: 只能访问静态成员
}
int getId() const { return id; }
};
// 必须在类外定义并初始化静态成员变量
int Counter::count = 0;
int main() {
// 无需创建对象即可调用静态函数
std::cout << Counter::getCount() << std::endl; // 0
Counter c1, c2;
std::cout << c1.getId() << std::endl; // 1
std::cout << c2.getId() << std::endl; // 2
std::cout << Counter::getCount() << std::endl; // 2
}
⚠️ 常见错误: 忘记在类外定义静态成员变量,导致链接错误。
mutable 关键字
class ThreadSafeCounter {
private:
int count = 0;
mutable std::mutex mtx; // mutable 允许在 const 函数中锁定
public:
// const 函数逻辑上不修改对象,但需要加锁
int get() const {
std::lock_guard<std::mutex> lock(mtx); // OK: mtx 是 mutable
return count;
}
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++count;
}
};
public、private和protected访问控制 ⭐⭐
三种访问修饰符的区别
| 访问修饰符 | 类内部 | 子类 | 外部 |
|---|---|---|---|
| public | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✗ |
| private | ✓ | ✗ | ✗ |
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3;
};
class Derived : public Base {
public:
void test() {
publicVar = 10; // OK: public 继承后仍是 public
protectedVar = 20; // OK: protected 在子类中可访问
// privateVar = 30; // Error: private 不可访问
}
};
int main() {
Base b;
b.publicVar = 100; // OK
// b.protectedVar = 200; // Error
// b.privateVar = 300; // Error
Derived d;
d.publicVar = 1000; // OK
// d.protectedVar = 2000; // Error: protected 在外部不可访问
}
friend
友元函数和友元类
class Box {
private:
double width;
double height;
public:
Box(double w, double h) : width(w), height(h) {}
// 1. 友元函数:允许外部函数访问私有成员
friend double getArea(const Box& box);
// 2. 友元类:允许另一个类的所有方法访问私有成员
friend class BoxPrinter;
// 3. 友元成员函数(需要前向声明)
friend void External::modifyBox(Box& box, double w);
};
// 友元函数定义
double getArea(const Box& box) {
return box.width * box.height; // 可以直接访问私有成员
}
// 友元类
class BoxPrinter {
public:
void print(const Box& box) {
// 可以访问 Box 的私有成员
std::cout << "Box: " << box.width << " x " << box.height << std::endl;
}
};
// 友元成员函数需要在 Box 定义前声明
class External {
public:
void modifyBox(Box& box, double w);
};
⚠️ 破坏封装性的使用建议:
- 友元应尽量少用,它破坏了封装性
- 仅在运算符重载(如
<<,>>)或紧密耦合的类之间使用 - 优先考虑使用 getter/setter 而非友元
hard copy和soft copy
深拷贝 vs 浅拷贝 ⭐⭐⭐
class ShallowCopy {
private:
int* data;
public:
ShallowCopy(int val) {
data = new int(val);
}
// ⚠️ 默认拷贝构造函数执行浅拷贝
// 两个对象指向同一块内存!
~ShallowCopy() {
delete data;
}
};
class DeepCopy {
private:
int* data;
size_t size;
public:
DeepCopy(size_t s, int val) : size(s) {
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = val;
}
}
// 拷贝构造函数 - 深拷贝
DeepCopy(const DeepCopy& other) : size(other.size) {
data = new int[size]; // 分配新内存
for (size_t i = 0; i < size; ++i) {
data[i] = other.data[i]; // 复制数据
}
}
// 赋值运算符 - 深拷贝
DeepCopy& operator=(const DeepCopy& other) {
// ⚠️ 自赋值检查
if (this == &other) {
return *this;
}
// 分配新内存并复制
int* newData = new int[other.size];
for (size_t i = 0; i < other.size; ++i) {
newData[i] = other.data[i];
}
// 释放旧内存
delete[] data;
// 更新成员
data = newData;
size = other.size;
return *this;
}
~DeepCopy() {
delete[] data;
}
};
⚠️⚠️ 拷贝构造函数 vs 赋值运算符
class MyClass {
public:
int* data;
// 拷贝构造函数 - 创建新对象时用已有对象初始化
MyClass(const MyClass& other) {
std::cout << "Copy Constructor" << std::endl;
data = new int(*other.data);
}
// 赋值运算符 - 已有对象之间的赋值
MyClass& operator=(const MyClass& other) {
std::cout << "Copy Assignment" << std::endl;
if (this != &other) { // 自赋值检查
*data = *other.data; // 注意:这里假设内存已分配
}
return *this;
}
};
int main() {
MyClass a;
a.data = new int(10);
MyClass b = a; // 拷贝构造函数(新对象创建)
MyClass c(a); // 拷贝构造函数(显式调用)
MyClass d;
d = a; // 赋值运算符(已有对象)
}
规则 of Three/Five ⭐⭐
// Rule of Three (C++98): 如果定义了以下任意一个,应该定义全部三个
// - 析构函数
// - 拷贝构造函数
// - 拷贝赋值运算符
// Rule of Five (C++11): 加上移动语义
// - 析构函数
// - 拷贝构造函数
// - 拷贝赋值运算符
// - 移动构造函数
// - 移动赋值运算符
// Rule of Zero: 优先使用智能指针和容器,避免手动管理资源
class RuleOfFive {
int* data;
size_t size;
public:
// 构造函数
explicit RuleOfFive(size_t s = 0) : size(s), data(new int[s]) {}
// 1. 析构函数
~RuleOfFive() { delete[] data; }
// 2. 拷贝构造函数
RuleOfFive(const RuleOfFive& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
// 3. 拷贝赋值运算符
RuleOfFive& operator=(const RuleOfFive& other) {
if (this != &other) {
RuleOfFive temp(other); // 拷贝并交换惯用法
swap(temp);
}
return *this;
}
// 4. 移动构造函数 (C++11)
RuleOfFive(RuleOfFive&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 源对象置空
other.size = 0;
}
// 5. 移动赋值运算符 (C++11)
RuleOfFive& operator=(RuleOfFive&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
void swap(RuleOfFive& other) noexcept {
using std::swap;
swap(data, other.data);
swap(size, other.size);
}
};
构造函数和析构函数 ⭐⭐⭐
默认构造函数
class MyClass {
public:
int x;
std::string name;
// 默认构造函数
MyClass() : x(0), name("default") {
std::cout << "Default constructor" << std::endl;
}
// 带参数的构造函数
explicit MyClass(int val) : x(val), name("param") {
std::cout << "Parameterized constructor" << std::endl;
}
// 委托构造函数 (C++11)
MyClass(int val, const std::string& n) : MyClass(val) {
name = n;
std::cout << "Delegating constructor" << std::endl;
}
};
// ⚠️ 隐式转换问题
void func(MyClass obj) { }
int main() {
MyClass a; // 默认构造函数
MyClass b(10); // 参数化构造函数
MyClass c{20, "test"}; // C++11 列表初始化
// func(5); // Error: explicit 阻止隐式转换
}
拷贝构造函数
class Array {
int* data;
size_t size;
public:
Array(size_t s) : size(s), data(new int[s]) {}
// 拷贝构造函数
Array(const Array& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
// 禁用拷贝
// Array(const Array&) = delete;
~Array() { delete[] data; }
};
移动构造函数 (C++11)
class Buffer {
char* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new char[s]) {}
// 移动构造函数 - 转移资源所有权
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 源对象置空,避免重复释放
other.size = 0;
}
// 移动赋值运算符
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // 释放当前资源
data = other.data; // 接管资源
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
~Buffer() { delete[] data; }
};
Buffer createBuffer() {
Buffer buf(1000);
return buf; // 返回值优化/移动语义
}
int main() {
Buffer a = createBuffer(); // 移动构造
Buffer b(500);
b = createBuffer(); // 移动赋值
}
初始化列表 ⚠️
class Member {
public:
Member(int x) { std::cout << "Member(" << x << ")" << std::endl; }
Member(const Member&) { std::cout << "Member copy" << std::endl; }
};
class Test {
const int constVal; // const 成员必须在初始化列表初始化
int& ref; // 引用成员必须在初始化列表初始化
Member m; // 没有默认构造函数的成员
int x, y;
public:
// ⚠️ 初始化顺序由声明顺序决定,不是初始化列表顺序!
Test(int a, int b)
: constVal(a), // 必须先初始化
ref(constVal), // 引用初始化
m(100), // 调用 Member(int)
x(a),
y(b) // 可以用前面初始化的成员
{
// 这里赋值会调用拷贝赋值,效率低于初始化列表
// m = Member(200); // 先默认构造,再拷贝赋值 - 效率低
}
};
析构函数的重要性
class Resource {
FILE* file;
int* array;
public:
Resource(const char* filename, size_t size) {
file = fopen(filename, "r");
array = new int[size];
}
// ⚠️ 析构函数:确保资源释放,避免内存泄漏
~Resource() {
// 按创建顺序的逆序释放
delete[] array; // 先释放 array
if (file) {
fclose(file); // 再关闭文件
}
}
};
void riskyFunction() {
Resource r("data.txt", 1000);
// 如果这里抛出异常,析构函数仍然会被调用!
throw std::runtime_error("Oops!");
} // r 的析构函数自动调用
继承和多态 ⭐⭐⭐
继承的概念和实现
三种继承方式
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
// 1. public 继承:保持原有的访问级别
class PublicDerived : public Base {
// publicVar -> public
// protectedVar -> protected
// privateVar -> 不可访问
};
// 2. protected 继承:public 变成 protected
class ProtectedDerived : protected Base {
// publicVar -> protected
// protectedVar -> protected
// privateVar -> 不可访问
};
// 3. private 继承(默认):全部变成 private
class PrivateDerived : private Base {
// publicVar -> private
// protectedVar -> private
// privateVar -> 不可访问
};
继承中的构造函数调用顺序
class GrandBase {
public:
GrandBase() { std::cout << "GrandBase" << std::endl; }
};
class Base : public GrandBase {
public:
Base() { std::cout << "Base" << std::endl; }
};
class Member {
public:
Member() { std::cout << "Member" << std::endl; }
};
class Derived : public Base {
Member m; // 成员对象
public:
// 构造顺序:GrandBase -> Base -> Member -> Derived
Derived() { std::cout << "Derived" << std::endl; }
// 析构顺序相反
};
int main() {
Derived d;
// 输出:
// GrandBase
// Base
// Member
// Derived
}
多态的实现和应用
静态多态 vs 动态多态
// ========== 静态多态(编译期)==========
// 1. 函数重载
void print(int x) { std::cout << "int: " << x << std::endl; }
void print(double x) { std::cout << "double: " << x << std::endl; }
void print(const std::string& s) { std::cout << "string: " << s << std::endl; }
// 2. 模板
class Rectangle { public: void draw() const { std::cout << "Rectangle" << std::endl; } };
class Circle { public: void draw() const { std::cout << "Circle" << std::endl; } };
template<typename T>
void drawShape(const T& shape) {
shape.draw(); // 编译期确定调用哪个 draw
}
// 3. CRTP (Curiously Recurring Template Pattern)
template<typename Derived>
class Shape {
public:
void draw() const {
static_cast<const Derived*>(this)->drawImpl();
}
};
// ========== 动态多态(运行期)==========
class Animal {
public:
virtual void speak() const { // 虚函数
std::cout << "Some sound" << std::endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() const override { // 覆盖虚函数
std::cout << "Woof!" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() const override {
std::cout << "Meow!" << std::endl;
}
};
int main() {
// 静态多态
print(42); // int 版本
print(3.14); // double 版本
Rectangle r;
Circle c;
drawShape(r); // Rectangle::draw
drawShape(c); // Circle::draw
// 动态多态
Animal* animals[] = { new Dog(), new Cat() };
for (auto* animal : animals) {
animal->speak(); // 运行时确定调用哪个版本
}
}
虚函数和纯虚函数 ⭐⭐⭐
virtual 关键字
class Base {
public:
// 虚函数 - 可以在子类中覆盖
virtual void func1() { std::cout << "Base::func1" << std::endl; }
// 纯虚函数 - 抽象方法,子类必须实现
virtual void func2() = 0;
// ⚠️ 虚析构函数 - 确保通过基类指针删除派生类对象时正确析构
virtual ~Base() { std::cout << "Base destructor" << std::endl; }
};
class Derived : public Base {
public:
void func1() override { // C++11 override 关键字
std::cout << "Derived::func1" << std::endl;
}
void func2() override {
std::cout << "Derived::func2" << std::endl;
}
~Derived() { std::cout << "Derived destructor" << std::endl; }
};
虚函数表原理
class Base {
int x;
public:
virtual void f() {}
virtual void g() {}
};
class Derived : public Base {
int y;
public:
void f() override {} // 覆盖 Base::f
// g() 继承自 Base
virtual void h() {} // 新增虚函数
};
// 内存布局示意:
// Base 对象: [vptr][x]
// |
// v
// [Base::f][Base::g]
//
// Derived 对象: [vptr][x][y]
// |
// v
// [Derived::f][Base::g][Derived::h]
// ⚠️ 虚函数的开销:
// 1. 每个对象增加一个指针(vptr)的大小
// 2. 虚函数调用通过指针间接调用,有轻微性能损失
// 3. 内联优化可能失效
纯虚函数和抽象类
// 抽象类 - 包含纯虚函数的类,不能实例化
class Shape {
public:
virtual double area() const = 0; // 纯虚函数
virtual double perimeter() const = 0; // 纯虚函数
virtual void print() const { // 可以有实现的虚函数
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
// 具体类 - 实现所有纯虚函数
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
double perimeter() const override {
return 2 * (width + height);
}
};
// Shape s; // Error: 抽象类不能实例化
Rectangle r(3, 4); // OK
Shape* s = &r; // OK: 可以用指针/引用指向派生类
虚析构函数 ⚠️⚠️⚠️
class Base {
public:
// ⚠️ 如果类有任何虚函数,析构函数必须是虚函数!
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() {
delete[] data; // 如果 Base 析构不是虚函数,这行不会执行!
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 如果 ~Base 不是虚函数,只调用 ~Base(),内存泄漏!
}
抽象类和接口
抽象类的定义
// C++ 没有 interface 关键字,用纯虚函数类实现
class IPrintable {
public:
virtual void print() const = 0;
virtual ~IPrintable() = default;
};
class ISerializable {
public:
virtual std::string serialize() const = 0;
virtual void deserialize(const std::string& data) = 0;
virtual ~ISerializable() = default;
};
// 多重继承实现接口
class Document : public IPrintable, public ISerializable {
std::string content;
public:
// 实现 IPrintable
void print() const override {
std::cout << content << std::endl;
}
// 实现 ISerializable
std::string serialize() const override {
return content;
}
void deserialize(const std::string& data) override {
content = data;
}
};
接口设计模式
// 策略模式 - 使用接口实现可替换算法
class ISortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual ~ISortStrategy() = default;
};
class QuickSort : public ISortStrategy {
public:
void sort(std::vector<int>& data) override {
quickSort(data, 0, data.size() - 1);
}
private:
void quickSort(std::vector<int>&, int, int) { /* ... */ }
};
class MergeSort : public ISortStrategy {
public:
void sort(std::vector<int>& data) override {
// 归并排序实现
}
};
class Sorter {
std::unique_ptr<ISortStrategy> strategy;
public:
void setStrategy(std::unique_ptr<ISortStrategy> s) {
strategy = std::move(s);
}
void sort(std::vector<int>& data) {
if (strategy) {
strategy->sort(data);
}
}
};
运算符重载 ⭐⭐
可重载和不可重载的运算符 ⚠️
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// ===== 可重载的运算符 =====
// 算术运算符 - 成员函数
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// 复合赋值运算符
Complex& operator+=(const Complex& other) {
real += other.real;
imag += other.imag;
return *this;
}
// 一元运算符
Complex operator-() const {
return Complex(-real, -imag);
}
// 前缀递增
Complex& operator++() {
++real;
return *this;
}
// 后缀递增 ⚠️ int 参数用于区分前缀/后缀
Complex operator++(int) {
Complex temp(*this);
++real;
return temp;
}
// 下标运算符
double operator[](int index) const {
return (index == 0) ? real : imag;
}
// 函数调用运算符(仿函数)
double operator()(double x) const {
return real * x + imag;
}
// ===== 不可重载的运算符 =====
// . (成员访问)
// .* (成员指针访问)
// :: (作用域解析)
// ?: (三元条件)
// sizeof
// typeid
// static_cast, dynamic_cast, const_cast, reinterpret_cast
// 友元声明,用于非成员函数重载
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
friend std::istream& operator>>(std::istream& is, Complex& c);
};
// 输入输出运算符 - 必须是非成员函数
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << "(" << c.real << ", " << c.imag << "i)";
return os;
}
std::istream& operator>>(std::istream& is, Complex& c) {
is >> c.real >> c.imag;
return is;
}
成员函数 vs 非成员函数重载
class Vector2D {
double x, y;
public:
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// 成员函数重载:左操作数必须是 Vector2D
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
// 非成员函数重载:允许隐式转换
friend Vector2D operator*(double scalar, const Vector2D& v) {
return Vector2D(scalar * v.x, scalar * v.y);
}
Vector2D operator*(double scalar) const {
return Vector2D(x * scalar, y * scalar);
}
};
int main() {
Vector2D v1(1, 2), v2(3, 4);
Vector2D v3 = v1 + v2; // OK: 成员函数
Vector2D v4 = 2.0 * v1; // OK: 非成员函数
Vector2D v5 = v1 * 2.0; // OK: 成员函数
// Vector2D v6 = v1 * 2; // 如果只有成员函数,int 会隐式转换
}
⚠️ 最佳实践: 对称的运算符(+、-、*、/)建议实现为非成员函数,以支持左右操作数的隐式转换。
模板编程与元编程
模板的定义和使用 ⭐⭐
函数模板
// 通用交换函数
template<typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// 多类型参数模板
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
// C++14 自动推导返回类型
template<typename T, typename U>
auto multiply(T a, U b) {
return a * b;
}
// 非类型模板参数
template<typename T, size_t N>
size_t arraySize(const T (&)[N]) {
return N; // 编译期获取数组大小
}
int main() {
int x = 1, y = 2;
swap(x, y); // 编译器推导为 swap<int>
double a = 1.5;
auto result = add(x, a); // 推导为 add<int, double>
int arr[10];
std::cout << arraySize(arr) << std::endl; // 10
}
类模板
template<typename T>
class Stack {
private:
std::vector<T> data;
public:
void push(const T& value) {
data.push_back(value);
}
void pop() {
if (!data.empty()) {
data.pop_back();
}
}
T& top() {
return data.back();
}
bool empty() const {
return data.empty();
}
// 模板类内的模板方法
template<typename U>
void pushMultiple(U begin, U end) {
for (auto it = begin; it != end; ++it) {
push(*it);
}
}
};
// 模板别名
template<typename T>
using IntMap = std::map<int, T>;
int main() {
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
Stack<std::string> stringStack;
stringStack.push("Hello");
IntMap<std::string> map; // std::map<int, std::string>
}
模板特化和偏特化
全特化
template<typename T>
class Storage {
public:
void store(const T& value) {
std::cout << "General storage" << std::endl;
data = value;
}
private:
T data;
};
// 全特化:针对特定类型的完全定制
template<>
class Storage<bool> {
public:
void store(bool value) {
std::cout << "Optimized bool storage" << std::endl;
// 使用位运算优化存储
data = value ? 1 : 0;
}
private:
unsigned char data;
};
// 函数模板全特化
template<typename T>
bool isEqual(T a, T b) {
return a == b;
}
template<>
bool isEqual<double>(double a, double b) {
const double EPSILON = 1e-9;
return std::abs(a - b) < EPSILON;
}
偏特化
template<typename T, typename U>
class Pair {
public:
void describe() { std::cout << "General Pair" << std::endl; }
};
// 偏特化1:第一个类型为指针
template<typename T, typename U>
class Pair<T*, U> {
public:
void describe() { std::cout << "First is pointer" << std::endl; }
};
// 偏特化2:两个类型相同
template<typename T>
class Pair<T, T> {
public:
void describe() { std::cout << "Same types" << std::endl; }
};
// 偏特化3:指针类型
template<typename T, typename U>
class Pair<T*, U*> {
public:
void describe() { std::cout << "Both are pointers" << std::endl; }
};
int main() {
Pair<int, double> p1; // General Pair
Pair<int*, double> p2; // First is pointer
Pair<int, int> p3; // Same types
Pair<int*, double*> p4; // Both are pointers
}
模板元编程简介
编译期计算
// 编译期阶乘计算
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
// C++14/17 变量模板
template<int N>
constexpr int factorial = Factorial<N>::value;
// C++17 constexpr if
template<int N>
constexpr int fibonacci() {
if constexpr (N <= 1) {
return N;
} else {
return fibonacci<N - 1>() + fibonacci<N - 2>();
}
}
// SFINAE - 条件模板启用
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
doubleValue(T value) {
return value * 2;
}
template<typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
doubleValue(T value) {
return value * 2.0;
}
// C++20 concepts (更简洁的 SFINAE)
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
template<Arithmetic T>
T add(T a, T b) {
return a + b;
}
int main() {
constexpr int fact5 = Factorial<5>::value; // 120,编译期计算
constexpr int fib10 = fibonacci<10>(); // 55,编译期计算
int x = doubleValue(5); // 调用整数版本
double y = doubleValue(3.5); // 调用浮点版本
}
Friend Class
友元类的使用场景
// 场景1:紧密耦合的类需要互相访问私有成员
class Engine;
class Car {
private:
int speed;
Engine* engine;
public:
Car();
void accelerate();
friend class Engine; // Engine 可以访问 Car 的私有成员
};
class Engine {
private:
int horsepower;
Car* car;
public:
void setCar(Car* c) { car = c; }
void boost() {
// 可以直接访问 Car 的私有成员
if (car) {
car->speed += 50; // 访问私有成员 speed
}
}
friend class Car; // Car 也可以访问 Engine 的私有成员
};
Car::Car() : speed(0), engine(new Engine()) {
engine->setCar(this);
// 可以访问 Engine 的私有成员
engine->horsepower = 200;
}
// 场景2:容器类和迭代器类
class Container;
class Iterator {
private:
Container* container;
size_t index;
public:
Iterator(Container* c, size_t i) : container(c), index(i) {}
int& operator*();
Iterator& operator++();
friend class Container; // Container 可以创建 Iterator
};
class Container {
private:
int data[100];
size_t size;
public:
Iterator begin() { return Iterator(this, 0); }
Iterator end() { return Iterator(this, size); }
friend class Iterator; // Iterator 可以访问 data
};
int& Iterator::operator*() {
return container->data[index]; // 访问 Container 的私有成员
}
Iterator& Iterator::operator++() {
++index;
return *this;
}
// ⚠️ 友元关系不传递、不继承
class A { friend class B; };
class B { friend class C; };
// C 不是 A 的友元!
Nested Class
嵌套类的定义和访问
class Outer {
private:
int outerPrivate;
static int outerStatic;
public:
int outerPublic;
// 嵌套类定义
class Inner {
private:
int innerPrivate;
public:
int innerPublic;
void show() {
std::cout << "Inner class" << std::endl;
// ⚠️ 不能直接访问 Outer 的非静态成员
// outerPrivate = 10; // Error!
// 可以访问静态成员
outerStatic = 20; // OK
}
// 通过 Outer 对象访问
void accessOuter(Outer& outer) {
outer.outerPrivate = 30; // OK: 可以访问 Outer 的私有成员
outer.outerPublic = 40; // OK
}
};
// 嵌套类的使用
Inner inner; // Outer 对象可以包含 Inner 对象
void test() {
Inner i; // 在类内部可以直接使用 Inner
i.innerPublic = 10; // 可以访问 public 成员
// i.innerPrivate = 20; // Error: private 成员不可访问
}
};
int Outer::outerStatic = 0;
// 类外定义嵌套类方法
class Outer::Inner2 {
public:
void func();
};
void Outer::Inner2::func() {
// 实现
}
int main() {
// 在类外需要使用作用域解析符
Outer::Inner innerObj;
innerObj.innerPublic = 100;
// innerObj.innerPrivate = 200; // Error
Outer outer;
outer.test();
}
STL(标准模板库)
STL(Standard Template Library,标准模板库)是 C++ 标准库的核心组成部分,提供了通用的容器、算法、迭代器和函数对象,是高效 C++ 编程的基础。
STL容器:vector、list、map等
STL 容器分为序列容器、关联容器和无序关联容器三大类。
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <array>
#include <forward_list>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
int main() {
// ==================== 序列容器 ====================
// vector: 动态数组,随机访问 O(1),尾部插入删除 O(1)
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // 尾部添加
vec.insert(vec.begin() + 2, 10); // 在索引2插入
vec.erase(vec.begin()); // 删除第一个元素
std::cout << "Vector: ";
for (const auto& item : vec) {
std::cout << item << " ";
}
std::cout << std::endl;
// list: 双向链表,任意位置插入删除 O(1)
std::list<int> lst = {1, 2, 3};
lst.push_back(4);
lst.push_front(0);
lst.insert(++lst.begin(), 99); // 在第二个位置插入
// deque: 双端队列,两端操作 O(1),支持随机访问
std::deque<int> deq = {1, 2, 3};
deq.push_back(4);
deq.push_front(0);
// array: 固定大小数组(C++11)
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << "Array size: " << arr.size() << std::endl;
// forward_list: 单向链表,更省内存
std::forward_list<int> flst = {1, 2, 3};
flst.push_front(0); // 只能头部插入
// ==================== 关联容器(基于红黑树)====================
// set: 有序唯一集合,操作 O(log n)
std::set<int> s = {3, 1, 4, 1, 5}; // 重复被忽略,自动排序
s.insert(2);
s.erase(3);
// multiset: 有序可重复集合
std::multiset<int> ms = {1, 2, 2, 3, 3, 3};
std::cout << "Count of 3: " << ms.count(3) << std::endl;
// map: 有序键值对
std::map<std::string, int> scores;
scores["Alice"] = 90;
scores.insert({"Bob", 85}); // C++11 列表初始化
for (const auto& [name, score] : scores) { // C++17 结构化绑定
std::cout << name << ": " << score << std::endl;
}
// multimap: 可重复键
std::multimap<std::string, int> mm;
mm.insert({"Alice", 90});
mm.insert({"Alice", 95});
// ==================== 无序关联容器(基于哈希表,C++11)====================
// unordered_set: 无序唯一集合,平均 O(1)
std::unordered_set<int> us = {3, 1, 4, 1, 5};
// unordered_map: 无序键值对
std::unordered_map<std::string, int> um;
um["apple"] = 5;
um["banana"] = 3;
return 0;
}
重要: 选择容器的关键原则:
- 需要频繁随机访问 → 使用
vector- 频繁在中间插入删除 → 使用
list- 频繁在两端插入删除 → 使用
deque- 需要有序且去重 → 使用
set/map- 只需快速查找,不关心顺序 → 使用
unordered_set/unordered_map
STL算法:sort、find、accumulate等
STL 算法库提供了大量通用的算法,通过迭代器操作容器。
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <functional>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
// ==================== 排序算法 ====================
// sort: 快速排序(通常 introsort)
std::sort(vec.begin(), vec.end());
// 降序排序
std::sort(vec.begin(), vec.end(), std::greater<int>());
// stable_sort: 稳定排序,保持相等元素的相对顺序
std::stable_sort(vec.begin(), vec.end());
// partial_sort: 部分排序,前 n 个元素有序
std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// nth_element: 使第 n 个元素处于排序后的位置
std::nth_element(vec.begin(), vec.begin() + 4, vec.end());
// ==================== 查找算法 ====================
vec = {3, 1, 4, 1, 5, 9, 2, 6};
// find: 线性查找
auto it = std::find(vec.begin(), vec.end(), 5);
if (it != vec.end()) {
std::cout << "Found 5 at position " << std::distance(vec.begin(), it) << std::endl;
}
// binary_search: 二分查找(要求已排序)
std::sort(vec.begin(), vec.end());
bool found = std::binary_search(vec.begin(), vec.end(), 5);
// lower_bound: 第一个不小于目标的位置
auto lb = std::lower_bound(vec.begin(), vec.end(), 4);
// upper_bound: 第一个大于目标的位置
auto ub = std::upper_bound(vec.begin(), vec.end(), 4);
// equal_range: 返回 lower_bound 和 upper_bound
auto range = std::equal_range(vec.begin(), vec.end(), 1);
// find_if: 条件查找
auto it2 = std::find_if(vec.begin(), vec.end(), [](int x) { return x > 5; });
// ==================== 数值算法 ====================
// accumulate: 累加
int sum = std::accumulate(vec.begin(), vec.end(), 0);
// 累乘
int product = std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
// inner_product: 内积
std::vector<int> vec2 = {1, 2, 3, 4, 5, 6, 7, 8};
int dot = std::inner_product(vec.begin(), vec.begin() + 3, vec2.begin(), 0);
// partial_sum: 前缀和
std::vector<int> prefix(vec.size());
std::partial_sum(vec.begin(), vec.end(), prefix.begin());
// ==================== 修改算法 ====================
// copy
std::vector<int> copy(vec.size());
std::copy(vec.begin(), vec.end(), copy.begin());
// fill
std::fill(copy.begin(), copy.end(), 0);
// generate
int n = 0;
std::generate(copy.begin(), copy.end(), [&n]() { return n++; });
// replace
std::replace(copy.begin(), copy.end(), 3, 99);
// remove-erase 惯用法
vec = {1, 2, 3, 2, 4, 2, 5};
vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
// vec: 1, 3, 4, 5
// unique: 去重(相邻重复)
vec = {1, 1, 2, 2, 2, 3, 3};
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
// reverse
std::reverse(vec.begin(), vec.end());
// rotate
vec = {1, 2, 3, 4, 5};
std::rotate(vec.begin(), vec.begin() + 2, vec.end());
// vec: 3, 4, 5, 1, 2
// ==================== 集合算法 ====================
std::vector<int> set1 = {1, 2, 3, 4, 5};
std::vector<int> set2 = {4, 5, 6, 7, 8};
std::vector<int> result;
// set_union: 并集
std::set_union(set1.begin(), set1.end(), set2.begin(), set2.end(),
std::back_inserter(result));
// set_intersection: 交集
result.clear();
std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),
std::back_inserter(result));
// set_difference: 差集
result.clear();
std::set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(),
std::back_inserter(result));
// ==================== 其他算法 ====================
// count/count_if
int count = std::count(vec.begin(), vec.end(), 5);
int count_if_result = std::count_if(vec.begin(), vec.end(),
[](int x) { return x % 2 == 0; });
// all_of/any_of/none_of (C++11)
bool all_positive = std::all_of(vec.begin(), vec.end(),
[](int x) { return x > 0; });
// min_element/max_element/minmax_element
auto min_it = std::min_element(vec.begin(), vec.end());
auto max_it = std::max_element(vec.begin(), vec.end());
return 0;
}
易错点:
remove不会真正删除元素,只是将要保留的元素移到前面,返回新的逻辑结尾。必须使用erase真正删除元素(remove-erase 惯用法)。
STL迭代器:输入迭代器、输出迭代器、前向迭代器等
迭代器是 STL 的通用访问接口,不同的迭代器类别支持不同的操作。
#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
#include <iterator>
int main() {
// ==================== 迭代器类别 ====================
// 1. 输入迭代器:只读,单次遍历(istream_iterator)
// 2. 输出迭代器:只写,单次遍历(ostream_iterator, back_inserter)
// 3. 前向迭代器:可读写,多次遍历,只能前进(forward_list)
// 4. 双向迭代器:可前进后退(list, set, map)
// 5. 随机访问迭代器:可任意位置访问(vector, deque, array)
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst = {1, 2, 3, 4, 5};
// ==================== vector - 随机访问迭代器 ====================
auto vit = vec.begin();
std::cout << "vec[2] via iterator: " << *(vit + 2) << std::endl; // 支持算术运算
std::cout << "Distance: " << (vec.end() - vit) << std::endl;
std::advance(vit, 3); // 向前移动 3 步
// ==================== list - 双向迭代器 ====================
auto lit = lst.begin();
++lit;
--lit;
// lit + 2; // 错误!双向迭代器不支持算术运算
std::advance(lit, 2); // 正确,但内部是循环 ++
// ==================== 插入迭代器 ====================
std::vector<int> dest;
auto back_it = std::back_inserter(dest); // 尾部插入
*back_it = 1; // 相当于 dest.push_back(1)
*back_it = 2;
// 流迭代器
std::ostream_iterator<int> output_it(std::cout, " ");
std::copy(vec.begin(), vec.end(), output_it);
std::cout << std::endl;
// 反向迭代器
std::cout << "Reverse: ";
for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
// ==================== 迭代器辅助函数 ====================
// std::next, std::prev (C++11)
auto next_it = std::next(vec.begin(), 2);
auto prev_it = std::prev(vec.end(), 2);
// std::distance
auto dist = std::distance(vec.begin(), vec.end());
return 0;
}
重要: 迭代器可能因容器修改而失效。
vector插入元素后所有迭代器可能失效;list插入不会使迭代器失效。使用前要解容器的迭代器失效规则。
STL函数对象和lambda表达式
函数对象(Functor)是重载了 operator() 的类,可以像函数一样调用。
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
// 自定义函数对象
class GreaterThan {
private:
int threshold;
public:
explicit GreaterThan(int t) : threshold(t) {}
bool operator()(int value) const {
return value > threshold;
}
};
// 带状态的函数对象
class SumAccumulator {
private:
double sum = 0;
int count = 0;
public:
void operator()(double value) {
sum += value;
++count;
}
double getAverage() const { return count > 0 ? sum / count : 0; }
};
int main() {
std::vector<int> nums = {1, 5, 3, 8, 2, 9, 4, 7, 6};
// ==================== 标准函数对象 ====================
std::plus<int> add;
std::greater<int> greater_than;
std::less<int> less_than;
std::cout << "10 + 5 = " << add(10, 5) << std::endl;
// ==================== 自定义函数对象 ====================
auto it = std::find_if(nums.begin(), nums.end(), GreaterThan(5));
if (it != nums.end()) {
std::cout << "First number > 5: " << *it << std::endl;
}
// 带状态的函数对象
SumAccumulator acc = std::for_each(nums.begin(), nums.end(), SumAccumulator());
std::cout << "Average: " << acc.getAverage() << std::endl;
// ==================== Lambda 表达式 ====================
// 基本语法: [捕获](参数) -> 返回类型 { 函数体 }
// 简单 lambda
auto square = [](int x) { return x * x; };
std::cout << "Square of 5: " << square(5) << std::endl;
// 值捕获
int threshold = 5;
auto greater_than_threshold = [threshold](int x) {
return x > threshold;
};
// 引用捕获
int sum = 0;
std::for_each(nums.begin(), nums.end(), [&sum](int x) { sum += x; });
// 隐式捕获 [=] 值捕获所有,[&] 引用捕获所有
auto lambda1 = [=](int x) { return x > threshold; };
auto lambda2 = [&]() { ++threshold; };
// C++14 泛型 lambda
auto generic_add = [](auto a, auto b) { return a + b; };
std::cout << "generic_add(1, 2) = " << generic_add(1, 2) << std::endl;
std::cout << "generic_add(1.5, 2.5) = " << generic_add(1.5, 2.5) << std::endl;
// ==================== Lambda 在算法中的应用 ====================
// 排序
std::sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; });
// 查找
auto it2 = std::find_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0 && x > 5;
});
// 变换
std::vector<int> squared;
std::transform(nums.begin(), nums.end(), std::back_inserter(squared),
[](int x) { return x * x; });
// 计数
int even_count = std::count_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
// 删除满足条件的元素
nums.erase(std::remove_if(nums.begin(), nums.end(),
[](int x) { return x < 5; }), nums.end());
// ==================== std::function ====================
std::function<int(int, int)> operation;
operation = [](int a, int b) { return a + b; };
std::cout << "Add: " << operation(3, 4) << std::endl;
operation = [](int a, int b) { return a * b; };
std::cout << "Multiply: " << operation(3, 4) << std::endl;
return 0;
}
重要: C++14 的泛型 Lambda 大大增强了 Lambda 的灵活性。
std::function可以存储任何可调用对象,但有一定的性能开销。
STL适配器:stack、queue、priority_queue等
容器适配器是在其他容器之上提供特殊接口的容器。
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <functional>
struct Task {
std::string name;
int priority;
bool operator<(const Task& other) const {
return priority < other.priority; // priority_queue 默认是大顶堆
}
};
int main() {
// ==================== stack ====================
std::stack<int> stk;
stk.push(1);
stk.push(2);
stk.push(3);
std::cout << "Stack top: " << stk.top() << std::endl;
stk.pop();
std::cout << "Stack size: " << stk.size() << std::endl;
// 遍历(需要复制)
std::stack<int> stk_copy = stk;
std::cout << "Stack contents: ";
while (!stk_copy.empty()) {
std::cout << stk_copy.top() << " ";
stk_copy.pop();
}
std::cout << std::endl;
// ==================== queue ====================
std::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
std::cout << "Queue front: " << q.front() << std::endl;
std::cout << "Queue back: " << q.back() << std::endl;
q.pop();
std::cout << "Queue front after pop: " << q.front() << std::endl;
// ==================== priority_queue ====================
// 默认大顶堆
std::priority_queue<int> max_heap;
max_heap.push(3);
max_heap.push(1);
max_heap.push(4);
std::cout << "Max heap (descending): ";
while (!max_heap.empty()) {
std::cout << max_heap.top() << " ";
max_heap.pop();
}
std::cout << std::endl;
// 小顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
min_heap.push(3);
min_heap.push(1);
min_heap.push(4);
std::cout << "Min heap (ascending): ";
while (!min_heap.empty()) {
std::cout << min_heap.top() << " ";
min_heap.pop();
}
std::cout << std::endl;
// 自定义类型的 priority_queue
std::priority_queue<Task> task_queue;
task_queue.push({"Write code", 3});
task_queue.push({"Fix bug", 5});
task_queue.push({"Write docs", 1});
std::cout << "Tasks by priority:" << std::endl;
while (!task_queue.empty()) {
auto task = task_queue.top();
task_queue.pop();
std::cout << " [" << task.priority << "] " << task.name << std::endl;
}
return 0;
}
重要:
stack、queue和priority_queue不提供迭代器,也不允许遍历。如果需要遍历,需要复制容器或使用其他数据结构。
异常处理
异常处理是 C++ 中处理错误和异常情况的重要机制。
try、catch和throw
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
// 自定义异常类
class MyException : public std::exception {
private:
std::string message;
int errorCode;
public:
MyException(const std::string& msg, int code)
: message(msg), errorCode(code) {}
const char* what() const noexcept override {
return message.c_str();
}
int getErrorCode() const { return errorCode; }
};
// 可能抛出异常的函数
double divide(double a, double b) {
if (b == 0) {
throw std::runtime_error("Division by zero!");
}
return a / b;
}
int main() {
// ==================== 基本异常处理 ====================
try {
double result = divide(10, 0);
std::cout << "Result: " << result << std::endl;
} catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << std::endl;
}
// ==================== 多个 catch 块 ====================
try {
std::vector<int> data = {1, 2, 3};
// data.at(10) = 5; // 抛出 out_of_range
throw MyException("Custom error", 42);
} catch (const std::out_of_range& e) {
std::cerr << "Out of range: " << e.what() << std::endl;
} catch (const MyException& e) {
std::cerr << "MyException: " << e.what() << ", Code: " << e.getErrorCode() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Standard exception: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception caught" << std::endl;
throw; // 重新抛出
}
// ==================== 函数try块(用于构造函数)====================
class Resource {
private:
int* data;
public:
Resource() try : data(new int[1000000000]) {
std::cout << "Resource acquired" << std::endl;
} catch (const std::bad_alloc& e) {
std::cerr << "Failed to allocate: " << e.what() << std::endl;
throw;
}
~Resource() { delete[] data; }
};
// ==================== noexcept 说明符 ====================
auto safeFunction = []() noexcept {
// 承诺不抛出异常
};
return 0;
}
重要: 总是按引用捕获异常(
const ExceptionType&),避免对象切片问题。基类析构函数应该声明为虚函数。
assert
#include <iostream>
#include <cassert>
#include <cmath>
// 自定义 assert 消息(C++17)
#ifdef NDEBUG
#define ASSERT_WITH_MSG(condition, msg) ((void)0)
#else
#define ASSERT_WITH_MSG(condition, msg) \
do { \
if (!(condition)) { \
std::cerr << "Assertion failed: " << #condition \
<< "\nMessage: " << msg \
<< "\nFile: " << __FILE__ \
<< "\nLine: " << __LINE__ << std::endl; \
std::abort(); \
} \
} while (0)
#endif
// 静态断言(编译期)
template<typename T>
void processPositive(T value) {
static_assert(std::is_arithmetic<T>::value, "T must be arithmetic type");
assert(value > 0); // 运行期断言
std::cout << "Processing: " << value << std::endl;
}
class Vector3D {
private:
double x, y, z;
public:
Vector3D(double x, double y, double z) : x(x), y(y), z(z) {
assert(std::isfinite(x) && std::isfinite(y) && std::isfinite(z));
}
double normalize() {
double len = std::sqrt(x*x + y*y + z*z);
assert(len > 0 && "Cannot normalize zero vector");
x /= len; y /= len; z /= len;
// 后置条件检查
double newLen = std::sqrt(x*x + y*y + z*z);
assert(std::abs(newLen - 1.0) < 1e-10);
return len;
}
};
int main() {
// 基本 assert
int x = 5;
assert(x > 0);
// 带消息的断言
ASSERT_WITH_MSG(x == 5, "x should be 5");
// 静态断言
processPositive(3.14);
// 使用断言的类
Vector3D v(3, 4, 0);
double originalLen = v.normalize();
// ⚠️ 不要在 assert 中包含副作用!
// 错误:assert(++i > 0); // 在 release 中不会执行!
return 0;
}
重要:
assert在定义了NDEBUG的发布版本中会被移除。永远不要在assert中包含副作用代码。使用static_assert进行编译期检查。
nothrow
#include <iostream>
#include <new> // for std::nothrow
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed" << std::endl; }
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
int main() {
// ==================== nothrow new ====================
// 普通 new 失败时抛出 std::bad_alloc
// nothrow new 失败时返回 nullptr
int* p1 = new (std::nothrow) int[1000000000];
if (p1 == nullptr) {
std::cerr << "Memory allocation failed!" << std::endl;
} else {
std::cout << "Memory allocated successfully" << std::endl;
delete[] p1;
}
// 对象分配
MyClass* obj = new (std::nothrow) MyClass();
if (obj == nullptr) {
std::cerr << "Object allocation failed!" << std::endl;
} else {
delete obj;
}
// ==================== 比较 ====================
// 异常版本
try {
int* p2 = new int[10000000000];
delete[] p2;
} catch (const std::bad_alloc& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
// nothrow 版本
int* p3 = new (std::nothrow) int[10000000000];
if (p3 == nullptr) {
std::cerr << "Allocation returned nullptr" << std::endl;
} else {
delete[] p3;
}
// ==================== 使用场景 ====================
// 1. 实时系统:不能容忍异常带来的开销
// 2. 需要手动检查分配结果的情况
// 3. 分配失败后有回退策略的情况
auto allocateWithFallback = [](size_t size) -> int* {
int* p = new (std::nothrow) int[size];
if (p == nullptr) {
std::cerr << "Full allocation failed, trying smaller..." << std::endl;
p = new (std::nothrow) int[size / 2];
}
return p;
};
int* fallback_p = allocateWithFallback(10000000000);
if (fallback_p) {
std::cout << "Fallback allocation succeeded" << std::endl;
delete[] fallback_p;
}
return 0;
}
重要:
nothrow只针对内存分配失败。如果构造函数抛出异常,仍然会传播。nothrow 主要用于需要精细控制或无法使用异常处理的场景。
Modern C++特性
C++11新特性
C++11是现代C++的里程碑版本,引入了众多革命性特性,彻底改变了C++的编程范式。
1. auto 关键字 - 自动类型推导
auto 让编译器自动推导变量类型,简化代码编写,特别是在处理复杂类型时。
#include <vector>
#include <map>
#include <string>
int main() {
// 基本用法
auto i = 42; // int
auto d = 3.14; // double
auto s = "hello"; // const char*
// 复杂类型推导 - 避免冗长的类型书写
std::vector<std::map<std::string, int>> vec;
// 传统写法:std::vector<std::map<std::string, int>>::iterator it = vec.begin();
auto it = vec.begin(); // 简洁清晰
// 范围for循环中使用
for (auto& elem : vec) {
// elem 自动推导为 std::map<std::string, int>&
}
return 0;
}
重要:
auto会去除引用和cv限定符(const/volatile)。如需保留,需显式添加&或const。
易错点: 使用
auto推导初始化列表时,auto x = {1, 2, 3}推导为std::initializer_list<int>,而非std::vector<int>。
2. 范围for循环 (Range-based for loop)
简洁地遍历容器或数组,代码更直观安全。
#include <vector>
#include <iostream>
#include <map>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 值拷贝方式遍历(修改不影响原容器)
for (auto x : vec) {
x *= 2; // 只修改拷贝,原vec不变
}
// 引用方式遍历(可修改原容器)
for (auto& x : vec) {
x *= 2; // 原vec被修改
}
// const引用方式(只读,性能更好)
for (const auto& x : vec) {
std::cout << x << " ";
}
// 遍历map
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}};
for (const auto& [name, score] : scores) { // C++17结构化绑定,C++11需用pair
std::cout << name << ": " << score << std::endl;
}
return 0;
}
重要: 范围for循环依赖于
std::begin()和std::end(),自定义容器需提供这些接口。
易错点: 不要在范围for循环中修改容器大小(增删元素),会导致迭代器失效。
3. 智能指针 (Smart Pointers)
自动管理内存,避免内存泄漏和悬挂指针问题。
#include <memory>
#include <iostream>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void doWork() { std::cout << "Working\n"; }
};
int main() {
// unique_ptr - 独占所有权,不可复制,可移动
{
std::unique_ptr<Resource> res = std::make_unique<Resource>();
res->doWork();
// 离开作用域自动释放
}
// shared_ptr - 共享所有权,引用计数
{
std::shared_ptr<Resource> res1 = std::make_shared<Resource>();
{
std::shared_ptr<Resource> res2 = res1; // 引用计数+1
std::cout << "Count: " << res1.use_count() << std::endl; // 2
} // 引用计数-1,但不释放
std::cout << "Count: " << res1.use_count() << std::endl; // 1
} // 引用计数为0,释放资源
// weak_ptr - 弱引用,不增加引用计数,用于打破循环引用
std::weak_ptr<Resource> weak;
{
std::shared_ptr<Resource> shared = std::make_shared<Resource>();
weak = shared;
std::cout << "Expired: " << weak.expired() << std::endl; // 0 (false)
if (auto locked = weak.lock()) { // 使用时需lock转换为shared_ptr
locked->doWork();
}
}
std::cout << "Expired: " << weak.expired() << std::endl; // 1 (true)
return 0;
}
重要: 优先使用
std::make_unique和std::make_shared,它们更安全(异常安全)且效率更高。
易错点: 不要用裸指针创建多个
shared_ptr,会导致多次释放。正确做法:auto p = std::make_shared<T>(),然后传递p。
4. Lambda 表达式
匿名函数,简化回调和临时函数对象的编写。
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9};
// 基本语法: [捕获](参数) -> 返回类型 { 函数体 }
// 1. 无捕获lambda
auto print = [](int x) { std::cout << x << " "; };
std::for_each(nums.begin(), nums.end(), print);
// 2. 值捕获(捕获时复制)
int factor = 2;
auto multiply = [factor](int x) { return x * factor; };
// factor 的修改不影响lambda内部
// 3. 引用捕获
int sum = 0;
std::for_each(nums.begin(), nums.end(), [&sum](int x) { sum += x; });
// 4. 隐式捕获
auto lambda1 = [=]() { return factor + sum; }; // 全部值捕获
auto lambda2 = [&]() { return factor + sum; }; // 全部引用捕获
auto lambda3 = [=, &sum]() { return factor + sum; }; // factor值捕获,sum引用捕获
// 5. mutable lambda(允许修改值捕获的变量)
int count = 0;
auto counter = [count]() mutable { return ++count; };
std::cout << counter() << std::endl; // 1
std::cout << counter() << std::endl; // 2
std::cout << count << std::endl; // 0 (原变量不变)
// 实际应用:自定义排序
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; }); // 降序排序
return 0;
}
重要: Lambda默认是
const的,要修改捕获变量需加mutable关键字,或使用引用捕获。
易错点: 以引用方式捕获局部变量,在lambda超出该变量作用域后使用会导致悬空引用崩溃。
5. 右值引用与移动语义
避免不必要的拷贝,大幅提升性能,特别是在处理大对象时。
#include <iostream>
#include <vector>
#include <string>
class BigData {
int* data;
size_t size;
public:
// 构造函数
BigData(size_t n) : size(n), data(new int[n]) {
std::cout << "Constructor\n";
}
// 析构函数
~BigData() {
delete[] data;
std::cout << "Destructor\n";
}
// 拷贝构造函数(深拷贝)
BigData(const BigData& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
std::cout << "Copy Constructor\n";
}
// 拷贝赋值运算符
BigData& operator=(const BigData& other) {
std::cout << "Copy Assignment\n";
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
// 移动构造函数(转移资源所有权)
BigData(BigData&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 将源对象置空
other.size = 0;
std::cout << "Move Constructor\n";
}
// 移动赋值运算符
BigData& operator=(BigData&& other) noexcept {
std::cout << "Move Assignment\n";
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};
// 工厂函数返回临时对象
BigData createData() {
BigData d(1000);
return d; // NRVO/移动语义优化
}
int main() {
// 场景1:使用临时对象(右值)
BigData d1 = createData(); // 触发移动构造(或NRVO优化直接构造)
// 场景2:显式移动
BigData d2(500);
BigData d3 = std::move(d2); // d2资源被转移,现在d2为空
// d2 现在处于"有效但未指定状态",不要再使用
// 场景3:emplace_back 避免拷贝
std::vector<BigData> vec;
vec.reserve(10);
vec.emplace_back(100); // 直接在vector内存中构造,无拷贝/移动
return 0;
}
重要: 被
std::move的对象虽然仍有效(可析构、可赋值),但值未指定,不要再依赖其原有值。
易错点: 移动构造函数和移动赋值必须标记
noexcept,否则标准库容器在某些操作(如扩容)时不会使用它们,导致性能损失。
6. 初始化列表 (Initializer List)
统一初始化语法,避免窄化转换,支持自定义类型的列表初始化。
#include <vector>
#include <map>
#include <string>
class MyClass {
int x, y, z;
public:
// 构造函数支持初始化列表
MyClass(int a, int b, int c) : x(a), y(b), z(c) {}
// std::initializer_list 构造函数
MyClass(std::initializer_list<int> list) {
auto it = list.begin();
x = (it != list.end()) ? *it++ : 0;
y = (it != list.end()) ? *it++ : 0;
z = (it != list.end()) ? *it++ : 0;
}
};
int main() {
// 统一初始化语法 {}
int a{5}; // 直接初始化
int b = {10}; // 拷贝初始化(效果相同)
// 防止窄化转换(编译错误)
// int c{3.14}; // ERROR: double 到 int 是窄化转换
int d(3.14); // OK,但会截断(传统语法的坑)
// 容器初始化
std::vector<int> vec{1, 2, 3, 4, 5};
std::map<std::string, int> map{{"Alice", 90}, {"Bob", 85}};
// 自定义类型
MyClass obj1{1, 2, 3}; // 调用 initializer_list 构造
MyClass obj2 = {4, 5, 6}; // 同上
// 数组风格初始化
int arr[]{1, 2, 3, 4, 5};
// auto 与初始化列表
auto list = {1, 2, 3}; // std::initializer_list<int>
return 0;
}
重要: 统一初始化
{}会进行窄化转换检查,是更安全的初始化方式,应优先使用。
易错点: 当类同时有普通构造函数和
std::initializer_list构造函数时,{}初始化优先匹配initializer_list版本,可能导致意外行为。
7. nullptr - 空指针常量
类型安全的空指针,替代有歧义的 NULL 和 0。
#include <iostream>
void foo(int x) {
std::cout << "foo(int): " << x << std::endl;
}
void foo(int* p) {
std::cout << "foo(int*): " << p << std::endl;
}
void foo(double* p) {
std::cout << "foo(double*): " << p << std::endl;
}
int main() {
// NULL 通常被定义为 0 或 (void*)0,类型不明确
foo(NULL); // 调用 foo(int),可能不是预期行为!
foo(0); // 调用 foo(int)
// nullptr 类型为 std::nullptr_t,可隐式转换为任意指针类型
foo(nullptr); // 编译错误!存在歧义:int* 和 double*
foo(static_cast<int*>(nullptr)); // 明确调用 foo(int*)
// 检查空指针的正确方式
int* p = nullptr;
if (p == nullptr) { // 清晰明确
std::cout << "p is null\n";
}
return 0;
}
重要: 始终使用
nullptr代替NULL或0,它是类型安全的,能避免重载解析的歧义。
8. 类型别名 - using
比 typedef 更直观、功能更强的类型别名机制。
#include <vector>
#include <map>
#include <functional>
// 函数指针类型:传统typedef vs using
typedef void (*FuncPtr_old)(int, int);
using FuncPtr = void (*)(int, int); // 更清晰,与变量声明一致
// 模板别名 - typedef 无法实现
template<typename T>
using Vec = std::vector<T>; // 模板类型别名
template<typename K, typename V>
using Map = std::map<K, V>;
// 复杂类型的简化
template<typename T>
using Callback = std::function<void(T)>;
int main() {
Vec<int> v; // 等价于 std::vector<int>
Map<std::string, int> m;
// 与模板结合使用
Callback<int> onComplete = [](int result) {
// 处理结果
};
return 0;
}
重要: 模板别名只能用
using,这是C++11替代typedef的关键优势。
C++14新特性
C++14在C++11基础上进行了小幅但实用的改进,主要是对泛型和lambda的增强。
1. 泛型 Lambda
Lambda参数可以使用 auto,实现类似模板的功能。
#include <iostream>
#include <vector>
#include <string>
int main() {
// C++11: 只能指定具体类型
// auto lambda = [](int x) { return x * 2; };
// C++14: 泛型lambda,参数使用auto
auto multiply = [](auto a, auto b) {
return a * b;
};
// 可用于多种类型
std::cout << multiply(3, 4) << std::endl; // int: 12
std::cout << multiply(2.5, 4.0) << std::endl; // double: 10
std::cout << multiply(std::string("Hi"), 3) << std::endl; // string: HiHiHi
// 泛型lambda在STL中的应用
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<std::string> strs = {"a", "b", "c"};
auto print = [](const auto& elem) {
std::cout << elem << " ";
};
for (const auto& n : nums) print(n);
for (const auto& s : strs) print(s);
return 0;
}
重要: 泛型lambda实际上生成了一个带有模板
operator()的匿名仿函数类。
2. Lambda 捕获表达式
允许在捕获列表中初始化变量。
#include <iostream>
#include <memory>
int main() {
int x = 10;
// C++11: 只能捕获现有变量
// auto f = [x]() { return x * 2; };
// C++14: 捕获时初始化新变量
auto f = [y = x + 5]() { // y是新变量,值为15
return y;
};
std::cout << f() << std::endl; // 15
// 移动捕获 - 特别有用!
auto ptr = std::make_unique<int>(42);
// C++11无法直接移动捕获unique_ptr,C++14可以:
auto task = [p = std::move(ptr)]() {
std::cout << *p << std::endl;
};
// ptr 现在为空(被移动到lambda中)
task(); // 输出 42
// 通用捕获模式
auto multiplier = [factor = 2](auto x) {
return x * factor;
};
return 0;
}
重要: 捕获表达式
[var = expr]创建的是lambda的新数据成员,而非外部变量的引用。
易错点: 捕获表达式在lambda定义时求值,而非调用时。不要在捕获表达式中使用可能变化的值。
3. 自动返回类型推导
函数返回类型可由编译器自动推导。
#include <vector>
// C++11: 需要尾置返回类型
template<typename T, typename U>
auto add_old(T t, U u) -> decltype(t + u) {
return t + u;
}
// C++14: 自动推导返回类型
template<typename T, typename U>
auto add(T t, U u) { // 返回类型自动推导为 decltype(t + u)
return t + u;
}
// 可用于普通函数(不仅模板)
auto createVector() {
return std::vector<int>{1, 2, 3}; // 返回类型推导为 std::vector<int>
}
// 递归函数需要显式声明(C++14无法推导递归)
auto factorial(int n) -> int { // 递归必须显式声明返回类型
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
auto result = add(1, 2.5); // double
auto vec = createVector();
return 0;
}
重要: 如果函数有多个返回语句,它们必须推导出相同的类型,否则编译错误。
易错点: 返回类型推导依赖于所有返回表达式的类型一致性。混合返回
int和double会导致推导失败。
4. 变量模板
可以定义变量模板。
#include <iostream>
#include <type_traits>
// 常量变量模板
template<typename T>
constexpr T pi = T(3.1415926535897932385);
// 类型特征变量模板
template<typename T>
constexpr bool is_pointer_v = std::is_pointer<T>::value;
// 使用
int main() {
// 获取不同类型的pi值
std::cout << pi<int> << std::endl; // 3
std::cout << pi<double> << std::endl; // 3.14159...
std::cout << pi<float> << std::endl; // 3.14159f
// 类型检查
static_assert(is_pointer_v<int*> == true);
static_assert(is_pointer_v<int> == false);
return 0;
}
重要: C++14引入变量模板的概念,C++17中标准库大量添加了
_v后缀的变量模板(如is_integral_v)。
5. std::make_unique
补充了与 make_shared 对应的工厂函数。
#include <memory>
class Widget {
int id;
public:
Widget(int i) : id(i) {}
int getId() const { return id; }
};
int main() {
// C++11: 没有 make_unique,需要直接构造
// std::unique_ptr<Widget> w(new Widget(42)); // 不够安全
// C++14: 使用 make_unique
auto w1 = std::make_unique<Widget>(42); // 更安全,异常安全
// 数组版本
auto arr = std::make_unique<int[]>(10); // 10个int的数组
arr[0] = 100;
// 对比:make_shared vs make_unique
auto shared = std::make_shared<Widget>(100);
auto unique = std::make_unique<Widget>(200);
return 0;
}
重要:
std::make_unique提供了异常安全性。如果构造函数抛异常,不会泄漏已分配的内存。
6. 放宽的 constexpr 函数限制
C++14允许 constexpr 函数包含更多语句。
#include <array>
// C++11 constexpr: 只能有单个return语句
// C++14 constexpr: 允许局部变量、循环、if语句等
constexpr int factorial(int n) {
int result = 1; // C++11不允许:局部变量
for (int i = 1; i <= n; ++i) { // C++11不允许:循环
result *= i;
}
return result;
}
constexpr int fibonacci(int n) {
if (n <= 1) return n; // C++11不允许:多个return
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 实际应用:编译期计算数组
int main() {
constexpr int fact5 = factorial(5); // 120,编译期计算
std::array<int, factorial(5)> arr; // 编译期确定数组大小
return 0;
}
重要: C++14的 constexpr 函数几乎与普通函数一样灵活,但所有操作必须是编译期可确定的。
C++17新特性
C++17是渐进式改进,引入了许多让代码更简洁、更安全的特性。
1. 结构化绑定 (Structured Bindings)
一键解构tuple、pair和struct,告别繁琐的 std::get。
#include <tuple>
#include <map>
#include <string>
#include <utility>
struct Point {
double x, y, z;
};
int main() {
// 解构pair
std::pair<int, std::string> p = {42, "answer"};
auto [num, str] = p; // num=42, str="answer"
// 解构tuple
std::tuple<int, double, char> t = {1, 3.14, 'a'};
auto [i, d, c] = t;
// 解构struct(公开非静态数据成员)
Point pt{1.0, 2.0, 3.0};
auto [x, y, z] = pt;
// 遍历map的神器
std::map<std::string, int> scores{{"Alice", 90}, {"Bob", 85}};
for (const auto& [name, score] : scores) {
// 不再需要:const auto& pair,然后 pair.first, pair.second
std::cout << name << ": " << score << std::endl;
}
// 修改绑定值(使用引用)
auto& [ref_x, ref_y, ref_z] = pt;
ref_x = 10.0; // pt.x 也被修改
// 忽略某些值(C++17不能直接忽略,C++20可以)
auto [first, second, third] = t; // 必须全部绑定
return 0;
}
重要: 结构化绑定创建了别名,而非副本。使用
auto&或const auto&可以避免拷贝大对象。
易错点: 绑定数组时,
auto [a, b, c]是拷贝,如需修改原数组,使用auto& [a, b, c]。
2. if/switch 语句初始化
在条件语句中直接初始化变量,缩小作用域。
#include <map>
#include <string>
#include <iostream>
#include <mutex>
int main() {
std::map<std::string, int> data{{"key", 42}};
// C++17前:需要在if外声明变量
// auto it = data.find("key");
// if (it != data.end()) { ... }
// C++17: if 带初始化
if (auto it = data.find("key"); it != data.end()) {
// it 只在if语句内有效
std::cout << "Found: " << it->second << std::endl;
} // it 在这里销毁
// it 在这里不可见(编译错误)
// switch 带初始化
switch (int x = rand() % 10; x) {
case 0: std::cout << "Zero"; break;
default: std::cout << "Non-zero: " << x; break;
}
// 配合锁使用 - 自动管理锁的生命周期
std::mutex mtx;
if (std::lock_guard<std::mutex> lock(mtx); true) {
// 临界区代码,lock在此自动释放
}
return 0;
}
重要: 初始化语句中声明的变量只在整个if/switch语句块中有效,有助于限制变量作用域。
3. 类模板参数推导 (CTAD)
构造模板类时自动推导模板参数。
#include <vector>
#include <map>
#include <tuple>
#include <string>
#include <memory>
// C++17前:必须显式指定模板参数
// std::pair<int, std::string> p(1, "one");
// C++17: 编译器自动推导
int main() {
// pair和tuple
std::pair p(1, "one"); // 推导为 pair<int, const char*>
std::tuple t(1, 2.5, "hello"); // 推导为 tuple<int, double, const char*>
// 容器
std::vector vec{1, 2, 3}; // vector<int>
std::map m{{"a", 1}, {"b", 2}}; // map<const char*, int>
// 智能指针(配合make函数)
auto ptr = std::make_unique<int>(42);
// 自定义推导指引(Deduction Guides)
// 可指导编译器如何推导复杂场景
return 0;
}
重要: CTAD减少了模板参数的冗余书写,但要注意类型推导可能与你预期不同(如
const char*而非std::string)。
4. constexpr if - 编译期条件
在模板中根据类型进行编译期分支,替代SFINAE。
#include <type_traits>
#include <iostream>
#include <vector>
#include <string>
// C++17前:使用模板特化或SFINAE
// C++17: constexpr if
template<typename T>
void print(const T& value) {
if constexpr (std::is_integral_v<T>) {
// 只有T是整数类型时,这部分才会编译
std::cout << "Integer: " << value << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String: " << value << std::endl;
} else {
std::cout << "Other: " << value << std::endl;
}
}
// 实际应用:统一的序列化接口
template<typename T>
std::string serialize(const T& value) {
if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(value);
} else if constexpr (std::is_same_v<T, std::string>) {
return "\"" + value + "\"";
} else {
// 容器类型
std::string result = "[";
bool first = true;
for (const auto& elem : value) {
if (!first) result += ", ";
result += serialize(elem);
first = false;
}
result += "]";
return result;
}
}
int main() {
print(42); // Integer
print(3.14); // Other
print(std::string("hello")); // String
std::vector<int> vec{1, 2, 3};
std::cout << serialize(vec) << std::endl; // [1, 2, 3]
return 0;
}
重要:
if constexpr是编译期分支,不满足条件的分支完全不编译,可用于处理模板中不存在的成员或方法。
易错点:
if constexpr只在模板上下文中有效,普通函数中它只是普通if,不会导致分支被丢弃。
5. std::string_view
零拷贝的字符串视图,替代const string&作为函数参数。
#include <string>
#include <string_view>
#include <iostream>
// 旧方式:接收const string&,C风格字符串会隐式构造
void process_string_old(const std::string& s) {
std::cout << s << std::endl;
}
// 新方式:使用string_view,无拷贝,支持所有字符串类型
void process_string(std::string_view sv) {
// 提供与string类似的接口
std::cout << "Length: " << sv.size() << ", Content: " << sv << std::endl;
// 子串操作(零拷贝,仅移动指针和长度)
auto sub = sv.substr(0, 5); // O(1),不分配新内存
// 注意:string_view 不拥有内存!
}
int main() {
std::string str = "Hello, World!";
const char* cstr = "C-style string";
// 都可以无缝传递,无内存分配
process_string(str); // string
process_string(cstr); // const char*
process_string("literal"); // 字符串字面量
// 从子串创建,无拷贝
std::string_view view(str.c_str() + 7, 5);
std::cout << view << std::endl; // World
return 0;
}
重要:
string_view是非 owning 视图,确保被引用的字符串在 view 生命周期内有效。不要返回局部变量的string_view。
易错点:
string_view不是以null结尾的!使用C API需要显式构造std::string。
6. std::optional
表示可能不存在的值,替代返回特殊值或抛出异常。
#include <optional>
#include <iostream>
#include <string>
// 可能失败的查找操作
std::optional<int> find_user_age(const std::string& name) {
if (name == "Alice") return 30;
if (name == "Bob") return 25;
return std::nullopt; // 表示无值
}
// 带默认值的场景
std::optional<std::string> get_config(const std::string& key) {
// 模拟配置读取
if (key == "host") return "localhost";
return std::nullopt;
}
int main() {
// 检查是否有值
auto age = find_user_age("Alice");
if (age.has_value()) {
std::cout << "Age: " << age.value() << std::endl;
}
// 更简洁的写法
if (age) {
std::cout << "Age: " << *age << std::endl; // 解引用
}
// 安全取值(带默认值)
auto unknown_age = find_user_age("Unknown");
std::cout << unknown_age.value_or(-1) << std::endl; // -1
// 原地构造
std::optional<std::string> opt;
opt.emplace("constructed in place"); // 避免拷贝
// 重置为空
opt.reset();
return 0;
}
重要:
std::optional比返回特殊值(如-1、nullptr)更清晰、更安全,语义明确。
易错点: 直接解引用空的
optional是未定义行为!务必先检查或使用value_or。
7. 折叠表达式 (Fold Expressions)
简化可变参数模板的编写。
#include <iostream>
// C++11/14: 需要递归展开
// C++17: 一元右折叠
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 一元右折叠: (a + (b + (c + ...)))
}
template<typename... Args>
void print_all(Args... args) {
(std::cout << ... << args) << std::endl; // 折叠输出
}
template<typename... Args>
bool all_true(Args... args) {
return (args && ...); // 逻辑与折叠
}
template<typename T, typename... Args>
void push_all(std::vector<T>& vec, Args... args) {
(vec.push_back(args), ...); // 逗号运算符折叠
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 15
print_all("Hello", " ", "World", "!"); // Hello World!
std::cout << std::boolalpha;
std::cout << all_true(true, true, true) << std::endl; // true
std::cout << all_true(true, false, true) << std::endl; // false
return 0;
}
重要: 折叠表达式支持多种运算符:
+,-,*,/,%,^,&,|,<<,>>,&&,||,,。
8. 内联变量 (Inline Variables)
允许在头文件中定义变量,解决跨翻译单元的变量定义问题。
// config.h - 头文件
#pragma once
#include <string>
// C++17前:头文件中定义变量会导致多重定义错误
// 需要用 extern 声明,在单个 cpp 文件中定义
// C++17: 内联变量可在头文件中定义
inline constexpr int MAX_BUFFER_SIZE = 4096;
inline const std::string APP_NAME = "MyApp";
// 模板类的静态成员也可内联
template<typename T>
class Singleton {
inline static T* instance = nullptr; // C++17前需在类外定义
public:
static T& getInstance() {
if (!instance) instance = new T();
return *instance;
}
};
重要:
inline变量在所有翻译单元中共享同一地址,解决了头文件定义全局变量的难题。
C++20新特性
C++20是C++11之后最大的版本,引入概念、协程、模块等革命性特性。
1. 概念 (Concepts)
对模板参数进行约束,让编译错误更易读,代码更清晰。
#include <concepts>
#include <vector>
#include <iostream>
// 定义概念:可相加的类型
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>; // 要求支持+运算且结果可转为T
};
// 定义概念:数值类型
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// 定义概念:可迭代
template<typename T>
concept Iterable = requires(T t) {
{ t.begin() } -> std::input_or_output_iterator;
{ t.end() } -> std::input_or_output_iterator;
};
// 使用概念约束模板
// 语法1:requires子句
template<typename T>
requires Addable<T>
T add(T a, T b) {
return a + b;
}
// 语法2:简写形式(推荐)
auto multiply(Numeric auto a, Numeric auto b) {
return a * b;
}
// 语法3:模板参数直接约束
template<Iterable Container>
void print_all(const Container& c) {
for (const auto& elem : c) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
// 组合概念
template<typename T>
concept AddableNumeric = Numeric<T> && Addable<T>;
// 使用标准库概念
#include <ranges>
template<std::ranges::range R>
auto sum_range(R&& r) {
return std::ranges::fold_left(r, 0, std::plus{});
}
int main() {
std::cout << add(1, 2) << std::endl; // OK
std::cout << add(1.5, 2.5) << std::endl; // OK
// add("hello", "world"); // 编译错误:不满足Addable概念
std::vector<int> vec{1, 2, 3, 4, 5};
print_all(vec); // OK
return 0;
}
重要: Concepts让模板错误从几百行晦涩的SFINAE错误变成清晰的”不满足XXX概念”。
2. 范围库 (Ranges)
惰性求值的管道操作,让数据处理代码更优雅。
#include <ranges>
#include <vector>
#include <iostream>
#include <algorithm>
int main() {
std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 传统方式:多个独立算法调用
std::vector<int> temp;
std::copy_if(nums.begin(), nums.end(), std::back_inserter(temp),
[](int n) { return n % 2 == 0; });
std::vector<int> result;
std::transform(temp.begin(), temp.end(), std::back_inserter(result),
[](int n) { return n * n; });
// C++20 Ranges: 管道操作,惰性求值
auto view = nums
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
// 遍历时才真正计算
for (int n : view) {
std::cout << n << " "; // 4 16 36 64 100
}
std::cout << std::endl;
// 更多视图操作
auto view2 = nums
| std::views::drop(3) // 跳过前3个
| std::views::take(4) // 取4个
| std::views::reverse; // 反转
// 生成无限序列
auto even_numbers = std::views::iota(0) // 0, 1, 2, 3, ...
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::take(10); // 取前10个偶数
// 转换为容器
std::vector<int> collected(view.begin(), view.end());
// 范围算法(不再需要传递begin/end迭代器)
std::ranges::sort(nums, std::greater{}); // 降序排序
return 0;
}
重要: Ranges视图是惰性的,只有在遍历时才会执行计算。可以组合无限序列而不会内存溢出。
3. 协程 (Coroutines)
支持挂起和恢复执行的函数,用于异步编程和生成器。
#include <coroutine>
#include <iostream>
#include <optional>
// 简单的生成器实现
template<typename T>
struct Generator {
struct promise_type {
T current_value;
auto get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
auto yield_value(T value) {
current_value = value;
return std::suspend_always{};
}
};
using Handle = std::coroutine_handle<promise_type>;
Handle handle;
explicit Generator(Handle h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
// 禁止拷贝,允许移动
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) : handle(other.handle) { other.handle = nullptr; }
class Iterator {
Handle handle;
public:
explicit Iterator(Handle h) : handle(h) {}
Iterator& operator++() {
handle.resume();
return *this;
}
bool operator!=(const Iterator& other) const {
return handle != other.handle;
}
T operator*() const {
return handle.promise().current_value;
}
};
Iterator begin() {
handle.resume();
return Iterator(handle);
}
Iterator end() {
return Iterator(nullptr);
}
};
// 使用协程定义斐波那契数列生成器
Generator<int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a; // 挂起并返回值
int next = a + b;
a = b;
b = next;
}
}
// 无限序列生成器
Generator<int> counter(int start = 0) {
while (true) {
co_yield start++;
}
}
int main() {
std::cout << "Fibonacci (10 terms): ";
for (auto n : fibonacci(10)) {
std::cout << n << " ";
}
std::cout << std::endl;
std::cout << "Counter: ";
auto c = counter(100);
auto it = c.begin();
for (int i = 0; i < 5; ++i, ++it) {
std::cout << *it << " "; // 100 101 102 103 104
}
std::cout << std::endl;
return 0;
}
重要: 协程的
co_await,co_yield,co_return三个关键字会改变普通函数为协程。协程状态存储在堆上。
易错点: 协程需要自定义 promise_type 来定义行为,标准库尚未提供通用的
std::generator,需要自己实现或使用第三方库。
4. 模块 (Modules)
替代头文件,加快编译速度,解决宏污染问题。
// ===== math_module.ixx (模块接口文件) =====
export module math; // 声明模块名
import <string>; // 导入标准库模块(C++23)
// 导出声明
export int add(int a, int b);
// 导出类
export class Calculator {
public:
int multiply(int a, int b);
static std::string version();
};
// 导出模板
export template<typename T>
T square(T value) {
return value * value;
}
// ===== math_module.cpp (模块实现文件) =====
module math; // 实现模块
int add(int a, int b) {
return a + b;
}
int Calculator::multiply(int a, int b) {
return a * b;
}
std::string Calculator::version() {
return "Math Module v1.0";
}
// ===== main.cpp (使用模块) =====
import math; // 导入模块(无重复定义问题)
// 对比:#include "math.h" 会复制文本,可能导致重复定义
int main() {
int sum = add(3, 4);
Calculator calc;
int product = calc.multiply(5, 6);
auto sq = square(5.5); // 模板实例化
return 0;
}
重要: 模块不是文本替换(不像
#include),而是编译后的二进制接口,编译速度可提升10倍以上。
易错点: 模块和头文件可以共存,但不能混用宏来条件导出。模块不支持宏导出(这是设计上的优点)。
5. 三路比较运算符 (<=>)
自动生成所有比较运算符,简化类定义。
#include <compare>
#include <iostream>
#include <string>
class Version {
int major, minor, patch;
public:
Version(int m, int n, int p) : major(m), minor(n), patch(p) {}
// 定义 <=> 自动生成 ==, !=, <, <=, >, >=
auto operator<=>(const Version&) const = default;
// C++20前需要写6个比较运算符!
// bool operator==(const Version& other) const { ... }
// bool operator!=(const Version& other) const { ... }
// bool operator<(const Version& other) const { ... }
// ... 等等
};
// 自定义比较逻辑
class Person {
std::string name;
int age;
public:
Person(std::string n, int a) : name(std::move(n)), age(a) {}
// 自定义 <=>:先比年龄,再比名字
std::strong_ordering operator<=>(const Person& other) const {
if (auto cmp = age <=> other.age; cmp != 0) return cmp;
return name <=> other.name;
}
// == 可能需要单独定义(如果成员比较逻辑不同)
bool operator==(const Person& other) const = default;
};
int main() {
Version v1(1, 0, 0), v2(1, 1, 0);
if (v1 < v2) std::cout << "v1 < v2" << std::endl;
if (v1 <= v2) std::cout << "v1 <= v2" << std::endl;
if (v1 != v2) std::cout << "v1 != v2" << std::endl;
// <=> 返回三种结果类型:strong_ordering, weak_ordering, partial_ordering
auto result = 5 <=> 10; // strong_ordering::less
return 0;
}
重要:
= default生成的<=>按成员字典序比较。如有自定义需求,需手动实现。
6. 指定初始化 (Designated Initialization)
像C语言一样按名称初始化结构体成员。
#include <iostream>
#include <string>
struct Point {
double x;
double y;
double z = 0.0; // 默认成员初始化器
};
struct Config {
std::string host = "localhost";
int port = 8080;
bool ssl = false;
};
int main() {
// C++20: 指定初始化
Point p1{.x = 1.0, .y = 2.0}; // z 使用默认值 0.0
Point p2{.x = 3.0, .y = 4.0, .z = 5.0};
// 乱序初始化(C++20允许)
Point p3{.y = 10.0, .x = 5.0}; // 但顺序初始化更推荐
// 实际应用:配置结构
Config prod{.host = "api.example.com", .port = 443, .ssl = true};
Config local{.port = 3000}; // 其他用默认值
// 与初始化列表结合
Point points[] = {
{.x = 0, .y = 0},
{.x = 1, .y = 1},
{.x = 2, .y = 4, .z = 8}
};
return 0;
}
重要: 指定初始化必须按成员声明顺序进行(C++20不允许乱序),且不能跳过成员。
7. consteval 和 constinit
更强的编译期保证。
#include <iostream>
// consteval: 强制编译期求值,运行时调用是编译错误
consteval int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
// constexpr: 编译期或运行期都可以
constexpr int maybe_compile_time(int n) {
return n * 2;
}
// constinit: 强制静态/线程局部变量编译期初始化
constinit int global_value = factorial(5); // 120,编译期确定
// constinit int bad = maybe_compile_time(rand()); // 错误:rand()不是编译期常量
int main() {
constexpr int f5 = factorial(5); // OK,编译期
// int f = factorial(6); // 错误:运行时调用consteval函数
int runtime = 10;
int m = maybe_compile_time(runtime); // OK,运行期求值
constexpr int c = maybe_compile_time(10); // OK,编译期求值
// constinit 变量可以在运行期修改
global_value = 100; // OK
return 0;
}
重要:
consteval= 必须是编译期;constexpr= 可以是编译期;constinit= 必须编译期初始化但可运行期修改。
8. 日历和时区库
现代日期时间处理,告别C风格time.h。
#include <chrono>
#include <iostream>
#include <format>
int main() {
namespace chrono = std::chrono;
// 系统时间点
auto now = chrono::system_clock::now();
// 日期字面量(C++20)
using namespace chrono;
auto birthday = 1990y/January/15d; // 1990年1月15日
// 时间间隔
auto duration = 2h + 30min + 45s;
std::cout << "Duration: " << duration << std::endl;
// 日期运算
auto tomorrow = chrono::floor<days>(now) + days(1);
auto next_week = chrono::floor<days>(now) + weeks(1);
// 格式化输出(C++20)
// std::cout << std::format("{:%Y-%m-%d %H:%M:%S}", now) << std::endl;
// 时区处理(C++20)
// auto tz = chrono::locate_zone("Asia/Shanghai");
// auto local_time = chrono::zoned_time(tz, now);
// 常见操作
auto today = chrono::floor<days>(now);
chrono::year_month_day ymd(today);
std::cout << "Year: " << (int)ymd.year() << std::endl;
return 0;
}
重要: C++20的
<chrono>提供了类型安全的时间计算,不同的时间单位不能隐式混用(如秒和毫秒)。
学习建议:
- C++11 是必学的现代C++基础,所有新特性都应该掌握
- C++14/17 提供了便利的语法糖,逐步学习即可
- C++20 的 Concepts 和 Ranges 改变了编程范式,值得深入学习
- 编译器支持: GCC 10+、Clang 10+、MSVC 2019+ 对 C++20 支持较好
Comments