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=释放空间

相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
15天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
43 4
|
1月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
18 1
|
1月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
41 0
【C++打怪之路Lv6】-- 内存管理
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
48 6
|
30天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
25 0
|
1月前
|
C++
C/C++内存管理(下)
C/C++内存管理(下)
50 0
|
1月前
|
存储 Linux C语言
C/C++内存管理(上)
C/C++内存管理(上)
39 0
|
1月前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
41 0