三.C++ 内存管理(进阶)(二)

C/C++ 内存管理高频考点详解(第 9--16 题)

承接 3.1--3.9。每题含「一句话概括 + 原理 + 代码/可视化 + 面试追问」。

编排提示:第 10 与 13 题(智能指针)、第 11 与 15 题(内存泄漏)高度重复,

本文刻意分工------10/11 讲正面原理与全景,13/15 讲缺点陷阱与实操,建议正式题单中合并。


9. struct 的内存对齐与内存占用计算

一句话:编译器为了让 CPU 高效访存,会在成员之间"塞空白",所以结构体大小不等于成员大小简单相加------成员排列顺序直接决定占用。

为什么要对齐

CPU 通过数据总线按"字"(4/8 字节)批量取内存。若一个 int 落在奇数地址、横跨两次总线读取,轻则多一个周期,重则在 ARM/MIPS 上直接触发对齐异常。因此编译器规定:每个成员的偏移量必须是其自身大小(或指定对齐值)的整数倍;结构体总大小必须是其最大成员对齐数的整数倍,不足则尾部补齐。

计算规则与可视化

复制代码
#include <cstdio>
struct A { char c; int i; short s; };
// c@0  □pad@1-3  i@4-7  s@8-9  □pad@10-11   => sizeof = 12
struct B { char c; short s; int i; };
// c@0  □pad@1  s@2-3  i@4-7                  => sizeof = 8
#pragma pack(1)
struct C { char c; int i; short s; };          // 取消对齐 => 7
#pragma pack()
int main(){ printf("%zu %zu %zu\n",sizeof(A),sizeof(B),sizeof(C)); } // 12 8 7

struct A 内存图(数字=字节偏移)
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ c │░░░│░░░│░░░│ i │ i │ i │ i │ s │ s │░░░│░░░│
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
 0               4               8              12
结论:把小成员聚拢、大成员靠后/靠前排序,可省内存(A=12,B=8)。

面试追问与嵌入式要点

  • #pragma pack(n):强制按 n 字节对齐,常用于网络协议头、文件格式等"一字节都不能差"的场景;代价是可能产生非对齐访问,跨平台要谨慎。
  • static_assert(sizeof(A)==12):网络/存储结构体常用编译期断言锁死大小,防止编译器或选项变化导致协议错位。
  • 嵌入式 :寄存器结构体用 __packed 或精确对齐映射硬件地址;DMA 缓冲区常需缓存行(cache line,一般 64 字节)对齐以避免 false sharing;结构体里放数组、嵌套结构体时,嵌套体按其自身最大成员对齐。
  • 64 位注意 :指针 8 字节,long 在 Linux 64 位是 8 字节,同一结构体在 32/64 位、不同编译器下大小可能不同,这也是跨平台二进制协议要显式定宽类型(int32_t 等)的原因。
  • 空类与虚函数 :没有任何成员的空类大小是 1 而非 0(保证两个对象地址不同);一旦有虚函数,对象开头会多一个虚表指针 vptr(64 位下 8 字节),且它参与对齐,常是面试计算的隐藏项。
  • 位域省空间struct{unsigned a:3; b:5;} 可把多个标志压进一个字,但位域跨字节的排布由实现定义,跨平台通信同样不建议依赖。
  • 计算口诀:先按声明顺序逐个放,每个成员相对结构体起点的偏移必须能被"该成员对齐值与 pack 值的较小者"整除,不够就填白;最后整体大小向上补齐到"最大对齐值与 pack 较小者"的整数倍。

10. 智能指针的定义与作用

一句话:智能指针是用 RAII 包装裸指针的类对象,把"何时释放"交给作用域和引用计数自动管理,从语言机制上消灭忘记 delete。

它解决什么痛点

裸指针 T* 只存地址,不表达"所有权"------谁负责释放、何时释放、异常路径会不会漏,全靠程序员记。一旦函数中途 return 或抛异常,后面的 delete 被跳过,立即泄漏。智能指针在构造时持有资源、析构时自动释放,即使栈展开也能正确清理。

三大智能指针

复制代码
#include <memory>
#include <cstdio>
struct A { ~A(){ puts("dtor"); } };

void unique_demo() {
    std::unique_ptr<A> p(new A);   // 独占,出作用域自动 delete
    // std::unique_ptr<A> q = p;   // 编译错误:不可拷贝
    auto q = std::move(p);         // 只能移动,所有权转移
}                                  // q 析构 -> 打印 dtor

void shared_demo() {
    auto p = std::make_shared<A>();// 引用计数=1
    { auto q = p; }               // 拷贝,计数 2 -> 离开内层 1
}                                  // 归 0,打印 dtor
指针 所有权 拷贝 典型用途
unique_ptr 独占 禁拷贝、可 move 默认首选、工厂返回、Pimpl
shared_ptr 共享计数 可拷贝 多处共享同一对象生命周期
weak_ptr 不拥有 依附 shared 破循环引用、观察者、缓存

作用总结与追问

  • 自动释放:作用域结束/计数归 0 即回收,异常安全。
  • 明确所有权语义:看指针类型就知道资源归谁,代码自文档化。
  • 可定制删除器:能管理文件句柄、socket、malloc 内存等非 new 资源。
  • 为什么默认用 unique_ptr? 零开销(和裸指针同大小)、语义最清晰;只有确实需要共享时才升级 shared_ptr。
  • make_shared/make_unique 优势:异常安全、少一次分配;优先使用。
  • 自定义删除器 :智能指针不只管 new,还能管任意资源------unique_ptr<FILE,decltype(&fclose)> 管文件、unique_ptr<int,void(*)(int*)> 管 malloc 内存或硬件句柄,析构时自动调用你给的清理函数,这是 RAII 思想的延伸。
  • 和垃圾回收(GC)的区别:Java/Go 的 GC 在运行期扫描、有停顿且时机不可控;C++ 智能指针靠确定性析构,资源在确定的作用域/计数点释放,无后台停顿,适合实时与嵌入式,但把生命周期管理的责任交给了程序员的设计。
  • Pimpl 惯用法 :用 unique_ptr<Impl> 把私有成员藏到 .cpp,降低编译依赖、隐藏实现,是 unique_ptr 的经典工程应用。
  • 一句话:智能指针不是"更聪明的指针",而是"把 delete 自动化的对象",选哪种取决于资源是独占、共享还是仅观察。

11. 内存泄漏以及解决方法

一句话:内存泄漏不是内存"丢了",而是你失去了对一块已分配堆内存的唯一引用,导致它永远无法回收------进程不退出就持续堆积直至 OOM。

泄漏是怎么发生的

复制代码
void leak_demo(bool ok) {
    int* p = new int[1000];
    if (!ok) return;        // 提前返回,delete 被跳过 -> 泄漏
    process(p);
    delete[] p;             // 只有走到这里才释放
}

void still_leak() {
    auto* q = new int;
    throw 1;                // 异常抛出,delete 不可达 -> 泄漏
    delete q;
}

正常: new ──> 使用 ──> delete ──> 归还堆
泄漏: new ──> 使用 ──> 指针丢失/提前退出/异常
                         └─> 堆块无人引用,进程结束前永不回收

危害与典型场景

  • 长期运行的服务(后端、模型推理、嵌入式守护进程)中,泄漏缓慢累积,最终 OOM 被系统杀死;嵌入式堆本就小,几 KB 泄漏都可能致命。
  • 常见来源:配对遗漏、异常/提前返回路径、容器只增不减、第三方库回调持有对象、C++ 智能指针循环引用(逻辑泄漏)。

检测与解决

手段 说明
编码规范 RAII + 智能指针,业务代码不写裸 delete
AddressSanitizer 编译加 -fsanitize=address,运行即报泄漏堆栈,速度快、首选
valgrind valgrind --leak-check=full ./prog,无需重编译但慢
系统观察 Linux /proc/<pid>/status 的 VmRSS、top 看是否单调上涨
自研 重载 operator new/delete 记账,嵌入式常用

根本解法是所有权设计:能栈上就栈上;堆上用 unique_ptr 表达独占、shared_ptr 表达共享并配 weak_ptr 断环;资源获取即初始化,让释放不依赖人记得。

补充辨析

  • 泄漏 vs 内存碎片:泄漏是内存再也回不来;碎片是空闲内存都在、但被切成小块无法满足大分配,二者都会让 RSS 持续上涨,排查时要区分------碎片可换分配器(jemalloc/tcmalloc)或内存池缓解,泄漏必须修代码。
  • "still reachable" 算不算泄漏:程序退出时仍被全局指针引用的内存,valgrind 标为 still reachable,操作系统会整体回收,严格说不算泄漏;但长期不退出的服务里,只增不减的全局缓存等价于泄漏。
  • 嵌入式特殊性:没有虚拟内存、没有 OOM killer,堆一旦耗尽 malloc 直接返回 NULL 且可能长期不重启,泄漏是致命的;常用固定块内存池从设计上杜绝泄漏与碎片,并对每次分配做水位监控。
  • 排查思路:先确认 RSS 是否单调不降(排除正常缓存),再用 ASan 定位分配栈,最后回到所有权设计修复,而不是到处加 delete。

12. 空指针和悬空指针的区别

一句话:空指针"明说自己什么都不指"(好查、解引用立刻崩);悬空指针"假装还指着一个活对象",但那块内存已释放(未定义行为、时好时坏,最难查)。

三者对比

空指针 null 悬空指针 dangling 野指针 wild
成因 显式置 nullptr 指向的对象已释放/栈帧已回收 指针从未初始化
是否有效 明确无效 看似有效实则无效 随机值
解引用 立即段错误,易定位 UB:可能正常、可能崩、可能数据错乱 UB,随机崩溃
排查难度

代码与时间线

复制代码
int* p = nullptr;      // 空指针:明确不指向任何对象
// *p = 1;            // 解引用空指针 -> 立即段错误(反而好查)

int* q = new int(5);
delete q;              // 内存已释放
// 此刻 q 成为悬空指针:地址还在,但对象已不存在
// *q = 1;            // UB!有时"看起来正常",因为内存还没被覆盖

int* r;                // 野指针:未初始化,值随机
// *r = 1;            // UB

悬空指针生命周期:
 q=new ──> 对象存活 ──> delete q ──> 内存可被任意复用
                         │
                         └─ q 仍存旧地址,成为"定时炸弹"
                            下次 new 可能恰好分到同块内存,
                            通过 q 改写 = 破坏新对象,极难复现

防范方法

  • delete 后立刻置空delete p; p=nullptr;(治标)。
  • unique_ptr 释放后不再访问;需要观察者用 weak_ptr ,访问前 lock()/expired() 检测对象是否还活着。
  • 警惕返回局部变量地址 (栈对象随函数返回销毁,指针立刻悬空)和引用/迭代器失效(vector 扩容后旧迭代器全部悬空)。
  • 编译器/静态分析(clang-tidy、-Wuninitialized)和 ASan 能抓出大部分未初始化与 use-after-free。

补充辨析

  • nullptr 与 NULL 的区别 :NULL 在 C++ 里本质是整数 0,参与函数重载时会匹配到 f(int) 而非 f(int*),造成歧义;nullptr 有独立类型 std::nullptr_t,能正确匹配指针重载,现代 C++ 一律用 nullptr。
  • 为什么空指针崩溃反而是"好事":解引用 nullptr 通常立刻在固定地址触发段错误,堆栈明确、必现好查;悬空指针则可能读到/写到"看似合法"的旧内存,表现为偶发数据错乱,是线上最难复现的 bug 之一。
  • 悬空指针三大来源:delete 后未置空、返回局部变量/临时对象的地址、容器扩容或元素删除后旧引用/迭代器失效。
  • 最佳实践 :初始化即指向有效对象或 nullptr;释放后立即置空或让智能指针直接离开作用域;跨作用域观察对象用 weak_ptr 并先 expired()/lock() 判断,绝不持有裸引用长期使用。

13. 智能指针的作用与缺点

一句话:智能指针解决了释放问题,但不是银弹------shared_ptr 有循环引用、原子计数开销和控制块成本,用错反而制造更隐蔽的 bug。(本题专讲缺点,作用见第 10 题。)

缺点一:shared_ptr 循环引用导致逻辑泄漏

复制代码
#include <memory>
struct Node { std::shared_ptr<Node> next; };
auto a=std::make_shared<Node>(), b=std::make_shared<Node>();
a->next=b; b->next=a;   // 互相持有,离开作用域计数仍为1 -> 泄漏
// 修复:一方改用 weak_ptr,使用时 lock() 提升

a ─shared─▶ b
a ◀─shared─ b     环上计数永不归0
改为:a ◀─weak─ b  weak 不增计数,环被打破

缺点二:性能与内存开销

  • 每次拷贝/析构 shared_ptr 都要原子增减引用计数,高并发热路径上是真实开销;控制块与对象分离时还多一次间接访问。
  • shared_ptr 体积是裸指针两倍(对象指针 + 控制块指针)。
  • 线程安全有边界:引用计数本身线程安全,但所指对象不是,多线程读写同一对象仍需加锁。

缺点三:误用陷阱

  • 同一裸指针构造两个 shared_ptr,生成两个独立控制块,对象被 delete 两次。
  • 类内部把 this 交给 shared_ptr 会重复管理,需继承 enable_shared_from_this
  • 滥用 shared_ptr 会让对象生命周期"看似共享实则失控",本该随请求结束的对象被某个长生命周期容器意外续命。
  • unique_ptr 虽零开销,但不能拷贝,跨所有权转移必须 move,Pimpl 场景需在 .cpp 显式处理析构。

结论 :默认 unique_ptr,shared_ptr 只在真正共享时用且必查循环引用,函数传参优先 const T& 而非按值拷贝 shared_ptr。

补充:容易被忽略的代价

  • weak_ptr 也有成本 :它要访问同一个控制块,判断 expired()lock() 都涉及对弱引用计数的原子操作,且 lock() 在并发下可能失败,必须检查返回值。
  • "线程安全"的常见误解 :多个线程拷贝/销毁不同的 shared_ptr 实例(即使指向同一对象)是安全的;但多线程同时读写同一个 shared_ptr 实例仍需加锁或用 atomic_shared_ptr;而所指对象的数据竞争与智能指针无关,照样要加锁。
  • 别名构造(aliasing constructor)shared_ptr<Member>(owner, memberPtr) 可让一个 shared_ptr 指向对象的成员却共享整块对象的所有权,用不好会延长父对象生命或制造悬空,属于高级且易错特性。
  • 不是用了智能指针就不泄漏:全局/静态 shared_ptr 长期持有、被无界容器缓存、循环引用,都会让对象"合法地"一直活着,这种逻辑泄漏比裸 new 更隐蔽,工具未必报警。
  • 调试难度:引用计数分散在各处,对象到底被谁持有、为何迟迟不析构,追踪起来比单一 delete 点更费劲,必要时要在控制块或析构函数打点观察。

14. 运行时内存分区:数据区包括哪些部分

一句话:程序运行内存分"代码区 + 数据区",数据区再细分为静态区、堆、栈、常量区和内存映射区------判断数据在哪,看它的生命周期由谁掌控。

总体布局

复制代码
高地址
┌────────────┐
│ 栈 ↓        │  局部变量/参数/返回地址,自动管理
│   (空闲)    │
│ 堆 ↑        │  malloc/new,手动管理
├────────────┤
│ 内存映射区   │  mmap、动态库 .so、大块映射
├────────────┤
│ 数据区       │  .data 已初始化全局/静态
│            │  .bss  未初始化全局/静态(启动清零)
│ 常量区.rodata│  字符串字面量、const 常量
├────────────┤
│ 代码区.text  │  机器指令,只读可执行
低地址

数据区各部分对照

分区 存什么 生命周期 管理
局部变量、函数参数、返回地址 函数/作用域 编译器自动
动态分配数据 手动决定 new/delete
.data 初始化了的全局/静态变量 整个进程 OS
.bss 未初始化或置0的全局/静态 整个进程 OS,启动清零
.rodata 字符串常量、静态 const 整个进程 OS,只读
映射区 mmap 文件/匿名页、动态库 手动映射 OS/mmap

代码验证地址高低

复制代码
#include <cstdio>
int data_v=1; int bss_v; const int ro=3;
int main(){
    int stack_v; static int static_v;
    int* heap_v=new int;
    printf("text函数地址 %p\n",(void*)&main);
    printf("rodata     %p\n",(void*)&ro);
    printf(".data      %p\n",(void*)&data_v);
    printf(".bss       %p %p\n",(void*)&bss_v,(void*)&static_v);
    printf("heap       %p\n",(void*)heap_v);
    printf("stack      %p\n",(void*)&stack_v);
    delete heap_v;
}
// 地址依次:代码/常量/静态最低,堆居中,栈最高,与布局图吻合

易错点:static 局部变量虽写在函数内却在 .data/.bss,不在栈;字符串字面量在 .rodata 不可写;指针变量本身在栈,它指向的动态对象在堆。

补充理解

  • 为什么要分区:不同数据的生命周期、读写权限、增长方式完全不同------代码要只读可执行且多进程共享,栈要自动快速伸缩,堆要灵活手动分配。分区让操作系统能用页表分别设置权限(NX 栈、只读代码段),兼顾安全与效率。
  • 栈与堆相向而生:栈从高地址向下、堆从低地址向上,中间留出大块虚拟地址空间按需使用,二者不会预先占满物理内存(用到才映射物理页)。
  • 现代保护机制:栈溢出保护(canary)、ASLR 地址随机化、只读重定位(RELRO)都建立在清晰分区的基础上;理解分区也是理解缓冲区溢出攻击与防护的前提。
  • 常量折叠:相同字符串字面量可能被编译器合并到同一 .rodata 地址,所以不要依赖两个字符串字面量地址不同。
  • 一句话答题框架:代码区放指令;数据区按生命周期分三类------全程存在的静态/常量区、自动回收的栈、手动管理的堆,外加 mmap 映射区。

15. C++ 内存泄漏(模式、检测与实操)

一句话:C++ 泄漏的特殊之处在于"配对、异常、数组、循环引用"四类坑,检测靠 ASan/valgrind,根治靠 RAII 与智能指针。(本题侧重 C++ 特有模式与工具实操,通用原理见第 11 题。)

C++ 四类典型泄漏

复制代码
// ① 配对错误:new[] 用 delete(UB,可能只析构一个)
auto p = new int[10]; delete p;        // 错:应 delete[] p

// ② 异常路径泄漏
void f(){
    auto* q=new Object;
    may_throw();                       // 抛异常则下面 delete 不可达
    delete q;
}

// ③ 所有权不清:抛给别人却没人接
Object* make(){ return new Object; }   // 调用方忘记 delete

// ④ 智能指针循环引用(逻辑泄漏,内存工具未必报"未释放")
struct X{ std::shared_ptr<X> p; };     // 见第13题

检测工具实操

复制代码
# AddressSanitizer(推荐:快、准、带堆栈)
g++ -fsanitize=address -g leak.cpp -o leak && ./leak
# 输出会直接指出 "detected memory leak" 及分配调用栈

# valgrind(不需特殊编译,但慢几倍)
g++ -g leak.cpp -o leak
valgrind --leak-check=full --show-leak-kinds=all ./leak
# definitely lost 才是真泄漏;still reachable 多为退出时未释放的全局缓存
工具 优点 局限
AddressSanitizer 快、定位精确、还能抓越界/UAF 需重编译、内存开销大
valgrind 不用重编译、信息全 慢,不适合实时/嵌入式
VS 诊断/堆快照 Windows 图形化、可对比快照 平台受限
重载 new/delete 记账 嵌入式无工具环境可用 只能统计、信息有限

根治原则

  • 一律 RAII:std::unique_ptr/std::shared_ptr + make_unique/make_shared
  • 接口即契约:函数返回堆对象时直接返回 unique_ptr,把"谁释放"写进类型。
  • 资源(文件、锁、socket)也用智能指针+自定义删除器或专门的 guard 类管理。
  • Code review 重点盯:裸 new、裸 delete、异常分支、容器无限增长、shared_ptr 环。

补充:线上与第三方场景

  • 为什么测试没泄漏、线上却 OOM:泄漏常藏在低频错误分支(某个异常路径、某个罕见请求),测试覆盖不到却在长期运行中累积;所以服务要靠长时间压测和线上 RSS 监控曲线发现,而不是只跑单测。
  • 第三方库的所有权约定 :很多泄漏来自没看清文档------get() 出的指针是否归你释放、回调是否会持有对象、工厂返回的是借用指针还是所有权;跨库边界尤其要统一"谁分配谁释放"(必要时用分配器/删除器配对)。
  • 句柄类泄漏更隐蔽:文件描述符、socket、锁、GPU 显存、数据库连接不是堆内存,却同样会"泄漏"耗尽,RAII guard 与智能指针思路完全适用。
  • 显存泄漏(模型服务高发):CUDA/推理框架的 tensor、cache 若被长生命周期对象引用就不释放,表现为显存单调上涨直到 OOM,排查要结合框架自身的内存分析工具。
  • 预防优于检测:代码评审守住"资源即对象"原则、CI 集成 ASan、服务接入内存监控告警,比线上崩溃后回查代价低得多。

16. C++ 智能指针在模型服务中的选择

一句话:模型服务里"模型对象大而只读、多请求共享"用 shared_ptr 常驻,"每请求上下文"用 unique_ptr 独占,"缓存"用 weak_ptr 防续命,热路径避免 shared_ptr 原子开销。

典型模型推理服务的对象分层

复制代码
┌─────────────────────────────────────────────┐
│ ModelEngine(单例,进程生命周期)              │
│   shared_ptr<const TransformerModel> model_  │ ← 大、只读、多线程共享
│   shared_ptr<const Tokenizer>      tokenizer│
├─────────────────────────────────────────────┤
│ 每个推理请求(线程池 worker 处理)             │
│   unique_ptr<RequestContext> ctx            │ ← 请求独占,结束即释放
│   shared_ptr<KVCache> cache  (请求内多模块共享)│
├─────────────────────────────────────────────┤
│ LRU 缓存:map<key, weak_ptr<CachedResult>>   │ ← weak 不阻止淘汰
└─────────────────────────────────────────────┘

选择原则与代码

复制代码
// ① 模型:加载昂贵、只读、被所有请求共享 -> shared_ptr<const T>
class Engine {
    std::shared_ptr<const Model> model_;          // const 表明只读、线程安全共享
public:
    void reload() {                              // 热更新:构建新模型后原子替换
        auto nm = std::make_shared<const Model>(load_new());
        std::atomic_store(&model_, nm);          // 旧模型等在用的请求结束自动释放
    }
    Response infer(const Req& r) {
        auto m = std::atomic_load(&model_);      // 拿到一份共享所有权,防止推理中被换
        return m->run(r);
    }
};

// ② 请求上下文:一次请求独占 -> unique_ptr,零开销、结束自动回收
std::unique_ptr<RequestContext>
handle(std::unique_ptr<RequestContext> ctx) {    // 所有权 move 进来
    ctx->kv = std::make_shared<KVCache>();       // 请求内部多阶段共享才用 shared
    run_preprocess(*ctx); run_decode(*ctx);
    return ctx;                                  // 随响应结束自动释放
}

// ③ 结果缓存:weak_ptr 不阻止内存被回收,实现"内存够就命中,不够就重算"
std::unordered_map<Key, std::weak_ptr<Result>> cache_;

工程要点与追问

  • 为什么模型用 shared_ptr<const> const 从类型上保证多线程只读无需加锁;shared 让"正在用旧模型的请求"在热更新后仍安全,最后一个请求结束旧模型才析构,实现无锁平滑切换。
  • 热路径别按值传 shared_ptr :每次拷贝都是一次原子操作,高 QPS 下累积可观。函数内只读用 const T& 或裸观察指针 T*(调用期对象必活),只有需要"延长生命周期"才拷贝一份 shared_ptr。
  • 显存/内存视角:模型权重常以 GB 计,绝不能随请求复制,必须共享同一只读副本;请求级 KV cache 则要确保 unique_ptr 及时释放,避免并发请求堆积 OOM------这是大模型服务最常见的 OOM 原因。
  • 缓存用 weak_ptr :缓存不应成为对象存活的理由,weak_ptr 让 GC/淘汰策略掌握主动权,命中时 lock() 成功才用,失败则重新计算。
  • 选型口诀:独占用 unique,共享才 shared,观察/缓存用 weak,热路径传引用;大对象共享只读、小对象随请求生灭。
相关推荐
吴声子夜歌16 分钟前
Java——类、对象及方法(一)
java·开发语言
阿里嘎多学长25 分钟前
2026-08-29 GitHub 热点项目精选
开发语言·程序员·github·代码托管
DLite27 分钟前
开源一个静态类型语言——NLang
c++·编译器·nlang
এ慕ོ冬℘゜1 小时前
JavaScript学习心得:从只会语法,到真正会写业务逻辑
开发语言·javascript·ecmascript
uoKent1 小时前
c++中new和malloc的区别
java·jvm·c++
2601_966949651 小时前
五档盘口数据对量化交易有什么价值?从信号判断到策略风险的完整分析
开发语言·人工智能·python·数据分析·量化·股票数据·quantdash
会周易的程序员1 小时前
aiDgeController 软PLC虚拟机stvm集成测试报告
c++·物联网·架构·集成测试·st·软plc·iec61131
小吴学不废Java1 小时前
Python 基础语法
开发语言·python
布莱克6052 小时前
队列详解:定义、分类、应用场景及与栈的区别(C/C++ 代码讲解)
c语言·开发语言·c++·队列