1.定义
它是一种泛化的编程方式,其实现原理为程序员编写一个函数/类的代码示例,让编译器去填补出不同的函数实现。允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
2.模板
模板是泛型编程的基础,是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector 或 vector 。可以使用模板来定义函数和类,我们来具体分析一下模板函数和模板类的创建和使用:
2.1 模板函数
我们想实现像Python一样,一个带有参数的方法,它的相同参数可以传递不同类型的值。我们通过下面的例子来了解一下:
#include<iostream>
using namespace std;
template<typename T>
void Test(T& arg1,T& arg2) // 这是一个实现两个变量值交换的函数
{
T temp = arg1;
arg1 = arg2;
arg2 = temp;
}
// typename 是定义模板的关键字,可以用class来替代(注意不能用struct)
int main()
{
int a = 10, b = 20;
double c = 5.2, d = 10.5;
Test(a, b);
Test(c, d);
cout << a << " " << b << endl;
cout << c << " " << d << endl;
}
// 输出结果:
20 10
10.5 5.2
// 我们交换了int类型的a与b的值,double类型c和d的值
如果我们将int和double同时传给Swap这个函数,那么编译器会报错,表示模板参数T不明确,那么我们需要做如下改动:
(1)把函数传参中的引用去掉
(2)把a强制转换成double类型,Swap((double)a, c)
#include<iostream>
using namespace std;
template<typename T>
void Test(T arg1,T arg2) // 把“&”引用去掉
{
T temp = arg1;
arg1 = arg2;
arg2 = temp;
cout << arg1 << " " << arg2 << endl;
}
int main()
{
int a = 10;
double c = 5.2;
Test((double)a, c);
cout << "a:" << a << " c:" << c << endl;
}
// 输出结果:
5.2 10
a:10 c:5.2
// a,c值没有变,是因为我们传参是值传递
接下来我们看一下,多个模板参数的情况:
#include<iostream>
#include<typeinfo>
using namespace std;
template<typename T1, typename T2>
void Info(T1 arg1,T2 arg2)
{
cout << typeid(arg1).name() << endl;
cout << typeid(arg2).name() << endl;
}
int main()
{
int a = 10;
double c = 5.2;
Info(a, c);
cout << "a:" << a << " c:" << c << endl;
}
// 输出结果:
i
d
a:10 c:5.2
可以看到,实际上函数在调用这个模板的时候,已经实例化了这个函数(即替换模板参数为正确参数类型)这时候在后台处理的时候,其实Show函数已经实例化为了下面这个样子
void Info(int arg1,double arg2)
{
cout << typeid(arg1).name() << endl;
cout << typeid(arg2).name() << endl;
}
2.2 函数模板实例化
上面的方式,是编译器自动帮我们实例化模板参数。在实际使用中,我们还可以自己指定实例化为什么类型
- 利用强制类型转换
- 使用
<int>
直接指定实例化为int类型
#include<iostream>
using namespace std;
template<typename T>
void Test(T arg1,T arg2) // 把“&”引用去掉
{
T temp = arg1;
arg1 = arg2;
arg2 = temp;
cout << arg1 << " " << arg2 << endl;
}
int main()
{
int a = 10;
double c = 5.2;
Test((double)a, c); // 强制类型转换
Test<int>(a, c); // 直接指定
cout << "a:" << a << " c:" << c << endl;
}
/*
使用第二种方式的时候,编译器会对另外一个不匹配的参数进行隐式类型转换。如果转换不成功,则会报错。
另外注意的是,函数模板参数T同样可以用来作为返回值,但是不能通过返回值来推断参数T的类型。比如下面这个函数,我们在使用的时候就需要直接指定模板参数T,而不能写一个int* ptr=test(10)让编译器通过“返回值是int*接收的,所以函数模板参数T是int”来推断。
*/
2.3 函数模板实例化
函数模板支持给予参数缺省值,当一个参数不确定的时候,函数模板是支持给予缺省值的
template<typename T=char>
T* Test(int num)
{
return new T[num];
}
当有多个模板参数时,缺省值需要从右往左给,当然函数模板的传参也支持缺省值:
#include<iostream>
using namespace std;
template<typename T>
void Test(T arg1,T arg2=20) // 把“&”引用去掉
{
T temp = arg1;
arg1 = arg2;
arg2 = temp;
cout << arg1 << " " << arg2 << endl;
}
int main()
{
int a = 10;
Test(a);
}
2.4 模板函数与普通函数同时存在情况
函数在调用的时候,首先会去调用已经存在的函数。当参数和已存在的函数不匹配时,才会调用函数模板
#include<iostream>
using namespace std;
template<typename T>
void Test(T arg1,T arg2 = 90)
{
cout << "Test temp " << arg1 << " " << arg2 << endl;
}
void Test(int arg1,int arg2)
{
cout << "Test " << arg1 << " " << arg2 << endl;
}
int main()
{
int a = 10, b = 20;
double c = 5.2, d = 10.5;
Test(a);
Test(a, b);
Test(a, (int)c); // 强转
Test((double)a, c); // 强转
Test<int>(a, c); // 直接指定为int
}
// 输出结果:
Test temp 10 90
Test 10 20
Test 10 5
Test temp 10 5.2
Test temp 10 5
2.5 函数模板不支持定义和声明分离
函数模板的声明和定义要放在一个头文件中。在部分使用场景,会使用.hpp
来表示这个头文件是包含了函数定义的(即.h和.cpp
的集合体)。需要注意,这并不是一个硬性要求,你也可以直接使用.h
,并将声明和定义放入其中。因为单独的.h
声明会在源文件顶部展开,而此时函数模板正常推演参数,但编译器并没有找到函数的实现,即这是一个没有地址的函数。从而导致编译器找不到函数的地址,产生了符号表的链接错误。其实是有的,我们可以在模板函数定义的.cpp
中对我们需要使用的函数进行显式实例化指定
//头文件
//声明
template<typename T>
void Test(T arg1, T arg2);
//源文件
//定义
template<typename T>
void Test(T arg1, T arg2)
{
cout << arg1 << " " << arg2 << endl;
}
//在源文件中显式实例化
template
void Test<int>(int arg1, int arg2);
template
void Test<double>(double arg1, double arg2);
显式实例化需要对我们要用的所有函数进行实例化,比如你需要用double类型,只显示实例化了int类型是不行的,依旧会报错。这样感觉非常多余……!所以还是把声明和定义放在同一个文件里面清晰明了一些。
3 类模板
正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:
template <class type> class class-name {
.
.
.
}
在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。文章来源:https://www.toymoban.com/news/detail-530955.html
下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:文章来源地址https://www.toymoban.com/news/detail-530955.html
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
// 输出结果:
7
hello
Exception: Stack<>::pop(): empty stack
到了这里,关于C++中的泛型详细讲解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!