C++:模板初阶
1.模板出现的背景
当我们写一个函数时,如果遇到函数接收的参数类型具有多样性,那么我们就需要重复地写多个函数,例如下面最简单的交换函数:
#include<iostream>
using namespace std;void Swap_char(char&a,char&b)
{cout << a << " " << b << endl;char tmp = a;a = b;b = tmp;cout << a << " " << b << endl;cout << endl;
}void Swap_int(int& a, int& b)
{cout << a << " " << b << endl;int tmp = a;a = b;b = tmp;cout << a << " " << b << endl;cout << endl;
}void Swap_double(double& a, double& b)
{cout << a << " " << b << endl;double tmp = a;a = b;b = tmp;cout << a << " " << b << endl;cout << endl;
}int main()
{char a = 'a', b = 'b';int c = 3, d = 6;double e = 3.0, f = 6.0;Swap_char(a,b);Swap_int(c,d);Swap_double(e,f);return 0;
}
一个简单的交换函数只是因为接收的参数有int,char,double三种类型就写出了三个结构类似的交换函数,在写代码时是非常忌讳类似这种重复代码的形式。因此C++的祖师爷就研究出来了“模板”这一种概念。
2.模板的概念
在C++中,模板是一种可重用的代码结构,用于生成其他代码或类。模板允许我们定义通用的函数或类,并根据需要自动创建具体的实例。
3.模板的分类
模板分为函数模板和类模板。
3.1函数模板
函数模板允许我们定义一个通用的函数,该函数可以接受不同类型的参数,并根据实际的参数类型进行类型推断来确定具体的函数实例。函数模板的定义通常以关键字“template”开头,后跟模板参数列表和函数体。
3.1.1单种数据类型
还是以刚刚所说的交换函数为例,将前面的交换函数改成模板形式:
#include<iostream>
using namespace std;template<class T>void Swap(T& a, T& b)
{cout << a << b << endl;T tmp=0;tmp = a;a = b;b = tmp;cout << a << b << endl;
}int main()
{int i = 1, j = 2;double m = 1.1, n = 2.2;char a = 'a', b = 'b';Swap(i,j);Swap(m, n);Swap(a, b);return 0;
}
运行结果如下:我们发现使用一个函数模板就可以将不同类型数据成功地交换。
代码中的“T”就代表一个通用的类型,在用户输入参数时可以推导出参数的类型并完成相应的指令。这个时候我们想一个问题:如果同时出现函数模板和一个确定的函数,那么编译器会如何选择呢?我们来看下面这段代码:
#include<iostream>
using namespace std;template<class T>void Swap(T& a, T& b)
{cout << a << b << endl;
}//void Swap(int& a, int& b)
//{
// cout << a << b << endl;
// int tmp = 0;
// tmp = a;
// a = b;
// b = tmp;
// cout << a << b << endl;
//}int main()
{int i = 1, j = 2;Swap(i,j);return 0;
}
注释Swap(参数是int&)的代码段时,起作用的相当于是函数模板;当取消下面代码段的注释时,此时出现了函数重载(函数名相同,但是参数不同),此时主函数调用Swap函数,起作用的就是(参数是int&)的Swap函数。原因在于函数模板相当于便于不知道参数具体类型时可以根据传入的参数自动推导,而如果有一个现成的确定了参数类型的同名函数,那么模板函数就可以“休息”了,不用发挥作用。从而导致取消注释前和取消注释后代码运行的结果不一样。
3.1.2多种数据类型
代码如下:
#include<iostream>
using namespace std;template<class T1,class T2>T2 Add(const T1& a,const T2& b)
{return a + b;
}int main()
{int i = 1;double j = 2.2;//推导实例化cout << Add(i, j) << endl;cout << Add(i, (int)j) << endl;//显示实例化cout << Add<int>(i, j) << endl;cout << Add<double>(i, j) << endl;//显示实例化return 0;
}
这段代码使用了Add函数的函数模板,因为相加的两个数有可能属于不同类型,所以需要T1和T2两个“模板变量”(自己取的名字) 。同时在引用函数的时候分为推导实例化和显示实例化:推导是根据导入参数的类型进行判断,可以利用(类型名)这种方式修改变量的类型,和强转有相似之处;显示实例化是直接使用<类型名>改变函数的返回值。
3.2类模板
类模板的大致思路相同,拿出我们之前用C语言实现过的栈来说,栈中存放的可能是int , char , double ...等多种类型的数据,为例防止重复地写相同的代码,我们继续使用前面所说的“T”来定义数组的类型,代码如下:
#include<iostream>
using namespace std;template<class T>class Stack
{
public:Stack(int n=4):_array(new T[n]),_size(0),_capacity(n){}~Stack(){delete[] _array;_array = nullptr;_size = _capacity = 0;}void PushStack(const T& a){if (_size == _capacity){T* tmp = new T[_capacity*2];memcpy(tmp,_array,_capacity*sizeof(T));_array = tmp;_capacity *= 2;}_array[_size] = a;_size++;}void PrintStack(){int i = 0;while (_size - i){cout << _array[i] << " ";i++;}}private:T* _array;size_t _size;size_t _capacity;};int main()
{Stack <int> s1;s1.PushStack(1);s1.PushStack(2);s1.PushStack(3);s1.PushStack(4);s1.PrintStack();cout << endl;Stack <char> s2;s2.PushStack('a');s2.PushStack('b');s2.PushStack('c');s2.PushStack('d');s2.PrintStack();cout << endl;return 0;
}
如上述代码所示,s1\s2是两个不同的栈(一个存int,一个存char),这个是C语言typedef做不到的,因为typedef终究还是只能存储一个类型,只是这个类型可以修改而已,但是不能同时存在。代码运行结果如下:
注意:类模板都是显示实例化。
感谢阅读,如有错误恳请批评指正