C++知识点 -- 内存管理及模板初阶

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);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值