C++ Primer Plus笔记07

C++ Primer Plus笔记07

函数模板

函数模板是统一通用的函数描述,也就是说,其中的泛型可用具体的类型替换。
也就是,编译器允许以泛型(而不是具体类型)的方式来编写程序。

假设定义了一个交换两个int值的函数,假设要交换两个double值,可以复制原本的代码,并用所有的double替换int,但这种方法麻烦且容易出错。用函数模板可以自动完成这一过程。

示例

template <typename AnyType>  
void Swap(AnyType &a, AnyType &b)
{
   AnyType temp;
   temp = a;
   a = b;
   b = temp;
}

第一行:指出要建立一个模板,并将类型命名为AnyType但一般为了省事可以直接命名为T。
关键字template和typename是必需的,typename可以和class互换。
必须使用尖括号。
模板并不创建任何函数,只是告诉编译器如何定义函数。

重载模板

需要对多种类型使用相同算法的函数时,可使用模板。
使用不同算法时需要重载模板。
重载模板时,和重载一般函数一样,需要函数特征标不同。
如:

template <typename T>
void Swap(T &a, T &b);   //原来的模板

template <typename T>
void Swap(T *a, T *b, int c);   //新的模板,并不是所有特征标都要说泛型的

显式具体化

定义了如下结构:

struct job
{
   char name[40];
   double salary;
   int floor;
};

另外,假设希望能够交换两个这种结构的内容。

temp = a;
a = b;
b = temp;

C++允许将结构赋给结构。然而,假设只想交换salary和floor成员,则需要不同的代码。而Swap的参数仍是两个job结构的引用,因此无法使用模板重载来提供其他的代码。

此时可以使用显式具体化

第三代具体化(ISO/ANSI C++标准)
1.对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。
2.显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
3.具体化优先于常规模板,而非模板函数优于具体化和常规函数。

例:

//非模板函数原型
void Swap(job &,job &);

//模板函数原型
template <typename T>
void Swap(T &, T &);

//显式具体化模板函数原型
template <> void Swap<job>(job &,job &);

其实显式具体化是什么意思呢?
就是当你的模板函数的功能无法实现特定类型的使用时,可以让编译器特定地实现某一个特定类型的方法。

...
struct job
{
   char name[40];
   double salary;
   int floor;
};

template <class T>   //#1
void Swap(T &,T &);

template <> void Swap<job>(job &,job &);  //#2

int main()
{
   double u,v;
   ...
   Swap(u,v);  //用#1
   job a,b;
   ...
   Swap(a,b);  //用#2
}

其中显式具体化的原型还可以这么编写:template <> void Swap(job &, job &); //省略<job>

实例化与具体化

编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation)。函数调用Swap(i,j);就会生成Swap()的一个实例,该实例使用了int类型。模板并非函数定义,但使用int的模板实例时函数定义。这种实例化方法被称为隐式实例化(implicit instantiation),因为程序调用Swap()函数时提供了int参数。
C++还允许显式实例化(explicit instantiation)。这意味着可以直接命令编译器创建特定的实例,如Swap<int>();

原型为:template void Swap<int>(int, int); //显式实例化
编译器看到这种声明后,将使用Swap()模板生成一个使用int类型的实例。也就是说:“使用Swap()模板生成int类型的函数定义。”
原型为:template <> void Swap<int>(int, int); //显式具体化
编译器看到这种声明后,翻译说:“不要使用Swap()模板生成int类型的函数定义,而是专门为int类型显式地定义的函数定义”

template <class T>                           //函数模板
void Swap(T &,T &);

template <> void Swap<int>(int &, int &);   //显式具体化
template <> void Swap(int &, int &);        //显式具体化

template void Swap<int>(int, int);          //显式实例化

例:

template <class T>
T Add(T a, T b)
{
   return a+b;
}
...
int m = 6;
double x = 10.2;
cout << Add<double>(x, m) << endl;    //显式实例化 

可以发现上面的x,m与模板不匹配,因为需要类型一样的参数,但使用函数模板实例化成double后,强行转换类型成double类型的实例,返回一个double类型的返回值。

总结

隐式实例化、显式实例化和显式具体化统称为具体化(specialization)。表示的都是使用具体类型的函数定义而不是通用描述。

...
template <typename T>
void Swap (T &,T &);        //函数模板,template prototype

template <> void Swap<job>(job &, job &);  //函数模板具体化,explicit specialization for job

int main()
{
   template void Swap<char>(char &,char &);    //函数模板显式实例化,explicit instantiation for char
   short a,b;
   ...
   Swap(a,b);          //函数模板隐式实例化,implicit template instantiation for short
   job n,m;
   ...
   Swap(n,m);          //函数模板显式具体化,,explicit specialization for job
   char g,h;
   ...
   Swap(g,h);          //函数模板显式实例化,explicit instantiation for char
}

编译器选择使用哪个函数版本

对于函数重载、函数模板和函数模板重载,需要决定一个良好的策略来决定为函数调用使用哪一个函数定义。这个过程叫函数解析
1.创建候选函数列表。包含名称相同的所有函数和模板函数。
2.使用候选函数列表创建可行列表,参数的数目正确,有些可以被强制转换类型。
3.确定是否有最佳的可行函数。没有则函数调用出错。

例:

//调用以下函数:
may('B');

//有以下函数原型:
void may(int);                           //#1
void may(float, float = 3);              //#2
void may(char);                          //#3
char * may(const char *);                //#4
char may(const char &);                  //#5
template<class T> void may(const T &);   //#6
template<class T> void may(T *);         //#7

#4和#7不可行,因为整型类型不能被隐式转换成指针。

通常从最佳到最差的顺序如下:

1.完全匹配,但常规函数优先于模板
2.提升转换(例如,char和short自动转换为int,float自动转换成double)
3.标准转换(例如,int转换为char,long转换为double)
4.用户定义的转换,如类声明中定义的转换

在例子中,#1优于#2,因为char到int的转换是提升转换,而char到float的转换是标准转换。#3,#5,#6优于#1,#2,因为他们都是完全匹配的。#3优于#5和#6,因为#6是模板。

完全匹配

例:

struct blot
{int a;
char b[10];
};

blot ink = {25,"sport"};
...
//以下都是完全匹配的:
void recycle(blot);
void recycle(const blot);
void recycle(blot &);
void recycle(const blot &);

较具体的模板函数优先:显式具体化优于模板隐式生成的具体化。
非模板函数优于模板函数。

重载解析优先级总结

如果只存在一个匹配的函数,则选择他;如果存在多个,但其中只有一个是非模板函数,则选择非模板函数;如果都为模板,但有一个更具体 ,则选择更具体的;如果都合适,则会出现错误。

是什么类型

在编写模板函数时,一个问题是并非总能知道应在声明中使用哪种类型。

template<class T1, class T2>
void ft(T1 x, T2 y)
{
   ...
   ?type? xpy = x + y;
   ...
}

xpy到底是什么类型呢?
如果一个是int另一个是double,和应该是double。
如果一个是short另一个是int,和应该是int。
如果一个是short另一个是char,和应该是int。

关键字decltype

上面的情况可以用这样的关键字:

int x;
decltype(x) y;      //让y的类型和x一样

或者:

decltype(x + y) xpy;      //让xpy的类型和x+y的类型一样
xpy= x + y;

如果参数是一个左值,则var为指向其类型的引用。

double xx = 4.4;
decltype((xx)) r2 = xx;   //r2是 double &
decltype(xx) w = xx;      //w是double

如果需要多次声明,可结合typedef和decltype:

template <class T1, class T2>
void ft(T1 x, T2 y)
{
...
typedef decltype(x+y) xytype;
xytype xpy = x + y;
xytype arr[10];
xytype & rxy = arr[2];
... 
}

还有一种相关问题decltype不能解决:

template <class T1, class T2>
?type? gt(T1 x, T2 y)
{
   ...
   return x + y;
}

此时还没有声明x和y,所以不能使用decltype。

新的使用方法为:

double h(int x, float y);    //prototype
auto h(int x, float y) -> double;      //new

这将返回类型移到了参数声明后面。
->double被称为后置返回类型。结合decltype就能解决上述问题:

template <class T1, class T2>
?type? gt(T1 x, T2 y) ->decltype(x + y)
{
   ...
   return x + y;
}

总结

1.通过使用inline关键用于函数定义,并在首次调用该函数前提供其函数定义,可以使得C++编译器将该函数视为内联函数。也就是说,编译器不是让程序跳到独立的代码段来执行程序,而是用相应的代码替换函数调用。只有函数很小且重复时才使用,占用内存较多。
2.引用变量是一种伪装指针,他允许变量创建别名。主要被用于处理结构和类对象的函数参数。基类引用可以指向派生类。
3.C++可以定义默认值,如果省略了相应函数则使用默认值。默认值在函数声明时设定。

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页