C++ 高级特性
一、C++11新特性概述
记忆口诀:C++11新特性,语法库扩双提升;auto decltype智能指,nullptr范围循环使;右值引用move效,无序容器正则到;Lambda匿名函数好,代码简洁效率高。
1. 语法改进
- 统一初始化方法
- 成员变量默认初始化
- auto关键字:编译器自动推断类型
- decltype:推导表达式类型
- 智能指针:std::shared_ptr、std::unique_ptr
- 空指针nullptr:替代NULL,类型明确
- 基于范围的for循环:简化容器遍历
- 右值引用和move语义:提高资源转移效率
2. 标准库扩充
- 无序容器(哈希表):类似map但效率更高
- 正则表达式:模式匹配字符串
- Lambda表达式:定义匿名函数
二、智能指针
记忆口诀:智能指针分三类,shared_ptr共享随;引用计数来管理,线程安全要注意;unique_ptr独占权,禁止拷贝所有权;weak_ptr旁观态,lock检查免崩溃。
1. shared_ptr(共享指针)
- 实现机制:基于引用计数,多指针共享同一资源
- 核心组件:
- 模板指针T* ptr:指向实际对象
- 引用计数器:共享引用次数,决定资源释放时机
- 重载操作符:*、->、=,支持指针操作
- 线程安全:
- 多线程读同一shared_ptr安全
- 多线程写同一shared_ptr不安全
- 多线程写共享引用计数的不同shared_ptr安全
2. unique_ptr(独占指针)
- 独占所有权:同一时刻只能有一个unique_ptr指向对象
- 自动销毁:离开作用域时自动销毁所指对象
- 禁止拷贝:不支持普通拷贝和赋值操作
- 所有权转移:可通过release或reset转移所有权
3. weak_ptr(弱引用指针)
- 配合shared_ptr使用:观测资源使用情况
- 不增加引用计数:不影响资源的生命周期
- 检查有效性:使用lock()检查指针是否有效
三、类型推导
记忆口诀:auto编译推类型,必须初始化立即;函数参数成员变,数组模板不能见;decltype表达式,类型保留不执行;两者配合返回值,模板编程更灵活。
1. auto关键字
- 编译期类型推导:必须立即初始化
- 多变量声明限制:同一行变量类型推导不能有二义性
- 使用限制:
- 不能用作函数参数
- 不能用作类的非静态成员变量
- 不能定义数组(可定义指针)
- 无法推导出模板参数
- 引用和cv属性处理:
- 非引用/指针声明:忽略引用和cv属性
- 引用/指针声明:保留引用和cv属性
2. decltype关键字
- 表达式类型推导:不执行表达式,仅分析类型
- 引用和cv属性:保留表达式的引用和cv属性
- 推导规则:
- 表达式:与表达式类型相同
- 函数调用:与函数返回值类型相同
- 左值表达式:返回左值引用类型
3. auto与decltype配合使用
1 | template<typename T, typename U> |
四、右值引用与移动语义
记忆口诀:左值有名可取址,右值无名不可指;将亡值来资源转,移动语义效率显;完美转发std::forward,参数原样不改变;深浅拷贝要分清,移动构造性能升。
1. 左值与右值
- 左值:可放等号左边,可取地址,有名称
- 右值:不可放等号左边,不可取地址,无名称
- 特殊情况:
- 字符串字面值"abcd"是左值
- ++i、--i是左值,i++、i--是右值
2. 将亡值
- 定义:即将销毁的值,可通过"盗取"内存获取
- 优化作用:避免内存释放和分配,延长值的生命周期
- 应用场景:完成移动构造或移动赋值操作
3. 引用类型
- 左值引用:对左值的引用,必须立即初始化
- 右值引用:对右值的引用,可用std::move强制转换左值
4. 移动语义
- 本质:资源所有权转移,非拷贝
- 实现:通过移动构造函数和std::move实现
- 适用范围:仅对实现了移动构造函数的类有效
- 浅拷贝与深拷贝:
- 浅拷贝:指针指向同一块内存
- 深拷贝:重新分配内存存储资源
5. 完美转发
- 定义:将函数实参原样转发给其他函数
- 实现:通过std::forward()函数模板实现
五、范围for循环与列表初始化
记忆口诀:范围循环真方便,变量冒号对象连;列表初始化用花括,信息丢失编译器说;代码简洁又安全,C++11特性添。
1. 范围for循环
- 语法:for(变量:对象) 表达式
- 应用场景:
- 遍历string的每个字符
- 遍历vector等容器元素
示例:
1 | std::vector<int> arr(5, 100); |
2. 列表初始化
- 语法:使用花括号{}进行初始化
- 安全性:内置类型初始化时,若存在信息丢失风险,编译器报错
示例:
1 | int x = 0; // 传统初始化 |
六、Lambda表达式
记忆口诀:Lambda表达式,匿名函数便;方括捕获列表,圆括参数见;返回类型可省略,函数体在花括号间;捕获方式多种选,引用值捕获灵活变;STL算法结合用,代码简洁效率显。
1. 语法结构
1 | [capture list] (parameter list) -> return type {function body} |
2. 变量捕获方式
- []:不捕获任何变量
- [&]:引用方式捕获所有变量
- [=]:值方式捕获所有变量(创建时拷贝)
- [=, &foo]:引用捕获foo,其他值捕获
- [&, foo]:值捕获foo,其他引用捕获
- [bar]:值方式捕获bar,不捕获其他变量
- [this]:捕获所在类的this指针
3. 可变Lambda
- 添加mutable关键字:允许修改值捕获的变量
4. STL算法中的应用
- 优势:简化STL算法中谓词函数的使用
- 示例:自定义排序规则
1 | int arr[] = {6, 4, 3, 2, 1, 5}; |
七、异常处理机制
记忆口诀:异常处理三剑客,try-throw-catch要记牢;try块包裹风险代,throw抛出异常来;catch捕获处理它,类型匹配最重要;标准异常层次清,基类派生各不同;构造析构少抛异,RAII技术资源保;C++没有finally,智能指针来帮忙。
1. 传统错误处理
- 终止程序:如assert,用户体验差
- 返回错误码:需要手动查找对应错误
2. C++异常处理关键字
- try:包裹可能抛出异常的代码块
- throw:抛出异常,中断当前执行流程
- catch:捕获并处理异常
3. 异常匹配原则
- 类型决定匹配:异常类型匹配catch块类型
- 最近匹配原则:调用链中最近的匹配catch块被执行
- 对象拷贝:抛出异常时生成异常对象的拷贝
- catch(...):捕获任意类型异常,但无法获知具体类型
- 派生类匹配:可使用基类捕获派生类异常
4. 异常安全
- 构造函数异常:可能导致对象不完整,析构函数不会被调用
- 析构函数异常:可能导致资源泄漏
- 资源管理:使用RAII技术确保资源正确释放
5. 标准库异常体系
- std::exception:所有标准库异常的基类
- std::bad_alloc:内存分配失败
- std::bad_cast:类型转换失败
- std::logic_error:逻辑错误基类
- std::invalid_argument:无效参数
- std::domain_error:参数超出定义域
- std::length_error:容器长度超限
- std::runtime_error:运行时错误基类
- std::overflow_error:数值溢出
- std::underflow_error:数值下溢
- std::range_error:数值超出有效范围
- std::out_of_range:访问容器越界
6. 无finally关键字
- C++不支持finally关键字
- 使用RAII技术代替finally功能
八、RAII技术
记忆口诀:RAII技术真是好,资源管理离不了;构造获取资源到,析构自动释放掉;智能指针管内存,文件锁连不用恼;异常发生也不怕,资源泄漏杜绝了。
1. 定义
- 资源获取即初始化:通过对象生命周期管理资源
- 目的:确保资源在适当的时机自动释放
2. 应用场景
- 内存管理:使用智能指针(shared_ptr、unique_ptr)
- 文件操作:使用文件流对象自动关闭文件
- 锁管理:使用锁包装器(lock_guard、unique_lock)自动释放锁
- 网络连接:自定义RAII类管理连接的建立和关闭
九、命名空间
记忆口诀:命名空间划地盘,避免冲突是关键;作用域符双冒号,成员访问要记好;嵌套命名多层包,using指令简化调;匿名空间文件限,内部链接属static。
1. 定义与使用
- 定义:使用namespace关键字划分作用域
- 访问:使用作用域解析运算符::访问成员
示例:
1 | namespace MyNamespace { |
2. 嵌套命名空间
- 命名空间可嵌套定义
- 访问时使用多个作用域解析运算符
3. using指令
- using namespace 命名空间:直接使用所有成员
- using 命名空间::成员:只引入特定成员
4. 匿名命名空间
- 无名称命名空间,成员作用域限制在定义文件内
- 等价于添加static关键字,具有内部链接属性
十、内联函数
记忆口诀:内联函数inline,编译展开开销减;短小频繁调用好,代码体积可能添;相比宏定义安全,类型检查调试便;编译器优化有选择,长函数可能被忽略。
1. 定义与特点
- 定义:使用inline关键字声明
- 编译时展开:将函数调用替换为函数体
- 减少调用开销:适合短小、频繁调用的函数
- 增大代码体积:可能增加可执行文件大小
2. 与宏的区别
- 类型检查:内联函数进行类型检查,宏不检查
- 作用域:内联函数遵循C++作用域规则,宏作用域到文件结束
- 调试:内联函数可调试,宏不可调试
- 安全性:内联函数更安全,避免宏的运算符优先级问题
十一、不可变字符串类实现
记忆口诀:不可变字符串,内容创建不变样;引用计数来管理,拷贝赋值共享享;计数为零资源放,内存管理高效强;禁用拷贝有两法,delete私有各有长。
1. 设计要点
- 不可变性:创建后内容不可修改
- 引用计数:高效管理内存,实现复制和赋值
2. 核心功能
- 构造函数:初始化字符串数据和引用计数
- 拷贝构造:共享数据,增加引用计数
- 赋值运算符:减少原引用计数,共享新数据
- 析构函数:减少引用计数,计数为0时释放资源
- 辅助方法:length()获取长度,c_str()获取C风格字符串
3. 禁用拷贝功能
- C++11:使用=delete语法禁用拷贝构造和赋值
- 早期C++:将拷贝构造和赋值声明为私有成员
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.