今天呢,我们来学习一下C++的模板与泛型,主要学习模板的概念、函数模板的定义和调用。
一、模板的概念
1、所谓泛型编程是以独立于任何特定类型的方式编写代码,使用泛型编程时,我们需要提供具体程序实例操作的类习惯或者值;
2、模板是泛型编程的基础,模板是创建类或者函数的蓝图或者这公式,我们给这些蓝图提供足够的信息,让这些蓝图或者公式真正的转变为具体的类或者函数,这种转变发生在编译时。
3、类模板支持将类型作为参数的程序设计方式,从而实现了对泛型程序设计的直接支持。也就是说,C++模板机制允许在定义类、函数时将类型作为参数。
模板一般分为函数模板和类模板,本次学习,我们只讨论函数模板,类模板我们在后续的更新中进行讨论。
二、函数模板的定义和调用
1、函数模板的定义
我们看一段下面的代码
int funAdd(int i1, int i2)
{
int ret = i1 + i2;
return ret;
}
// 函数重载
double funAdd(double i1, double i2)
{
int ret = i1 + i2;
return ret;
}
从代码中可以看出这是一个函数重载,是一个同名函数,只是参数类型和返回值不同,那么我们怎样将这段代码改变为函数模板,其实很简单,让我们看看下面的代码
// 函数模板(公式、样板)
template <typename T>
T funAdd(T a, T b)
{
T ret = a + b;
return ret;
}
如上,我们的函数模板就定义成功了。
函数模板的特性:
(1)模板定义是用template关键字开头的,后面跟<>,<>里面称为模板参数列表(模板实参),如果模板参数列表里有多个参数,则用逗号分开。<>里必须至少有一个模板参数,模板参数前面有个typename/class(class不是用来定义类的)关键字;如果模板参数列表里面有多个模板参数,那你就要用多个typename/class 例如 <typename T,typename Q>;
(2)模板参数列表里面表示在函数定义中用到的“类型”或者“值”,也和函数参数列表类似。那我们用的时候,有的时候得指定模板实参给他,指定的时候,我们要用<>把模板实参包起来。有的时候不需要我们指定模板实参给他,系统自己能够根据一些信息推断出来;
(3)funAdd()这个函数声明了一个名字为T的类型参数,这里注意,这个T实际是类型,这个T到底代表的是什么类型,编译器在编译的时候会针对funAdd这个函数确定。
2、函数模板的调用
函数模板调用和函数调用区别不大,调用的时候,编译器会根据你调用这个函数模板时的实参去推断模板参数列表里的参数(形参)的类型。模板参数有时是推断出来的(有时需要你提供),推断的依据是根据调用这个函数的实参来推断的。
当然有的时候,光凭借函数实参是推断不出来模板参数,这个时候我们就得<>来主动的提供模板参数。
#include <iostream>
using namespace std;
// 函数模板(公式、样板)
template <typename T>
T funAdd(T a, T b)
{
T ret = a + b;
return ret;
}
int main()
{
// 系统认为是int型,所以系统能推断
// 出来模板的形参是个int型,也就是说
// 那个参数T是int型
int ret = funAdd(1, 2);
cout << ret << endl;
// 编译器在推断这个模板的形参类型之后,
// 编译器就为我们实例化一个特定版本的函数
double dret = funAdd(1.2, 2.3);
cout << dret << endl;
// 报错,系统不知道该推断成int还是double
//double ret2 = funAdd(1, 2.3);
getchar();
return 0;
}
模板函数执行结果
// int ret = funAdd(1, 2);调用之后,
// 编译器给我们的模板函数funAdd实例化
// 出来int类型版本的函数
int funAdd(int a, int b)
{
int ret = a + b;
return ret;
}
// double dret = funAdd(1.2, 2.3);调用之后,
// 编译器给我们的模板函数funAdd实例化出
// 来double类型的版本
double funAdd(double a, double b)
{
int ret = a + b;
return ret;
}
三、非类型模板参数
因为T前面有一个typename/class,这表示T代表一个类型,是一个类型参数;那么在模板参数列表里,还可以定义非类型模板参数,非类型模板参数代表的是一个值。
既然非类型模板参数代表一个值,那么我们肯定不能用typename/class这种关键字修饰这个值,我们现在就要用以前学习的传统类型名来指定非类型参数,比如非类型参数S,如果这个类型是整形,则定义为int S.
template<typename T,int S>
当模板参数实例化时,这种非类型模板参数的值值,或者是用户提供的值,或者是编译器推断的,都有可能;但是,这些值必须都是常量表达式,因为实例化这些模板是编译器在编译的时候来实例化的;
实例:
#include <iostream>
using namespace std;
template <int a, int b>
int funAdd2()
{
int ret = a + b;
return ret;
}
template <typename T, int a, int b>
int funAdd2(T c)
{
int ret = (int)c + a + b;
return ret;
}
// 对两个字符串进行比较
template<unsigned int L1, unsigned int L2>
inline // 模板函数可以是inline的,
//inline的位置放在模板参数列表之后
int charsComp(const char(&p1)[L1], \
const char(&p2)[L2])
{
return strcmp(p1, p2);
}
int main()
{
// 显示指定模板参数---在<>中提供额外的信息
int ret = funAdd2<12, 13>();
cout << ret << endl; //25
//int a = 12;
// 报错,非类型模板参数,值必须是在编译的时候
// 就能够确定,
//int result = funAdd2<a, 13>();
//因为实例化函数模板实在编译的时候干的事。
int ret2 = funAdd2<int, 11, 12>(13);
cout << ret2 << endl; //36
// 系统会以我们<>传递过去的类型为准,
//而不是用13来推断什么类型
int ret3 = funAdd2<double, 11, 12>(13);
cout << ret3 << endl; //36
// 没有提供非类型模板参数,系统会根据test1的
// 长度6个,test长度5个,取代L1,L2;
int ret4 = charsComp("test1", "test");
cout << ret4 << endl; // 1
getchar();
return 0;
}
执行结果
模板定义不会导致编译器生成代码,只有在我们调用这个函数模板时,使编译器为我们实例化了一个特定版本的函数之后,编译器才会生成代码。
好了,今天的分享到这里就结束了,如果大家喜欢的话,可以给我点个赞哦!!!
网友评论