从零开始的C++生活——第四讲

本文重点讲解C++中的replacement-new(定位new)及其在内存池场景下的应用,并系统介绍函数模板与类模板的核心机制,包括模板实例化(隐式/显式)、多参数模板、重载规则及类型推导限制。内容涵盖模板解决代码冗余的原理、实例化过程本质、非模板函数与模板函数的调用优先级,以及类模板必须显式实例化的特性。

在上一讲,我向大家介绍了C++内存管理以及new/delete的相关知识,在开启这一讲之前,我想先补充一点上一讲未交代完的知识点,replacement-new相关。

由于,现阶段知识储备有限,我先简单通俗的说一说内存池的概念,不会介绍太深。当需要动态申请内存时,我们需要到堆区申请,一次两次还好,频率过高难免有些浪费时间,这时候就有内存池出现了,所谓内存池你可以理解为它本来就预留了一些向堆区申请的空间,然后你就不用再去找堆区要了,直接让内存池给你。

简单介绍过后,我来介绍我们今天要讲的定位new表达式也就是replacement-new,它用于在已分配的原始内存空间中调用构造函数初始化对象,完美契合我们内存池的前提,因为内存池就是已分配的原始内存空间。

我简单演示一下用法。

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>

using namespace std;
class A
{
public:
	A(int a = 10)
		:_a(a)
	{
		cout << "A(int a = 10)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void Print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* pa = (A*)malloc(sizeof(A));
	new(pa)A(20);
	pa->Print();
	pa->~A();
	free(pa);


	return 0;
}

这段代码完美地为我们演示了什么叫多此一举......确实啊,你调用new/delete自动调用构造析构还整这么麻烦干啥呢?还是那句话,内存池可以用,现在确实展示不出来,只是为了让大家了解一下用法,显式调用构造函数一般我们也确实用不到。

那么接下来,我们正式进入第四讲模板的学习。

模板之于C++有着举足轻重的作用,以往我们写个Swap函数写个Add函数,你由于不同类型的考量需要写一堆,C++即使有重载也十分麻烦,明明大家都是干着差不多的任务,却要写好多次,代码十分冗余,模板就是来解决这个问题的。

template<class T>
void Swap(T& t1, T& t2)
{
	T tmp = t1;
	t1 = t2;
	t2 = tmp;
}

int main()
{

	int a = 10;
	int b = 2;
	double c = 1.1;
	double d = 2.9;
	Swap(a, b);
	Swap(c, d);



	return 0;
}

这是基本的语法,template<class T>其中class你可以写typename,T这个名字你随便。

看看这段代码,思考一个问题,传double和int的这两个Swap是一个函数吗?我可以很明确的告诉你,如果你调试,进的都是这个函数,那能证明这俩是一个函数吗?

看到了吧,这其实是两个函数,也就是说,函数模板的原理是根据预定模板生成对应函数,本来这个工作是你来完成的,但是由于工作比较简单,就交给编译器了,本质不变,仍然是两个函数,只是省事且不冗余,代码观感更好。当你传入int编译器会推断出相关类型执行相应操作。由函数模板生成对应函数的过程我们称为函数模板的实例化。

实例化分为隐式实例化和显式实例化两种,上边我为大家介绍的就是隐式实例化。

什么时候隐式实例化就不够了呢?

template<class T>
T Add(const T& t1, const T& t2)
{
	return t1 + t2;
}
int main()
{

	int a = 10;
	int b = 2;
	double c = 1.11;
	double d = 2.9;
	cout << Add(a, b) << endl;
	cout << Add(c, d) << endl;
	cout << Add(a, (int)c) << endl;
	cout << Add<int>(a, c) << endl;
	cout << Add<double>(a, c) << endl;




	return 0;
}

你想要不同类型的变量相加,这个时候既定方式不管用了,在函数模板中,一般不会对类型进行隐式类型转换,所以要么就你手动转换类型要么就显式实例化。

还有一种情况,你必须显式实例化,当你没有传泛型参数的时候,编译器怎么知道你要用什么类型,所以你必须指定。

上述讲的都是只有一个模板参数的情况,但是现实中有多个模板参数的情况还是很常见的,毕竟你不能保证在一个函数里一直只用一种变量类型吧。

最简单的情况,Add可以实现一个整型加一个浮点数吧,你来回显式实例化,也很麻烦。

最后介绍一下函数模板的一些规则。

一个非模板函数可以和一个同名的函数模板同时存在,而且模板函数还可以实例化为该非模板函数。

如果非模板函数和模板函数其他条件都相同优先调用非模板函数。

模板函数内部不允许自动类型转换。

介绍完函数模板之后,还有类模板,接下来我介绍一下类模板。

template<class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_parr(new T[capacity])
		,_capacity(capacity)
		,_size(0)
	{

	}
	void Push(const T& t);
private:
	T* _parr;
	int _capacity;
	int _size;

};
template<class T>
void Stack<T>::Push(const T& t)
{
	if (_capacity == _size)
	{
		//手动扩容
		T* tmp = new T[2 * _capacity];
		memcpy(tmp, _parr, sizeof(T) * _size);
		delete[] _parr;
		_parr = tmp;
		_capacity *= 2;
	}
	_parr[_size] = t;
	_size++;

}

int main()
{

	Stack<int> s1;
	Stack<double> s2;




	return 0;
}

和函数模板不同的是,类模板必须要显式实例化,和它类似的是,Stack<int>和Stack<double>不是同一种类型。

值得注意的是,C++需要手动扩容,但是现在没办法说为什么,请期待后续讲解。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值