C++中的泛型详细讲解
创始人
2024-03-03 01:07:05
0

1.定义

它是一种泛化的编程方式,其实现原理为程序员编写一个函数/类的代码示例,让编译器去填补出不同的函数实现。允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。

2.模板

模板是泛型编程的基础,是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector vector 。可以使用模板来定义函数和类,我们来具体分析一下模板函数和模板类的创建和使用:

2.1 模板函数

我们想实现像Python一样,一个带有参数的方法,它的相同参数可以传递不同类型的值。我们通过下面的例子来了解一下:

#include
using namespace std;template
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
using namespace std;template
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
#include
using namespace std;template
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类型
#include
using namespace std;template
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(a, c);        // 直接指定cout << "a:" << a << " c:" << c << endl;
}
/*
使用第二种方式的时候,编译器会对另外一个不匹配的参数进行隐式类型转换。如果转换不成功,则会报错。另外注意的是,函数模板参数T同样可以用来作为返回值,但是不能通过返回值来推断参数T的类型。比如下面这个函数,我们在使用的时候就需要直接指定模板参数T,而不能写一个int* ptr=test(10)让编译器通过“返回值是int*接收的,所以函数模板参数T是int”来推断。
*/

2.3 函数模板实例化

函数模板支持给予参数缺省值,当一个参数不确定的时候,函数模板是支持给予缺省值的

template
T* Test(int num)
{return new T[num];
}

当有多个模板参数时,缺省值需要从右往左给,当然函数模板的传参也支持缺省值:

#include
using namespace std;template
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
using namespace std;template
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(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
void Test(T arg1, T arg2);
//源文件
//定义
template
void Test(T arg1, T arg2)
{cout << arg1 << " " << arg2 << endl;
}
//在源文件中显式实例化
template
void Test(int arg1, int arg2);
template
void Test(double arg1, double arg2);

显式实例化需要对我们要用的所有函数进行实例化,比如你需要用double类型,只显示实例化了int类型是不行的,依旧会报错。这样感觉非常多余……!所以还是把声明和定义放在同一个文件里面清晰明了一些。

3 类模板

正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:

template  class class-name {
.
.
.
}

在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。

下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:

#include 
#include 
#include 
#include 
#include using namespace std;template 
class Stack { private: vector elems;     // 元素 public: void push(T const&);  // 入栈void pop();               // 出栈T top() const;            // 返回栈顶元素bool empty() const{       // 如果为空则返回真。return elems.empty(); } 
}; template 
void Stack::push (T const& elem) 
{ // 追加传入元素的副本elems.push_back(elem);    
} template 
void Stack::pop () 
{ if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); }// 删除最后一个元素elems.pop_back();         
} template 
T Stack::top () const 
{ if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); }// 返回最后一个元素的副本 return elems.back();      
} int main() 
{ try { Stack         intStack;  // int 类型的栈 Stack stringStack;    // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout << intStack.top() < cerr << "Exception: " << ex.what() <::pop(): empty stack

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...