文章目录
1. 函数模板的定义
函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int 或者double )替换。通过将类型 作为参数 传递给模板,可使编译器生成该类型的函数。
由于类型是用参数表示的,因此模板特性有时也被成为参数化类型。
2. 为什么需要这种特性?
如果有函数void swap(int a, int b)
功能就是交换 a和 b 的值,但是这个时候想要交换的是 两个 double 类型的值,一种方法是 复制原来的代码。并用double 替换所有的 int, 如果需要交换两个 char 值,可以再次使用同样的技术,进行各种修改将浪费宝贵的时间且容易出错。如果进行手工修改,则可能会漏掉一个int, 不可靠!
C++的函数模功能能够自动完成 这一过程,可以节省时间,而且更加的可靠
3. 怎么建立一个模板
template <typename AnyType>
void swap(AnyType &a, AnyType &b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}
第一行指出,要建立一个模板,并将类型命名为Anytype。关键字template和 typename是必须的,除非可以使用关键字 class 代替 typename,另外必须使用 尖括号,类型名可以随便命名,符合命名规则就可以。
一般都会使用 T
模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int的函数时,编译器将按模板方式创建这样的函数, 并用 int 代替 AnyType。
在标准 C++ 98 添加关键字typename之前,c++使用关键字class来创建模板,也就是说,可以这样编写模板定义。
template <class AnyType>
void swap(AnyType &a, AnyType &b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}
4. 一个简单的例子演示模板
1. template.cpp
#include <iostream>
template <typename T> // or class T
void Swap(T &a, T &b);
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
int main()
{
using namespace std;
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
int i = 10;
int j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
Swap(i,j); // generates void Swap(int &, int &)
cout << "Now i, j = " << i << ", " << j << ".\n";
double x = 24.5;
double y = 81.7;
cout << "x, y = " << x << ", " << y << ".\n";
cout << "Using compiler-generated double swapper:\n";
Swap(x,y); // generates void Swap(double &, double &)
cout << "Now x, y = " << x << ", " << y << ".\n";
#if 0
//这一段编译通过不了,说明生成函数的实现,是在编译阶段
int m = 4;
double n = 6.4;
Swap(m, n);
#endif
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
template <typename T> // or class T
void Swap(T &a, T &b)
{
T temp; // temp a variable of type T
temp = a;
a = b;
b = temp;
}
如果加了下面这几行代码,会报错,因为没有办法生成Swap(int&, double&)
函数
book@book-desktop:~/meng-yue/c++/function/03$ g++ -o template template.cpp
template.cpp: In function ‘int main()’:
template.cpp:29: error: no matching function for call to ‘Swap(int&, double&)’
正常情况下的运行结果,成功的交换了彼此的值!
book@book-desktop:~/meng-yue/c++/function/03$ ./template
---------------开始--->公众号:梦悦foundation---------------
i, j = 10, 20.
Now i, j = 20, 10.
x, y = 24.5, 81.7.
Using compiler-generated double swapper:
Now x, y = 81.7, 24.5.
---------------结束--->公众号:梦悦foundation---------------
book@book-desktop:~/meng-yue/c++/function/03$
5. 重载的模板
需要多个不同类型使用同一种算法的函数时,可以使用模板,但是并不是所有的类型都是用相同的算法,为了满足这种需求,可以重载常规函数定义那样重载 模板定义。
和常规重载一样,被重载的模板的函数特征标必须不同。
template <typename T> // original template
void Swap(T &a, T &b);
template <typename T> // new template
void Swap(T *a, T *b, int n);
在后一个模板中,最后一个参数的类型为具体类型(int),而不是泛型,并非所有的模板参数都必须是模板参数类型。
编译器遇到第一个 Swap()
函数调用时,发现它有两个int 参数,因此将它与原来的模板匹配。但第二次调用将两个int数组和一个int值用作参数,这与新模板匹配。
overload_template.cpp
// twotemps.cpp -- using overloaded template functions
#include <iostream>
template <typename T> // original template
void Swap(T &a, T &b);
template <typename T> // new template
void Swap(T *a, T *b, int n);
void Show(int a[]);
const int Lim = 8;
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
int main()
{
using namespace std;
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i,j); // matches original template
cout << "Now i, j = " << i << ", " << j << ".\n";
int d1[Lim] = {1,3,5,7,9,11,13,15};
int d2[Lim] = {2,4,5,8,10,12,14,16};
cout << "Original arrays:\n";
Show(d1);
Show(d2);
Swap(d1,d2,Lim); // matches new template
cout << "Swapped arrays:\n";
Show(d1);
Show(d2);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template <typename T>
void Swap(T a[], T b[], int n)
{
T temp;
for (int i = 0; i < n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
void Show(int a[])
{
using namespace std;
cout << "{";
cout << a[0] << ", " << a[1] << ", ";
cout << a[2] << ", " << a[3] << ", ";
cout << a[4] << ", " << a[5] << ", ";
cout << a[6] << ", " << a[7];
cout << "}";
cout << endl;
}
程序的运行结果
book@book-desktop:~/meng-yue/c++/function/03$ ./overload_template ---------------开始--->公众号:梦悦foundation---------------
i, j = 10, 20.
Using compiler-generated int swapper:
Now i, j = 20, 10.
Original arrays:
{1, 3, 5, 7, 9, 11, 13, 15}
{2, 4, 5, 8, 10, 12, 14, 16}
Swapped arrays:
{2, 4, 5, 8, 10, 12, 14, 16}
{1, 3, 5, 7, 9, 11, 13, 15}
---------------结束--->公众号:梦悦foundation---------------
book@book-desktop:~/meng-yue/c++/function/03$
6. 模板的局限性
模板函数中有一些类型可能处理不了
如a > b
就处理不了 数组和结构,
怎么处理这种问题,一种方式是重载运算符,另外一种解决方案是为特定类型提供具体化的模板定义。下面就来介绍这种解决方案。
7. 显式具体化
然而可以提供一个具体化函数定义,称之为显式具体化,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不在寻找模板。
1. 第三代具体化
- 对于给定的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及它们的重载版本。
- 显式具体化的定义应以
template<>
打头,并通过名称来指出类型。 - 具体化优于常规模板,而非模板函数优先于具体化和常规模板。
Swap<job>
中的 <job>
是可选的,因为函数的参数类型表明,这是一个job的具体化,因此该原型也可以这样编写:
template <> void Swap(job &, job &);
2. 一个显式具体化的实际例子
explicit.cpp
下面的例子,没有更换结构体的所有成员,只是使用了显式具体化来更换需要交换的两个成员 salary和 year;
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
template <typename T>
void Swap(T &a, T &b);
struct job
{
char name[40];
double salary;
int year;
};
// explicit specialization
template <> void Swap<job>(job &j1, job &j2);
void Show(job &j);
int main()
{
using namespace std;
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i,j); // generates void Swap(int &, int &)
cout << "Now i, j = " << i << ", " << j << ".\n";
job j1 = {"j1:meng-yue", 26000.60, 1};
job j2 = {"j2:梦悦foundation", 34000.72, 2};
cout << "Before job swapping:\n";
Show(j1);
Show(j2);
Swap(j1, j2); // uses void Swap(job &, job &)
cout << "After job swapping:\n";
Show(j1);
Show(j2);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
template <typename T>
void Swap(T &a, T &b) // general version
{
T temp;
temp = a;
a = b;
b = temp;
}
// swaps just the salary and year fields of a job structure
template <> void Swap<job>(job &j1, job &j2) // specialization
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.year;
j1.year = j2.year;
j2.year = t2;
}
void Show(job &j)
{
using namespace std;
cout << j.name << ":" << j.salary << "-" << j.year << " years"<< endl;
}
程序的运行结果:结果发现只交换了其中两个成员
book@book-desktop:~/meng-yue/c++/function/03$ ./explicit
---------------开始--->公众号:梦悦foundation---------------
i, j = 10, 20.
Using compiler-generated int swapper:
Now i, j = 20, 10.
Before job swapping:
j1:meng-yue:26000.60-1 years
j2:梦悦foundation:34000.72-2 years
After job swapping:
j1:meng-yue:34000.72-2 years
j2:梦悦foundation:26000.60-1 years
---------------结束--->公众号:梦悦foundation---------------
book@book-desktop:~/meng-yue/c++/function/03$
思考:
不显示具体化,这样行不行??
template <typename T>
void Swap(T &a, job &b);
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
template <typename T>
void Swap(T &a, T &b);
struct job
{
char name[40];
double salary;
int year;
};
template <typename T>
void Swap(T &a, job &b);
// explicit specialization
//template <> void Swap<job>(job &j1, job &j2);
void Show(job &j);
int main()
{
using namespace std;
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i,j); // generates void Swap(int &, int &)
cout << "Now i, j = " << i << ", " << j << ".\n";
job j1 = {"j1:meng-yue", 26000.60, 1};
job j2 = {"j2:梦悦foundation", 34000.72, 2};
cout << "Before job swapping:\n";
Show(j1);
Show(j2);
Swap(j1, j2); // uses void Swap(job &, job &)
cout << "After job swapping:\n";
Show(j1);
Show(j2);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
template <typename T>
void Swap(T &a, T &b) // general version
{
T temp;
temp = a;
a = b;
b = temp;
}
// swaps just the salary and year fields of a job structure
#if 0
template <> void Swap<job>(job &j1, job &j2) // specialization
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.year;
j1.year = j2.year;
j2.year = t2;
}
#endif
void Show(job &j)
{
using namespace std;
cout << j.name << ":" << j.salary << "-" << j.year << " years"<< endl;
}
答案是不行的,调用swap(j1, j2)
会出现歧义,不知道使用哪一个,因为两个都能匹配!
3. 关于优先级的一个例子, 普通函数和模板函数优先级
priority.cpp
显式实例化 是可以和 普通函数共存的,例如void Swap(int &a, int &b)
和 template void Swap<int>(int &a, int &b);
是可以放在同一个文件的,而Swap<int>
应该和Swap不是一个函数。
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
template <typename T>
void Swap(T &a, T &b);
template void Swap<int>(int &a, int &b);
void Swap(int &a, int &b)
{
cout << "Swap(int &a, int &b)" << endl;
int temp;
temp = a;
a = b;
b = temp;
}
struct job
{
char name[40];
double salary;
int year;
};
//template <typename T>
//void Swap(T &a, job &b);
// explicit specialization
template <> void Swap<job>(job &j1, job &j2);
void Show(job &j);
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i,j); // generates void Swap(int &, int &)
cout << "Now i, j = " << i << ", " << j << ".\n";
job j1 = {"j1:meng-yue", 26000.60, 1};
job j2 = {"j2:梦悦foundation", 34000.72, 2};
cout << "Before job swapping:\n";
Show(j1);
Show(j2);
Swap(j1, j2); // uses void Swap(job &, job &)
cout << "After job swapping:\n";
Show(j1);
Show(j2);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
template <typename T>
void Swap(T &a, T &b) // general version
{
cout << "Swap(T &a, T &b)" << endl;
T temp;
temp = a;
a = b;
b = temp;
}
// swaps just the salary and year fields of a job structure
#if 1
template <> void Swap<job>(job &j1, job &j2) // specialization
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.year;
j1.year = j2.year;
j2.year = t2;
}
#endif
void Show(job &j)
{
using namespace std;
cout << j.name << ":" << j.salary << "-" << j.year << " years"<< endl;
}
运行结果:
meng-yue@ubuntu:~/MengYue/c++/function/03$ ./priority
---------------开始--->公众号:梦悦foundation---------------
i, j = 10, 20.
Using compiler-generated int swapper:
Swap(int &a, int &b)
Now i, j = 20, 10.
Before job swapping:
j1:meng-yue:26000.60-1 years
j2:梦悦foundation:34000.72-2 years
After job swapping:
j1:meng-yue:34000.72-2 years
j2:梦悦foundation:26000.60-1 years
---------------结束--->公众号:梦悦foundation---------------
如果能够直接找到匹配的函数定义,就不需要通过函数模板来生成了!
8. 实例化和具体化
为进一步了解模板,必须理解 实例化和具体化。记住,在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。
编译器使用模板为特定类型生成函数定义时,得到的是模板实例。
例如上面的代码中函数调用swap(i, j)
导致编译器生成swap()
的一个实例,该实例使用int
类型。模板并非函数定义,但使用int
的模板实例是函数定义。
这种实例化方式 被称为 隐式实例化,因为编译器之所以知道需要进行定义,是由于程序调用swap()
函数时提供了int
参数
1. 显式实例化
最初,编译器只能通过隐式实例化来使用模板生成函数定义,但现在c++还允许显式实例化,这意味着可以直接命令编译器创建特定的实例,如swap<int>()
。 其语法是声明所需的种类-----用<>
符号指示类型,并在声明前加上关键字template
template void swap<int>(int , int);
实现这种特性的编译器看到上述声明后,将使用 swap()
模板生成一个使用int
类型的实例。也就是说,该声明的意思是 “使用swap()模板生成 int类型的函数定义”
与显式实例化不同的是,显式具体化使用下面两个等价的声明
template <> void swap<int>(int &, int &);
template <> void swap(int &, int &);
警告:试图在同一个文件中使用同一种类型的显式实例和显式具体化将出错。
two_instantiation.cpp,两者共存会怎么样!
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
template <typename T>
void Swap(T &a, T &b);
template void Swap<int >(int &, int &);
void Swap(int &a, int &b)
{
cout << "Swap(int &a, int &b)" << endl;
int temp;
temp = a;
a = b;
b = temp;
}
struct job
{
char name[40];
double salary;
int year;
};
//template <typename T>
//void Swap(T &a, job &b);
// explicit specialization
template <> void Swap<job>(job &j1, job &j2);
template <> void Swap<int>(int &j1, int &j2);
void Show(job &j);
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
cout.precision(2);
cout.setf(ios::fixed, ios::floatfield);
int i = 10, j = 20;
cout << "i, j = " << i << ", " << j << ".\n";
cout << "Using compiler-generated int swapper:\n";
Swap(i,j); // generates void Swap(int &, int &)
cout << "Now i, j = " << i << ", " << j << ".\n";
job j1 = {"j1:meng-yue", 26000.60, 1};
job j2 = {"j2:梦悦foundation", 34000.72, 2};
cout << "Before job swapping:\n";
Show(j1);
Show(j2);
Swap(j1, j2); // uses void Swap(job &, job &)
cout << "After job swapping:\n";
Show(j1);
Show(j2);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
template <typename T>
void Swap(T &a, T &b) // general version
{
cout << "Swap(T &a, T &b)" << endl;
T temp;
temp = a;
a = b;
b = temp;
}
// swaps just the salary and year fields of a job structure
#if 1
template <> void Swap<job>(job &j1, job &j2) // specialization
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.year;
j1.year = j2.year;
j2.year = t2;
}
//template <> void Swap<int>(int &j1, int &j2) // specialization
//{
// cout << "template <> void Swap<int>(int &j1, int &j2)" << endl;
// int temp;
// temp = j1;
// j1 = j2;
// j2 = temp;
//}
#endif
void Show(job &j)
{
using namespace std;
cout << j.name << ":" << j.salary << "-" << j.year << " years"<< endl;
}
编译结果,报错了,实例化和具体化共存会报错!
meng-yue@ubuntu:~/MengYue/c++/function/03$ g++ -o two_instantiation two_instantiation.cpp
two_instantiation.cpp:37:44: error: specialization of ‘void Swap(T&, T&) [with T = int]’ after instantiation
template <> void Swap<int>(int &j1, int &j2);
^
meng-yue@ubuntu:~/MengYue/c++/function/03$
还可以通过在程序中使用函数来创建显式实例化。例如,请看下面的代码:
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;//显式实例化
2. 一个简单的 demo_func_explicit_instantiation.cpp 例子,演示上面的所说的是否正确!
一个简单的 demo_func_explicit_instantiation.cpp 例子,演示上面的所说的是否正确!
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
template <class T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
using namespace std;
int m = 6;
double x = 10.2;
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
cout << Add(x, m) << endl;
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
先不通过函数 来创建实例化,还是之前通用的那种做法,看看运行结果!
book@book-desktop:~/meng-yue/c++/function/03$ g++ -o demo_func_explicit_instantiation demo_func_explicit_instantiation.cpp
demo_func_explicit_instantiation.cpp: In function ‘int main()’:
demo_func_explicit_instantiation.cpp:20: error: no matching function for call to ‘Add(double&, int&)’
book@book-desktop:~/meng-yue/c++/function/03$
结果没有通过编译,因为 Add函数去寻找 Add(double&, int&)
,模板生成的必须是两个类型一样的形参,所以会报错!
尝试更改成函数显式实例化来调用!
//cout << Add(x, m) << endl;
cout << Add<double>(x, m) << endl;
运行结果
book@book-desktop:~/meng-yue/c++/function/03$ book@book-desktop:~/meng-yue/c++/function/03$ g++ -o demo_func_explicit_instantiation demo_func_explicit_instantiation.cpp
book@book-desktop:~/meng-yue/c++/function/03$ ./demo_func_explicit_instantiation
---------------开始--->公众号:梦悦foundation---------------
16.2
---------------结束--->公众号:梦悦foundation---------------
book@book-desktop:~/meng-yue/c++/function/03$
隐式实例化,显式实例化,和显式具体化统称为具体化,他们的通用之处在于,他们表示的都是使用具体类型的函数定义,而不是通用描述。
引入显示实例化后,必须使用新的语法——在声明中使用前缀template
和 template <>
以区分显示实例化和显式具体化。
下面的代码做了一个总结
#include <iostream>
template <class T>
void Swap( T &, T &); // 函数模板的声明
struct meng_yue {
int counter;
int index;
};
template <> void Swap<meng_yue>(meng_yue &, meng_yue &); // 显式具体化
int main(int argc, char * argv [ ])
{
template void Swap<char>(char &, char &);// 为char 类型生成显示实例化
short s1, s2;
Swap(s1, s2); //short类型隐式实例化
meng_yue m1, m2;
Swap(m1, m2);//使用 meng_yue 作为参数,显式具体化
char c1, c2;
Swap(c1, c2);//使用 char 显示实例化
return 0;
}
9. 去哪获取笔记和详细的资料
代码资料路径