C++知识点 – 内存管理及模板初阶
文章目录
一、内存管理
1.C/C++的内存分布
全局变量在静态区(数据段);
静态变量在静态区;
局部变量存在栈区;
数组存在栈区;
指针存在栈区;
常量字符串存在常量区(代码段);
malloc开辟的动态内存在堆区;
对于char char2[] = “abcd”; 这条代码的意思是在常量区创建一个常量字符串abcd\0,并将它拷贝到char2数组中;
对于char* pChar3 = “abcd”; 这条代码的意思是pChar这个指针指向常量区的字符串abcd\0;
相同的字符串在常量区只会存在一个;
例题:
2.new/delete操作内置类型
new和delete是操作符,不是函数;
代码如下:
void test()
{
//申请一个int对象
int* p1 = new int;
//申请5个int的数组
int* p2 = new int[5];
//申请一个int对象,初始化为0
int* p3 = new int(0);
//c++11支持new[]用{}初始化,c++98不支持
int* p4 = new int[3]{ 1,2,3 };
//new/delete 和 new[]/delete[]一定要配和使用
delete p1;
delete[] p2;
delete p3;
delete[] p4;
}
3.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;
};
void test2()
{
//对于自定义类型,new和delete除了开辟空间外,还会调用构造函数和析构函数
A* p1 = new A;
A* p2 = new A[3];
A* p3 = new A(5);
delete p1;
delete[] p2;
delete p3;
}
int main()
{
test2();
return 0;
}
对于自定义类型,new和delete除了开辟空间外,还会调用构造函数和析构函数;
具体操作为:
new:1.堆上申请空间;2.调用构造函数初始化;
delete:1.调用析构函数清理对象中的资源;2.释放空间;
自定义类型最好有默认构造函数,否则容易编译不过;
4.operator new与operator delete函数
编译器在进行new操作时,会调用operator new函数,该函数实际通过malloc来申请空间,申请成功直接返回,申请失败会抛出异常;
而进行delete操作时,编译器会调用operator delete函数,该函数实际通过free来释放空间。
5.定位new
定位new用于已分配的原始内存空间中调用构造函数初始化一个对象;
常与内存池配合使用;
代码如下:
//定位new
A* p5 = (A*)malloc(sizeof(A));
if (p5 == nullptr)
{
perror("malloc");
}
new (p5)A(10);
//定位new用于已分配的原始内存空间中调用构造函数初始化一个对象
//常与内存池配合使用
6.new/delete 和 malloc/free的区别
二、模板初阶
1.函数模板
格式:
template<typename T1, typename T2, …>
返回值类型 函数名(参数列表){}
代码如下:
//template<calss T> // class也可以
template<typename T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
实例化:
1.隐式实例化
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void test3()
{
int a1 = 3, a2 = 4;
double b1 = 1.22, b2 = 3.54;
Add(a1, a2);
Add(b1, b2);
//Add(a1, b1);
Add(a1, (int)b1);
}
编译器在看到Add的实例化后,需要推演其实参类型,通过实参a1将T推演为int,通过b1将T推演为double;
但是在Add(a1, b1);中,编译器无法确定T的具体类型,会报错,此时就需要用户进行处理:
(1)自己强制转换(如上);(2)显式实例化;
2.显式实例化
void test3()
{
int a1 = 3, a2 = 4;
double b1 = 1.22, b2 = 3.54;
Add<int>(a1, b1);
}
template<typename T>
T* func(int n)
{
T* a = new T;
return a;
}
//因为形参不是T类型,返回值是T类型,编译器无法进行推演
//所以该模板必须显式实例化才能调用
void test3()
{
func<int>(3);
}
2.类模板
类模板格式:
template<typename T1, typename T2, …>
class 类名
{
//类内成员定义
};
模板不支持分离编译;
模板在同一个文件中,是可以声明和定义分离的;
代码如下:
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
: _a(nullptr)
, _top(0)
, _capacity(0)
{
if (capacity > 0)
{
_a = new T[capacity];
_capacity = capacity;
_top = 0;
}
}
~Stack()
{
delete[] _a;
_a = nullptr;
_top = _capacity = 0;
}
//c++不建议用malloc,因为自定义类型初始化需要调用构造函数
void Push(const T& x)
{
if (_capacity == _top)
{
//1.开新空间
//2.拷贝数据
//3.释放旧空间
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
T* tmp = new T[newcapacity];
if (_a)
{
memcpy(tmp, _a, sizeof(T) * _top);
delete[] _a;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top] = x;
_top++;
}
void Pop()
{
assert(_top > 0);
_top--;
}
bool Empty()
{
return _top == 0;
}
const T& top()
{
assert(_top > 0);
return _a[_top - 1];
}
//返回栈顶数据可以传引用返回,数据存在堆上,因为函数栈帧销毁后,数据不消失
//传引用返回可以修改原数据,若不想被修改,可以加上const
private:
T* _a;
size_t _top;
size_t _capacity;
};
void test4()
{
Stack<int> st1;
Stack<int> st2(100);
st1.Push(5);
st1.Push(6);
}