C++ 模板是泛型编程的核心工具,是与类型无关的代码 "模具",编译器会根据传入的类型自动生成对应类型的具体代码,解决函数重载代码冗余、维护难的问题。
函数模板
格式:
java
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
注意:typename是用来定义模板参数关键字
举例:实现两个数值的交换
不用模板:我们会发现,写了很多功能相同的函数
java
#include<iostream>
using namespace std;
//c++支持函数的重载,如下实现不同类型参数传入,功能相同:都为交换两数
void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
void Swap(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
void Swap(char& a, char& b)
{
char temp = a;
a = b;
b = temp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
double c = 1.1, d = 2.2;
Swap(c, d);
return 0;
}

使用函数模板:解决了上面的问题
下面我们是否调用同一个函数?
答案是:不是,我们不能调用函数模板,我们调用的是函数模板实例化生成的对应参数类型的函数,模板实例化是发生在编译阶段,编译器会根据调用的类型,生成对应的函数代码。
java
//改进写法
//模板
template<class T>
void Swap(T& x1, T& x2) {
T x = x1;
x1 = x2;
x2 = x;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);//Swap(a, b) 会实例化出 void Swap(int&, int&)
double c = 1.1, d = 2.2;
Swap(c, d);//Swap(a, b) 会实例化出 void Swap(int&, int&)
return 0;
}

总结:模板就是将本来应该我们做的重复的事情交给了编译器
类模板:
java
template<class T>
class Stack_cpp {
...};
类模板的实例化:
类名<类型> 对象名;
Stack_cpp<int> s1;
Stack_cpp<double> s2;
如下例子:使用类模板同时实现两种类型的栈
java
//类模板
//实现栈
template<class T>
class Stack_cpp {
public:
// 构造函数:初始化成员变量
Stack_cpp()
:_a(nullptr)
, _size(0)
, _capacity(0)
{
}
// 析构函数:释放动态开辟的内存
~Stack_cpp() {
delete[] _a; // 释放数组空间
_a = nullptr; // 指针置空,防止野指针
_size = _capacity = 0;
}
// 入栈:向栈顶添加元素
void Push(const T& x) {
// 检查容量,满了就扩容
if (_size == _capacity) {
// 新容量:空栈给4,非空栈翻倍
int new_capacity = _capacity == 0 ? 4 : _capacity * 2;
// 开辟新空间
T* tmp = new T[new_capacity];
// 如果原来有数据,拷贝过去
if (_a != nullptr) {
for (int i = 0; i < _size; ++i) {
tmp[i] = _a[i];
}
// 释放旧空间
delete[] _a;
}
// 指向新空间,更新容量
_a = tmp;
_capacity = new_capacity;
}
// 尾插(入栈)
_a[_size] = x;
_size++;
}
// 出栈:删除栈顶元素
void Pop() {
// 栈空不能出栈,断言报错
assert(_size > 0);
_size--;
}
// 获取栈顶元素
T& Top() {
// 栈空不能取栈顶,断言报错
assert(_size > 0);
cout << _a[_size - 1] << endl;
return _a[_size - 1];
}
// 判断栈是否为空
bool Empty() {
return _size == 0;
}
// 获取栈中元素个数
int Size() {
return _size;
}
private:
T* _a;
int _size;
int _capacity;
};
//模板:当我们想要同时定义两个栈时,我们可以使用模板来解决问题
int main() {
Stack_cpp<int> stack_int;//定义int类型的栈
stack_int.Push(1);//隐含this指针
stack_int.Top();
stack_int.Push(2);
stack_int.Top();
stack_int.Push(3);
stack_int.Top();
stack_int.Push(4);
stack_int.Top();
Stack_cpp<double> stack_double;//定义double类型的栈
stack_double.Push(1.2);
stack_double.Top();
stack_double.Push(2.2);
stack_double.Top();
stack_double.Push(3.2);
stack_double.Top();
stack_double.Push(4.2);
stack_double.Top();
return 0;
}

使用C++实现栈的好处
1.自动调用构造函数初始化和析构函数销毁
2.有封装性,不是谁都可以修改(类的访问限定符修饰)
3.可以同时定义两个类型不同的栈,用模板来解决
java
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
class Stack_CPP
{
public:
//构造函数
Stack_CPP()
:_a(nullptr)
, _size(0)
, _capacity(0) {
}
//析构函数
~Stack_CPP() {
delete[] _a;
_a = nullptr;
_size = _capacity = 0;
}
//下标访问运算符 [] 重载
//让你的栈可以像普通数组一样,用 stack[i] 直接访问第 i 个元素
T& operator[](size_t i) {
assert(i < _size);//断言判断i是否小于_size
return _a[i];//返回引用 T&,才能修改栈里的元素
}
size_t size()const {
return _size;
}
//类里面声明,类外面定义
void PushBack(const T& x);
void Pop();
private:
T* _a;
size_t _size;
size_t _capacity;
};
//类外面定义
template<class T>
void Stack_CPP<T>::PushBack(const T& x) {
//1.如果空间不够就扩容
if (_size == _capacity) {
size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
T* tmp = new T[newcapacity];
//2.如果原来的空间有内容,拷贝到新空间中
if (_a) {
memcpy(tmp, _a, sizeof(T) * _size);
delete[] _a;//删除原来的内容
}
_a = tmp;//有空间,没空间都要这一步
_capacity = newcapacity;
}
//3.入栈
_a[_size] = x;
++_size;
}
template<class T>
void Stack_CPP<T>::Pop() {
//当_size>=0时,可以进行出栈
if (_size > 0) {
_size--;
}
}
int main() {
Stack_CPP<int> stack;
stack.PushBack(1);
stack.PushBack(2);
stack.PushBack(3);
stack.PushBack(4);
//遍历打印
for (size_t i = 0; i < stack.size(); ++i) {
cout << stack[i] << " ";
}
cout << endl;
for (size_t i = 0; i < stack.size(); ++i) {
stack[i] *= 2;
}
for (size_t i = 0; i < stack.size(); ++i) {
cout << stack[i] << " ";
}
cout << endl;
}

模板中的隐式实例化和显示实例化
java
//模板中的隐式实例化和显示实例化
#include<iostream>
using namespace std;
template<class T>
T add(const T& x, const T& y) {//x 和 y 必须是同一种类型
return x + y;
}
int main() {
int a = 10, b = 20;
double c = 1.1, d = 2.2;
//隐式实例化(T类型是编译器自己推导的)
add(a, b);
//add(a, c);//编译出错
add(a, (int)c);//上面的修改结果
//显示实例化,指定类型
add<int>(a, d);
return 0;
}
函数模板与普通函数的匹配规则
java
#include<iostream>
using namespace std;
int add(const int& a, const int& b) {
return a + b;
}
template<class T>
T add(const T& x, const T& y) {//x 和 y 必须是同一种类型
return x + y;
}
int main() {
//add(1, 2);//与非模板匹配,编译器不需要使用类模板实例化函数
add<int>(1, 2);//调用编译器使用类模板实例化的函数
return 0;
}

