文章目录
前言
我们都知道,如果想要实现交换函数的时候。C语言中呢是不支持函数重载的,所以需要手动实现函数,而且函数名不能相同。但是如果交换的类型有很多呢?比如整形,浮点型,字符型等之类的类型呢?那就要手动实现每个函数。虽然C++可以函数重载,但是依然要手动实现函数。那要怎样可以通过一个函数来实现所有不同类型的交换呢?那就要用到模板了!
模板呢我们今天来了解两个模板:
1.函数模板(Function templates)
2.类模板(class templates)
一、函数模板
1、函数模板的概念以及格式
概念:
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
格式:
template<typename T1,typename T2 ,...,template Tn>
返回值类型 函数名(参数列表){}
例如:实现交换函数
cpp
template <typename T>//模板类型
void Swap(T& x, T& y)//函数模板
{
T tmp = x;
x = y;
y = tmp;
}
**注意:**template是用来定义模板参数的关键字,也可以是class,但是不能用struct代替class
2、函数模板的原理
函数模板是一个蓝图,他本身并不是函数 。是编译器用使用方式产生特定具体类型的函数模具。所以其实模板就是要把我们重复做的事交给编译器。在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
3、函数模板的实例化
函数的实例化分为:
1.隐式实例化
隐式实例化就是编译器根据实参来推导模板参数的实际类型,
2.显示实例化
在函数名后的<>中指定模板参数的实际类型
cpp
#include<iostream>
using namespace std;
template<class T>
T Swap(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout<<Swap(a, a1)<<endl;
cout << Swap(b, b1) << endl;
return 0;
}
先看代码,这里呢代码是可以正常运行的。那是因为我们在传参的时候传的是相同类型的参数。那我们思考一下,要是传不同类型的参数还会正常运行吗?像这样:
cpp
#include<iostream>
using namespace std;
template<class T>
T Swap(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout << Swap(a, b) << endl;
return 0;
}
ok,这里代码是会报错的。运行不了的。这里需要注意的是,当编译器在实例化时,需要推演参数类型,就拿上面举例,这里编译器通过实参a将T推演为int类型,把b推演成double类型。但是模板类型中只有一个T,所以这里编译器无确定时int类型还是double类型而报错。
如果想要代码正常运行下去,那么就要用到一些方法来处理一下:这里呢有三种方法:
- 用户自己手动强制转换
cpp
#include<iostream>
using namespace std;
template<class T>
T Swap(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout << Swap((double)a, b) << endl;//强制转换
cout << Swap(a,(int) b) << endl;//强制转换
return 0;
}
- 显示实例化
cpp
#include<iostream>
using namespace std;
template<class T>
T Swap(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout << Swap<int>(a, b) << endl;//显示实例化
cout << Swap<double>(a, b) << endl;//显示实例化
return 0;
}
- 增加一个模板参数
cpp
#include<iostream>
using namespace std;
template<class T1,class T2>
T1 Swap(const T1& x, const T2& y)//这里返回类型时可以改变的,这里也可以时T2,
根据情况而定
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout << Swap(a, b) << endl;
cout << Swap(a1, b1) << endl;
return 0;
}
注意:在模板中,编译器是不会进行类型转换操作的。
4、模板参数的匹配原则
1.一个非函数模板可以和一个同名的函数模板同时存在,而且这个函数模板还可以被实例化成一个非函数模板。
cpp
#include<iostream>
using namespace std;
template<class T>//通用加法函数
T Swap(const T& x, const T& y)
{
return x + y;
}
int Swap(int x, int y)//int的加法函数
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout << Swap(1, 2) << endl;//与非函数模板匹配时,编译器不需要特化。
cout << Swap<int>(1, 2) << endl; //调用编译器特化的Add版本
return 0;
}
2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
cpp
#include<iostream>
using namespace std;
template<class T1,class T2>//通用加法函数
T1 Swap(const T1 & x, const T2& y)
{
return x + y;
}
int Swap(int x, int y)//int的加法函数
{
return x + y;
}
int main()
{
int a = 10,a1=20;
double b = 2.2,b1=3.3;
cout << Swap(1, 2) << endl;// 与非函数模板类型完全匹配,不需要函数模板实例化
cout << Swap(1, 2.2) << endl; // 模板函数可以生成更加匹配的版本,编译器根据实
参生成更加匹配的Add函数
return 0;
}
注意:模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
二、类模板
1.类模板的定义与格式
cpp
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
2.类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

3.用类模板来实现栈
了解了模板之后,实现栈不同类型的数据的储存。不像之前在栈中用typedef那样了麻烦了。如果哪一个数据储存int 一个数据储存double等。那么typedef就不行了。这个时候模板的优势就体现出来了。
cpp
#include<iostream>
using namespace std;
template <class T>
class Stack
{
public:
Stack(int n = 4)//默认构造函数
:_arr(new T[n])
, _top(0)
, _capacity(n)
{}
~Stack()//析构函数
{
delete[]_arr;
_arr = nullptr;
_top = _capacity = 0;
}
void Push(const T&x)//插入数据
{
if (_top == _capacity)//扩容
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _arr, sizeof(T) * _top);
delete[]_arr;
_arr = tmp;
_capacity *= 2;
}
_arr[_top++] = x;
}
private:
T* _arr;
int _top;
int _capacity;
};
int main()
{
Stack<int> str1;//储存int类型
str1.Push(1);
str1.Push(2);
str1.Push(3);
str1.Push(4);
Stack<double>str2;//储存double类型
str2.Push(1.1);
str2.Push(2.2);
str2.Push(3.3);
str2.Push(4.4);
return 0;
}
当成员函数的定义和声明分离时,这里需要注意的是:类模板呢只能在类里面使用,出了类就不能使用了。这个时候就要重新定义一个模板了。就像这样那个:
cpp
#include<iostream>
using namespace std;
template <class T>
class Stack
{
public:
Stack(int n = 4)//默认构造函数
:_arr(new T[n])
, _top(0)
, _capacity(n)
{}
~Stack()//析构函数
{
delete[]_arr;
_arr = nullptr;
_top = _capacity = 0;
}
void Push(const T& x);//插入数据
private:
T* _arr;
int _top;
int _capacity;
};
template<class T>//重新定义模板
void Stack<T>::Push(const T& x)//类模板要显示实例化
{
if (_top == _capacity)//扩容
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _arr, sizeof(T) * _top);
delete[]_arr;
_arr = tmp;
_capacity *= 2;
}
_arr[_top++] = x;
}
int main()
{
Stack<int> str1;//储存int类型
str1.Push(1);
str1.Push(2);
str1.Push(3);
str1.Push(4);
Stack<double>str2;//储存double类型
str2.Push(1.1);
str2.Push(2.2);
str2.Push(3.3);
str2.Push(4.4);
return 0;
}
那现在如果把定义的模板哪里改称其他的呢?比如X,他还能继续运行吗?其实是可以的。因为这里X与T其实就是一个代号。当我们在调用函数的时候就编译器就已经确定了T 的类型这里只不过是把X替换成了T。
三、模板的优点以及缺点
优点:
-
代码重用:模板允许程序员编写独立于特定数据类型的代码。 这意味着可以为多种数据类型重用相同的代码,减少了代码的冗余。
-
类型安全:模板在编译时进行类型检查,这有助于捕获类型错误,并减少运行时错误的可能性。由于类型信息在编译时是已知的,编译器可以生成更高效的代码。
-
灵活性:模板提供了极高的灵活性,因为它们可以在任何支持所需操作的数据类型上使用。 这包括用户定义的类型和STL(StandardTemplate Library)容器等内置类型。
-
性能:由于模板在编译时生成特定类型的代码,因此它们通常比使用虚函数或函数指针的方法更快。 此外,由于类型信息在编译时是已知的,编译器可以进行优化。
-
抽象:模板提供了一种抽象机制,使得程序员可以专注于算法本身,而不是关注数据类型的具体实现。 这有助于保持代码的清晰和简。
缺点:
-
编译时间:由于模板在编译时生成代码,因此使用大量模板的代码可能会导致编译时间显著增加。特别是当模板实例化涉及复杂类型或大量类型时,编译时间可能会变得非常长.
-
代码膨胀:模板实例化会导致生成的代码量增加,因为每个不同的类型参数组合都会生成不同的代码。 这可能会增加最终可执行文件的大小。
-
错误信息难以理解:当模板代码出现错误时,编译器生成的错误信息可能会非常难以理解。 由于模板实例化涉及多个层次的代码生成和类型推导,因此错误可能源自多个地方,并且错误信息可能非常冗长和复杂。
-
对编译器的要求:为了充分利用模板的功能,需要一个支持模板的编译器。 尽管大多数现代C++编译器都支持模板,但某些编译器可能无法完全支持某些模板特性或优化。
-
使用门槛:对于初学者来说,模板可能相对较难理解和使用。
总结
今天的分享就到这里了。祝大家天天开心!充实每一天。