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

C++智能指针上

一、裸指针

        “裸指针”是最基础的,直接存储内存地址的指针类型。特点:①它本身没有自动的内存管理机制:如它不会自动释放内存,也不会检查是否指向有效的内存区域;②直接操作内存地址,不进行任何的边界检查:如空指针解引用。

int main()
{
    int x = 10;
    int*p = &x;           //裸指针p指向x
    cout << *p << endl;   //输出x的值,10
    return 0;
}

1、动态内存分配:new 与 malloc 详解

        在 C++ 中,动态内存分配是指在程序运行时(而非编译时)从堆(heap)上分配内存。主要有两种方式:new/delete:C++ 的运算符       malloc/free:C 标准库函数。

①new在堆分配的内存是无名的,返回一个指向该对象的指针。

    //分配单个对象
    int* p = new int;       //p指向一个动态分配的、未初始化的无名对象
    int* p2 = new int(42);  //分配并初始化为42
    int* p3 = new int();    //值初始化,*p3=0

    //分配数组
    int* arr = new int[10];//分配10个int的数组

    //释放内存
    delete p;
    delete[] arr;

②malloc返回void*,需要强制类型转换。

    //分配内存
    int* p = (int*)malloc(sizeof(int));          //分配一个int
    int* arr = (int*)malloc(10 * sizeof(int));   //分配10个int的数组

    //释放内存
    free(p);
    free(arr);

③两者区别:

a.基本内存分配与初始化:new支持初始化;malloc不初始化,分配后必须手动赋值,否则读取是未定义的行为。

b.类对象的内存管理:new自动调用构造函数,delete自动调用析构函数;malloc只是分配内存,需要配合placement new使用,且必须手动调用析构函数。

c.数组处理:new[]和delete[]配合使用;malloc需要手动计算数组大小、初始化每个元素。

d.内存分配失败处理:new在失败时抛出异常(std::bad_alloc);malloc在失败时返回NULL/nullptr。

e.与realloc的配合:

//new没有直接对应的realloc功能,需要手动实现:
    int* arrNew = new int[5];
    int* newArrNew = new int[10];
    std::copy(arrNew, arrNew + 5, newArrNew); //将 arrNew 的前5个元素复制到 newArrNew
    delete[] arrNew;
    arrNew = newArrNew;

//malloc可以配合realloc使用
    int* arr = (int*)malloc(5 * sizeof(int));
    // 扩展内存到10个int
    int* newArr = (int*)realloc(arr, 10 * sizeof(int));
    if (newArr)
    {
        arr = newArr;
        arr[8] = 42;  //使用新分配的空间
    }
    free(arr);

2、malloc两种内存分配方式

        ①brk()方式:分配小块内存(通常是小于128KB),通过brk()系统调用会将堆顶指针向更高的内存地址移动,获取内存空间。被free释放时,并不会将内存归还给操作系统,而是将其缓冲在内存池中,供后续分配请求复用。
优点:直接操作堆,内存分配速度较快。缺点:频繁使用,堆内将产生越来越多不可用的碎片,导致“内存泄漏”。
        ②mmap()方式:当用户请求的内存较大时(通常大于128KB),mmap()在进程的虚拟地址空间中(堆和栈的中间,称为文件映射区域的地方)找一块空闲的虚拟内存。free释放时,会把内存归还给操作系统,内存得到真正释放。
缺点:向操作系统申请内存,是通过系统调用的,执行系统调用是要进入内核态的,然后回到用户态,运行态的切换会耗费不少时间。分配的内存释放都会归还给操作系统。所以每次分配的虚拟地址都是缺页状态,在第一次访问该虚拟地址的时候,就会触发缺页中断。

二、智能指针

        “智能指针”是原始指针的封装,解决了裸指针带来的内存管理问题,可以自动管理内存的生命周期。

1、unique_ptr  “独占所有权”

①三种创建方式

#include <iostream>
using namespace std;
#include <memory>

class MyClass {
public:
    MyClass() { cout << "MyClass created!" << endl; }
    MyClass(int x, double y) {}    // 带参数的构造函数
    ~MyClass() { cout << "MyClass destroyed!" << endl; }
};

int main()
{
    //创建unique_ptr的三种方式:
    //1、使用make_unique  "推荐!!!"
    unique_ptr<MyClass> ptr = make_unique<MyClass>();//函数模板必须使用函数调用运算符 () 来调用。
    auto ptr1 = make_unique<MyClass>(42, 3.14); // 正确:传递参数

    //2、使用new运算符
    unique_ptr<MyClass> ptr2(new MyClass());
    //3、使用unique_ptr的构造函数
    MyClass* rawPtr = new MyClass();
    unique_ptr<MyClass> ptr3(rawPtr);

    return 0;
}

②成员函数get()获取原始指针(裸指针)

    MyClass* rawPtr = ptr.get();  //像普通指针一样使用它

注意:get()不改变所有权,unique_ptr仍然拥有资源的所有权,并在销毁时自动释放资源,手动对rawPtr调用delete会导致双重释放,尽量避免滥用get()。

③调用成员函数和解引用

④不可以copy只可以move

不允许复制是为了保证资源管理的唯一性。如果允许就会有多个指针共享相同的资源,导致双重释放等问题。

⑤reset()清空智能指针

reset方法将智能指针指向的对象释放,并将智能指针设置为nullptr。unique_ptr是独占拥有权,因此它会立即释放资源。

⑥作函数参数,值/引用/常量引用传递/右值引用

按值传递:传递所有权。

按引用传递:

常量引用传递时,函数无法修改指针的状态。

右值引用:值传递会触发一次移动构造,右值引用直接绑定到传入的右值,没有额外移动

                  传递所有权。

⑦以值返回:优化为移动,转移所有权。

相关文章:

  • 深入探索Spark-Streaming:从Kafka数据源创建DStream
  • C语言-函数-1
  • 【刷题】第三弹——二叉树篇(上)
  • 【C++ 真题】P3456 [POI2007] GRZ-Ridges and Valleys
  • AI大模型从0到1记录学习 数据结构和算法 day20
  • 【Linux】网络基础和socket(4)
  • SQL进阶知识:六、动态SQL
  • 济南国网数字化培训班学习笔记-第二组-5节-输电线路设计
  • 解决 PostgreSQL 检查约束导致的数据插入异常问题
  • 网络IP冲突的成因与解决方案
  • 三维重建模块VR,3DCursor,MPR与VR的坐标转换
  • 二叉树的创建,增加,前序遍历
  • Bytebase 取得 SOC 2 Type 1 认证
  • 第55讲:农业人工智能的跨学科融合与社会影响——构建更加可持续、包容的农业社会
  • YOLOv5改进(十)-- 轻量化模型MobileNetv4
  • Sharding-JDBC 系列专题 - 第十篇:ShardingSphere 生态与未来趋势
  • PHYBench:首个大规模物理场景下的复杂推理能力评估基准
  • C++23文本编码革新:迈向更现代的字符处理
  • 2025年3月电子学会青少年机器人技术(五级)等级考试试卷-理论综合
  • 10.接口而非实现编程
  • 特朗普:泽连斯基的言论对和平谈判非常有害
  • 富力地产:广州富力空港假日酒店第一次拍卖流拍,起拍价约2.77亿元
  • 中国空间站已在轨实施了200余项科学与应用项目
  • 哈佛大学就联邦经费遭冻结起诉特朗普政府
  • 泽连斯基:乌英法美将在伦敦讨论停火事宜
  • 教育部召开全国中小学幼儿园安全工作视频会议:加强校园安防建设