news 2026/10/2 21:10:16

【C++进阶】智能指针的使用及其原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++进阶】智能指针的使用及其原理

目录

1 裸指针痛点:异常场景内存泄漏

2 RAII 资源获取即初始化

3 C++ 标准库四种智能指针总览

4 auto_ptr:C++98 旧智能指针,为什么被废弃

5 unique_ptr:独占式智能指针

5.1 核心特性

5.2 管理数组:unique_ptr 特化版本

5.3 explicit 构造、operator bool

6 shared_ptr:共享所有权,引用计数机制

6.1 引用计数为什么必须存堆上,不能用静态成员

6.2 make_shared 原理与优势

6.3 operator bool

7 删除器 Deleter 深度讲解【面试高频坑点】

8 weak_ptr 弱指针,解决 shared_ptr 循环引用

8.1 循环引用完整复现与泄漏成因

8.2 weak_ptr 核心特性

9 shared_ptr 线程安全细节【面试高频】

10 简易模拟实现(课件源码,仅理解原理,不等于标准库完整实现)

11 内存泄漏完整讲解

什么是内存泄漏

危害

内存泄漏产生的典型场景

检测工具

12 智能指针发展历史

13 选型最佳实践与避坑清单

14 面试高频问答

Q1:RAII 是什么?

Q2:auto_ptr 为什么废弃?

Q3:unique_ptr 为什么要删除拷贝构造?

Q4:shared_ptr 引用计数为什么不能用 static 静态成员?

Q5:shared_ptr 循环引用泄漏原理?怎么解决?

Q6:weak_ptr 为什么不能直接访问资源?lock () 作用是什么?

Q7:shared_ptr 哪些部分线程安全?

Q8:unique_ptr 和 shared_ptr 删除器的语法区别?

Q9:make_shared 优势是什么?

Q10:explicit 对智能指针起到什么作用?

1 裸指针痛点:异常场景内存泄漏

课件核心场景:函数内连续多次 new,函数中间抛出异常,直接跳过后面 delete,造成内存泄漏。 手动 try‑catch 处理会变得极度繁琐:不仅业务代码要套 try,如果其中某一步 new 本身抛出 bad_alloc 异常,前面已经 new 出来的内存同样要释放,需要多层嵌套捕获。

#include<iostream> #include<exception> using namespace std; double Divide(int a, int b) { if (b == 0) { throw "Divide by zero condition!"; } return static_cast<double>(a) / b; } void Func() { int* array1 = new int[10]; int* array2 = new int[10]; // 这一步new也可能抛bad_alloc int len, time; cin >> len >> time; cout << Divide(len, time) << endl; // 如果Divide抛出异常,下面两行永远不会执行,内存泄漏! delete[] array1; delete[] array2; } int main() { try { Func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (const exception& e) { cout << e.what() << endl; } catch (...) { cout << "未知异常" << endl; } return 0; }

如果不用智能指针,想要安全释放,需要写如下臃肿代码:

void Func() { int* array1 = new int[10]; int* array2 = nullptr; try { array2 = new int[10]; int len, time; cin >> len >> time; cout << Divide(len, time) << endl; } catch (...) { delete[] array1; delete[] array2; throw; } delete[] array1; delete[] array2; }

每多一个 new,就要增加分支处理,业务逻辑被资源释放逻辑严重污染。

根本问题:new/delete是手动管理,函数任意提前退出(return /break/throw 异常),都有可能跳过 delete。

2 RAII 资源获取即初始化

RAII(Resource Acquisition Is Initialization)资源获取即初始化核心四条原则:

  1. 获取资源:在构造函数完成资源申请(new、fopen、pthread_mutex_init);
  2. 资源托管给栈对象:资源交由栈上对象管理;
  3. 对象生命周期内,资源保持有效,可以正常访问;
  4. 释放资源:在析构函数完成资源释放。

栈对象关键特性:无论函数正常 return、还是异常栈展开 (stack‑unwind) 退出,栈对象一定会调用析构函数,资源一定会释放。

智能指针就是 RAII 范式的典型实例,除了析构释放资源,还重载*、->、[]模仿原生指针的使用语法。

简易 RAII 原型(仅演示原理,不考虑拷贝问题)

template<class T> class SmartPtr { public: SmartPtr(T* ptr) : _ptr(ptr) {} ~SmartPtr() { cout << "delete执行" << _ptr << endl; delete[] _ptr; } // 模仿指针解引用 T& operator*() { return *_ptr; } // 模仿指针成员访问 T* operator->() { return _ptr; } // 数组下标访问 T& operator[](size_t i) { return _ptr[i]; } private: T* _ptr; };

使用 RAII 改造上面泄漏示例,不需要手写任何 delete:

void Func() { SmartPtr<int> sp1 = new int[10]; SmartPtr<int> sp2 = new int[10]; int len, time; cin >> len >> time; cout << Divide(len, time) << endl; // 无论是否抛异常,sp1 sp2出作用域析构自动释放内存 }

3 C++ 标准库四种智能指针总览

头文件:<memory>

表格

智能指针标准版本所有权语义拷贝 / 移动行为底层实现使用场景
auto_ptrC++98独占拷贝时转移管理权,源对象置空裸指针❌C++11 起废弃,禁止使用
unique_ptrC++11独占所有权删除拷贝构造 / 拷贝赋值,只支持 std::move 移动裸指针一份资源只归一个对象持有,优先使用
shared_ptrC++11共享所有权支持拷贝、移动裸指针 +堆上分配引用计数控制块多个对象共同持有同一份资源
weak_ptrC++11弱引用,不拥有资源仅可以从 shared_ptr 构造 / 赋值仅保存资源裸指针,不修改计数解决 shared_ptr 循环引用,不执行资源释放

公共通用特性:

  1. 构造函数使用explicit修饰,禁止裸指针隐式转换为智能指针对象,规避隐蔽 bug;
  2. 重载operator bool,支持if(sp)直接判断智能指针是否持有有效资源;
  3. 默认释放行为调用普通delete;支持自定义删除器,处理数组、文件句柄、锁等其他资源。

4 auto_ptr:C++98 旧智能指针,为什么被废弃

auto_ptr 的拷贝语义:发生拷贝构造 / 拷贝赋值时,把资源管理权转移给目标对象,自身内部指针置为 nullptr。

模拟核心行为:

template<class T> class auto_ptr { public: auto_ptr(T* ptr) :_ptr(ptr) {} // 拷贝构造:转移管理权,源对象置空 auto_ptr(auto_ptr<T>& sp) { _ptr = sp._ptr; sp._ptr = nullptr; } auto_ptr<T>& operator=(auto_ptr<T>& ap) { if(this != &ap) { delete _ptr; _ptr = ap._ptr; ap._ptr = nullptr; } return *this; } ~auto_ptr() { delete _ptr; } T& operator*(){return *_ptr;} T* operator->(){return _ptr;} private: T* _ptr; };

致命缺陷:

auto_ptr<int> p1(new int(10)); auto_ptr<int> p2(p1); // p1的资源转移给p2,p1变成空指针 // *p1; // 运行时崩溃!p1已经悬空

一旦发生拷贝,源对象直接悬空,而拷贝会发生在很多隐式场景(函数传参、函数返回值),极易触发空指针访问,因此 C++11 直接废弃auto_ptr,工程严禁使用。

5 unique_ptr:独占式智能指针

5.1 核心特性

  1. 独占语义:同一时刻,一份资源只能被一个 unique_ptr 持有;
  2. 使用= delete显式删除拷贝构造函数、拷贝赋值运算符,编译期直接禁止拷贝;
  3. 仅支持移动构造unique_ptr(unique_ptr&&)、移动赋值operator=(unique_ptr&&),通过std::move转移资源所有权;move 之后源对象内部指针置空;
  4. 离开作用域析构,执行 delete 释放资源。

#include<memory> #include<iostream> using namespace std; struct Date { int _year; int _month; int _day; Date(int y=1,int m=1,int d=1):_year(y),_month(m),_day(d){} ~Date(){cout<<"~Date()"<<endl;} }; int main() { unique_ptr<Date> up1(new Date(2025,1,1)); // unique_ptr<Date> up2(up1); // 编译报错,拷贝被delete禁止 unique_ptr<Date> up3(move(up1)); // move转移所有权 cout << (up1 ? "up1有效" : "up1为空") << endl; // up1已经被置空 cout << up3->_year << endl; return 0; }

5.2 管理数组:unique_ptr<T[]>特化版本

普通unique_ptr<T>析构调用delete,不能释放数组;标准库提供模板特化unique_ptr<T[]>,析构调用delete[]。

unique_ptr<Date[]> arr(new Date[5]); arr[0]._year = 2025;

5.3 explicit 构造、operator bool

  1. explicit unique_ptr(T* p),不允许裸指针隐式转换
// unique_ptr<Date> up = new Date(); //编译报错,explicit禁止隐式转换 unique_ptr<Date> up(new Date()); // 正确,显式构造
  1. 重载operator bool,判断是否持有有效资源
if(up3) { cout<<"持有资源"<<endl; } if(!up1) { cout<<"up1为空"<<endl; }

6 shared_ptr:共享所有权,引用计数机制

场景:一份资源需要多个对象同时使用,资源最后一个使用者销毁时,才释放堆内存。

6.1 引用计数为什么必须存堆上,不能用静态成员

错误思路:把计数写成类静态static int _count。

静态成员属于整个类,所有 shared_ptr 对象共用同一个计数;多个独立资源会互相干扰,完全错误。

✅正确实现: 每 new 一份资源,同时在堆上 new 一块独立的引用计数;不同 shared_ptr 拷贝的时候,共享同一份计数块。

  • 构造:资源指针_ptr+ new 一个计数,初始值为 1;
  • 拷贝构造:复制_ptr与计数指针_pcount,++(*_pcount);
  • 析构:--(*_pcount);计数减到 0,调用删除器释放资源,同时释放计数块本身内存;
  • 赋值:先 release 释放自己旧资源,接管对方资源与计数,计数 ++。

6.2 make_shared 原理与优势

auto sp = make_shared<Date>(2025,10,1);

make_shared<T>(args...):一次内存分配,同时分配对象内存 + 引用计数控制块内存。

  • 普通shared_ptr<Date> sp(new Date(...)):两次堆分配:①对象 ②控制块;
  • make_shared:只一次堆分配,减少内存碎片,效率更高。

注意:同样 explicit,禁止裸指针隐式转换

// shared_ptr<Date> sp = new Date(); //编译报错 shared_ptr<Date> sp(new Date()); auto sp2 = make_shared<Date>(2025,10,1); //推荐写法

6.3 operator bool

if(sp) { // 资源有效 }

接口use_count()返回当前引用计数值。

7 删除器 Deleter 深度讲解【面试高频坑点】

智能指针默认析构执行普通delete;当资源不是new出来的堆对象(new [] 数组、FILE * 文件句柄、互斥锁),必须自定义删除器。

删除器本质:任意可调用对象:仿函数、函数指针、lambda 表达式。

⚠unique_ptr 与 shared_ptr 删除器语法巨大差异(面试常考)

  1. unique_ptr<T, Deleter>:删除器类型写在模板参数列表;对象构造时可以不传删除器实例(仿函数无参可以默认构造);
  2. shared_ptr<T>:模板参数没有删除器,删除器对象必须作为构造函数实参传入。

完整示例,三种删除器写法:仿函数、函数指针、lambda

#include<memory> #include<cstdio> using namespace std; //1.仿函数删除器 template<class T> struct DeleteArray { void operator()(T* ptr) { delete[] ptr; } }; //2.普通函数指针 template<class T> void DelArrayFunc(T* ptr) { delete[] ptr; } //管理文件句柄仿函数 struct FClose { void operator()(FILE* fp) { cout << "执行fclose" << endl; fclose(fp); } }; int main() { //======== unique_ptr 删除器:模板参数指定类型 ======== //仿函数,不需要传实例,默认构造 unique_ptr<Date, DeleteArray<Date>> up1(new Date[5]); //函数指针作为删除器,必须传入函数 unique_ptr<Date, void(*)(Date*)> up2(new Date[5], DelArrayFunc<Date>); //lambda删除器,decltype获取lambda类型 auto lam = [](Date* p){delete[] p;}; unique_ptr<Date, decltype(lam)> up3(new Date[5], lam); //======== shared_ptr 删除器:构造函数传可调用对象 ======== shared_ptr<Date> sp1(new Date[5], DeleteArray<Date>()); shared_ptr<Date> sp2(new Date[5], DelArrayFunc<Date>); shared_ptr<Date> sp3(new Date[5], [](Date* p){delete[] p;}); //管理FILE*文件句柄示例 shared_ptr<FILE> fp(fopen("test.txt","w"), FClose()); return 0; }

标准库提供特化:unique_ptr<T[]>、shared_ptr<T[]>,内置删除器自动调用delete[],不需要手动写删除器。

8 weak_ptr 弱指针,解决 shared_ptr 循环引用

8.1 循环引用完整复现与泄漏成因

场景:双向链表两个节点,互相保存 shared_ptr。

#include<memory> #include<iostream> using namespace std; struct ListNode { int _data; shared_ptr<ListNode> _next; shared_ptr<ListNode> _prev; ~ListNode() { cout << "~ListNode() 节点析构" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); cout << n1.use_count() << endl; //1 cout << n2.use_count() << endl; //1 n1->_next = n2; n2->_prev = n1; cout << n1.use_count() << endl; //2 cout << n2.use_count() << endl; //2 //main结束,n1、n2局部对象析构,计数各减为1 //节点内部_next、_prev仍然互相持有shared_ptr,计数永远不为0,内存泄漏! return 0; }

泄漏逻辑拆解:

  1. main 函数结束,局部对象 n1、n2 析构,两个节点引用计数从 2 → 1;
  2. 节点 A 的_next是 shared_ptr,指向节点 B;节点 B 的_prev是 shared_ptr 指向节点 A;
  3. 两个节点都还有引用计数 = 1,不会调用析构;
  4. 节点不析构,则_next、_prev成员不会销毁;形成死循环,内存永远释放不了。

✅修复:双向关联其中一方改为weak_ptr

struct ListNode { int _data; shared_ptr<ListNode> _next; weak_ptr<ListNode> _prev; //weak_ptr不会增加引用计数 ~ListNode() { cout << "~ListNode() 节点析构" << endl; } };

8.2 weak_ptr 核心特性

  1. weak_ptr 不参与资源所有权,不执行 RAII;不能直接 new 资源初始化 weak_ptr;只能从 shared_ptr 拷贝 / 赋值构造;
  2. 绑定 shared_ptr 的时候,不会增加 shared_ptr 的引用计数;
  3. 接口:
    • expired():返回 bool,判断绑定的资源是否已经被释放过期;
    • lock():返回shared_ptr<T>;资源有效返回有效的 shared_ptr;资源销毁返回空 shared_ptr。
  4. ❗weak_ptr没有重载operator*和operator->,不能直接访问资源,必须 lock 提升为 shared_ptr 之后访问,防止访问已经销毁的悬空资源。
int main() { shared_ptr<string> sp1 = make_shared<string>("hello"); weak_ptr<string> wp = sp1; cout << wp.expired() << endl; //false,资源存活 cout << wp.use_count() << endl; //use_count返回绑定的shared_ptr计数 sp1.reset(); //释放资源 cout << wp.expired() << endl; //true,资源过期 // auto& ref = *wp; //编译报错,weak_ptr没有解引用 auto spTemp = wp.lock(); if(spTemp) { cout << *spTemp << endl; } return 0; }

9 shared_ptr 线程安全细节【面试高频】

两条必须区分的重点:

  1. 引用计数的修改是线程安全:标准库内部引用计数使用std::atomic原子类型;多线程拷贝、析构 shared_ptr,增减引用计数不会发生数据竞争。
  2. 被 shared_ptr 托管的业务对象本身,完全没有线程安全保护!多个线程读写对象里面的成员变量,需要业务代码自己加互斥锁,shared_ptr 不会帮你做同步。

误区:很多同学误以为用了 shared_ptr,被管理对象多线程访问就是安全,这是错误。

手写简易版本如果直接用普通int* _pcount,多线程下计数会乱掉;要线程安全必须改成atomic<int>* _pcount。

10 简易模拟实现(课件源码,仅理解原理,不等于标准库完整实现)

注意:这份模拟做简化,weak_ptr 没有实现控制块,lock 能力不完备,仅用于理解基础原理。

#include<iostream> #include<functional> using namespace std; namespace bit { template<class T> class auto_ptr { public: auto_ptr(T* ptr) :_ptr(ptr) {} auto_ptr(auto_ptr<T>& sp) { _ptr = sp._ptr; sp._ptr = nullptr; } auto_ptr<T>& operator=(auto_ptr<T>& ap) { if(this != &ap) { delete _ptr; _ptr = ap._ptr; ap._ptr = nullptr; } return *this; } ~auto_ptr() { delete _ptr; } T& operator*(){return *_ptr;} T* operator->(){return _ptr;} private: T* _ptr; }; template<class T> class unique_ptr { public: explicit unique_ptr(T* ptr = nullptr) :_ptr(ptr) {} ~unique_ptr() { delete _ptr; } // 删除拷贝 unique_ptr(const unique_ptr<T>& sp) = delete; unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete; // 移动语义 unique_ptr(unique_ptr<T>&& sp) { _ptr = sp._ptr; sp._ptr = nullptr; } unique_ptr<T>& operator=(unique_ptr<T>&& sp) { if(this != &sp) { delete _ptr; _ptr = sp._ptr; sp._ptr = nullptr; } return *this; } T& operator*(){return *_ptr;} T* operator->(){return _ptr;} private: T* _ptr; }; template<class T> class shared_ptr { public: explicit shared_ptr(T* ptr = nullptr) :_ptr(ptr) ,_pcount(new int(1)) {} template<class D> shared_ptr(T* ptr, D del) :_ptr(ptr) ,_pcount(new int(1)) ,_del(del) {} shared_ptr(const shared_ptr<T>& sp) { _ptr = sp._ptr; _pcount = sp._pcount; _del = sp._del; ++(*_pcount); } void release() { if(--(*_pcount) == 0) { _del(_ptr); delete _pcount; _ptr = nullptr; _pcount = nullptr; } } shared_ptr<T>& operator=(const shared_ptr<T>& sp) { if(_ptr != sp._ptr) { release(); _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); _del = sp._del; } return *this; } ~shared_ptr() { release(); } T* get() const { return _ptr; } int use_count() const { return *_pcount; } T& operator*(){return *_ptr;} T* operator->(){return _ptr;} private: T* _ptr; int* _pcount; function<void(T*)> _del = [](T* p){delete p;}; }; template<class T> class weak_ptr { public: weak_ptr() :_ptr(nullptr) {} weak_ptr(const shared_ptr<T>& sp) :_ptr(sp.get()) {} weak_ptr<T>& operator=(const shared_ptr<T>& sp) { _ptr = sp.get(); return *this; } private: T* _ptr = nullptr; }; }

11 内存泄漏完整讲解

什么是内存泄漏

程序逻辑错误,堆内存已经不再被业务使用,但是没有执行释放。

注意:不是物理内存消失;进程还存在,但是程序已经失去这块内存的使用权,无法再次释放它。

危害

  1. 短生命周期程序:进程结束,操作系统回收进程全部虚拟内存,泄漏几乎无影响。
  2. 长期运行的后台服务、守护进程、服务器程序:泄漏不断累积,内存占用持续上涨,响应越来越慢,最终 OOM 被系统杀死。

内存泄漏产生的典型场景

  1. new 之后忘记写 delete;
  2. 异常、return 提前退出,delete 没有执行;
  3. shared_ptr 循环引用;
  4. 复杂分支逻辑,部分分支漏掉释放逻辑。

检测工具

  • Linux:valgrind工具检测内存泄漏;Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客
  • Windows:VLD 内存泄漏检测库。windows下的内存泄露检测工具VLD使用_windows内存泄漏检测工具-CSDN博客

解决思路分两类:

  1. 事前预防:RAII 智能指针优先,尽量避免裸 new/delete;编码规范;
  2. 事后排查:泄漏检测工具定位泄漏点。

12 智能指针发展历史

  1. C++98:auto_ptr,存在严重设计缺陷;
  2. Boost 库提供:scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr;
  3. TR1 技术报告:把 boost 部分智能指针引入,非正式标准;
  4. C++11 正式标准:
    • unique_ptr对标 boost 的scoped_ptr;
    • shared_ptr、weak_ptr直接吸纳 boost 实现进入标准库。

13 选型最佳实践与避坑清单

✅推荐做法

  1. 优先unique_ptr,独占语义,开销最小;只有真正需要共享资源的时候,才用shared_ptr;
  2. 创建 shared_ptr 优先make_shared,减少内存分配次数,降低内存碎片;
  3. 双向链表、环形结构警惕循环引用,使用weak_ptr打断循环;
  4. 管理数组、文件句柄:优先标准特化版本,或者自定义删除器。

❌避坑清单

  1. 禁止使用auto_ptr;
  2. 不要把同一个原生裸指针,交给两个互不相关的 shared_ptr 管理,会造成双重释放;
  3. weak_ptr 不能直接解引用访问资源,必须 lock 提升为 shared_ptr;
  4. shared_ptr 只保证引用计数线程安全,托管对象读写需要自己加锁;
  5. 区分删除器语法:unique_ptr 删除器写模板参数,shared_ptr 删除器写构造实参。

14 面试高频问答

Q1:RAII 是什么?

资源获取即初始化,将资源交给栈对象托管;构造获取资源,析构释放资源;无论函数正常返回还是异常栈展开,栈对象析构一定会执行,保证资源释放。智能指针是 RAII 典型实现。

Q2:auto_ptr 为什么废弃?

拷贝时直接转移资源管理权,源对象置空;拷贝后源对象悬空,隐式拷贝场景极易空指针崩溃,C++11 废弃。

Q3:unique_ptr 为什么要删除拷贝构造?

unique_ptr 是独占所有权语义,一份资源同一时刻只能被一个对象管理;拷贝会破坏独占语义,编译期 delete 禁止拷贝,只能 std::move 移动。

Q4:shared_ptr 引用计数为什么不能用 static 静态成员?

static 属于整个类,所有对象共用一份计数;不同独立资源需要各自独立计数块,计数必须每一份资源单独 new 在堆上。

Q5:shared_ptr 循环引用泄漏原理?怎么解决?

两个对象内部互相持有 shared_ptr;外部 shared_ptr 析构之后,内部成员还互相持有,引用计数无法降到 0,资源不会释放。把其中一个成员改成 weak_ptr,weak_ptr 不增加引用计数,打破循环。

Q6:weak_ptr 为什么不能直接访问资源?lock () 作用是什么?

weak_ptr 不拥有资源,无法判断资源是否已经销毁。lock () 尝试提升,返回 shared_ptr,如果资源已经释放返回空 shared_ptr。expired () 用来判断资源是否过期。

Q7:shared_ptr 哪些部分线程安全?

引用计数增减是原子操作,多线程拷贝析构 shared_ptr 安全;被托管的业务对象本身没有任何同步保护,读写成员需要用户加锁。

Q8:unique_ptr 和 shared_ptr 删除器的语法区别?

unique_ptr 删除器类型放在模板参数;shared_ptr 删除器是构造函数的实参。

Q9:make_shared 优势是什么?

一次性分配内存,对象与控制块放在同一块内存,减少堆分配次数,降低内存碎片。

Q10:explicit 对智能指针起到什么作用?

禁止裸指针隐式转换为智能指针对象,防止不经意发生裸指针交给智能指针管理的隐蔽错误。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 21:08:11

Canvas画圆点避坑指南:从坐标、arc到粒子动画与高清屏优化

我第一次用Canvas画圆点是在做一个小工具的时候。需求特别简单&#xff1a;用户在页面上每点一下&#xff0c;就在对应位置留下一个圆点。当时我觉得半小时就能搞定&#xff0c;结果足足折腾了两天。一开始是画不出圆&#xff0c;后来是坐标对不上&#xff0c;再后来发现屏幕上…

作者头像 李华
网站建设 2026/10/2 21:07:26

外卖系统数据库课程设计:从ER建模到窗口函数实战

简介&#xff1a;本资源是一份完整的数据库课程设计报告范文&#xff0c;面向高校计算机、软件工程等专业本科生&#xff0c;解决课程设计中系统选题、需求分析、数据库建模与文档撰写等核心难点。报告以“外卖点餐管理系统”为案例&#xff0c;覆盖项目背景、用户角色权限&…

作者头像 李华
网站建设 2026/10/2 21:06:18

我把前端测试写成了一个Skill:一句话让 AI 点完整个控制台

Hello&#xff0c;大家好~ 在我们平常的前端测试工作中&#xff0c;由于前端自动化的不稳定&#xff0c;经常需要人工重复去回归页面的功能&#xff0c;比如&#xff0c;发版前打开控制台&#xff0c;翻一遍分页、点一遍按钮、盯一眼报错——规则明确、高度重复&#xff0c;但每…

作者头像 李华
网站建设 2026/10/2 21:00:06

C语言的输入与输出语法

C语言的输入与输出语法 1格式 在开头有与#include<stdio.h>等这是头文件&#xff08;像你给它一本字典来运行你的代码&#xff09; 后面是 int main(){ }这像信的正文里面是你的代码 其中要注意每一行后要加分号&#xff08;;&#xff09; 在这最后是return 0&#xff1b…

作者头像 李华