Lambda表达式
需要预先了解: 函数 lambda 表达式 通过使用lambda表达式可以在代码中就地定义短小的函数逻辑,同时能捕获外层作用域的变量作为内部状态,其核心语法如下: [捕获列表](参数列表) { 函数体 } 例如: []() { std::cout << "this is lambda!" << std::endl; } 1. 无参数时,括号可以省略 [] { std::cout << "this is lambda!" << std::endl; } 2. 可以在函数体后紧跟一个括号,表示直接调用该匿名函数 [] { std::cout << "this is lambda!" << std::endl; }(); lambda 表达式的捕获方式 值捕获 值捕获:捕获变量的副本,在lambda定义时完成拷贝。 显式捕获:写法为[变量名1, 变量名2, ...],用于显式捕获外界的变量 隐式捕获:写法为[=...
函数指针与std::function
需要预先了解: 函数 指针 函数指针 函数的地址 函数的机器指令存储在内存的.text段里,每个函数对应.text段中的一段连续的机器指令序列,其起始地址(即函数第一条指令所在的内存地址)就是这个函数的地址。 函数指针 函数指针是指向函数的指针类型,它存储函数的入口地址,通过这个地址可以间接调用对应的函数。 其声明语法为返回值类型 (*函数指针名)(参数列表): int add(int a, int b); int (*p)(int, int); // p 是类型为 int (*)(int, int)的函数的指针 函数名也可以隐式转换为函数的入口地址 因此有以下两种等价的写法: int (*p)(int, int) = add; int (*p)(int, int) = &add; 调用函数指针也有等价的两种写法: std::cout << p(1, 2) << std::endl; std::cout << (*p)(1, 2) << std::endl; std::function std:...
智能指针与RAII
需要预先了解: 指针 原始指针的缺点 原始指针/裸指针:如int* p,需要手动管理内存,容易造成以下问题: 1. 忘记delete/free会导致内存泄露: // 局部变量 ptr 指向的堆内存未释放,造成内存泄露 void func { Type* ptr = new Type(); ptr->do_something(); } 2.使用释放后的悬挂指针,导致未定义行为: Type* ptr = new Type(); delete ptr; ptr->do_something(); 3. 重复释放同一块内存,导致未定义行为或程序崩溃 Type* ptr = new Type(); delete ptr; delete ptr; // 重复释放同一块内存 RAII(Resource Acquisition Is Initialization) RAII(直译为“资源获取即初始化”):在 对象构造阶段获取资源,在 对象析构阶段释放资源;依靠 C++ 对象自动生命周期管理(离开作用域自动调用析构),保证资源一定会被释...
移动语义与std::move
需要预先学习: 引用与左右值 移动语义 移动语义(Move Semantics)是 C++11 引入的一项重要特性,它的核心思想是:当你把一个对象的资源"转移"到另一个对象时,不需要做深拷贝,而是直接把指针/句柄"偷"过来,让原对象变成一个空壳。这样可以避免大量不必要的拷贝开销,大幅提升性能。 拷贝的缺点 在传统 C++ 中,对象的传递依赖拷贝。对于管理堆内存的类(如 std::string、std::vector),拷贝意味着: 分配新的堆内存 逐字节复制原对象的数据 原对象和目标对象各自拥有独立的内存 #include <cstring> #include <iostream> class MyString { public: MyString(const char* str) { _size = std::strlen(str); _data = new char[_size + 1]; std::strcpy(_da...
联合与std::variant
需要预先了解: 内存基础 变量与常量 联合(Union) 联合(union) 是一种特殊的数据类型,它允许在同一块内存空间中存储不同类型的数据,但同一时刻只能有一个成员是有效的。定义语法与 struct 类似,只是把关键字换为 union: union Data { int i; double d; char c; }; 使用时像结构体一样访问成员,但所有成员共享同一块内存: #include <iostream> int main() { Data u; u.i = 42; // 写入 int std::cout << u.i << std::endl; // 输出: 42 u.d = 3.14; // 写入 double——覆盖了原来的 int std::cout << u.d << std::endl; /...
模版基础
需要预先学习: 函数 函数模版 函数模版(function template)是一份"函数蓝图"——你只需要写一次逻辑,编译器就能为不同的类型自动生成对应的函数版本。 基础语法 函数模版的声明以 template <typename T> 开头,其中 T 是一个模版参数(template parameter),代表一个待定的类型。使用时,编译器会根据你传入的实参类型推导出 T 的具体类型。 #include <iostream> template <typename T> T max(T a, T b) { return (a > b) ? a : b; } int main() { std::cout << max(3, 7) << '\n'; // T 推导为 int,输出 7 std::cout << max(3.14, 2.71) << '\n'; // T 推导为 double,输出 3.1...
可变长参数模版
需要预先学习: 模版基础 可变长参数模版 普通模版的参数数量是固定的——template <typename T1, typename T2> 只能接受两个类型。而可变长参数模版(variadic template)打破了这一限制:可以写出接受任意数量、任意类型参数的模版。 这是 C++11 引入的特性,std::tuple、std::make_shared、std::thread 的构造函数等大量标准库组件都是基于它实现的。 基础语法 可变长参数模版的核心是参数包(parameter pack)。用 typename... Args 声明一个类型参数包,用 Args... args 声明一个函数参数包: template <typename... Args> void print(Args... args) { // args 是一个参数包,包含调用时传入的全部实参 } Args 是一个类型参数包(type parameter pack),代表零个或多个类型;args 是一个函数参数包(function parame...
extern "C"
需要预先了解: extern、static与inline关键字总结 函数 extern “C” 的使用 extern “C” 的作用是告诉 C++ 编译器:这个名字(函数或变量)按 C 语言的规则处理链接。它解决的是 C++ 程序调用 C 语言库的问题。 为什么需要 extern “C” 现实中有大量优秀的 C 库——OpenGL、SDL、zlib、sqlite 等。C++ 程序直接 #include 它们的头文件并调用函数时,链接阶段往往会失败,报出 undefined reference 错误。原因在于:C 编译器和 C++ 编译器给同一个函数生成的符号名不一样(详见本篇后半部分)。 extern "C" 就是给 C++ 编译器的一条指令:“这个函数的符号,按 C 的方式生成。” 基本用法 1. 单个声明: extern "C" int add(int a, int b); 2. 代码块(多个声明一次搞定): extern "C" { int add(int a, int b); const char* ...
完美转发
需要预先了解: 引用与左右值 移动语义与std::move 模版基础 转发的问题 写一个包装函数(wrapper)时,你常常希望它把参数"原样"转交给另一个函数——参数的类型、值类别(左值/右值)、const 属性都保持不变。这件事比听起来难,先看直接的做法有什么问题。 void target(int& x) { /* 左值版本 */ } void target(int&& x) { /* 右值版本 */ } template <typename T> void wrapper(T arg) // 按值传递 { target(arg); } 按值传递有两个问题: 参数被拷贝了一份(右值时走移动构造,但左值时一定是深拷贝),大对象开销明显 无论调用者传的是左值还是右值,arg 在函数内部都是一个有名字的左值——target(arg) 永远调用左值版本,值类别信息丢失 改用引用传递呢?T& 只能绑定左值,...
Traits(类型萃取)
需要预先学习: 模版基础 Traits(类型萃取) 写泛型代码时,你迟早会遇到一个问题:模版参数 T 到底是个什么类型? 是指针还是普通对象?是整数还是浮点数?去掉引用之后是什么?这些信息在编译期是存在的,但到了运行期已经"擦除"——if 只能根据值做判断,判断不了类型。 **Traits(类型萃取)**就是解决这个问题的技术:定义一类专门的 traits 类,把"关于类型的信息"编码成类成员——你给它一个类型,它告诉你一个编译期的事实(“是不是指针”),或给出一个关联的类型(“去掉指针后剩下的类型”)。“萃取”(extraction)指的正是从类型中提取这些隐藏信息的过程。 注:typeid 虽然能在运行期给出类型名,但泛型代码需要的是能参与编译期计算的答案——用来开分支、声明变量、约束模版,这些 typeid 都做不到。 用偏特化实现编译期分支 从一个需求出发:写一个泛型函数 reset——如果 T 是指针,就 delete 并置空;否则赋一个默认值。这个判断必须发生在编译期,而普通的 if 做不到:它的两个分支都要...


