模板(template)

一.引子:

问题:

交换下面的数据

1.两个int型

2.两个double型

3.两个char型

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right)
{
	int temp = left;
	left = right;
	right = temp;
}

我们发现要想交换这三组数,由于他们的数据类型不同,我们必须写三个不同的交换函数,

虽然C++引用(别名)传参解决了指针的一些弊端

以及函数重载让我们不用费心费力给相同功能的函数起名字

但是这样做依旧得写三个函数来实现两个变量的交换,这样很麻烦,怎么办呢?

C++创建者提出模板来解决这个问题:

//template<typename T>//typename<==>class
template<class T>
void Swap(T& left, T& right)
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 10, b = 20;
	double x = 1.3, y = 2.4;
    char m = 'a', n = 'b';
	Swap(a, b);
	Swap(x, y);
    Swap(m, n);
    return 0;
}

那么我们使用了模板之后,Swap用的是同一个函数吗?显然不是,为什么呢,请看下面.

二.模板的原理

 

模板的本质实际上是把    本来人要做的事(写三个函数)   交给   编译器   去做,让编译器代替我们写出这三个不同的函数.

当然,你展开标准命名空间后可以不用写swap函数,直接调用C++库里面的即可,这里只是举个例子方便我们理解模板,如果你想了解更多C++库里面的知识可以查阅帮助文档

帮助文档链接:https://cplusplus.com/reference/

三.模板使用过程中存在的问题

(一).问题1

1.推演实例化错误

请看下面这两段代码

int Add(const int& left, const int& right)
{
	return left + right;
}
int main()
{
	Add(1, 2);
	Add(1.1, 2);
	//由于传的是引用,所以并不是直接传参,而是先会有一份临时拷贝,这个临时拷贝对象具有常性
	//double->const double->const int隐式类型转换会报警告,但是不会报错
	return 0;
}
template<typename T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2) << endl;//推演实例化报错
	return 0;
}

第一段代码会报警告,隐式类型转换从couble->const int

第二段代码推演实例化错误,  1.1是double型,   2是int型

那T究竟是double还是int呢?编译器无法确定,因此会报错

2.三种解决方法

隐式实例化

	cout << Add((int)1.1, 2) << endl;
	cout << Add(1.1,(double)2) << endl;

显式实例化

	cout << Add<int>(1.1, 2) << endl;
	cout << Add<double>(1.1,2) << endl;

给模板设定两个参数(不推荐)

template<typename T1, typename T2>
T1 Add(const T1& left, const T2& right)
{
	return left + right;
	//1+2.2会发生整型提升
	//整型提升:不够int的提到int
	//超过int的提升为较大的那个
	//这里被提升为了double
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2) << endl;
	cout << Add(1, 2.2) << endl;
	//这里会报警告,把参数传递过去后
	return 0;
}

3.显式实例化的应用场景

template<class T>
T* Func(int n)
{
	T* a = new T[n];//new n个T类型的对象
	return a;
}
int main()
{
	Func<int>(10);
}

Func函数参数里面没有T,编译器无法自行推演,因此需要显式实例化 

(二).问题2

下面代码只是样例,不推荐写出这样的代码

//专门处理int型的加法函数
int Add(int left, int right)
{
	return left + right;
}

//通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}

int main()
{
	cout << Add(1, 2) << endl;//调第一个
	cout << Add(1.1, 2.2) << endl;//调第二个
	return 0;
}
//通用加法函数1
template<class T>
T Add(T left, T right)
{
	return left + right;
}

//通用加法函数2
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}

int main()
{
	cout << Add(1, 2) << endl;//调第一个
	cout << Add(1.1, 2.2) << endl;//调第一个
	cout << Add(1, 2.2) << endl;//调第二个
	return 0;
}

得出结论:编译器会调用现成的,尽量匹配的.

四.模板的具体应用-栈

typedef char STDataType;
class Stack
{
private:
	STDataType* _a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;//int
	Stack st2;//char
	return 0;
}

要创建一个存放char,存放一个int的栈如果不用模板的化就得写两个栈

template<typename T>
class Stack
{
public:
	//构造函数
	Stack(size_t capacity = 0)
		: _a(nullptr)
		, _capacity(0)
		, _top(0)
	{
		if (capacity > 0)
		{
			_a = new T[capacity];
			_capacity = capacity;
			_top = 0;
		}
	}
	~Stack()
	{
		delete[] _a;
		_a = nullptr;
		_capacity = _top = 0;
	}

	void Push(const T& x)
	{
		if (_top == _capacity)
		//栈满了或者栈是空的
		{
			size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
			//1.开新空间
			//2.拷贝数据
			//3.释放旧空间
			T* tmp = new T[newCapacity];
            //C++中尽量不要使用malloc,因为如果栈中存自定义类型的话,malloc无法访问类中的 
            //private变量,而new在创建空间的同时已经调用构造函数把创造的空间初始化了
			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];
		//1.为什么加const
		//避免栈中数据被修改
		/*
		Stack<int> st1;
		st1.Push(1);
		st1.Push(2);
		st1.Push(3);
		st1.Top()++;
		st1.Top() *= 2;
		*/
		//2.为什么加引用
		//因为栈中数据全存放在堆空间.函数结束,不会被销毁,可以用引用作为别名
	}

private:
	T* _a;
	size_t _top;
	size_t _capacity;
	//下面实际上也是用初始化列表
	/*T* _a = nullptr;
	size_t _top = 0;
	size_t _capacity = 0*/;
};

int main()
{
	try
	{
		Stack<int> st1;
		st1.Push(1);
		st1.Push(2);
		st1.Push(3);
		st1.Push(4);
		st1.Push(5);
		st1.Push(6);
		while (!st1.Empty())
		{
			cout << st1.Top() << " ";
			st1.Pop();
		}
		cout << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

另外,模板不支持分离编译(声明放在.h,定义放在.cpp)

那么类模板想要声明定义分离怎么办?

例如上面栈中的Push函数太长了,想在类外面定义,写在本文件中

虽然不可以写在其他文件,但是可以写在同一个文件中,例如:

template<typename T>
class Stack
{};
//..............
template<class T>
void Stack<T>::Push(const T& x)
//现在T在类外面,必须指定类域,函数才能访问类中的私有变量
{
	if (_top == _capacity)
		//栈满了或者栈是空的
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		//1.开新空间
		//2.拷贝数据
		//3.释放旧空间
		T* tmp = new T[newCapacity];
		if (_a)
		{
			memcpy(tmp, _a, sizeof(T)*_top);
			delete[] _a;
		}
		_a = tmp;
		_capacity = newCapacity;
	}
	_a[_top] = x;
	++_top;
}


int main()
{
	try
	{
		Stack<int> st1;
		st1.Push(1);
		st1.Push(2);
		st1.Push(3);
		st1.Push(4);
		st1.Push(5);
		st1.Push(6);
		while (!st1.Empty())
		{
			cout << st1.Top() << " ";
			st1.Pop();
		}
		cout << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

像这样函数直接在.h文件中定义,一般把文件后缀名写为.hpp

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值