因为C++兼容C语言,所以C语言中内存管理的在C++中可以继续使用 ,并且C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理
开辟1个int类型的空间
int* p1 = (int*)malloc(sizeof(int)); --C语言
int* p2 = new int; --C++
free(p1);
delete p2;
开辟10个int类型的空间
int* p1 = (int*)malloc(sizeof(int) * 10); --C语言
int* p2 = new int[10]; --C++
free(p1);
delete[] p2;
malloc/new和free/delete 针对内置类型没有任何差别,只是用法不同
#include
using namespace std;
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _val;
ListNode(int val = 0)
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{
cout << "ListNode(int val = 0)" << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
//C语言 malloc 只是开空间
ListNode* node1 = (ListNode*)malloc(sizeof(ListNode));
//C++ new 开空间,调用构造函数初始化
ListNode* node2 = new ListNode(5);
//单纯释放空间
free(node1);
//析构函数清理+释放空间
delete(node2);
return 0;
}
我们发现使用new和delete来构建对象不仅可以开辟空间还可以调用构造函数对对象进行初始化,释放空间的同时还可以调用析构函数清理自定义类型。
建议在C++中,无论内置还是自定义类型的申请释放,尽量使用new和delete
在C++11中新增数组自定义类型的赋值
ListNode* arr_node = new ListNode[4]{ 1,2,3,4 }; --可以通过大括号初始化
delete[] arr_node;
ListNode *p = new ListNode(1);
new = operator new + ListNode构造函数
operator new = malloc + 失败抛异常机制
使用方法:
ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
free(p1);
//它的用法和malloc完全一样,功能都是堆上申请释放空间
ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));
operator delete(p2);
区别:
失败后处理方式不一样,malloc失败返回NULL,但是operator new失败后抛异常
malloc:
void* p3 = malloc(0xfffffffff);
if (p3 == NULL)
{
cout << "malloc fail" << endl;
}
operator new:
try
{
void* p4 = operator new(0xfffffffff);
}
catch (exception& e)
{
cout << e.what() << endl;
}
placement - new(定位new)
将开空间和调用初始化分离,完成先开空间后初始化的实现过程
//等价于直接使用 ListNode* n = new ListNode(3)
ListNode* n = (ListNode*)operator new(sizeof(ListNode));
new(n)ListNode(3); //定位new, placement-new ,显示调用构造函数
//相当于 delete n
n->~ListNode();// 析构函数可以直接显示调用
operator delete(n);
相同:
1.malloc/free和new/delete都是在堆上申请空间,并且需要用户手动释放
不同:
1.malloc和free是函数, new和delete是操作符
2.malloc申请的空间不会初始化,new可以初始化
3.malloc申请空间需要手动计算空间大小并传递, new只需要在后面跟上空间类型即可,如果是多对象同时申请[ ]还可以指定个数
4.malloc返回值是void* ,在使用时必须强制类型转换,new不需要,因为new后面跟的就是类型
5.malloc申请失败,返回NULL,使用需要判断是否为空,new不需要,但是new需要捕获异常
6.申请自定义类型对象时,malloc/free’只会开辟/释放空间,不会调用构造函数和析构函数,而new在申请空间后会调用析构函数,delete释放空间前,会调用析构函数对自定义类型对象进行清理
1.malloc、new申请空间本质是将一块内存的使用权交给你
2.当你不需要这块空间了,通过free,delete 释放内存空间,将使用权还给操作系统。操作系统可以再分配给别人
3.进程正常结束,没有释放的内存也会被释放掉。所以一般程序内存泄漏也还好,危害并不是很大
4.长期运行的程序,如服务器上的程序,如果王者荣耀后台,美团后台服务,滴滴后台服务存在内存泄漏,那么服务器的运行过程中,操作系统可分配的内存会越来越少,无用内存会越来越多,导致服务器变卡,变慢最终挂掉
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = a;
}
void Swap(double& a, double& b)
{
double tmp = a;
a = b;
b = a;
}
当我们想要写一个交换值的Swap()函数时,发现只要参数的类型不相同,我们就必须再次构建一个逻辑相同的Swap()函数,构成函数重载,不断重复相似的步骤,会浪费很多时间,所以C++中给我们开设了一个功能,模板
函数模板
定义1:template<class T> //T Type
定义2:tmeplate<typename T> //定义模板参数,可以用typename,也可以用class
void Swap(T& x1, T& x2)
{
T tmp = x1;
x1 = x2;
x2 = tmp;
}
当然我们也可以定义多个模板参数
例:template<typename T1, typename T2>
但是我们两次调用的Swap函数,是否是同一个函数呢
我们发现两个函数的地址是不一样的,所以调用的函数并不相同
当我们在调用模板函数时,编译器会通过实参的类型推演出形参的类型(T)是double,int,char等
然后编译器会通过推演出的类型进行函数构建,生成对应的重载函数
本质上:本来应该由我们写的代码,但是因为我们不想重复,所以我们给编译器一个模板,编译器帮我们写
template <class T>
T Add(T& x1 ,T& x2)
{
return x1 + x2;
}
int main()
{
int a = 1;
int b = 2;
double c = 1.1;
double d = 2.2;
Swap(a, b);
Swap(c, d);
cout << Add(a, b) << endl; --成功 --隐式指定类型
cout << Add(c, d) << endl; --成功 --隐式制定类型
cout << a + c << endl; --成功
cout << Add(a, c) << endl; --报错
return 0;
}
我们发现Add(a, c)发生了报错,原因是因为根据实参生成的两个T不同,在传输给编译器时,编译器分不清楚T应该生成哪一种类型。所以无法生成对应有效函数。我们可以通过显示制定类型来解决报错
cout << Add<int>(a, b) << endl;
cout << Add<double>(c, d) << endl;
在函数名后加上<类型>,编译器就会通过我们指定的类型生成对应函数来进行调用,我们输入的实参也会根据形参转换成形参的格式进行计算
普通函数和模板函数可以同时存在,若正常调用会优先普通函数,若加上显示制定实例化,或者普通函数无法完成计算,会调用模板函数
可以想象成编译器也很懒,有现成的就用现成的,有现成的匹配函数就绝不去自己生成
typedef int VDateType; //使用typedef来改变值的类型
class vector
{
public:
private:
VDateType *a;
int _size;
int _capacity;
};
//虽然这样我们也可以完成类的类型转换但是却解决不了以下情况
int main()
{
vector v1; //int
vector v2 //double
//我们不能运用一个类的定义,生成两个类型不同的相似类
}
使用模板定义模板类
template<class T> //定义模板参数
class vector
{
public:
private:
T* a;
int _size;
int _capacity;
};
int main()
{
vector<int> v1; --显示制定类型
vector<double> v2;
return 0;
}
接下来我们就来完善一下我们的模板类
namespace clx //使用命名空间,防止我们的实验于stl中的vector产生冲突
{
template <typename T>
class vector
{
public:
//构造函数
vector()
:_a(nullptr)
, _size(0)
, _capacity(0)
{}
//尾插
void push_back(T val)
{
if (_capacity == _size)
{
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity; //若空间不够进行扩容
T* tmp = new T[newcapacity];
if (tmp)
{
memcpy(tmp, _a, sizeof(T) * _size); //将原来的_a数据拷向我们的新空间
delete[] _a; //释放我们原来的_a空间
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size] = val;
_size++;
}
//[]的运算符重载,可以快速访问顺序表中存储的数据
T& operator[](int pos)
{
if (pos < _size)
{
return _a[pos];
}
else
{
cout << "访问越界数据" << endl;
}
}
//析构函数
~vector()
{
delete[] _a;
_size = _capacity = 0;
}
private:
T* _a;
int _size;
int _capacity;
};
}
int main()
{
clx:: vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
> 这里是引用
v1.push_back(4);
v1.push_back(5);
cout << v1[2] << endl;
clx::vector<double> v2;
v2.push_back(1.1);
v2.push_back(2.2);
v2.push_back(3.3);
v2.push_back(4.4);
v2.push_back(5.5);
cout << v2[3] << endl;
return 0;
}
1.这个模板类比较简单,就不采用声明定义分离了。但是这种直接在类中定义和声明的函数,我们的编译器会默认把它当成内联函数,在调用的时候会自动展开。
2.模板类不支持分离编译,也就是不要将模板类定义在.h,声明卸载.cpp中
但是定义和声明时还会出现一点小问题,接下来我们来讲一讲
//声明
T& operator[](int pos);
//定义
template <class T>
T& clx::vector<T>::operator[](int pos) //模板类必须得带上
{
{
if (pos < _size)
{
return _a[pos];
}
else
{
cout << "访问越界数据" << endl;
}
}
}
1.再类外面,编译器就不认识T了,所以我们要将T在函数体前面重新声明一边,告诉编译器T是模板的参数类型
2.命名空间和类都要写,因为是模板类必须将形参也写上类名<类型>
3. 2.模板类不支持分离编译,也就是不要将模板类定义在.h,声明卸载.cpp中,应该全部编写.h文件中
以上即是【初识C++】3.1内存管理和模板的全部内容,接下来我们将进入STL库的学习。如果这篇文章对你有帮助,请点一个赞吧
上一篇【初识C++】2.3类与对象(下)