函数模板
- 思考:如果重载的函数,其解决问题的逻辑是一致的、函数体语句相同,只是处理的数据类型不同,那么写多个相同的函数体,是重复劳动,而且还可能因为代码的冗余造成不一致。
- 解决:使用模板
例:求绝对值的模板
函数模板语法
-
语法形式
template <模板参数表>
模板参数表
的内容可以有:- 类型参数:
class(或typename)标识符(通常写作T)
- 常量参数:
类型说明符 标识符
- 模板参数:
template <参数列表> class 标识符
函数模板的示例
#include <iostream> using namespace std; template <class T> // 定义函数模板 void outputArray(const T *array, int count){ for(int i = 0; i < count; i++) cout << array[i] << " "; // 如果数组元素是类的对象,需要该对象所属类重载流插入运算符"<<" cout << endl; } int main(){ const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20; int a[A_COUNT] = {1,2,3,4,5,6,7,8}; double b[B_COUNT] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8}; char c[C_COUNT] = "Welcome!"; cout << " a array contains:" << endl; outputArray(a, A_COUNT); cout << " b array contains:" << endl; outputArray(b, B_COUNT); cout << " c array contains:" << endl; outputArray(c, C_COUNT); return 0; } 运行结果如下: a array contains: 1 2 3 4 5 6 7 8 b array contains: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 c array contains: W e l c o m e!
注意:
- 一个函数模板并非自动可以处理所有类型的数据
- 只有能够进行函数模板中运算的类型,可以作为类型实参
- 自定义的类,需要重载模板中的运算符,才能作为类型实参
- 类型参数:
类模板
类模板的作用
使用类模板可以让用户为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数,某些成员函数的返回值,能取任意类型(包括基本类型和用户自定义类型)
类模板的声明
- 类模板
template <模板参数表> class 类名 {类型元声明};
- 如果需要在类模板以外定义成员函数,则要采用以下的形式:
template <模板参数表> 函数类型 类名<模板参数标识符列表>::函数名(参数表)
类模板示例
#include <iostream>
#include <cstdlib>
using namespace std;
struct Student{
int id;
float gpa;
};
template <class T>
class Store{ //类模板:实现对任意类型数据进行存取
private:
T item; // item用于存放任意类型的数据
bool haveValue; // haveValue标记item是否已被存入内容
public:
Store();
T &getElem(); // 提取数据的函数
void putElem(const T &x); //存入数据的函数
};
template<class T>
Store<T>::Store():haveValue(false){}
template<class T>
T& Store<T>::getElem(){
//如试图在放置数据前提取数据,则终止程序
if(!haveValue){
cout << "No item present!" << endl;
exit(1); // 使程序完全退出,返回操作系统
}
return item; // 返回item中存放的数据
}
template <class T>
void Store<T>::putElem(const T& x){
haveValue = true;
item = x;
}
int main(){
Store<int> s1, s2;
s1.putElem(3);
s2.putElem(-7);
cout << s1.getElem() << " " << s2.getElem() << endl;
Student g = {1000,23};
Store<Student> s3;
s3.putElem(g);
cout << "The student id is " << s3.getElem().id << endl;
Store<double> d;
cout << "Retrieving object D...";
cout << d.getElem() << endl; // d未初始化,执行函数D.getElement()时导致程序终止
return 0;
}
模板类的分离编译的错误
将类模板的声明和实现分离到对应的.hpp
和.cpp
文件中,最终编译时会报错,如下:
D:\Code\CPPCode\Chapter1Domo\main.cpp|213|undefined reference to `Store<int>::Store()'|
但是把类模板的声明和实现写到一个文件中,就不会报错。所以可以得到的一点经验是:
- 类模板的声明和实现放到一起进行编译
紧接着,我们想的问题是:为什么类模板的声明和实现按常规的头文件和源文件分开写的格式行不通?要想清楚这个问题,就要明白模板类和模板函数到底是怎样的运行机制。
模板类不能直接编译成机器码供CPU读取运行。
模板类是为了我们编写的代码能够处理更多类型的数据而设计出来的代码编写语法,从而让我们更加专注于算法本身,而不是把时间浪费在为处理不同类型的数据而编写除了数据类型外几乎一样的代码上,或者为了处理不同类型的数据小心翼翼地调用实现逻辑相同但名字不同的类或函数上。在编译阶段,模板类或模板函数是被编译器这样处理的:编译器根据我们实际调用该模板类或模板函数时真实传递的实参类型推断出模板参数表
中的各个标识符具体是什么类型,从而参照模板类或模板函数生成一个参数类型明确的concrete类或函数,那么就要求在生成具体的、参数类型明确的类或函数时,编译器不仅要知道模板类、模板函数的声明是怎样的,还要知道模板类或模板函数的现实是怎样的,只有这样,才能生成一个完整的、可实例化对象的类或可被调用的函数。但是如果采用头文件和源文件的方式分别放置声明和实现,那么编译器走的就是对不同源文件分别编译、最终链接obj文件的路线,所以这样也就无法生成一个concrete的类或函数,所以编译必然报错。除非将来有一天编译器能够跨文件联合编译,这个问题就迎刃而解了,但目前貌似不行,当然这只是我的片面理解。