C++函数模板与内存管理详解

简介: C++函数模板与内存管理详解

情景引入:

现在假设我们要写一个swap函数交换两个值的大小,我们知道在之前讲过的函数重载是支持不同类型的参数重载的,因此我们只要写出int ,double ,char ,float ,bool的类型函数重载就行了,但是这个函数的功能差距不大,就因为类型不同我们就要写这么多,这显然不划算,因此C++里面提出了一种方法,支持模糊的类型匹配————模板



一:模板

我们前面知道模板支持模糊匹配,但是这并不代表模板的类型匹配不严格,相反模板里面是不允许类型的隐式转化(除非显示标注类型),从某种意义上来说,模板的类型匹配更加严格,那这是什么情况呢?看起来我们确实利用函数模板偷懒了很多,其实真正意义上并没有偷懒,只是我们偷的懒被系统做了而已。我们先看下面这个模板

template<class A,typename B>//类模板的定义
void swap(A& a,B& b){
A temp=a;             
a=b;
b=temp;
}          出了作用域若想要再次使用模板就需要重新定义,
            模板只管模板定义处的下一行或者下一个作用域

那么编译器到底是怎么帮我们偷懒的呢?在我们没有使用这个函数的时候是并没有这个函数的,当我们使用的时候编译器通过对类型的辨认如何形成这个函数,在通过这个函数去进行操作。

现在有这么一个函数的模板

template<typename B>
void swap(B& a,B& b){
B temp=a;             
a=b;
b=temp;
}

如果我们传一个这个参数过去会怎么样呢?

int a=1;
double b=0.0;
swap(a,b);

编译器会帮我们成功的交换吗?事实是编译器会报错,这是因为只有一个模板却有两个类型,有些情况就是这样的那我们怎么办呢?当然有办法,那就是使用的时候显示指定类型,如下

int a=1;
double b=0.0;
swap<int>(a,b);
swap<double>(a,b);

二:内存管理

1)内存分布

在C++中内存大概可以分为五块

向下生长的栈空间:栈是用来储存局部变量和函数的空间,因为使用的时候,地址是越来越小,所以称为向下生长,因为这一块空间用完了它就会向比它地址低的空间去申请,并且栈只支持静态增长。

向上生长的堆:堆是支持动态增长的,像malloc,calloc,realloc,new的空间都是在这里开辟的,这里的空间直到程序结束才会释放,因此需要我们手动释放开辟的空间,但要注意释放空间不能多次释放。使用这里的空间地址是越来越大的,因为这一块空间用完了它就会向比它地址高的空间去申请

内存映射段:内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

数据段:存储全局数据和静态数据

代码段:存储可执行代码和只读常量

2)new和delete

在C++中new和delete是类似与C里面的malloc和realloc还有calloc的开辟空间的手段

区别:new和delete是内置基本操作符,malloc等是函数,

new=开辟空间+调用构造函数

delete=调用析构函数+释放空间

malloc=开辟空间

free=释放空间

new,malloc,delete,free能混着用吗?

答案:不能,因为可能会造成内存泄漏,new的类里面可能还有在堆上开辟的空间,如果不调用析构函数释放会造成内存泄漏

有些人就要问了,为什么有了malloc和realloc和calloc还有发明new?

那我们想象一下,我们创建了一个类,我们使用malloc的方式给它开辟空间,我们该如果给它初始化呢?

这就暴露的malloc等的一个大弊端,malloc,calloc是无法给自定义类型初始化的,new应运而生,new支持对自定义类型的初始化。我们看代码,里面有从基本内置类型到类的new空间基本操作

int* a=new int;//只开辟空间,不初始化
int* a=new int(6);//给a开辟空间并且初始化为6
delete a;//释放空间
int* a=new int[6];//给a开辟一个数组大小为6
int* a=new int[6]{1,2,3,4,5,6};//给a开辟一个数组大小为6,并且全部赋值
int* a=new int[6];//数组里面全是随机值
int* a=new int[6]{};//数组里面全是0
delete[] a; //释放空间
Date a=new Date{2023,11,11};//日期类并且赋初值
delete a;释放空间
Date a[3]=new Date[3]{(2023,11,11),(2024,11,11),(2025,11,11)};//自定义类型数组赋初值
delete[] a;

看到上面,你有没有想为什么数组释放空间需要[],这个真的毫无用处吗?

错,首先我们想一下,编译器是如何判断这个数组有多大的空间,如果是你你会怎么做?编译器在开辟空间会多开四个字节,正好一个int大小,这个int用来储存数组的数量,通过这个int我们就可以准确的释放数组空间,这个int类型一般在开辟空间的前面,这样就可以快速准确拿到数组大小,释放空间,如果我们不使用[]这个,那么空间就会从中间被释放,导致报错。

3)简单介绍new和delete底层原理。

在new和delete的底层原理其实就是调用了被封装的malloc和free,malloc被封装在new的函数重载里面,free被封装在delete的函数重载里面,为什么要封装呢?这是因为malloc分配空间出错是直接返回0,而C++的要求的抛错,为了达到这个效果,必需封装一下malloc和free,并且不要忘记new里面会调用构造函数delete里面会调用析构函数。new的函数重载只负责开辟空间,delete的函数重载只负责释放空间,调用析构函数和构造函数还是在new运算符里面。

new=malloc+调用构造函数

delete=free+释放空间

malloc=开辟空间

free=释放空间

相关文章
|
3月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
127 26
|
3月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
242 15
|
4月前
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
60 1
|
4月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
123 0
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
92 0
|
7月前
|
存储 Linux C语言
C++/C的内存管理
本文主要讲解C++/C中的程序区域划分与内存管理方式。首先介绍程序区域,包括栈(存储局部变量等,向下增长)、堆(动态内存分配,向上分配)、数据段(存储静态和全局变量)及代码段(存放可执行代码)。接着探讨C++内存管理,new/delete操作符相比C语言的malloc/free更强大,支持对象构造与析构。还深入解析了new/delete的实现原理、定位new表达式以及二者与malloc/free的区别。最后附上一句鸡汤激励大家行动缓解焦虑。
|
7月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
7月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
353 6
|
8月前
|
安全 C++
【c++】模板详解(2)
本文深入探讨了C++模板的高级特性,包括非类型模板参数、模板特化和模板分离编译。通过具体代码示例,详细讲解了非类型参数的应用场景及其限制,函数模板和类模板的特化方式,以及分离编译时可能出现的链接错误及解决方案。最后总结了模板的优点如提高代码复用性和类型安全,以及缺点如增加编译时间和代码复杂度。通过本文的学习,读者可以进一步加深对C++模板的理解并灵活应用于实际编程中。
105 0
|
8月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。