【C++】初阶模板
泛型编程
模板是C++泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>。
我们可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。
函数模板
很多时候,我们在编程时会遇到这样的情形,如在通讯录程序中,我们想要实现两个联系人的信息互换,如我们要将张三和李四除了姓名之外的所有信息做交换:编辑
这个时候因为交换的数据类型并不相同,就需要我们编写很多Swap交换函数来完成这一功能,如:
编辑
仔细观察可以发现,这三个Swap交换函数除了参数类型不同,其余的函数逻辑是一模一样的,那么有没有一种方法可以简化这种重复又烦琐的工作呢?
答案是有的,接下来为大家介绍一下函数模板:
函数模板的定义
模板函数定义的一般形式如下所示:
template <typename T>
ret_type func_name(parameter list)
{
// 函数主体
}
其中,各个名称所代表的含义如下图:
编辑
了解了模板函数定义的一般形式后,我们尝试套用一下前面提到的Swap的案例:
template <typename T>
//此处typename也可换为class
void Swap(T& x, T& y)
{
T temp = x;
x = y;
y = temp;
}
再编写一个主函数程序测试一下函数模板的功能:
int main()
{
int a = 3, b = 5;
double c = 3.3, d = 5.5;
char ch1 = 'e', ch2 = 'f';
Swap(a, b);
Swap(c, d);
Swap(ch1, ch2);
cout << a << " - " << b << endl;
cout << c << " - " << d << endl;
cout << ch1 << " - " << ch2 << endl;
return 0;
}
测试结果如下图,可以看到,函数模板成功完成了不同的类型数据的交换工作:编辑
函数模板的实现原理
对于函数模板,还有一个值得我们思考的问题:下图1,2,3条语句都是通过调用同一个函数来完成其相应功能的吗?编辑
答案是否定的,它们调用的并不是同一个函数,这点可以通过查看汇编的方法来验证模板的底层实现:
编辑
也就是说,函数调用的其实是函数模板生成的具体的函数.由模板生成具体函数的这一过程也被称为模板的实例化:编辑
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此.
函数模板定义多个模板参数
函数模板的模板参数也可以定义多个,如:
template <typename T1, typename T2>
void Print(T1& x, T2& y)
{
cout << x << " - " << y << endl;
}
int main()
{
int a = 3;
double b = 1.35;
char ch = 'A';
Print(a, b);
Print(a, ch);
Print(b, ch);
return 0;
}
测试运行结果如下:编辑
函数模板的实例
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化
让编译器根据实参推演模板参数的实际类型.
template<class T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
//正常隐式实例化调用
Add(a1, a2);
Add(d1, d2);
//错误隐式实例化调用
Add(a1, d1);
/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
*/
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
Add(a1, (int)d1);
return 0;
}
编辑
显式实例化
在函数名后的<>中指定模板参数的实际类型.
template<class T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main(void)
{
int a = 10;
double b = 20.0;
// 显式实例化
Add<int>(a, b);
return 0;
}
编辑
显示实例化的实际应用场景,如下代码:
template<typename T>
T* Alloc(int n)
{
return new T[n];
}
int main()
{
double* pd = Alloc(10);
return 0;
}
编辑
因为模板无法根据参数隐式推导出返回值的类型,因此就只能通过显示实例化来完成这一功能:
template<typename T>
T* Alloc(int n)
{
return new T[n];
}
int main()
{
double* pd = Alloc<double>(10);
return 0;
}
编辑
类模板
正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:
template <class T1,class T2,...,class Tn>
class class_name
{
//类的相关定义
}
//类的实例化
class_name<T1,T2,...,Tn> object_name
在这里,T 是占位符类型名称,可以在类被实例化的时候进行指定。可以使用一个逗号分隔的列表来定义多个泛型数据类型。
类模板的主要用途是当我们想要在一个项目文件中创建可以存储不同数据类型的类,比如我们需要三个栈,一个存储整形数据,一个存储浮点型数据,一个存储字符型数据,这时候创建3个仅数据类型不同的栈类就很麻烦,因此我们可以选择使用类模板来完成这项工作.代码如下:
//类模板
template<class T>
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
void Push(const T& data)
{
//CheckCapacity();
_array[_size] = data;
_size++;
}
//其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
T* _array;
int _capacity;
int _size;
};
int main()
{
//存储整形数据的栈sti
Stack<int> sti();
//存储单精度浮点型数据的栈stf
Stack<float> stf();
//存储字符型数据的栈stc
Stack<char> stc();
return 0;
}
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类.
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
结语
希望这篇关于 C++模板 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!
- 点赞
- 收藏
- 关注作者
评论(0)