当前位置: 首页 > news >正文

C++复习核心精华

一、内存管理与智能指针

内存管理是C++区别于其他高级语言的关键特性,掌握好它就掌握了C++的灵魂。

1. 原始指针与内存泄漏

先来看看传统C++的内存管理方式:

void oldWay() {int* p = new int(42);  // 分配内存// 如果这里发生异常或提前return,下面的delete可能不会执行// 其他代码...delete p;  // 释放内存p = nullptr;  // 避免悬空指针}

这种方式有什么问题呢?太容易出错了!忘记delete、出现异常、提前返回...都会导致内存泄漏。就像你把菜做到一半,突然停电了,灶台上的火忘了关,后果很严重啊!

2. RAII原则与智能指针详解

C++引入了RAII原则(Resource Acquisition Is Initialization),资源的获取与初始化同时进行,资源的释放与对象销毁同时进行。这就像是给你的厨房装了个自动灭火器,不管发生什么,都能自动处理。

智能指针就是RAII的典型应用:

std::unique_ptr

独占所有权的智能指针,不能复制,但可以移动。

void betterWay() {std::unique_ptr<int> p1 = std::make_unique<int>(42);  // C++14引入// 或者在C++11中:std::unique_ptr<int> p1(new int(42));// 即使这里发生异常,p1也会自动释放内存// 独占所有权,不能复制// std::unique_ptr<int> p2 = p1;  // 编译错误!// 但可以转移所有权std::unique_ptr<int> p3 = std::move(p1);  // p1现在为空// 离开作用域时,p3自动释放内存}

unique_ptr的内部实现非常轻量,几乎没有性能开销,是最常用的智能指针。就像是你的私人助手,只为你一个人服务,效率极高。

std::shared_ptr与引用计数

共享所有权的智能指针,通过引用计数机制实现。

void sharedOwnership() {// 创建一个shared_ptrstd::shared_ptr<int> sp1 = std::make_shared<int>(100);std::cout << "引用计数: " << sp1.use_count() << std::endl;  // 输出:1{// 创建sp1的副本,共享所有权std::shared_ptr<int> sp2 = sp1;std::cout << "引用计数: " << sp1.use_count() << std::endl;  // 输出:2// 修改值,两个指针指向同一个对象*sp2 = 200;std::cout << "sp1指向的值: " << *sp1 << std::endl;  // 输出:200}  // sp2离开作用域,引用计数减1std::cout << "引用计数: " << sp1.use_count() << std::endl;  // 输出:1}  // sp1离开作用域,引用计数减为0,内存被释放

shared_ptr内部维护两块内存:一个是数据本身,一个是控制块(包含引用计数等信息)。这有点像合租房子,大家共同负责,最后一个离开的人负责关灯锁门。

std::weak_ptr与循环引用问题

weak_ptr不增加引用计数,用于解决循环引用问题。

class Node {public:std::string name;std::shared_ptr<Node> next;  // 指向下一个节点std::weak_ptr<Node> prev;    // 指向前一个节点(弱引用)Node(const std::string& n) : name(n) {}~Node() {std::cout << "销毁节点: " << name << std::endl;}};void circularReference() {auto node1 = std::make_shared<Node>("Node1");auto node2 = std::make_shared<Node>("Node2");// 创建循环引用node1->next = node2;node2->prev = node1;  // 弱引用不会增加引用计数// 使用弱引用if (auto temp = node2->prev.lock()) {  // 转换为shared_ptrstd::cout << "前一个节点是: " << temp->name << std::endl;}}  // 函数结束时,两个节点都能被正确释放

如果prev也使用shared_ptr,就会形成循环引用,导致内存泄漏。这就像两个人互相等对方先离开,结果谁也走不了。

3. 自定义删除器

有时我们需要在释放资源时执行特定操作,可以使用自定义删除器:

// 文件资源管理void customDeleter() {// 自定义删除器,确保文件正确关闭auto fileCloser = [](FILE* fp) {if (fp) {std::cout << "关闭文件" << std::endl;fclose(fp);}};// 使用自定义删除器的智能指针std::unique_ptr<FILE, decltype(fileCloser)> filePtr(fopen("data.txt", "r"), fileCloser);if (filePtr) {// 使用文件...char buffer[100];fread(buffer, 1, sizeof(buffer), filePtr.get());}// 离开作用域时,fileCloser会被调用}

这个例子完美展示了RAII的威力,无论函数如何退出,文件都会被正确关闭。就像雇了专业保洁员,走的时候一定会把屋子打扫干净。

二、模板编程的艺术

模板是C++最强大的特性之一,让你写出既通用又高效的代码。它不仅仅是代码复用工具,更是元编程的基础。

1. 函数模板深入理解

基本函数模板我们都了解,但你知道模板还能做这些事吗?

// 可变参数模板template<typename T>T sum(T value) {return value;}template<typename T, typename... Args>T sum(T first, Args... args) {return first + sum(args...);}// 使用int total = sum(1, 2, 3, 4, 5);  // 返回15std::string s = sum(std::string("Hello"), " ", "World");  // 返回"Hello World"

递归模板展开是一个非常强大的技术,这段代码的展开过程就像俄罗斯套娃,层层展开,最终计算出结果。

2. 类模板特化

模板特化允许我们为特定类型提供特殊实现:

// 主模板template<typename T>class DataHandler {public:void process(T data) {std::cout << "处理通用数据: " << data << std::endl;}};// 针对std::string的完全特化template<>class DataHandler<std::string> {public:void process(std::string data) {std::cout << "处理字符串: " << data << std::endl;// 字符串特有的处理逻辑...}};// 部分特化(针对指针类型)template<typename T>class DataHandler<T*> {public:void process(T* data) {if (data) {std::cout << "处理指针指向的数据: " << *data << std::endl;} else {std::cout << "空指针!" << std::endl;}}};

模板特化就像餐厅里的"定制菜单",根据不同的"食客"(类型)提供量身定制的"服务"(实现)。

3. SFINAE与类型萃取

SFINAE (Substitution Failure Is Not An Error) 是模板元编程的重要技术,允许编译器在模板实例化失败时继续尝试其他重载。

// 检查类型是否有size()成员函数template<typename T>struct has_size {private:template<typename C> static constexpr auto test(int) -> decltype(std::declval<C>().size(), bool()) { return true; }template<typename C> static constexpr bool test(...) { return false; }public:static constexpr bool value = test<T>(0);};// 根据类型特性选择不同实现template<typename Container>typename std::enable_if<has_size<Container>::value, void>::typeprintSize(const Container& c) {std::cout << "容器大小: " << c.size() << std::endl;}template<typename T>typename std::enable_if<!has_size<T>::value, void>::typeprintSize(const T&) {std::cout << "此类型没有size()方法" << std::endl;}

在C++17中,我们可以使用if constexpr简化这种代码:

template<typename Container>void printSize(const Container& c) {if constexpr (has_size<Container>::value) {std::cout << "容器大小: " << c.size() << std::endl;} else {std::cout << "此类型没有size()方法" << std::endl;}}

这种技术就像是编译时的"魔法侦探",能够根据类型的特性自动选择最合适的实现路径。

三、STL深度剖析

STL是C++标准库的核心部分,掌握它可以避免重复造轮子,大幅提高开发效率。

1. 容器性能对比与选择指南

不同容器有不同的性能特点,选择合适的容器至关重要:

容器随机访问插入 / 删除 (中间)插入 / 删除 (首 / 尾)查找特点
vectorO(1)O(n)O(1) 尾部O(n)连续内存,缓存友好
listO(n)O(1)O(1)O(n)双向链表,稳定迭代器
dequeO(1)O(n)O(1) 首尾O(n)分段连续内存
set/mapO(log n)O(log n)O(log n)O(log n)红黑树实现,有序
unordered_set/mapO(1) 平均O(1) 平均O(1) 平均O(1) 平均哈希表实现,无序
// 性能敏感场景选择指南void containerChoice() {// 1. 频繁随机访问,较少插入删除 -> vectorstd::vector<int> v;// 2. 频繁在两端操作 -> dequestd::deque<int> d;// 3. 频繁在中间插入删除 -> liststd::list<int> l;// 4. 需要有序并快速查找 -> set/mapstd::map<std::string, int> m;// 5. 需要最快的查找,不要求有序 -> unordered_set/mapstd::unordered_map<std::string, int> um;}

选择合适的容器就像选择合适的工具,木匠不会用锤子切木头,也不会用锯子钉钉子。

2. 算法与迭代器配合使用

STL的强大在于算法与容器的解耦,通过迭代器连接:

void algorithmDemo() {std::vector<int> numbers = {1, 5, 3, 4, 2};// 查找auto it = std::find(numbers.begin(), numbers.end(), 3);if (it != numbers.end()) {std::cout << "找到: " << *it << " 位置: " << std::distance(numbers.begin(), it) << std::endl;}// 排序std::sort(numbers.begin(), numbers.end());// 二分查找(要求已排序)bool exists = std::binary_search(numbers.begin(), numbers.end(), 3);// 变换std::vector<int> squared;std::transform(numbers.begin(), numbers.end(), std::back_inserter(squared),[](int x) { return x * x; });// 累加int sum = std::accumulate(numbers.begin(), numbers.end(), 0);// 自定义排序std::sort(numbers.begin(), numbers.end(), [](int a, int b) {return std::abs(a) < std::abs(b);  // 按绝对值排序});}

3. 自定义容器的迭代器

理解迭代器设计可以帮助我们更好地使用STL,甚至为自定义容器实现迭代器:

// 简单的环形缓冲区template<typename T, size_t Size>class CircularBuffer {private:T data_[Size];size_t head_ = 0;size_t tail_ = 0;size_t size_ = 0;public:// 迭代器实现class iterator {private:CircularBuffer<T, Size>* buffer_;size_t index_;size_t count_;public:// 迭代器类型定义(满足STL要求)using iterator_category = std::forward_iterator_tag;using value_type = T;using difference_type = std::ptrdiff_t;using pointer = T*;using reference = T&;iterator(CircularBuffer<T, Size>* buffer, size_t index, size_t count): buffer_(buffer), index_(index), count_(count) {}// 迭代器操作T& operator*() { return buffer_->data_[index_]; }iterator& operator++() {index_ = (index_ + 1) % Size;++count_;return *this;}bool operator!=(const iterator& other) const {return count_ != other.count_;}};// 容器方法void push(const T& value) {data_[tail_] = value;tail_ = (tail_ + 1) % Size;if (size_ < Size) {++size_;} else {head_ = (head_ + 1) % Size;  // 覆盖最老的元素}}// 提供迭代器iterator begin() { return iterator(this, head_, 0); }iterator end() { return iterator(this, head_, size_); }};// 使用示例void customContainerDemo() {CircularBuffer<int, 5> buffer;for (int i = 0; i < 7; ++i) {buffer.push(i);}// 此时缓冲区包含:2, 3, 4, 5, 6// 使用for-each循环(需要begin/end支持)for (const auto& value : buffer) {std::cout << value << " ";  // 输出:2 3 4 5 6}// 也可以与STL算法配合使用auto sum = std::accumulate(buffer.begin(), buffer.end(), 0);std::cout << "\n总和: " << sum << std::endl;  // 输出:20}

自定义迭代器需要满足特定的接口要求,这样才能与STL算法无缝配合。就像设计插头和插座,只要遵循标准,任何设备都能正常工作。

四、现代C++特性(C++11/14/17/20)

现代C++引入了大量新特性,极大提升了开发效率和代码质量。

1. 移动语义与右值引用

移动语义允许我们在不需要深拷贝的情况下转移资源所有权,大幅提升性能:

class BigData {private:int* data_;size_t size_;public:// 构造函数BigData(size_t size) : size_(size) {data_ = new int[size];std::cout << "分配 " << size << " 个整数" << std::endl;}// 析构函数~BigData() {delete[] data_;std::cout << "释放内存" << std::endl;}// 拷贝构造函数(深拷贝)BigData(const BigData& other) : size_(other.size_) {data_ = new int[size_];std::memcpy(data_, other.data_, size_ * sizeof(int));std::cout << "拷贝 " << size_ << " 个整数(昂贵操作)" << std::endl;}// 移动构造函数BigData(BigData&& other) noexcept : data_(other.data_), size_(other.size_) {other.data_ = nullptr;  // 防止源对象释放内存other.size_ = 0;std::cout << "移动资源(快速操作)" << std::endl;}// 移动赋值运算符BigData& operator=(BigData&& other) noexcept {if (this != &other) {delete[] data_;  // 释放当前资源// 窃取资源data_ = other.data_;size_ = other.size_;// 将源对象置于有效但可析构状态other.data_ = nullptr;other.size_ = 0;std::cout << "移动赋值(快速操作)" << std::endl;}return *this;}};// 演示移动语义优势void moveSemantics() {std::vector<BigData> v;std::cout << "创建临时对象并添加到vector:" << std::endl;v.push_back(BigData(1000000));  // 使用移动构造函数,避免深拷贝std::cout << "\n创建命名对象:" << std::endl;BigData d1(1000000);std::cout << "\n复制添加到vector:" << std::endl;v.push_back(d1);  // 使用拷贝构造函数,进行深拷贝std::cout << "\n移动添加到vector:" << std::endl;v.push_back(std::move(d1));  // 显式使用移动语义// 注意:此时d1已被移动,处于有效但未指定状态,不应再使用它的值}

移动语义就像是把一整本书直接交给别人,而不是复印一份再给他。在处理大型资源时,这种差异极其显著。

2. 完美转发与通用引用

完美转发允许函数模板精确地传递参数,保持其值类别(左值/右值):

// 工厂函数示例template<typename T, typename... Args>std::unique_ptr<T> make_unique(Args&&... args) {return std::unique_ptr<T>(new T(std::forward<Args>(args)...));}// 完美转发包装器template<typename Func, typename... Args>auto forwardingWrapper(Func&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) {std::cout << "转发参数到函数" << std::endl;return func(std::forward<Args>(args)...);}void perfectForwarding() {auto print = [](const std::string& s) { std::cout << "左值: " << s << std::endl; return s.length();};auto printRValue = [](std::string&& s) { std::cout << "右值: " << s << std::endl; return s.length();};std::string str = "Hello";// 转发左值forwardingWrapper(print, str);// 转发右值forwardingWrapper(printRValue, std::move(str));// 创建对象并完美转发参数auto p = make_unique<std::vector<int>>(5, 10);  // 创建包含5个10的vector}

完美转发就像是一个完美的中间人,既不添加任何东西,也不减少任何东西,原封不动地传递参数。

3. Lambda表达式与捕获技巧

Lambda表达式让函数式编程在C++中变得简单优雅:

void lambdaExamples() {int x = 10;// 基本lambdaauto add = [](int a, int b) { return a + b; };std::cout << "5 + 3 = " << add(5, 3) << std::endl;// 捕获变量auto addX = [x](int a) { return a + x; };std::cout << "5 + x = " << addX(5) << std::endl;// 引用捕获(可修改外部变量)auto incrementX = [&x]() { x++; };incrementX();std::cout << "x现在是: " << x << std::endl;  // 输出:11// 混合捕获int y = 20;auto calculate = [x, &y](int a) { y += a;  // 修改yreturn x * y;  // 使用x的副本};std::cout << "计算结果: " << calculate(5) << std::endl;std::cout << "y现在是: " << y << std::endl;  // y被修改// 捕获this指针struct Counter {int value = 0;auto increment() {// 捕获this指针,可访问成员变量return [this]() { ++value; };}void print() {std::cout << "计数: " << value << std::endl;}};Counter c;auto inc = c.increment();inc();inc();c.print();  // 输出:计数: 2// 初始化捕获(C++14)auto sum = [sum = 0](int value) mutable {sum += value;return sum;};std::cout << sum(1) << std::endl;  // 1std::cout << sum(2) << std::endl;  // 3std::cout << sum(3) << std::endl;  // 6}

Lambda表达式就像是随手写下的小纸条,简洁而直接,让代码更加紧凑易读。

4. constexpr与编译期计算

constexpr允许在编译期执行计算,提高运行时性能:

// 编译期计算斐波那契数列constexpr int fibonacci(int n) {return (n <= 1) ? n : fibonacci(n-1) + fibonacci(n-2);}// 编译期计算阶乘constexpr int factorial(int n) {return (n <= 1) ? 1 : n * factorial(n-1);}void constexprDemo() {// 编译期计算constexpr int fib10 = fibonacci(10);constexpr int fact5 = factorial(5);std::cout << "斐波那契(10) = " << fib10 << std::endl;std::cout << "阶乘(5) = " << fact5 << std::endl;// 编译期数组大小constexpr int size = factorial(5);int arr[size];  // 使用编译期常量作为数组大小// C++17: constexpr iftemplate<typename T>auto getValue(T t) {if constexpr (std::is_pointer_v<T>) {return *t;  // 指针类型} else {return t;   // 非指针类型}}int x = 42;int* p = &x;std::cout << getValue(x) << std::endl;  // 42std::cout << getValue(p) << std::endl;  // 42}

constexpr就像是提前做好的作业,编译器在编译时就把结果算出来了,运行时直接使用结果,不需要再计算。

总结

记住,C++的威力不仅在于它的特性,更在于如何巧妙地组合这些特性,解决实际问题。就像武术高手,招式并不是最重要的,关键是如何融会贯通,形成自己的"功夫"。

希望这些深度解析能帮助你更好地理解C++的精髓。有什么不明白的地方,或者想要了解的其他C++话题,随时告诉我!我们一起在C++的海洋中探索更多奥秘!

http://www.xdnf.cn/news/636733.html

相关文章:

  • Docker镜像与容器深度解析:从概念到实践的全面对比
  • PTA刷题笔记(难度预警!!!有详解)
  • 区块链可投会议CCF C--APSEC 2025 截止7.13 附录用率
  • leetcode 131. Palindrome Partitioning
  • Oracle 19c TFA工具的安装与使用详解
  • 【辰辉创聚生物】FGF信号通路相关蛋白:解码生命调控的关键枢纽
  • 第三十一天打卡
  • 医学写作供应商管理全流程优化
  • Github 今日热点 完全本地化的自主AI助手,无需API或云端依赖
  • 【JSON 】全面掌握JSON的相关知识
  • 上海医日健集团物联网专利技术领跑智慧药房赛道
  • C++编程单例模式详细解释---模拟一个网络配置管理器,负责管理和分发网络连接参数
  • 【OCCT+ImGUI系列】010-BRepMesh-网格化IncrementalMesh
  • 文本特征提取
  • GO 语言进阶之 进程 OS与 编码,数据格式转换
  • 【Leetcode 每日一题】2131. 连接两字母单词得到的最长回文串
  • 39.组合总和
  • leetcode560-和为k的子数组
  • arxml文件
  • JVM 的类加载机制
  • 进程管理(第二、三、四章)
  • 【车用永磁同步电机随机开关频率控制策略:高频谐波抑制的工程实践】
  • Python入门手册:条件判断
  • 云原生安全之网络IP协议:从基础到实践指南
  • mysql都有哪些锁?
  • 历年北京理工大学保研上机真题
  • 分布式缓存:ZSET → MGET 跨槽(cross‐slot)/ 并发 GET解决思路
  • 第十九章:数据治理之数据指标(一):数据指标工具之【指标口径管理系统】与【指标数据查询系统】
  • AnyIOasyncio 现代化方法
  • Ntfs!NtfsReadBootSector函数分析之nt!CcGetVacbMiss中得到一个nt!_VACB结构