函数模板的意义在于,可以在不同的参数下,起到同样的作用。
按照教程所说,它们使用泛型来定义函数,其中泛型可用具体的类型(如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等。
但仅以上代码来说,不支持(?)array、vector。因为这两种既是模板。(可能也因为能显示数组,所以不一样?)
就 以上代码 而言,也不支持结构(模板是允许结构类型的,只是这个不支持)。如果需要支持结构,则在编写代码的时候需要注意。例如,以下模板就适合结构作为参数名使用:
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在作用域之内,于是可以使用了。也可以正确提供类型了。