异常

try {
    // 可能出错的代码
    if (出错)
        throw 异常对象;
}
catch (异常类型1 e) {
    // 处理
}
catch (const exception& e) {
    // 处理
    cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}

异常的优缺点

  1. 优点
    展示错误更清晰
    错误码调用太深,异常直接处理
    第三方库也有异常
    有些没有返回值(void)的函数使用异常更方便
  2. 缺点
    执行流乱跳:
    正常流程、异常流程分开,代码逻辑不线性,难维护。
    容易内存泄漏:
    抛出异常时函数直接退出,new 的内存没 delete、锁没释放。
    → 解决:RAII(智能指针、lock_guard 等)。
    异常体系混乱复杂度很高。

C++ 异常安全等级

基本保证:不崩、不漏、状态合法就行。
强保证:要么成功,要么回滚,无中间态。
不抛保证:noexcept,绝对不抛异常。

怎么实现强异常安全?

Copy & Swap。先拷贝一份做修改,成功后再用不抛异常的 swap 替换原对象。

智能指针

什么是智能指针

用对象管理指针,利用 RAII 机制自动释放资源。
不用手动 delete,杜绝内存泄漏。

RAII

资源获得即初始化
(在构造函数申请资源,在析构函数释放资源)
利用对象生命周期管理资源,由编译器自动调用析构

智能指针原理

把原始指针封装成类对象
重载 *、-> 让它用起来像指针
出作用域 → 自动调用析构 → 自动释放内存

四种智能指针

std::auto_ptr

废弃,不要用!
拷贝时会把所有权偷走,原指针置空

std::unique_ptr

独占式智能指针
同一时间只有一个指针管理对象
不能拷贝,只能移动

unique_ptr<int> p1(new int(10));

    // 不能拷贝!编译报错
    // unique_ptr<int> p2 = p1;

    // 可以移动所有权
    unique_ptr<int> p2 = move(p1);

    // 现在 p1 为空,p2 拥有对象
    if (p1 == nullptr) {
        cout << "p1 为空" << endl;
    }

效率最高,无额外开销

std::shared_ptr

共享式智能指针
引用计数实现
多个指针指向同一个对象
计数为 0 才释放内存
有性能开销(原子操作)

shared_ptr<int> p1(new int(10));
    cout << p1.use_count() << endl;  // 1

    shared_ptr<int> p2 = p1;         // 拷贝,计数+1
    cout << p1.use_count() << endl;  // 2

    p1.reset();                      // 主动释放,计数-1
    cout << p2.use_count() << endl;  // 1

std::weak_ptr

弱引用指针
辅助 shared_ptr
不增加引用计数
专门解决 循环引用

wp.lock();       // 返回一个 shared_ptr,若对象已死则为空
wp.expired();    // 判断对象是否已销毁
shared_ptr<int> sp = make_shared<int>(10);
    weak_ptr<int> wp = sp;

    cout << sp.use_count() << endl;  // 1,weak_ptr 不增加计数

    if (!wp.expired()) {
        shared_ptr<int> temp = wp.lock();
        // 使用对象
    }

缺陷

循环引用
(shared_ptr 致命问题)

A->B  B->A
互相持有 shared_ptr
计数永远 >=1 → 内存泄漏!

解决:一方使用 weak_ptr
性能开销
引用计数是原子操作(线程安全)
频繁构造销毁++有开销

shared_ptr手撕

template <typename T>
class SharedPtr {
public:
    // 构造
    SharedPtr(T* ptr = nullptr) 
        : _ptr(ptr), _count(new int(1)) {}

    // 拷贝构造
    SharedPtr(const SharedPtr& other) {
        _ptr = other._ptr;
        _count = other._count;
        (*_count)++;  // 计数++
    }

    // 赋值重载(重点!必须先释放自己)
    SharedPtr& operator=(const SharedPtr& other) {
        if (this == &other) return *this;

        // 释放当前对象
        release();

        // 共享资源
        _ptr = other._ptr;
        _count = other._count;
        (*_count)++;  // 计数++

        return *this;
    }

    // 析构
    ~SharedPtr() {
        release();
    }

    // 重载 * ->
    T& operator*()  { return *_ptr; }
    T* operator->() { return _ptr; }

    int useCount() { return *_count; }

private:
    void release() {
        if (_ptr == nullptr) return;

        (*_count)--;
        if (*_count == 0) {
            delete _ptr;
            delete _count;
            _ptr = nullptr;
            _count = nullptr;
        }
    }

    T* _ptr;
    int* _count;  // 引用计数
};

赋值后要记得更新计数

定制删除器

默认智能指针用 delete 释放资源,定制删除器就是让你自己写 “怎么释放”。

适用场景:

不是 new 出来的内存(malloc / mmap)
文件指针 FILE*
套接字 socket
数据库连接
自定义清理逻辑

//关闭文件
 shared_ptr<FILE> sp(fopen("test.txt", "w"), [](FILE* pf) {
        cout << "fclose 文件" << endl;
        fclose(pf);
    });
// free 释放 malloc 内存
    shared_ptr<void> sp(malloc(100), [](void* p) {
    free(p);
//函数指针当删除器
void my_delete(int* p) {
    cout << "自定义删除\n";
    delete p;
}
int main() {
    shared_ptr<int> sp(new int, my_delete);
}
});
//数组可以直接用[]
shared_ptr<int[]> sp(new int[10]);

类型转换

static_cast
编译期转换,用于隐式类型转换,可以显示替换
没有类型检查,不安全

   // 1. 基本类型
int a = 10;
double b = static_cast<double>(a);

// 2. 基类 ↔ 派生类(无运行时检查,不安全!)
Base* base = new Derived;

dynamic_cast
运行期检查,用于基类到派生类转换
基类转到派生类失败会返回nullptr
必须要有虚函数!

   Base* base = new Derived;

// 基类 转 派生类
Derived* der = dynamic_cast<Derived*>(base);

if (der != nullptr) {
    // 转换成功
} else {
    // 转换失败
}

const_cast
用于去除或增加const性质

  const int a = 10;

// 去掉 const
int* p = const_cast<int*>(&a);
*p = 20;

reinterpret_cast
无关类型指针互转
极其危险,相当于暴力强制转换
底层对数据进行重新解释,危险

  int a = 10;
// 把 int 指针 强转成 char 指针
char* pc = reinterpret_cast<char*>(&a);

IO流

库的意义:
1、面向对象
istream / ostream / iostream 都是类
cin、cout 是全局对象
2、支持自定义类型流插入和提取

最常用的 4 个对象

cin 输入(键盘)
cout 输出(屏幕)
cerr 错误输出(无缓冲)
clog 日志输出

三大文件流类

ifstream 读文件
ofstream 写文件
fstream 读写文件(有缓冲)

#include <fstream>

// 写
ofstream ofs("test.txt");
ofs << "hello";

// 读
ifstream ifs("test.txt");
string s;
ifs >> s;

字符串流 stringstream

作用:字符串与数字互转、字符串分割、拼接

  1. 数字 → 字符串
int a = 123;
stringstream ss;
ss << a;
string s = ss.str();
  1. 字符串 → 数字
string s = "123";
stringstream ss(s);
int a;
ss >> a;

自定义类型重载 <<

operator<<(cout, obj);

class Person{
friend ostream& operator<<(ostream& out,const Person&p);
private:
int age=10;
};
ostream& operator<<(ostream&out,const Person&p){
out<<p.age;
return out;
}
Preson p;
cout<<p<<endl;
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐