
本文是 C 系列教程的第 9 篇。上一篇讲解了继承与多态本篇讲解动态内存管理的核心new/delete 配对、内存泄漏与悬垂指针、RAII 思想、智能指针unique_ptr/shared_ptr/weak_ptr、make_unique/make_shared、循环引用。一、动态内存基础1.1 new 与 delete动态内存堆内存在运行时分配生命周期由程序员控制#includeiostreamusingnamespacestd;intmain(){// 单个对象int*pnewint(42);// 分配并初始化cout*p *pendl;// 42deletep;// 释放内存必须pnullptr;// 悬垂指针置空// 数组int*arrnewint[5];for(inti0;i5;i)arr[i]i*10;coutarr[3] arr[3]endl;// 30delete[]arr;// 数组用 delete[]// 对象string*snewstring(Hello);cout*sendl;deletes;return0;}配对规则new配deletenew[]配delete[]。混用是未定义行为。1.2 内存泄漏#includeiostreamusingnamespacestd;voidleakExample(){int*pnewint(100);// 没有 delete p函数结束指针技失内存无法释放 → 内存泄漏}intmain(){for(inti0;i1000;i){leakExample();// 每次泄漏 4 字节}cout程序结束已泄漏内存endl;return0;}内存泄漏的危害程序长期运行内存持续增长最终耗尽内存导致崩溃。服务器程序尤其严重。1.3 悬垂指针与双重释放#includeiostreamusingnamespacestd;intmain(){int*p1newint(10);int*p2p1;// 两个指针指向同一块内存deletep1;// 释放// delete p2; // 错误双重释放 → 崩溃// *p2 20; // 错误p2 现在是悬垂指针指向已释放内存cout避免双重释放和悬垂指针访问endl;return0;}二、RAII 思想2.1 什么是 RAIIRAIIResource Acquisition Is Initialization资源获取即初始化把资源内存、文件、锁的获取放在构造函数中释放放在析构函数中。对象生命周期结束离开作用域时析构函数自动放资源。#includeiostreamusingnamespacestd;// 手动管理容易出错classBadResource{public:int*data;BadResource():data(newint[100]){}~BadResource(){delete[]data;}// 忘记写就泄漏};// RAII 思想已体现在之前的 String 类中构造分配、析构释放classSmartResource{private:int*data;public:SmartResource():data(newint[100]){cout获取资源endl;}~SmartResource(){delete[]data;cout自动释放资源endl;}};intmain(){{S martResource r;// 构造获取资源cout使用资源...endl;}// 析构自动释放即使有异常也会释放coutRAII 保证资源安全释放endl;return0;}2.2 RAII 的优势异常安全即使抛出异常析构函数也会执行。代码简洁无需手动清理。防止泄漏资源一定被释放。三、智能指针C113.1 unique_ptr独占所有权unique_ptr独占管理指针不能拷贝只能转移移动离开作用域自动释放#includeiostream#includememoryusingnamespacestd;classResource{public:Resource(){cout资源创建endl;}~Resource(){cout资源释放endl;}voidwork(){cout工作中...endl;}};intmain(){// 方式一直接构造unique_ptrResourcep1(newResource());// 方式二make_unique推荐C14autop2make_uniqueResource();// 不能拷贝// unique_ptrResource p3 p1; // 错误// 转移所有权unique_ptrResourcep3move(p1);// p1 在是空指针 p3-work();// 离开作用域自动释放return0;}使用场景独占资源的所有权如工厂函数返回值、容器中的对象。3.2 shared_ptr共享所有权shared_ptr使用引用计数多个指针共享同一对象最后一个释放时销毁对象#includeiostream#includememoryusingnamespacestd;classResource{public:intid;Resource(inti):id(i){cout创建资源 idendl;}~Resource(){cout释放资源 idendl;}};intmain(){// make_shared一次分配对象和计数块推荐 shared_ptrResourcep1make_sharedResource(1);coutp1 引用计数: p1.use_count()endl;// 1{shared_ptrResourcep2p1;// 拷贝计数 1shared_ptrResourcep3p1;// 拷贝计数 1cout计数: p1.use_count()endl;// 3coutp2-id p2-idendl;// 1}// p2, p3 销毁计数 -2cout计数: p1.use_count()endl;// 1// p1 销毁时计数归零释放资源return0;}3.3weak_ptr弱引用打破循环引用weak_ptr不增加引用计数用于观察shared_ptr 管理的对象解决循环引用问题#includeiostream#includememoryusingnamespacestd;classB;// 前向声明classA{public:shared_ptrBb_ptr;~A(){coutA 析构endl;}};classB{public:// 若这里用 shared_ptrA则 A 和 B 互相持有 → 循环引用都不释放weak_ptrAa_ptr;// 用 weak_ptr 打破循环~B(){coutB 析构endl;}};intmain(){shared_ptrAamake_sharedA();shared_ptrBbmake_sharedB();a-b_ptrb;b-a_ptra;// weak_ptr 不增加计数couta 计数: a.use_count()endl;// 1b 持有的是 weakcoutb 计数: b.use_count()endl;// 2a 持有 shared// 用 weak_ptr 访问需要 lock()if(autospb-a_ptr.lock()){cout通过 weak_ptr 访问 A 成功endl;}// 程序结束a 和 b 都能正常析构return0;}3.4 三种智能指针对比维度unique_ptrshared_ptrweak_ptr所有独占共享无观察拷贝禁止允许计数1允许移动允许允许允许引用计数无有有不增加开销最小较大较小适用独占资源多所有者打破循环引用四、智能指针实战4.1 用智能指针重构图形系统#includeiostream#includememory#includevectorusingnamespacestd;classShape{public:virtualdoublearea()const0;virtualvoidinfo()const0;virtual~Shape(){}};classCircle:publicShape{doubl e r;public:Circle(doubleradius):r(radius){}doublearea()constoverride{return3.14159*r*r;}voidinfo()constoverride{cout圆面积: area()endl;}};classRectangle:publicShape{doublew,h;public:Rectangle(doublea,doubleb):w(a),h(b){}doublearea()constoverride{returnw*h;}voidinfo()constoverride{cout矩形面积: area()endl;}};intmain(){// 容器存储智能指针自动管理生命周期无需手动 deletevectorunique_ptrShapeshapes;shapes.push_back(make_uniqueCircle(3));shapes.push_back(make_uniqueRectangle(4,5));doubletotal0;for(constautos:shapes){s-info();totals-area();}cout总面积: totalendl;// 离开作用域 vector 析构unique_ptr 自动释放所有 Shapereturn0;}4.2 工厂函数返回智能指针#includeiostream#includememoryusingnamespacestd;classLogger{public:virtualvoidlog(conststringmsg)const0;virtual~Logger(){}};classConsoleLogger:publicLogger{public:voidlog(conststringmsg)constoverride{cout[控制台] msgendl;}};classFileLogger:publicLogger{public:voidlog(conststringmsg)constoverride{cout[文件] msgendl;}};// 工厂函数返回 unique_ptr所有权转移给调用者unique_ptrLoggercreateLogger(conststringtype){if(typeconsole){returnmake_uniqueConsoleLogger();}else{returnmake_uniqueFileLogger();}}intmain(){autologgercreateLogger(console);logger-log(系统启动);loggercreateLogger(file);// 旧对象自动释放logger-log(切换到文件日志);return0;}4.3 正确使用规范#includeiostream#includememoryusingnamespacestd;classWidget{public:voidshow(){coutWidgetendl;}};voidprocess(shared_ptrWidgetp){cout处理中计数: p.use_count()endl;}intmain(){// 规范 1用 make_shared 而非 newshared_ptrWidgetwmake_sharedWidget();// 规范 2传参用值传递拷贝或用 constprocess(w);// 规范 3不要混用裸指针和能指针// Widget *raw w.get(); // get() 只读不拥有// 规范 4不要用裸指针创建多个 shared_ptr// Widget *rw new Widget();// shared_ptrWidget s1(rw);// shared_ptrWidget s2(rw); // 错误双重释放w-show();return0;}五、实战内存安全的链表用智能指针实现链表无需手动管理内存#includeiostream#includememoryusingnamespacestd;structNode{intdata;unique_ptrNodenext;// 每个节点独占下一个节点Node(intv):data(v){}};classLinkedList{private:unique_ptrNodehead;public:// 头插法voidpushFront(intvalue){autonewNodemake_uniqueNode(value);newNode-nextmove(head);// 转移旧头节点headmove(newNode);}voidprint()const{Node*curhead.get();while(cur){coutcur-data ;curcur-next.get();}coutendl;}// 析构head 释放时自动递归释放整个链表};intmain(){LinkedList list;list.pushFront(30);list.pushFront(20);list.pushFront(10);list.print();// 10 20 30// list 析构时整个链表自动释放无内存泄漏cout链表已自动释放endl;return0;}总结本篇讲解了 new/delete 配对、内存泄漏与悬垂指针的危害、RAII 思想、三种智能指针unique_ptr 独占、shared_ptr 共享、weak_ptr 观察及其选择、循环引用的打破并用智能指针链表串联实战。重点掌握RAII 的核心思想、三种智能指针的适用场景、make_unique/make_shared 的使用、循环引用的解决方案。下一篇将讲解文件与字符串流fstream、sstream、filesystem敬请期待