初识模板
- 引言
 - 函数模板
 - 定义
 - 实例化
 - 隐式实例化
 - 显式实例化
 
- 类模板
 - 定义
 - 实例化
 
- STL简介
 - STL六大组件
 
- 总结
 
引言
模板是泛型编程的基础
在之前我们介绍过函数重载,可以定义许多函数名相同参数列表不同的重载函数,来实现不同类型的相似操作。调用重载函数时会根据传参调用一个合适的重载函数,这方便了调用这类方法。
但是,函数重载还是有一些不足:
 重载的函数还是需要自己定义的,代码复用率不高。当需要增加一种类型的实现时,就需要我们再定义一个重载函数;重载函数的可维护性不高,如果出错了可能需要逐个修改每个重载函数。
泛型编程就可以解决这个问题,可以只写一份对所有类型通用的代码,在需要使用的时候由编译器生成相应的代码,是代码复用的一种手段。模板是泛型编程的基础
C++模板分为函数模板与类模板:
 
函数模板
函数模板代表了一类作用相同的函数,与类型无关。函数模板在使用时会由编译器自动实例化,生成一个特定类型的函数
定义
使用
 template<typename T1, typename T2, ...typename Tn>
 返回值 函数名(参数列表) {}
 可以定义一个函数模板,其中typename也可以用class代替。
例如一个交换函数模板:
template<typename T>
void Swap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
 
然后就可以像使用函数一样使用函数模板:
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}
 

 需要注意的是,template声明的typename只在其接下来的函数内有效
实例化
在使用函数模板时,编译器会生成一个指定类型的函数,即函数模板的实例化。
在指定生成函数的类型时,就有两种方式,即隐式与显式:
隐式实例化
隐式实例化即编译器根据调用时的实参自动指定模板参数的类型:
 例如这段代码:
template<typename T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	cout << Add(a, b) << endl;
	return 0;
}
 

 此时变量a与b均为int,编译器自然可以通过两个均为int的实参推演出T的类型为int,这并不难理解。
 但是当实参的类型不相同时,就无法确定T究竟是两个参数类型中的哪一个了:
	int a = 10;
	double b = 20;
	//cout << Add(a, b) << endl;  //错误代码,调用Add时模板参数不明确
 

 在模板中,不会擅自进行类型转换。 比如这种情况,在类型T不明确时,并不确定要将int转化为double还是将double转换为int。
解决的方法当然也很简单:
- 可以在传参时将实参的类型显式的转换为一致:
 
template<typename T>
T Add(const T& a, const T& b)
{
	return a + b;
}
int main()
{
	int a = 10;
	double b = 20;
	cout << Add(a, (int)b) << endl;
	return 0;
}
 

 需要注意的是,类型转换会生成一个临时变量,临时变量具有常性。所以如果模板的形参类型不是const修饰的话,就会发生权限放大,这种方法就是不可取的。
- 当然也可以显式的指定类型
T,这样编译器就会尝试进行隐式类型转换,即显式实例化: 
显式实例化
显式实例化即在调用时函数名后的<>中显式的指定模板参数的类型: 函数名<模板参数列表>(实参列表);
 例如上面的Add模板生成函数的调用:
int main()
{
	int a = 10;
	double b = 20;
	cout << Add<int>(a, b) << endl; // Add<int>(a, b)
	return 0;
}
 

 当指定了模板参数的类型后,编译器当然就能在传参时进行隐式类型转换了,转换失败时就会报错。
需要注意的是:
- 一个非模板函数可以和一个同名的函数模板同时存在,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板:
 
例如:
int Add(const int& a, const int& b)
{
	return a + b;
}
template<typename T1, typename T2>
T1 Add(const T1& a, const T2& b)
{
	return a + b;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1, 2.0) << endl;
	return 0;
}
 
在第一次调用Add函数时,实参类型均为int,所以优先调用非模板函数:
 
 第二次调用Add函数时,第一个参数类型为int,第二个参数为double,模板生成的Add函数能更好的匹配,所以调用模板:
 
 3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换:
类模板
类似与函数,我们也可以定义一种与类型无关的类,即类模板。在使用时,再由编译器实例化为对于指定模板参数类型的类类型。
定义
使用
 template<typename T1, typename T2, ...typename Tn>
 class 类模板名 {};
 可以定义一个函数模板,其中typename也可以用class代替。
例如我们可以写一个简陋的栈:
template<typename T>
class Stack
{
public:
	Stack(size_t size = 0, size_t capacity = 0)  //构造函数
		: _size(size)
		, _capacity(capacity)
		, _date(nullptr)
	{
		_date = new T[_capacity + 1]{ 0 };
	}
	void push(T n)    //压栈
	{
		if (_size == _capacity)
		{
			if (_capacity == 0)
			{
				reserve(6);
			}
			else
			{
				reserve(_capacity * 2);
			}
		}
		_date[_size] = n;
		++_size;
	}
	void reserve(size_t capacity)    
	{
		if (capacity > _capacity)
		{
			T* newdate = new T[capacity + 1]{ 0 };
			_capacity = capacity;
			for (int i = 0; i < _size; ++i)
			{
				newdate[i] = _date[i];
			}
			delete[] _date;
			_date = newdate;
		}
	}
	T top()
	{
		return _date[_size - 1];
	}
	size_t size()   
	{
		return _size;
	}
	~Stack()   //析构函数
	{
		delete[] _date;
		_size = 0;
		_capacity = 0;
	}
private:
	size_t _size;
	size_t _capacity;
	T* _date;
};
 
对于int型数据:
int main()
{
	Stack<int> nums;
	nums.push(1);
	nums.push(2);
	nums.push(3);
	nums.push(4);
	nums.push(5);
	cout << nums.top() << endl;
	cout << nums.size() << endl;
	return 0;
}
 

 对于char型数据:
int main()
{
	Stack<char> str;
	str.push('a');
	str.push('b');
	str.push('c');
	str.push('d');
	str.push('e');
	cout << str.top() << endl;
	cout << str.size() << endl;
	return 0;
}
 

 不难发现,对于不同的模板参数类型,这个简陋的栈可以实现其效果。
实例化
不同于函数模板,类模板不能通过参数来推断模板参数的类型,所以类模板的实例化必须在调用时的类模板名后的<>中显式指出模板参数。
例如对上面的栈模板的使用:
int main()
{
	//实例化类模板并实例化类对象
	Stack<char> str;
	Stack<int> nums;
	Stack<double> dnums;
	//对不同类型的栈堆栈
	str.push('a');
	str.push('b');
	nums.push(3);
	nums.push(4);
	dnums.push(20.0);
	dnums.push(30.0);
	//打印不同类型栈的栈顶元素及元素个数
	cout << str.top() << endl;
	cout << str.size() << endl;
	cout << nums.top() << endl;
	cout << nums.size() << endl;
	cout << dnums.top() << endl;
	cout << dnums.size() << endl;
	return 0;
}
 

需要注意的是:类模板名字不是真正的类,而实例化的结果才是真正的类
STL简介
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架
STL六大组件

 容器:string、vector、list、deque、map、set等
 算法:find、swap、reverse、sort等
(这里只做了解,接下来将会逐渐详细介绍)
总结
到此,关于模板初阶的内容就介绍完了
 包括函数模板与类模板
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦
















