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

模板初阶和C++内存管理

一. 函数模板

1.1 泛型编程

模板是C++语言中很重要的一部分,再介绍模板时候我们先来引入之前学过的交换函数:

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}
void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}
void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}

如上面的代码所示,仅仅是类型不一样但是我们却要写三个很相似的函数,其实操作是比较麻烦的,这时候就引入了模板,就是为了方便类似这样的函数,有一个模板可以套用的话,是不是会简单很多?这时候可能大家会想到使用函数重载,使用函数重载虽然可以实现,但是有一下几个不好的地方: 1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数 2. 代码的可维护性比较低,一个出错可能所有的重载均出错

1.2 函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生
函数的特定类型版本。注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器,在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

1.3 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化
和显式实例化。
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型

2. 显式实例化:在函数名后的<>中指定模板参数的实际类型 

int main(void)
{
int a = 10;
double b = 20.0;
// 显式实例化
Add<int>(a, b);
return 0;
}

二. 类模板

类模板的定义格式:

template<class T1, class T2, ..., class Tn>
class 类模板名
{
     // 类内成员定义
};

// Stack是类名,Stack<int>才是类型
Stack<int> st1; // int
Stack<double> st2; // double

同时在声明Push函数的时候我们不能直接使用Stack类名来当作类型,必须加上模板参数<T>,这一点需要自己额外记住。 

三. C++内存管理

3.1 内存分布

在学习C++的同时,掌握好它的内存空间是如何分配也是非常重要的,整个空间分为栈、堆、静态区(数据段)、常量区(代码段)

  • 栈(Stack):存储函数的局部变量、函数参数、返回地址等,生命周期随函数调用结束而销毁。
  • 堆(Heap):动态分配的内存(malloc/calloc/realloc 申请的内存),需要手动 free 释放,生命周期由程序员控制。
  • 数据段(静态区,Data Segment):存储全局变量和静态变量(static 修饰的变量),程序运行期间一直存在。
  • 代码段(常量区,Code Segment):存储程序的代码和只读常量(如字符串字面量 "abcd"),内容不可修改。
#include <algorithm>
using namespace std;
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{static int staticVar = 1;int localVar = 1;int num1[10] = { 1, 2, 3, 4 };char char2[] = "abcd";const char* pChar3 = "abcd";int* ptr1 = (int*)malloc(sizeof(int) * 4);int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}

对于上面的代码,大家能够准确的说出每一个变量都存储在哪里吗?

 3.2 C语言中动态内存管理方式:malloc/calloc/realloc/free 

void Test ()
{
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}

在回答代码中问题的时候,我先为大家解释清楚malloc、calloc、realloc的区别以及各自的作用。首先三者的作用分别是:malloc的原型是void* malloc(size_t size),例如(int*)malloc(5*sizeof(int));指的是分配5个int大小的空间,特点是分配指定大小(字节)的内存块,不初始化内存(内容为随机值)calloc的原型是void* calloc(size_t num,size_t size),例如(int*) calloc(5,sizeof(int));指的是分配5个int大小的空间,特点是分配num个元素、每个元素大小为size的内存块,自动初始化为 0。realloc的原型是void* realloc(void* ptr,size_t num),例如(int*) realloc(ptr,10);指的是把内存扩展为10个int大小,特点是调整已分配内存块的大小(扩大或缩小),可能移动内存位置。若原内存后空间不足,会重新分配并复制原内容

我们来回答为什么第二个问题,上述代码中还需要free(p2)吗?答案是不需要当 realloc 成功扩大内存时,若原内存块后空间充足,会直接在原地址上扩展,此时 p3 与 p2 指向同一地址。若原内存后空间不足,realloc 会在新位置分配内存,并自动复制原内容,然后释放原内存块(即 p2 不再有效),此时 p3 指向新地址。

3.3 C++内存管理方式

1. C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理
//new和delete
void Test()
{//动态申请一个int类型的空间int* ptr4 = new int;//动态申请一个int类型的空间并初始化为10int* ptr5 = new int(10);// 动态申请10个int类型的空间int* ptr6 = new int[10];delete ptr4;delete ptr5;delete[] ptr6;
}

在使用的过程中要注意如果是单独申请和释放的空间,使用new和delete操作符。申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

2.  new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数。

#include <iostream>
using namespace std;
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数A* p1 = (A*)malloc(sizeof(A));A* p2 = new A(1);free(p1);delete p2;//内置类型是几乎是一样的int* p3 = (int*)malloc(sizeof(int)); // Cint* p4 = new int;free(p3);delete p4;A* p5 = (A*)malloc(sizeof(A) * 10);A* p6 = new A[10];free(p5);delete[] p6;return 0;
}

 3.3.1operator new与operator delete函数

简单了解就是new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
http://www.xdnf.cn/news/15822.html

相关文章:

  • 功能安全之BIST的基本原理
  • 第7天 | openGauss中一个数据库中可以创建多个模式
  • 6月零售数据超预期引发市场波动:基于AI多因子模型的黄金价格解析
  • Axios泛型参数解析与使用指南
  • 当系统盘快满时,可以删除哪些数据
  • 排序【各种题型+对应LeetCode习题练习】
  • 如何阅读Spring源码
  • 【LVGL】Linux LVGL程序几十分钟后UI卡死
  • effective python 条款11 学会对序列做切片
  • Onload 用户指南 (UG1586)-笔记
  • 【机器学习】安装Jupyter及基本操作
  • 内存泄漏系列专题分析之二十九:高通相机CamX--Android通用GPU内存分配和释放原理
  • 虚拟商品自动化实践:闲鱼订单防漏发与模板化管理的技术解析
  • JVM常用运行时参数说明
  • 【C# in .NET】17. 探秘类成员-构造函数与析构函数:对象生命周期管理
  • [3-02-01].第01章:框架概述 - Spring生态
  • 基于Spring Boot的农村农产品销售系统设计与实现
  • 【Python】DRF核心组件详解:Mixin与Generic视图
  • ARINC818航空总线机载视频处理系统设计
  • 第二篇 html5和css3开发基础与应用
  • 28、鸿蒙Harmony Next开发:不依赖UI组件的全局气泡提示 (openPopup)和不依赖UI组件的全局菜单 (openMenu)、Toast
  • 数据结构入门:像整理收纳一样简单!
  • Jmeter系列(6)-测试计划
  • 李超线段树模板
  • Vue3 中使用 Element Plus 实现自定义按钮的 ElNotification 提示框
  • 「源力觉醒 创作者计划」_巅峰对话:文心 4.5 vs. DeepSeek / Qwen 3.0 深度解析(实战优化版)
  • Matlab打开慢、加载慢的解决办法
  • 构建直播平台大体的流程
  • 后端参数校验
  • Docker部署前后端分离项目——多项目共享环境部署