(一〇六)函数模板

简介:

函数模板的意义在于,可以在不同的参数下,起到同样的作用。

按照教程所说,它们使用泛型来定义函数,其中泛型可用具体的类型(如int、double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。

 

由于模板允许以泛型(而不是具体的类型)的方式编写程序,因此在有时也会被称为是通用编程。

 

由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。

 

格式:

template <typename xx>
void 函数名(xx &a, xx &b)
{
	xx name;
	name = a;
	a = b;
	b = name;
}

解释:

①xx指的是用于替代类型的名字,他将在后面被放置在平常置于 类型 的位置。例如,int name; 在这里是xx name; 

在参数列表,也变成xx &a和xx &b

xx可以用其他替代,就像变量名那样。

 

②template和typename是必须的。

 

 

使用函数模板的原因:

①以以上代码为例,作用是交换两个变量的值。

假设要交换2个int值,那么函数头为:void aaa(int &a,int&b);

假如又要交换2个double值,那么又需要一个函数:void bbb(double&a,double &b);

如果又需要交换2个char值,那么需要第三个函数。

使用函数模板的话,就可以合三(或更多)为一,降低工作量,减少出错几率。

 

②在C++98加入关键字typename之前,使用class,来创建模板,方法同typename。

如果不考虑向后兼容的问题,则应使用typename。若考虑到兼容,则可使用class。

 

如代码:

#include<iostream>
using namespace std;

template<typename xx>	//注意,这里是尖括号<>
void abc(xx & a, xx &b)	//函数模板不使用函数原型的话,且需要前置在其他函数之前。如果像普通函数那样使用,则需要函数原型,且函数原型包含template那一行。另外,需要交换的话,需要使用引用。
{
	xx c;
	c = a;
	a = b;
	b = c;
}

template<class xx>	//这行和下一行代码应该连续使用,这里也可以继续使用xx
void def(xx a, xx b)	
{
	cout << a << " " << b << endl;
	a++;
	b++;
	cout << a << " " << b << endl;
}

int main()
{
	char a = 'a';
	char b = 'b';
	cout << "a = " << a << ", b = " << b << endl;
	abc(a, b);
	cout << "a = " << a << ", b = " << b << endl;
	int c = 1;
	int d = 2;
	cout << c << " " << d << endl;
	abc(c, d);
	cout << c << " " << d << endl;

	def(a, b);
	def(c, d);
	system("pause");
	return 0;
}

输出:


a = a, b = b
a = b, b = a
1 2
2 1
b a
c b
2 1
3 2
请按任意键继续. . .
a = a, b = b
a = b, b = a
1 2
2 1
b a
c b
2 1
3 2
请按任意键继续. . .

对模板的解释:

①函数模板不能缩短可执行程序,最终的代码将不包含函数模板,只包含根据模板、参数而为程序生成的实际函数。

例如代码中的函数abc,最终将仍由两个函数来起作用。分别是void abc(int&a, int *b)和vodi abc(char&a , char &b);

 

②使用函数模板的好处,它使生成多个函数定义更简单、可靠。

例如上述代码,如果手动写的话,需要写两组(每组2个)重载函数。

 

③常见的情形是:将模板放入头文件之中,并在需要使用模板的文件中包含头文件。(头文件在第九章,所以还没学到)

 

④并非所有函数模板的参数类型,都是泛型(例如之前的xx),也可以是具体的类型。例如将 xx&b改为char &b等。

 

⑤注意,多个函数模板,每个之前都要加上template<typename 名字>这一行代码。

 

 

 

重载的模板:

使用函数重载的模板时,和函数重载的知识一样,需要用特征标区分开。

例如:

template <typename xx>
void aaa(xx a, xx b)
{
	函数内部代码
}
template <typename xx>
void aaa(xx a,xx b, int c)
{
	函数内部代码
}

注意:

①因为是函数模板,所以xx可以充当多种类型名使用。所以,需要一个额外的参数,来区分重载的两个函数。

 

②也可以用不同类型的参数来区分开。例如:


template <typename xx>
void aaa(xx a, xx b)	//参数为按值传递
{
	cout << a << ", " << b << endl;
}

template <typename xx>	//参数为指针
void aaa(xx *a,xx *b)
{
	cout << "a = " << a << endl;
}

当传递的参数为基本类型时,如int、double、char、string等,则使用第一个函数模板;

若传递的参数为指针时,则使用第二个模板(原因涉及到函数最佳化匹配,见后);

 

如代码:

#include<iostream>
#include<string>
using namespace std;

template <typename xx>//原型时需要带上这一行代码
void aaa(xx a, xx b);

template <typename xx>	//参数为指针
void aaa(xx *a, xx *b);

int main()
{
	int a = 1;
	int b = 2;
	aaa(a, b);

	string aa = "abc";
	string bb = "ggg";
	aaa(aa, bb);

	int *c = &a;
	int *d = &b;
	aaa(c, d);
	system("pause");
	return 0;
}

template <typename xx>
void aaa(xx a, xx b)	//参数为按值传递
{
	cout << a << ", " << b << endl;
	cout << a + b << endl;
}

template <typename xx>	//参数为指针
void aaa(xx *a, xx *b)
{
	cout << "a = " << a << endl;
}

输出:


1, 2
3
abc, ggg
abcggg
a = 0033F8DC
请按任意键继续. . .

总结:

①从以上可以看出,模板支持int、double、char、string,以及指针。由此可以推断出,模板也一定支持long、longlong、float、long double等。

 

但仅以上代码来说,不支持(?)arrayvector。因为这两种既是模板。(可能也因为能显示数组,所以不一样?)

 

以上代码 而言,也不支持结构(模板是允许结构类型的,只是这个不支持)。如果需要支持结构,则在编写代码的时候需要注意。例如,以下模板就适合结构作为参数名使用:

void abc(xx & a, xx &b)
{
	xx c;
	c = a;
	a = b;
	b = c;
}

原因在于模板的局限性。

 

 

模板的局限性:

如果使用模板,那么参数需要能满足模板内的函数。

以上面的template <typename xx>void aaa(xx a, xx b) 为例,函数内部有a+b的代码,在套用string类的变量aa和bb是可行的。

假如是a*b的代码,那么必然不支持string类,于是,只能使用int、double等算数类型的变量。

 

因此,这就是模板的局限性。

又比如假如使用结构,以以上代码而言,假如需要使用结构。那么参数要么是结构中的某个变量,要么在函数内的代码,预先附加逗号运算符。

例如代码1:

#include<iostream>
#include<string>
using namespace std;

struct mm
{
	int a;
};

template <typename xx>
void aaa(xx a, xx b);

int main()
{
	mm l, p;
	l.a = 1;
	p.a = 2;
	aaa(l, p);


	system("pause");
	return 0;
}

template <typename xx>
void aaa(xx a, xx b)	//参数为按值传递
{
	cout << a.a << ", " << b.a << endl;

}

在这种模板里,直接附加逗号运算符,因此,如果遇见非结构,或者其他结构里面,没有变量名为a的,就无法调用该函数。

 

例子2:

仅修改关键代码:

......

aaa(l.a, p.a);

......

void aaa(xx a, xx b)   

{

    cout<< a << ", " << b << endl;

 

}

 

在这个模板中,传递的参数是结构的某个变量,于是就可以适用非结构的情况。

 

个人感觉,模板有一些类似文本替换的特性。即用int、double等类型,替换模板的类型的名字(如上面代码之中的xx),然后用参数替代模板内的各个变量。

假如替换后不能正常运行,则模板无法使用;

假如替换后可以运行(C++允许这种方法),则模板可以使用。

 

因此,使用模板,只需要参数代入后,模板能正常运行,结果符合预期,就可以用。

 

 

 

显式具体化:

显示具体化,就其用途来说,编译器在找到与函数调用时匹配的具体化定义时,将使用该定义,而不是使用模板。

 

也就是说,如果函数在调用时,有匹配的具体化定义,他会优先使用它,如果没有,于是再找符合的模板。

 

具体化机制的形式,随着C++的演变而不断变化。(所以说可能有多种方式咯)

 

根据教程,以下是C++标准定义的形式——第三代具体化(ISO/ANSIC++标准),C++98标准使用以下的方法。

 

①对于给定的函数名,可以有非模板函数模板函数显式具体化模板函数、以及他们的重载版本

 

显式具体化的原型的格式:template<>void函数名<类型名>(参数列表)

注①:参数列表像非模板函数那样使用

注②:<类型名>可以被省略

 

非模板函数:void abc(int);

模板函数:template <class xx>void abc (xx);

显式具体化函数:template<>voidabc<double>(double);

重载版本(依次为):

void abc(int, int);

template<class xx>voidabc(xx a,xx b);

template<>voidabc<double>(double a,double b);

 

②显式具体化的原型和定义,应以template<>打头,并通过名称来指出类型。

如:template<>void abc<int>(int a); 这样,在使用int变量的时候,就会优先使用这个显示具体化的函数定义。

 

③具体化优先于模板,而非模板函数优先于具体化和常规模板。

 

如代码:

#include<iostream>
#include<string>

using namespace std;

void abc(int);	//非模板函数
void abc(int, int);	//非模板函数的重载函数
template<class xx>void abc(xx);	//模板函数
template<class xx>void abc(xx, xx);	//模板函数的重载函数
template<>void abc<double>(double);	//显式具体化
template<>void abc<double>(double, double);	//显式具体化的重载函数

//优先级为:非模板函数——》显式具体化——》模板函数


int main()
{
	int a = 1;
	int b = 2;
	double c = 3.1;
	double d = 4.5;
	char e = 'a';
	char f = 'b';
	string g = "hello";
	string h = "world";
	int*i = &a;
	int*j = &b;
	abc(a);	//int类型,调用非模板函数
	abc(a, b);	//int类型,调用非模板重载
	
	abc(c);	//double类型,调用显示具体化
	abc(c, d);	//调用显式具体化重载
	
	abc(e);	//char类型,调用模板
	abc(e, f);	//调用模板重载
	abc(g);	//string类型,模板
	abc(g, h);	//模板重载
	
	abc(i);	//参数为指针,调用模板
	abc(i, j);
	abc(*i);	//参数为指针的值(int类型),调用非模板函数
	abc(*i, *j);

	system("pause");
	return 0;
}

void abc(int a)
{
	cout << "a = " << a << endl;
}

void abc(int a,int b)
{
	cout << "a = " << a << ", b = " << b << endl;
}
template<>void abc<double>(double a)
{
	cout << "double a = " << a << endl;
}
template<>void abc<double>(double a, double b)
{
	cout << "double a = " << a << ", double b = " << b << endl;
}

template<class xx>void abc(xx a)
{
	cout << "xx = " << a << endl;
}
template<class xx>void abc(xx a,xx b)
{
	cout << "xx_1 = " << a << ", xx_2 = " << b << endl;
}

输出:


a = 1
a = 1, b = 2
double a = 3.1
double a = 3.1, double b = 4.5
xx = a
xx_1 = a, xx_2 = b
xx = hello
xx_1 = hello, xx_2 = world
xx = 0029FCD8
xx_1 = 0029FCD8, xx_2 = 0029FCCC
a = 1
a = 1, b = 2
请按任意键继续. . .

总结:

①单用指针作为参数时,若无相应的函数(参数为指针的),则调用模板,输出为指针(这里为地址);

 

②若使用指针外加解除运算符,则要看指针指向的地址的值是什么类型,如果该类型有对应的非模板函数、或显式具体化,则按优先顺序调用,若无,则使用模板。

 

③一个参数,如果匹配非模板函数,那么即使有适合他的显式具体化和模板函数,它依然会调用非模板函数;

 

如果他无法匹配模板函数,那么看是否有匹配他的显式具体化,如果有,则匹配之,忽视模板函数;

 

如果无法匹配显式具体化,则看是否有匹配它的模板函数,如果有,则匹配之;

 

如果以上三者都不匹配,那么编译器显示函数调用错误。

 

 

④同名函数可以同时存在非模板函数(及重载版本)、显式具体化(及重载版本)、模板函数(及重载版本);

 

⑤疑问:为什么在有非模板函数的情况下,使用显式具体化?不能用非模板函数替代显式具体化么?

某个解释(但我没看懂):某些时候必须使用显式特化,譬如模板元编程的时候作为递归的终止条件
template<int ival>int func(int*p)
{
  return *p+func<ival-1>(p+1)

}
template<>int func<1>func(int *p)
{
return *p;
}
void main()
{
 int ia[1000];
 func<1000>(ia);
}

 

 

 

 

实例化和具体化:

前提:函数在使用模板的时候,模板本身并不会生成函数定义,而是根据传递的参数来生成相应类型的定义,模板是一个生成函数定义的方案。

 

编译器在使用模板为特定类型生成函数定义时,得到的是函数实例(instantiation)。

例如:在上面那一个代码之中,调用函数abc(e); ,由于参数e是char类型,不匹配非模板函数和显式具体化,于是调用模板void abc(xx),导致编译器生成了abc()的一个实例。该实例为使用int类型。

 

模板并非函数定义,但使用int的模板实例(void abc(int))是函数定义,这种实例化方式称为隐式实例化(implicit instantiation)(和显式实例化相对应,注意,不是显式具体化),编译器之所以知道需要进行定义,是因为程序在调用模板abc()函数时,提供了int参数。(于是,对应提供double参数就生成使用double类型abc()的实例——如果没有显式实例化和非模板函数的话)。

 

显式实例化(explicit instantiation)在最初编译器是不允许的,只允许模板通过参数来隐式实例化,但后来C++允许了显式实例化,这意味着编译器可以直接命令编译器创建特定的实例,如abc<int>()

 

——具体来说,例如abc调用两个参数,一个是int a一个是double b,使用模板的话,需要两个参数类型相同(比如都是int),使用显式实例化调用函数abc<int>(a,b),则b被强制转换为int类型,于是模板创建了一个int类型的实例。

 

其语法是:template 返回类型函数名<类型名>(参数列表);   

例如:template voidabc<int>(int);

 

在能实现这种特性的编译器看到上述声明后,将使用函数模板生成一个使用int类型的实例。也就是说,该声明的意思是“使用函数模板生成int类型的函数定义。”

 

与显式实例化不同,显式具体化使用以下两个等价的声明之一:

(1)template<>void函数名<类型名>(参数列表);

如:template<>void abc<int>(int);

(2)template<>void函数名(参数列表);

如:template<>void abc(int);

 

显式具体化和显示实例化的区别在于,显式具体化的声明的意思是:“不要使用模板来生成函数定义,而应该使用专门为int类型显示地定义的函数定义”。也就是说,显式具体化必须有自己的函数定义(那显示实例化需要有么?应该不需要吧?

 

根据我查资料来看,显示实例化的一个用途,是在多个.cpp文件里,共享一个模板,

你是只有一个 cpp 的情况如果有多个 cpp 文件再使用这个模版你必须把它放在头文件里然后每个 cpp 都要 #include 这个头文件显示实例化之后头文件里只需要声明然后在其中一个 cpp 里面实现并显示实例化其它的 cpp 就可以直接用了.
具体可以 google 一下 "模版声明实现分离"

 

虽然并看不懂。

 

notice:试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错。

 

比较常见的显式实例化的使用方法是:

         doublea = 1.1;

         intb = 2;

         abc<int>(a,b); //强制转换为int类型实例化

         abc<double>(a,b);   //强制转换为double类型实例化

 

一般情况下,使用template voidabc<int>(int,int)这样的,似乎并没有什么必要,应该在需要的时候才使用显式实例化。大部分时候隐式实例化貌似可以搞定。

 

 

 

 

编译器选择哪个函数版本:

当函数涉及到函数重载(不同参数列表)、函数模板和函数模板重载(可能创建多种隐式实例化)时,C++需要一个良好的策略来决定为函数匹配哪一个函数定义,尤其是在有多个参数时,这个过程称为重载解析(overloading resolution)。

 

过程大致如下进行:

①创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数;(即如果调用1个参数,使用该名称的函数,凡是只需要1个参数但不考虑类型,就将被添加到可行函数列表)(只考虑特征标,不考虑返回值);

 

②使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包含实参类型与相应的形参类型完全匹配的情况。例如,使用float参数的函数调用可以将该参数转换为double,从而与double形参匹配,而模板可以为float生成一个实例。(而若函数特征标是指针,而float参数不能转换为指针,则这个函数不会被添加到可行函数列表之中)

 

③确定是否有最佳的可行函数。如果有,则使用它,如果没有,则该函数调用出错。

 

 

 

确定最佳的可行函数的优先级:

①完全匹配,但常规函数优先于模板;(按照匹配顺序,非模板优先于显式具体化优先于模板)

 

②提升转换(例如char(这个可以表示为ANSCII编码,是int值)、short之类可以自动转换为int,float可以自动转换为double(都是浮点类型));

 

③标准转换(例如int转换为char,long转换double(非浮点转换为浮点));

 

④用户定义的转换,如类声明中定义的转换。——这个不理解

 

提升转换指:

数据类型的内存大小是不一样的,有的小(如char),有的大(如int)

提升转换就是将小的换成大的

比如:

char a = 'A';

int i = a;

在执行赋值语句时,由于两者的数据类型不一样,所以先将字符变量a隐式转换为int型

这就是提升转换

 

关于内存大小(在我win7 64位电脑上):

char(1)<int(4)=float(4)=long(4)<double(8)=longlong(8)=long double(8);

 

标准转换包括:

A、从任何整数类型或枚举类型到其他整数类型(除了提升) ——long转换int

B、从任何浮点类型到其他浮点类型(除了提升转换)——double到float

C、从任何整数类型到浮点类型,或者反之——int到double、float到int

D、整数0到指针类型、任何指针类型到 void 指针类型——不懂

E、从任何整数类型、浮点类型、枚举类型、指针类型到bool

感觉是:占内存小的到占内存大的类型是提升转换,其他是标准转换。

 

例如:

提升转换:char->short(及以上),short到int(及同类),但似乎int到long long并不算。

其他为标准转换。

 

 

如有函数调用:

int a=1;

abc(a);

 

首先创建候选函数列表(特征标数量相同);

void abc(int*);     //1#  非模板函数

void abc(double);       //2#  非模板函数

char* abc(char);        //3#  非模板函数

char abc(char*);        //4#  非模板函数

double abc(float);      //5#  非模板函数

double* abc(double*);      //6#  非模板函数

template<class xx>voidabc(xx);     //7#          模板函数

template<>voidabc<int>(int);        //8#   显式具体化

注意:假如有模板template<class xx>xxabc(xx); 由于特征标和7#完全相同,只有返回值不同,而函数重载最重要的是特征表不同,在函数模板里,通常体现为函数参数数目不同,因此应判定其与7#不能共存。

 

 

然后创建可行函数列表:

1#是指针,无法被隐式转换,排除;

2#是int可以被转换为double,添加;

3#是int可以被转换为char,添加;

4#是指针,排除;

5#是float,添加;

6#是指针,排除;

7#可以,添加;

8#是显式具体化,int可以,添加;

于是可行函数列表有:

void abc(double);       //2#  非模板函数

char* abc(char);        //3#  非模板函数

double abc(float);      //5#  非模板函数

template<class xx>voidabc(xx);     //7#          模板函数

template<>voidabc<int>(int);        //8#   显式具体化

 

 

 

 

下来确定优先级:

2#,int转换为double,标准转换(参照标准转换C,浮点和整型互相转换),优先级③;

3#,int转换为char,标准转换(整型到整型,非提升),优先级③;

5#,int转换为float,标准转换(整型转浮点),优先级③;

7#,可以创建int实例,完全匹配,优先级①;

8#,参数为int,完全匹配,优先级①;

 

于是,有两个优先级①的,这个时候,考虑到显式具体化的优先级高于模板函数,于是,在7#、8#之间,选择8#。

 

如代码:

#include<iostream>
#include<string>

using namespace std;

void abc(int*);	//1#   非模板函数
void abc(double);	//2#   非模板函数
char* abc(char);		//3#   非模板函数
char abc(char*);		//4#   非模板函数
double abc(float);	//5#   非模板函数
double* abc(double*);	//6#   非模板函数
template<class xx>void abc(xx);	//7#	   模板函数
template<>void abc(int);		//8#   显式具体化


int main()
{
	int a = 1;
	abc(a);
	system("pause");
	return 0;
}


void abc(int*)
{
	cout << "1" << endl;
}
void abc(double a)
{
	cout << "2" << endl;
}
char* abc(char a)
{
	char*m = new char;
	cout << "3" << endl;
	return m;
}
char abc(char* a)
{
	char x = 'x';
	cout << "4" << endl;
	return x;
}
double abc(float a)
{
	cout << "5" << endl;
	return 1.1;
}
double* abc(double* a)
{
	double*m = new double;
	cout << "6" << endl;
	return m;
}
template<class xx>void abc(xx a)
{
	cout << "7" << endl;
}
template<>void abc<int>(int a)
{
	cout << "8" << endl;
}

输出:

8
请按任意键继续. . .

注:如果去掉模板和函数具体化,则2#、3#、5#都为标准转换,因为优先级相同。

完全匹配允许的无关紧要的转换:

从实参

到形参

Type

Type &

Type &

Type

Type []

Type *

Type (argument-list)

Type (*)(argument-list)——这条不理解

Type

const type

Type

volatile Type

Type*

const type*

Type*

volatile type

注:就象大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。

 

在以上的基础上,即完全匹配,函数仍有优先级用于匹配函数。

 

①指向非const指针的调用(但只对指针适用),优先匹配非const,其次才匹配const;使用const,则只能匹配const。

 

②如上面多次强调,非模板函数》》》显式具体化》》》模板函数(生成的隐式具体化)

 

③转换最小为原则。

例如两个模板,一个是非指针模板,一个是指针模板。

对于非指针模板来说,如果传递给他指针,他会生成指针的实例;

一个地址传递给两个模板时,

非指针模板说,我可以根据参数(指针)生成实例(带指针的实例);

指针模板说,我本身就是指针模板,所以直接可以生成指针实例;

于是调用了指针模板。

 

如代码:

template<class xx>voidabc(xx);     //非指针模板

template<class xx>voidabc(xx*);   //指针模板

int*a=new int;

abc(a);   //传递的是一个指针

 

于是调用了指针模板。

即若同时有指针和非指针模板,且特征标一样,那么指针会优先匹配指针,非指针会优先匹配非指针

 

 

 

用户自定义使用哪个函数:

如以上所说,非模板函数优先于显式具体化优先于模板函数。

但假如在同时存在的时候,要求优先调用模板函数,则使用在类型名和参数的括号之间,加入“<>”。例如abc<>(1);   这样会优先调用模板函数。

如果在<>加入类型名,则是显式实例化,带强制转换功能。例如abc<int>(1.1),那么1.1会被强制转换为1,再使用模板函数。

 

如代码:

#include<iostream>
#include<string>

using namespace std;

void abc(int);	//非模板
template <class xx>void abc(xx);	//模板
template<>void abc(double);	//显式具体化

int main()
{
	int a = 1;
	double b = 1.1;
	abc(a);	//使用非模板
	abc<>(b);	//使用显式具体化
	abc(b);	//显式具体化
	abc<>(a);	//使用模板
	abc<int>(b);	//使用模板
	abc<double>(a);	//使用显式具体化
	system("pause");
	return 0;
}

void abc(int a)
{
	cout << 1 << endl;
}

template<class xx>void abc(xx a)
{
	cout << 2 << endl;
}

template<>void abc(double a)
{
	cout << 3 << endl;
}

输出:


1
3
3
2
2
3
请按任意键继续. . .

 

总结:

①加入<>则优先使用模板(显式具体化也是模板范围之内);

 

②使用模板时,优先使用显式具体化。

如abc<double>(a);是显式实例化,a为int类型,被强制转换为double,于是匹配显式具体化。

 

③显式实例化时,优先强制转换,然后根据强制转换后的情况,决定匹配哪一个(但不会去匹配非模板函数)。

 

④如果能优先匹配显式具体化,那么加不加“<>”似乎并没有什么影响。

 

 

 

有多个参数时的函数:

当有多个参数的函数调用与有多个参数的原型进行匹配时,情况将非常复杂。

 

编译器必须考虑所有参数的匹配情况。如果找到比其他可行函数都合适的函数,则选择该函数。

 

一个函数要比其他函数都合适:

①所有参数的匹配程度都是最高,或者最高之一;

②至少一个参数的匹配程度最高,没有之一;

 

也就是说,哪怕有一个参数,不是最高或之一,就无法作为最合适。

 

 

 

模板函数的发展:

(一)关键字:decltype

 

假如有这么一个情况,函数的参数为两个不同类型的变量,模板如下写:

template<class T1,classT2>void abc(T1 a,T2 b);

那么假如要在函数内部,让a和b相加(即a+b),那么他们的和是什么类型?在调用函数前,并不能确定。

于是这么写:

template<classT1, class T2>void abc(T1 a, T2 b)

{

         ?type ? c = a + b;      //注意,这里的?type?并不能用,编译器会指出未声明的标识符

}

这个时候,c的类型,根据实际相加时的情况决定,因此,只能使用auto。

例如int和int类型相加为int,int和double相加为double,总之,会导致整型提升,然后再看结果如何,决定c的类型。

 

因此,C++98中没法声明c的类型(注意,auto是C++11中加入的)。

但可以这么写:

template<classT1, class T2>void abc(T1 a, T2 b)

{

         autoc = a + b;           //C++11可用

}

 

 

C++11中新增的关键字decltype提供了解决方案,他可以这么用:

int x;

decltype(x) y;

意思是,让y的类型同x。

括号里可以使用表达式。

如:decltype(a+b)c;

但这个时候,c只是和a+b的类型相同,但并不是把a+b的值赋给了c。

因此还需要 c=a+b;

也可以将其合并在一起。如上述代码可以改为:

template<classT1, class T2>void abc(T1 a, T2 b)

{

         decltype(a+ b)c = a+ b;

}

 

 

在以上代码之中,decltype看似和auto相同,但若括号里是一个函数,即若有函数:

int m(int a)

{
       cout<<a<<endl;

       return a;

}

 

decltype

decltype(m(1)) c;

根据m的返回类型是int,于是c是int类型。

auto:

auto c=m(1);      

这个时候,c也是int类型,但他会执行m函数,并给c赋值1。

 

 

decltype也可以搭配typedef(别名使用):

例如:

template<classT1, class T2>void abc(T1 a, T2 b)

{

         typedefdecltype(a+ b) xx;

         xx c = a + b;

         xx d = a - b;

}

 

在这个代码之中,xx就是a+b的和的类型的别名(比如说double类型);

那么xx c和xx d都是double类型。

 

 

(二)另一种函数声明语法(C++11后置返回类型)

例如上面的代码:

template<classT1, class T2>返回值类型abc(T1 a,T2 b)

{

         decltype(a+ b)c = a+ b;

         returnc;

}

 

这个时候,返回值类型c是什么,我们并不能知道。

 

同时,又因为此时还没声明a和b,他们不在作用域之内(编译器看不到他们,无法使用)。只有在声明参数后,才能使用decltype。

也就是说,函数原型不能这么写:

template<classT1, class T2> decltype(a+ b) abc(T1a,T2 b)

 

为此,C++新增了一种声明和定义函数的语法,下面使用内置类型来说明这种语法的工作原理。

例如:

int a(intm, int n);

可以写为:

autoa(int m, intn)->int;

 

这样的话,将返回类型移到了参数声明之后,->int被称为后置返回类型(trailing return type)。其中,auto在这里是一个占位符,表示后置返回类型提供的类型,这是C++11给auto新增的一种角色。这种语法也可以用于函数定义:

如上面的改为:

template<classT1, class T2> auto abc(T1 a, T2 b)->decltype(a+ b)

{

         decltype(a+ b)c = a+ b;

         returnc;

}

 

在这种情况下,decltype在参数之后,参数a和b在作用域之内,于是可以使用了。也可以正确提供类型了。

 


目录
相关文章
|
编译器 C++
57 C++ - 函数模板
57 C++ - 函数模板
51 0
|
4月前
|
存储 编译器 程序员
|
编译器 C++
C++函数模板与类模板
C++函数模板与类模板
79 0
|
编译器 C++
【C++】什么是函数模板/类模板?
1.什么是函数模板? 函数模板简单来说就是一个模板,与函数参数的类型无关,是一个模子,不是真正的函数,实例化的函数会根据实参的类型自动推导类型。
|
存储 编译器 C++
【C++要笑着学】泛型编程 | 函数模板 | 函数模板实例化 | 类模板(二)
本章将正式开始介绍C++中的模板,为了能让大家更好地体会到用模板多是件美事!我们将会举例说明,大家可以试着把自己带入到文章中,跟着思路去阅读和思考,真的会很有意思!如果你对网络流行梗有了解,读起来将会更有意思!
140 1
【C++要笑着学】泛型编程 | 函数模板 | 函数模板实例化 | 类模板(二)
|
搜索推荐 C++
C++模板(函数模板)
C++模板(函数模板)
112 0
|
编译器 C语言 C++
【C++要笑着学】泛型编程 | 函数模板 | 函数模板实例化 | 类模板(一)
本章将正式开始介绍C++中的模板,为了能让大家更好地体会到用模板多是件美事!我们将会举例说明,大家可以试着把自己带入到文章中,跟着思路去阅读和思考,真的会很有意思!如果你对网络流行梗有了解,读起来将会更有意思!
122 0
【C++要笑着学】泛型编程 | 函数模板 | 函数模板实例化 | 类模板(一)
|
编译器 C++
初识及C++模板,总结函数模板的特点以及具体使用
初识及C++模板,总结函数模板的特点以及具体使用
155 0
初识及C++模板,总结函数模板的特点以及具体使用
|
编译器 C++
C++ 函数模板
C++ 函数模板
225 0
C++ 函数模板