C++ 进阶核心知识点总结:模板、内存分区、new/delete、内存池

前言:本篇是 C++ 类与对象之后非常核心的底层内容,很多考研 408、C++ 面试都会高频考察,结合汇编、内存示意图帮你一次性吃透。 阅读对象:正在学习 C++,准备考研 / 面试,想要理解底层原理的同学

一、函数模板(模板实例化)

1. 什么是函数模板

函数模板是通用函数蓝图 ,不是真实函数。编译器根据你调用时传入的类型,自动实例化生成对应类型的函数。

cpp 复制代码
template<typename T>
void Swap(T& left, T& right)
{
    T tmp = left;
    left = right;
    right = tmp;
}

template<typename T:告诉编译器,T 是一个类型参数,代表任意类型。

2. 模板实例化(重点!结合汇编代码)

cpp 复制代码
int i = 10, j = 20;
Swap(i, j);   // 推导T=int,编译器生成 Swap<int>版本

double x = 10.1, y = 20.2;
Swap(x, y);   // 推导T=double,编译器生成 Swap<double>版本

看汇编截图:

✅ 核心结论:

  1. 模板本身不生成代码;调用不同类型,编译器才会生成多份重载函数,这个过程叫模板实例化。
  2. 不同类型,实例化出独立函数,地址不同。
  3. 模板支持类型推导,不用手动写Swap<int>(i,j),编译器自动识别。
    坑点: 问:模板会在编译期生成几份代码? 答:每一种不同的类型,就实例化一份函数 ;相同类型多次调用,只生成一份。
来这里提问一个问题,我可不可以先写一个自己的函数然后再写一个模板,那么编译器优先调用自己写的还是模板呀?
cpp 复制代码
void swap(int& a,int& b)
{
    int c = a;
    a = b;
    b = c;
}

template<class T>

void swap(T& a,T& b)
{
    T c = a;
    a = b;
    b = c;
}


int main()
{

    int a = 10;
    int b = 20;
    swap(a,b);
    return 0;
}

注意:C++ 重载决议规则:优先匹配非模板的普通函数;只有找不到合适普通函数时,才会去实例化函数模板。

调用 swap(a,b),实参是int&,正好和上面普通函数的形参完全匹配,直接选普通函数,模板不会被实例化

举个例子:你着急去买酱油,一家是楼下的超市,另一家是市里面的大超市,楼下的超市本来就有,直接买上用就好,不会直接去跑上几十公里去市里买,道理是一样的。


二、C/C++ 程序内存分区(对应内存分布图)

内存五大区域

  1. 栈区(A) :stack 存储:局部变量、函数参数、数组、临时对象。 特点:向下生长,自动申请自动释放,函数出作用域自动销毁。
  2. 堆区(B) :heap 存储:malloc / calloc / realloc / new手动开辟的空间。 特点:向上生长,手动申请、手动释放,忘记释放会内存泄漏。
  3. 数据段(静态区 C) 存储:全局变量、static 静态变量(全局静态、局部 static),程序启动就存在,程序结束才销毁。
  4. 代码段(常量区 D) 存储:可执行代码、只读常量字符串,只读,不能修改。
  5. 内核空间:操作系统使用,用户代码无法访问。

代码逐行解析

cpp 复制代码
int globalVar = 1;                // 全局变量 → C 数据段
static int staticGlobalVar = 1;   // 全局静态 → C 数据段
void Test()
{
    static int staticVar = 1;      // 局部static静态变量 → C 数据段,只初始化一次
    int localVar = 1;              // 局部变量 → A 栈区
    int num1[10] = { 1,2,3,4 };    // 局部数组 → A 栈区
    char char2[] = "abcd";         // 数组:栈开辟空间,把常量字符串拷贝进栈 → char2在A栈;
    const char* pChar3 = "abcd";   // pChar3指针变量本身在A栈;"abcd"字符串字面量在D常量区

    int* ptr1 = (int*)malloc(sizeof(int)*4); // ptr1指针变量A栈;*ptr1指向B堆
    int* ptr2 = (int*)calloc(4, sizeof(int));
    int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
    free(ptr1);
    free(ptr3);
}

✅ 高频考点区分:char char2[] 和 const char* pChar3

  • char char2[] = "abcd":在栈上创建数组,把常量区的 abcd 拷贝一份到栈数组,可以修改数组内容。
  • const char* pChar3 = "abcd":指针存栈,字符串在只读常量区,不能修改*pChar3。

三、C++ new 和 delete(底层执行流程)

我们之前的在类的那一部分已经详细学习过了,这里就是简简单单的的复习一遍

1. 单个对象 new /delete

cpp 复制代码
Date* p1 = new Date;
delete p1;

执行顺序:

  1. operator new:底层调用 malloc,在堆上开辟原始内存空间(只开空间,不初始化)
  2. 调用构造函数:在刚才开辟的内存上,初始化成员变量

new = 开堆内存 + 调用构造函数

delete 执行顺序:

  1. 调用析构函数:清理对象内部资源
  2. operator delete:底层调用 free,释放堆内存

delete = 调用析构函数 + 释放堆空间
对比 C 语言 malloc/free:malloc 只开辟原始内存,不会调用构造、析构!这是 C++ new 最大区别。

2. 数组 new \[\] /delete \[\] 重点(容易踩坑)

复制代码
Stack* p = new Stack[10];
delete[] p;

底层机制: new[]开辟空间时,会多开辟一块空间,保存数组对象的个数(图里的数字 10)

  • 作用:delete[]读取这个数字,知道要调用多少次析构函数!
    ⚠️ 硬性规则:new \[\] 必须搭配 delete \[\],new 单个对象搭配 delete,不能混用。 如果混用,程序行为未定义,大概率崩溃。

示意图解释: p 指针指向第一个对象,但是往前偏移 4/8 字节,存放数组元素计数。delete \[\] 读取这个数字,循环调用 10 次析构,再整体释放整块堆内存。

面试题:为什么 new \[\] 和 delete \[\] 不能和普通 delete 混用? 答:普通 delete 不会读取前面计数,只会调用一次析构,数组多个对象没有全部析构,资源泄漏,释放内存时地址错误,程序崩溃。


四、池化技术:内存池(你的项目核心!复试重点)

1. 为什么要有内存池?

直接频繁调用new/malloc向操作系统申请堆内存,有缺点:

  1. 系统调用开销大,频繁申请释放速度慢
  2. 频繁小块内存申请,造成内存碎片,内存利用率下降

池化思想:一次性向操作系统申请一大块连续内存,放到池子里面。 后续程序需要小块内存,直接从池子里面拿,不频繁向操作系统申请;释放的时候归还到池子,而不是直接还给操作系统。

通俗比喻:

妈妈 = 操作系统,取钱就是 malloc。 儿子频繁用钱(高频申请内存),每次都找妈妈取钱很麻烦。 爸爸一次性向妈妈拿 20 块钱,交给儿子,儿子用钱直接从这 20 块里面拿,用完放回,不用反复找妈妈。这就是内存池。

同类池化技术:

  • 内存池:管理内存分配释放
  • 线程池:预先创建一批线程,复用线程,避免频繁创建销毁线程
  • 连接池:数据库连接复用

2. 内存池优缺点

✅ 优点:

  1. 减少系统调用,分配内存速度更快
  2. 减少内存碎片,提升内存利用率

❌ 缺点:

1.一次性预先开辟大块内存,程序启动就占用内存,哪怕暂时不用。


全文知识点汇总:

  1. 函数模板 模板是蓝图,编译期根据调用类型,实例化生成对应类型函数 ;不同类型生成不同函数。汇编可以看到实例化后的函数符号Swap<int> Swap<double>。
  2. 内存分区
  • 栈:局部变量,自动销毁,向下生长
  • 堆:new/malloc 手动开辟,手动释放
  • 静态区:全局、static 变量,程序全程存在
  • 常量区:字符串字面量,只读
  1. new/delete new = operator new(malloc 开空间) + 构造函数 delete = 析构函数 + operator delete(free 释放) new \[\] 会额外保存对象数量,用来 delete \[\] 调用多次析构;new \[\] 和 delete \[\] 必须配对。
  2. 内存池(池化技术) 一次性向 OS 申请大块内存,程序从池子取小块内存,减少系统调用,减少内存碎片;适合高频申请释放内存场景。同类:线程池、连接池
相关推荐
Escalating_xu2 小时前
【C 语言】深入理解指针(1·下):指针运算、野指针、assert 与传址实战
java·c语言·开发语言
实心儿儿2 小时前
Qt — Qt 事件
开发语言·qt
周杰偷奶茶3 小时前
【Java】数据类型与变量
java·开发语言
code斗3 小时前
Java数据结构:堆详解
java·开发语言·数据结构
Frank_refuel3 小时前
【C++】Json库介绍和使用
c++
重生之小比特3 小时前
【C++进阶】map和set
java·开发语言·c++
lie..3 小时前
30天从零开始学AI应用开发(Day 12):像产品一样稳:流式输出、报错重试、花钱控制
开发语言·人工智能·php
傻啦嘿哟3 小时前
Python的默认参数把我坑惨了,原来写[]和写None的区别这么大
开发语言·python·机器学习
DeepAgent4 小时前
AI Agent 工程实践(49):一次真实优化——从 Agent v1 到 v2
开发语言·人工智能·agent