文章目录
0. 参数类型
- 概念
分为模板类型参数和非类型模板参数
0.1 非类型参数
非类型参数可以使一个整形,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期(参见c++ primer 第5版——第12章,第400页)
namespace jj02
{
template <unsigned N, unsigned M>
int Compare(const char (&p1)[N], const char (&p2)[M])
{
std::cout << " N = " << N << std::endl;
std::cout << " M = " << M << std::endl;
return strcmp(p1, p2);
}
void Test01()
{
//! 编译器会使用字面常量的大小来代替N和M的值
std::cout << Compare("hi", "mom") << std::endl;
}
}
0.2 模板类型参数
#include <iostream>
namespace jj01
{
template <typename T>
T Foo(T* p)
{
T tmp = *p; //< tmp的类型将是指针p指向的类型
//...
return tmp;
}
void Test01()
{
int a = 20;
int b = Foo(&a);
std::cout << "b = " << b << std::endl;
}
}
03 基础类型的明确初始化
C++标准库(第2版)——3.2.1基础类型的明确初始化
//! 模板函数内基础类型的初始化
template <typename T>
void F()
{
T x2; //< 随机值
T x = T(); //< 基础类型double会被初始化为0.0000
std::cout << x << " " << x2 << std::endl;
}
void Test01()
{
F<double>();
}
1. 使用方式
一般将模板放在头文件中,并在需要使用模板的文件中包含头文件
2. 编译器匹配规则
- 如果有多个原型,则编译器在选择原型时的优先级如下:
非模板函数 > 具体化模板函数 > 常规模板函数
3. 模板显示具体化的举例说明
题目:只希望交换两个Job结构中salary的数据,不希望交换name的数据
struct Job
{
char name[40];
double salary;
};
//! 常规模板
template <typename T>
void Swap(T &j1, T &j2)
{
T temp = j1;
j1 = j2;
j2 = temp;
}
//! 显示具体化模板函数
template <> void Swap<Job>(Job &j1, Job &j2)
{
Job temp;
temp.salary = j1.salary;
j1.salary = j2.salary;
j2.salary = temp.salary;
}
int main()
{
double u, v;
Swap(u, v); //< 调用常规模板
Job a, b;
Swap(a, b); //< 优先匹配具体化模板函数
}
结论:可以看出原先的模板只是针对常规交换,但如果需要对一些类型特殊处理,这时候就不适用了,需要使用模板具体化功能
4. 函数模板的本质
- 实例化:在代码中包含模板本身不会生成函数定义,它只是一个用于生成函数定义的方案,编译器使用模板为特定类型生成函数定义时,得到的是模板示例。
//! 1. 隐式实例化:
//! 这个例子则是隐式实例化,程序调用Swap()导致编译器生成Swap的示例,编译器之所以知道需要进行定义,
//! 是因为程序调用Swap()函数的是偶提供了int参数
template <typename T>
void Swap(T &j1, T &j2)
//! 2. 显示实例化:
//! C++还允许显示实例化,这意味着可以直接命令编译器创建特定的实例,语法为 template void Swap<int> (int &j1, int &j2), 该声明的意思是“使用Swap()模板生成int类型的函数定义”
template void Swap<int> (int &j1, int &j2)
//! 也可以在程序中使用函数来创建显示实例化,例如:
int a =20;int b = 30;
Swap<int>(a, b);
//! 3. 显示具体化:
//!与显示实例化不同的是,显示具体化使用下面两个等价的声明之一:
template <> void Swap<int> (int &, int &);
template <> void Swap(int &, int &);
//! 区别在于,这些声明的意思是“不要使用Swap()模板来生成函数定义,而应使用专门为int类型显示地定义的函数定义”。
//! 这些原型必须有自己的函数定义。显示具体化声明在关键字template后包含<>,而显示实例化没有。
警告:试图在同一个文件(或转换单元)中使用同一种类型的显示实例和显示具体化将出错。
5. 编译器选择使用哪个函数版本
C++ Primer plus(第六版)——8.5.5 编译器选择使用哪个函数版本
6. 关键字decltype(c++11)
6.1 问题:遇到无法声明的类型
template<typename T1, typename T2>
void ft(T1 x, T2 y)
{
?type> xpy = x + y; //< 这里的xpy是什么类型无法声明
}
decltype语法:decltype(expression) var
6.2 解决方案
- 情形一:
如果expression 是一个没有用括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符
double x = 5.5;
double y = 7.9;
double &rx = x;
const double *pd;
decltype(x) w; //< w is type double
decltype(rx) u = y; //< u is type double &
decltype(pd) v; //< v is type const double *
- 情形二:
如果express是一个函数调用,则var的类型与函数的返回值类型相同
long indeed(int);
decltype (indeed(3)) m; //< m is type int
注意:并不会实际调用函数。编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数。
- 情形三:
expression是一个左值,并且用括号括起的标识符:
double xx = 4.4;
decltype ((xx)) r2 = xx; //< r2 is double &
decltype (xx) w = xx; //< w is double (Stage 1 match)
- 情形四:
如果前面的条件都不满足,则var的类型与expression的类型相同:
int j = 3;
int &k = j;
int &n = j;
decltype(j+6) i1; //< i1 type int
decltype(100L) i2; //< i2 type long
decltype(k+n) i3; //< i3 type int
注意:虽然k和n都是引用,但表达式k+n不是引用;它是两个int的和,因此类型为int
总结:如果需要多次声明,可以结合使用typedef和decltype
template<typename T1, typename T2>
void ft (T1 x, T2 y)
{
typedef decltype(x + y) xytype;
xytype xpy = x + y;
xytype arr[10];
xytype & rxy = arr[2]; //< rxy a reference
}
7. 另一种函数声明语法(c++11 后置返回类型)
- 问题:无法预先知道a*b得到的类型,好像可以将返回类型设置为decltype(a*b),但现在还没声明参数a和b,不在作用域范围(编译器看不到它们,也无法使用它们),必须声明参数后使用decltype
- 解决措施:c++新增了一种声明和定义函数的语法。这个时候可以结合auto关键字,使用后置类型来声明,auto只是个占位符,代表函数Multiple -> 后面的声明
- 代码示例:
template <typename T1, typename T2>
auto Multiple(T1 a, T2 b) -> decltype(a*b) //< ->后面被称为后置返回类型,现在a和b在参数声明后面,因此a和b位于作用域内,可以使用它们
{
return a*b;
}
int main(void)
{
int a = 10;
double b = 20.3;
cout << Multiple(a, b) << endl;
return 0;
}
8. 参考书籍
C++ Primer plus(第六版)——8.5函数模板
9. 函数模板在类中的使用方式
- 头文件——Person.h
#pragma once
class CPerson
{
public:
CPerson(void);
~CPerson(void);
template <typename T>
void Swap(T &a, T& b);
};
//! 类中定义函数模板方式:只能和声音在同一个文件中,不能将模板声明和实现分离文件编译
template <typename T>
void CPerson::Swap(T& a, T& b)
{
T tem = a;
a = b;
b = tem;
}
源文件——test.cpp
#include <iostream>
#include "Person.h"
using namespace std;
int main(void)
{
int a = 3;
int b = 4;
CPerson per;
per.Swap<int>(a, b);
cin.get();
return 0;
}