CPP学习

2025年7月2日

  1. 异步调用

2025年4月22日

迭代器

  • 迭代器扮演着一种桥梁的角色,它们允许程序员以统一和抽象的方式访问容器中的元素

    • 标准模板库(STL)中的 std::vector 容器有一个 begin() 方法,该方法返回一个指向容器第一个元素的迭代器
  • 迭代器的几种类型
    输入迭代器 (Input Iterators): 只允许向前移动并读取元素。
    输出迭代器 (Output Iterators): 只允许向前移动并写入元素。
    前向迭代器 (Forward Iterators): 同时具备输入和输出迭代器的特性。
    双向迭代器 (Bidirectional Iterators): 可以向前和向后移动。
    随机访问迭代器 (Random Access Iterators): 提供对元素的随机访问

  • 在 C++ 的世界里,迭代器扮演着桥梁的角色,它们连接了算法和容器,为我们提供了一种优雅、高效的方式来操作数据

  • cbegin()和cend() 返回的是不可修改的const_iterator类型的迭代器

    • const_iterator 是 C++ 中的一种迭代器类型,用于遍历容器中的元素。与普通的迭代器不同,const_iterator 不允许修改所指向的元素的值。它通常用于只读访问容器的元素。
  • 对于常量容器,begin() 和 end() 也会返回 const_iterator 类型。这是因为在常量容器上,我们不能修改其元素的值,这与 cbegin() 和 cend() 的行为一致。
    ?什么是常量容器

    • 使用 const 关键字
    • 使用 const_iterator
    • 使用 std::array std::array<int, 5> numbers = {1, 2, 3, 4, 5};
  • 然而,在非常量容器上,使用 const auto& 在范围基于的 for 循环中不会改变 begin() 和 end() 的返回类型。它们仍然返回 iterator 类型,但是元素的引用是常量

学习一下 try catch

try {
        std::cout << "Element at index 2: " << numbers.at(2) << std::endl;//.at()访问时会带边界检查,如果越界会抛出out_of_range异常
    } catch (const std::out_of_range& e) {
        std::cerr << "Index out of range: " << e.what() << std::endl;
    }
  • 当使用 try-catch 结构时,了解 try 块中可能抛出的异常类型是非常重要的
    • 在 C++ 中,异常是通过类来表示的,所有异常类都继承自标准库中的 std::exception 类。
  • 多重捕获
     try {
         // 可能抛出多种异常的代码
     } catch (const std::out_of_range& e) {
         // 处理 std::out_of_range 异常
     } catch (const std::invalid_argument& e) {
         // 处理 std::invalid_argument 异常
     }
  • 自定义容器和迭代器

模板类和模板函数

在 C++ 中,模板是一种强大的特性,允许你编写通用的代码。模板可以用于类和函数,使它们能够处理不同的数据类型,而无需重复编写相同的代码

  • 模板类  模板类允许你定义一个类,该类可以处理多种数据类型。你可以定义一个类模板,然后在实例化时指定具体的数据类型。
  #include <iostream>

// 定义一个模板类,用于存储一对值
template <typename T>
class Pair {
private:
    T first, second;
public:
    Pair(T a, T b) : first(a), second(b) {}

    T getFirst() const { return first; }
    T getSecond() const { return second; }
};

int main() {
    Pair<int> intPair(1, 2);
    std::cout << "First: " << intPair.getFirst() << ", Second: " << intPair.getSecond() << std::endl;

    Pair<double> doublePair(3.14, 2.71);
    std::cout << "First: " << doublePair.getFirst() << ", Second: " << doublePair.getSecond() << std::endl;

    return 0;
}

经典lambda表达式

  • lambda 表达式是一种轻量级的匿名函数,允许你在代码中定义和使用函数,而无需显式地声明它们。lambda 表达式通常用于简化代码,特别是在需要传递函数作为参数的场合
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {5, 2, 8, 1, 3};

    // 使用 lambda 表达式对向量进行排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a < b; // 升序排序
    });

    // 输出排序后的结果
    std::cout << "Sorted numbers: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
  • 在实践中,通常建议优先使用标准容器,因为它们经过广泛测试和优化。但在需要特定功能或性能优化时,自定义容器成为了一个可行的选择。

  • 一个对比

常量迭代器

const std::vector<int> numbers = {1, 2, 3, 4, 5};
for (const auto& num : numbers) {
    std::cout << num << " ";
}

非常量迭代器

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (const auto& num : numbers) {
    std::cout << num << " ";
}

都不可以修改,后者是因为const auto&

关于const

  • 指针常量: const int* ptr = &a; 值不可以改变
  • 常量指针: int* const ptr = &a; 指针指向不可以改变
  • 常量成员函数 void display() const; 表示该函数不会修改类的成员变量

2025年4月17日

  • RAII 解决内存问题,不影响效率
  • 应该先重点攻克cpp三大件 容器、算法和迭代器

RAII(Resource Acquisition Is Initialization)设计模式详解


1. 什么是 RAII?

RAII(资源获取即初始化)是 C++ 中的核心设计模式,核心思想是:

  • 资源获取(如内存、文件句柄、锁)与对象的初始化绑定。
  • 资源释放对象的析构绑定。
  • 通过对象的生命周期自动管理资源,避免手动管理导致的泄露或错误。

2. RAII 的核心机制

(1) 资源获取在构造函数

  • 对象创建时,在构造函数中获取资源(如分配内存、打开文件)。
  • 如果资源获取失败,抛出异常,确保对象未完全构造时不会泄露资源。

(2) 资源释放在析构函数

  • 对象销毁时,在析构函数中自动释放资源。
  • 无论对象是因正常退出作用域还是异常而销毁,析构函数都会被调用。

3. RAII 的优势

优势 说明
自动释放 无需手动释放资源,避免忘记释放或重复释放。
异常安全 即使代码抛出异常,析构函数仍会调用,确保资源释放。
代码简洁 资源管理逻辑封装在类中,业务代码更清晰。
减少错误 避免悬空指针、内存泄漏等问题。
  • 悬空指针:指针指向的内存已被释放或失效,但指针本身未被置空,仍保留原地址。
  • 野指针:指针未初始化(随机指向非法内存),或指向已释放的内存(与悬空指针部分重叠)。
  • 内存泄漏:动态分配的内存未被释放,导致程序持续占用内存,最终可能耗尽系统资源。

4. RAII 的经典示例

示例 1:动态内存管理(智能指针)

C++ 的 std::unique_ptrstd::shared_ptr 是 RAII 的典型实现:

#include <memory>

void example_memory() {
    // 使用 unique_ptr 管理动态内存
    std::unique_ptr<int> ptr(new int(42));

    // 无需手动 delete,离开作用域时自动释放
}

示例 2:文件操作

封装文件句柄的打开和关闭:

#include <fstream>

class File {
public:
    File(const std::string& filename) {
        file.open(filename);
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~File() {
        if (file.is_open()) {
            file.close(); // 析构时自动关闭文件
        }
    }

    void write(const std::string& data) {
        file << data;
    }

private:
    std::fstream file;
};

void example_file() {
    try {
        File file("test.txt");
        file.write("Hello RAII!");
        // 离开作用域时,自动调用 ~File() 关闭文件
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
}

示例 3:互斥锁管理

确保锁在作用域结束时自动释放:

#include <mutex>

class LockGuard {
public:
    explicit LockGuard(std::mutex& mtx) : mutex(mtx) {
        mutex.lock(); // 构造函数中加锁
    }

    ~LockGuard() {
        mutex.unlock(); // 析构时解锁
    }

private:
    std::mutex& mutex;
};

void example_lock() {
    std::mutex mtx;
    {
        LockGuard lock(mtx); // 加锁
        // 临界区操作...
    } // 离开作用域,自动解锁
}

5. RAII 的对比分析

不使用 RAII 的问题

// 手动管理文件,容易忘记关闭或异常导致泄露
void bad_file_usage() {
    std::fstream file;
    file.open("test.txt");
    file << "Data";
    // 如果此处抛出异常,file.close() 不会被调用!
    file.close(); // 可能被忘记
}

使用 RAII 的改进

  • 无论是否发生异常,资源都会在析构函数中释放。
  • 代码逻辑更清晰,资源管理由类封装。

6. RAII 的关键设计原则

  1. 单一职责原则
    每个 RAII 类只管理一种资源。

  2. 禁止拷贝(或使用移动语义)
    若资源不可复制(如文件句柄),需禁用拷贝构造函数和赋值操作符,或实现移动语义。

  3. 异常安全
    构造函数中若资源获取失败,应抛出异常,避免返回部分构造的对象。


7. 常见 RAII 应用场景

资源类型 RAII 封装示例
动态内存 std::unique_ptr, std::shared_ptr
文件句柄 std::fstream, 自定义 File
网络连接 封装 Socket 连接类
图形资源(如 GPU) OpenGL 纹理管理类
数据库连接 封装连接池类

8. RAII 的进阶技巧

(1) 移动语义支持

允许资源所有权的转移,避免拷贝:

class Resource {
public:
    Resource() { /* 获取资源 */ }
    ~Resource() { /* 释放资源 */ }

    // 移动构造函数
    Resource(Resource&& other) noexcept {
        // 转移资源所有权
    }

    // 移动赋值操作符
    Resource& operator=(Resource&& other) noexcept {
        // 转移资源所有权
        return *this;
    }

    // 禁用拷贝
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
};

(2) 延迟初始化

某些资源可能需要在首次使用时初始化:

class LazyResource {
public:
    void use() {
        if (!initialized) {
            acquireResource(); // 首次使用时初始化
            initialized = true;
        }
        // 使用资源
    }

    ~LazyResource() {
        if (initialized) {
            releaseResource();
        }
    }

private:
    bool initialized = false;
};

9. 总结

  • RAII 的本质:通过对象的生命周期管理资源,确保安全释放。
  • 核心实现:构造函数获取资源,析构函数释放资源。
  • 适用场景:所有需要手动管理的资源(内存、文件、锁等)。
  • 优势:代码简洁、异常安全、避免资源泄露。

通过结合智能指针、自定义 RAII 类,可以大幅提升 C++ 程序的健壮性和可维护性。

posted @ 2025-03-22 14:49  無碍  阅读(170)  评论(0)    收藏  举报