目录
[(1) 内置类型](#(1) 内置类型)
[(2) 自定义类型](#(2) 自定义类型)
[2.new, delete与malloc, free对比](#2.new, delete与malloc, free对比)
[(1) 内置类型](#(1) 内置类型)
[(2) 自定义类型](#(2) 自定义类型)
[4. 定位new表达式](#4. 定位new表达式)
[2. 基本使用](#2. 基本使用)
[(1) 函数模板](#(1) 函数模板)
[(2) 类模板](#(2) 类模板)
一、内存管理
C语言中对内存管理主要借助的是malloc,calloc,realloc,free这几个库函数
而C++中进行内存管理借助的是new 与 delete这两个库函数
new是用来动态申请内存空间的,相当于malloc或者calloc
delete是用来手动释放动态申请的内存空间的,相当于free
1.new与delete基本用法
(1) 内置类型
① 申请与释放动态申请单个元素的空间
cpp
#include<iostream>
int main()
{
//只是开空间
int* p1 = new int; //申请一个int大小的空间,返回该空间的起始地址
char* p2 = new char; //申请一个char大小的空间,返回该空间的起始地址
//开空间+初始化
int* p3 = new int(1); //申请一个int大小的空间,并初始化这块空间为1
char* p4 = new char('w'); //申请一个char大小的空间,并初始化这块空间为'w'
//销毁动态申请空间
delete p1;
delete p2;
delete p3;
delete p4;
}
② 申请与释放连续的空间
cpp
#include<iostream>
int main()
{
//只是开空间
int* p1 = new int[5]; //申请5个int大小的空间,返回该空间的起始地址
char* p2 = new char[5]; //申请5个char大小的空间,返回该空间的起始地址
//开空间+初始化
int* p3 = new int[10]{1, 2, 3, 4, 5}; //申请5个int大小的空间,并初始化为1, 2, 3, 4, 5
char* p4 = new char[5]{'a','b','c','d','e'}; //申请5个char大小的空间,并初始化这块空间为'a','b','c','d','e'
//销毁动态申请空间
delete[] p1;
delete[] p2;
delete[] p3;
delete[] p4;
}
ps: 初始化时, [ ]里面写的是个数, ()里面写的是初始化内容
(2) 自定义类型
① 申请与释放动态申请单个元素的空间
cpp
#include<iostream>
class A
{
public:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
int main()
{
//只是开空间
A* p1 = new A; //创建1个A类型大小的空间
//开空间+初始化
A aa1; //A类型创建出了aa1对象
A* p2 = new A(aa1); //用aa1对象初始化申请的一个A类型大小的空间
A* p3 = new A(A()); //匿名对象初始化申请的一个A类型大小的空间(用缺省值)
A* p3 = new A(A(1)); //匿名对象初始化申请的一个A类型大小的空间(传实参)
A* p4 = new A(1); //1隐式类型转换成A类型的数据去初始化一个A类型大小空间
//销毁动态内存空间
delete p1;
delete p2;
delete p3;
delete p4;
}
② 申请与释放连续的空间
cpp
#include<iostream>
class A
{
public:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
int main()
{
//只是开空间
A* p1 = new A[2]; //创建2个A类型大小的空间
//开空间+初始化
A aa1;
A aa2;
A* p2 = new A[2]{ aa1,aa2 }; //用aa1,aa2对象初始化申请的2个A类型大小的空间
A* p3 = new A[2]{A(1),A(2)}; //两个匿名对象初始化申请的2个A类型大小的空间
A* p4 = new A[2]{1, 2}; //1, 2隐式类型转换成两个A类型的数据去初始化2个A类型大小空间
//销毁动态内存空间
delete p1;
delete p2;
delete p3;
delete p4;
}
2.new, delete与malloc, free对比
(1) 内置类型
cpp
#include<iostream>
int main()
{
//开辟5个int大小的空间
int* p1 = (int*)malloc(sizeof(int) * 5);
int* p2 = new int[5];
free(p1);
delete p2;
}
①new与malloc,delete与free 功能上没有实质差异
②new比malloc更简洁:
1). malloc需要用计算单个数据类型大小, 写进表达式,new不需要
2). malloc需要对返回值做强制类型转换,new不需要
③new在开空间的时候可以手动初始化,malloc不可以,即使是calloc也只是自动初始化成0
(2) 自定义类型
①new在申请空间时会调用构造函数,malloc不会,因此new可以在申请空间同时完成初始化, malloc无法初始化
②delete在释放空间时会调用析构函数,free不会,因此delet可以在释放空间同时可以完成对对象中资源的清理, 而free无法完成
cpp
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
free(p1);
cout << "-----------" << endl;
A* p2 = new A;
delete p2;
}
ps: 对于动态申请的连续空间,new和delete会多次调用构造函数和析构函数
(3)综合特点
①malloc和free是函数,new和delete是操作符
②malloc开辟空间失败,会返回空指针,因此我们在使用malloc时需要判断返回值是否为空,而new不需要做检查,new开辟失败的话会直接抛异常
3.new与delete的底层实现
new = 开辟空间 + 调用构造函数
delete = 调用析构函数 + 释放空间
而库中开辟空间和释放空间的实现是借助两个全局函数operator new 与 operator delete 完成的
而operator new本质是对 malloc的封装,不过是增加了一些机制,使得开辟失败能报异常
而operator delete本质是对 free 的直接封装
因此,operator new 和operaotor delete 使用起来和 malloc 与 free 是完全一样的
int main()
{
int* p1 = (int*)operator new(sizeof(int) * 10);
operator delete(p1);
}
4. 定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
格式: new (place_address) type(initializer-list), place_address必须是一个指针, initializer-list是参数列表, 如果构造函数需要传参,则必须传参
cpp
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)operator new(sizeof(A) * 10);
//显式调用构造函数
new(p1)A(10);
//显式调用析构函数
p1->~A();
operator delete(p1);
}
有些场景下会出现内存空间分配了,但是没有初始化的场景(内存池),这时就需要用定位new表达式对内存进行初始化
二、模板
1.引入机制
两数交换,是我们经常碰到的一个需求,因此我们经常会把两数交换逻辑封装成一个函数
cpp
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
但是我要交换的数据不是整形呢?是其他类型呢?再写一份太麻烦了,那我们typedef一下~
cpp
typedef int DataType;
void Swap(DataType& left, DataType& right)
{
DataType tmp = left;
left = right;
right = tmp;
}
这时我们只需要把int改成其他类型就行了,但是我如果想同时交换整形数据和其他类型数据呢?就算typedef也只能调用函数去交换固定类型的数据呀,于是还是得有多份逻辑相同的交换函数~
cpp
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
void Swap(double& left, double& right)
{
double tmp = left;
left = right;
right = tmp;
}
void Swap(char& left, char& right)
{
char tmp = left;
left = right;
right = tmp;
}
还是太麻烦了,因此C++引入了模板,模板如同现实中的模板, 比如说数学书把数学公式给你了,要根据数学公式去计算具体的题目,你只需要把公式中的符号替换成具体数字就行了~
C++中的模板就是给了编译器一个模子,让编译器根据不同的类型利用该模子生成代码
2. 基本使用
模板分为函数模板与类模板
(1) 函数模板
①概念:
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参化,根据实参类型产生特定的类型模板
②格式:
方式1) 关键字template <class T1,class T2, class T3 ··· >
方式2) 关键字template <typename T1, typename T2, typename T3···>
其中T1, T2, T3都是模板参数
cpp
template<class T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
③原理
模板本身并不是函数,只是一个模子,具体使用时编译器会根据实参类型将模板推演成函数,去之执行相关代码,因此之前需要我们做的事情交给编译器去完成了
④模板实例化
用不同类型参数使用模板称为模板的实例化(对比类的实例化---通过类创建出具体对象)
1)隐式实例化
编译器自动根据实参推演模板参数的类型
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
//以下代码均为隐式实例化
Add(1, 2);
Add(1.1, 2.2);
Add(1.1, 2);//(×) 传递参数不一致时,会报错,因为T也不知道该实例化成哪种类型
//强制类型转化同一类型
Add((int)1.1, 2);
Add(1.1, (double)2);
}
2)显式实例化
在函数名后的<>中手动指定模板参数的实际类型
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
//显式实例化
Add<int>(1, 2.2);
Add<double>(1, 2.2);
}
⑤模板参数的匹配原则
- 一个非模板函数可以和同名函数模板同时存在,且函数模板还可以被实例化成这个非模板函数
cpp
#include<iostream>
using namespace std;
//非模板函数
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
//函数模板
template<class T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 1, b = 2;
Swap<int>(a, b); //函数模板会实例化成上面的非模板函数
}
2)对于非模板函数和同名函数模板,如果其他条件相同,在调用函数时会优先调用非模板函数;如果模板可以产生一个更好匹配的函数,那么选择模板
cpp
#include<iostream>
using namespace std;
//函数模板
template <class T1, class T2>
T1 Add(T1 left, T2 right)
{
cout << "函数模板" << endl;
return left + right;
}
//非模板函数
int Add(int left, int right)
{
cout << "非模板函数" << endl;
return left + right;
}
int main()
{
Add(1, 2); //调用非模板函数
cout << "------------" << endl;
Add(1.1, 2); //函数模板实例化
}
3)模板函数不允许自动类型转化,但普通函数可以进行自动类型转换
cpp
void func(int a)
{
cout << a;
}
int main()
{
func(1.1);//自动进行类型转换
}
cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
Add(1.1, 2);//(×) 1.1不会自动转换成整形, 2也不会自动转换成浮点型
}
(2) 类模板
①格式:
与类函数是一样的,template <class T1, class T2, class T3···>
②模板实例化
类模板实例化需要在类模板名后面加上<>,将实例化类型写在<>即可
类模板名字不是真正的类,而实例化的结果才是真正的类
cpp
template<class T>
class Vector
{
public:
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
size_t Size() { return _size; }
T& operator[](size_t pos)
{
assert(pos < _size);
return _pData[pos];
}
private:
T* _pData;
size_t _size;
size_t _capacity;
};
int main()
{
//Vector是类名, Vector<int>才是类型
Vector<int> v1;
Vector<double> v2;
}
③类模板的声明和定义分离
cpp
template<class T>
class Vector
{
public:
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
// 使用析构函数演示:在类中声明,在类外定义。
~Vector();
void PushBack(const T& data);
void PopBack();
// ...
size_t Size() { return _size; }
T& operator[](size_t pos)
{
//assert(pos < _size);
return _pData[pos];
}
private:
T* _pData;
size_t _size;
size_t _capacity;
};
//析构函数的定义
template<class T>
Vector<T>::~Vector()
{
delete[] _pData;
_pData = nullptr;
}
int main()
{
Vector<int> v;
v.PushBack(1);
v.PushBack(1);
v.PushBack(1);
v.PushBack(1);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~