第21课 - 类模板 - 上
一.类模板的引入
1.1. 一些类主要用于存储和组织数据元素
如:数组类,链表类,Stack类,Queue类等等
C++中可以将模板的思想应用于类,使得类的可以不关注具体所操作的数据类型,而只关注类所需要实现的功能。
1.2 C++中的类模板
1.2.1 提供一种特殊的以相同的行为处理不同的类型
1.2.2 在类声明前使用template进行标识
1.2.3 <typename T>用于说明类中使用的泛指类型
template<typename T> class Operate { public: T add(T a, T b) { return a + b; } T minus(T a, T b) { return a - b; } };、
1.3 声明的泛指类型T可用于声明成员变量和成员函数
1.4 编译器对类模板的处理方式和函数模板相同
1.4.1 编译器从类模板通过具体类型产生不同的类
1.4.2 编译器在声明的地方对类模板代码本身进行编译
1.4.3 编译器在使用的地方对参数替换后的代码进行编译
二.类模板的应用
2.1 使用具体类型定义对象
Operate<int> op1; /* 对象op1用于处理Int型的加减法 */ Operate<double> op2; /* 对象op1用于处理double型的加减法 */ cout<<op1.add(5,4)<<endl; cout<<op2.minus(1.5,0.01)<<endl;
2.2 类模板的工程应用
2.2.1 由于类模板的编译机制不同,所以不能像普通类一样分开实现后,在使用时只包含头文件。
在工程实践上,一般会把类模板的定义直接放到头文件中!!
只有被调用的类模板成员函数才会被编译器生产可执行代码!!!
2.2.2 类模板工程应用步骤
a. 在模板类外部定义成员函数实现时,需要加上
template<typename T>的声明
Source Example 2.2.2: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; template<typename T> class Operate { public: T add(T a, T b); T minus(T a, T b); }; template<typename T> T Operate<T> :: add(T a, T b) { return a + b; } template<typename T> T Operate<T> :: minus(T a, T b) { return a - b; } int main(int argc, char** argv) { Operate<int> op1; /* 对象op1用于处理Int型的加减法 */ Operate<double> op2; /* 对象op1用于处理double型的加减法 */ cout<<op1.add(5,4)<<endl; cout<<op2.minus(1.5,0.01)<<endl; return 0; }
b. 由于类模板的编译机制不同,所以不能像普通类一样分开实现后,在使用时只包含头文件。
Source Example(修改数组类): /* array.h */ #include <iostream> #include "array.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char** argv) { Array<int> ia(10); for (int i = 0; i < 5; i++) { ia[i] = i; } return 0; } /* array.c */ #include "array.h" #include <iostream> template <typename T> Array<T> :: Array(int length) { iLength = length; if (length > 0) { pSpace = new int[length]; } printf ("pSpace = %08x\n", pSpace); } template <typename T> int Array<T> :: getLength() { return iLength; } template <typename T> Array<T> :: Array(const Array& obj) { iLength = obj.iLength; pSpace = new int[iLength]; printf ("pSpace = %08x\n", pSpace); for (int i = 0; i < iLength; i++) { pSpace[i] = obj.pSpace[i]; } } template <typename T> T& Array<T> :: operator[] (int i) { return pSpace[i]; } template <typename T> Array<T>& Array<T> :: operator= (const Array<T>& obj) { delete[] pSpace; iLength = obj.iLength; pSpace = new int[iLength]; for (int i = 0; i < iLength; i++) { pSpace[i] = obj.pSpace[i]; } return *this; } /* 重载比较操作符 */ template <typename T> bool Array<T> :: operator== (const Array<T>& obj) { bool ret = true; if (iLength == obj.iLength) { for (int i = 0; i < iLength; i++) { if (pSpace[i] != obj.pSpace[i]) { ret = false; break; } } } else ret = false; return ret; } template <typename T> bool Array<T> :: operator!= (const Array<T>& obj) { return !(*this == obj); } template <typename T> Array<T> :: ~Array() { delete[] pSpace; } /* main.c */ #include <iostream> #include "array.h" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int main(int argc, char** argv) { Array<int> ia(10); /* 链接会出错 */ for (int i = 0; i < 5; i++) { ia[i] = i; } return 0; }问题分析:
1. 编译普通类是, main.cpp包含.h,整个工程会对所有文件进行编译
执行ia[i] = i + 1;时,首先进行语法检查,然后进行链接。
ia[i]是操作符重载,根据成员函数来实现,对三个文件进行编译, operate[]的代码被生成,就链接过了。
2. 编译类模板时,第一次编译三个文件,进行语法检查
在第二次编译(第二次编译要生成代码)array.cpp时,发现都是函数模板,因此只检查语法错误,不生成代码。
编译ia[i] = i + 1;时,需要链接operate[]可执行代码,因此会链接出错。
因此,在main.cpp中需要#include <array.cpp>,而不是#include <array.h>
也需要在array.cpp中添加 #ifndef _ARRAY_DEF_H_
#define _ARRAY_DEF_H_
#endif _ARRAY_DEF_H_
文件后缀名修改为.hpp
注意:只有被调用的类模板成员函数才会被编译器生产可执行代码!!
(二次编译)
真正的数组类: /* array.h */ #ifndef _ARRAY_H_ #define _ARRAY_H_ template <typename T> class Array{ private: int iLength; T *pSpace; public: Array(int length); Array(const Array& a); int getLength(); ~Array(); T& operator[] (int i); Array& operator= (const Array& obj); bool operator== (const Array& obj); bool operator!= (const Array& obj); }; #endif /* _ARRAY_H_ */ /* array.hpp */ #ifndef _ARRAY_DEF_H_ #define _ARRAY_DEF_H_ #include "array.h" #include <iostream> template <typename T> Array<T> :: Array(int length) { iLength = length; if (length > 0) { pSpace = new T[length]; } printf ("pSpace = %08x\n", pSpace); } template <typename T> int Array<T> :: getLength() { return iLength; } template <typename T> Array<T> :: Array(const Array& obj) { iLength = obj.iLength; pSpace = new int[iLength]; printf ("pSpace = %08x\n", pSpace); for (int i = 0; i < iLength; i++) { pSpace[i] = obj.pSpace[i]; } } template <typename T> T& Array<T> :: operator[] (int i) { return pSpace[i]; } template <typename T> Array<T>& Array<T> :: operator= (const Array<T>& obj) { delete[] pSpace; iLength = obj.iLength; pSpace = new int[iLength]; for (int i = 0; i < iLength; i++) { pSpace[i] = obj.pSpace[i]; } return *this; } /* 重载比较操作符 */ template <typename T> bool Array<T> :: operator== (const Array<T>& obj) { bool ret = true; if (iLength == obj.iLength) { for (int i = 0; i < iLength; i++) { if (pSpace[i] != obj.pSpace[i]) { ret = false; break; } } } else ret = false; return ret; } template <typename T> bool Array<T> :: operator!= (const Array<T>& obj) { return !(*this == obj); } template <typename T> Array<T> :: ~Array() { delete[] pSpace; } #endif /* _ARRAY_DEF_H_ */ /* main.cpp */ #include <iostream> #include "array.hpp" /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; int main(int argc, char** argv) { Array<int> ia(5); for (int i = 0; i < ia.getLength(); i++) { ia[i] = i; } for (int i = 0; i < ia.getLength(); i++) { cout<<ia[i]<<endl; } Array<double> ib(5); for (int i = 0; i < ia.getLength(); i++) { ib[i] = i; } for (int i = 0; i < ia.getLength(); i++) { cout<<ib[i]<<endl; } return 0; }
三.类模板的特化
3.1 类模板可以被特化,即处理一些无法被处理的类型
Source Example 3.1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; template <typename T> class Test { public: T test(T v) { cout << "T test(T v)"<< endl; cout << "sizeof(T) = "<< sizeof(T)<<endl; return v; } }; int main(int argc, char** argv) { Test<int> t1; cout<<t1.test(4)<<endl; return 0; }
输出结果如下:
T test(T,v)
sizeof(T) = 4
但是,我们想要得到结果
T test(int,4)
sizeof(int) = 4
这时,就对Int类型进行特化
Source Example: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ using namespace std; template<typename T> class Test { public: T test(T v) { cout << "T test(T v)"<< endl; cout << "sizeof(T) = "<< sizeof(T)<<endl; return v; } }; template<> class Test<int> { public: int test(int v) { cout << "int test(int v)"<< endl; cout << "sizeof(int) = "<< sizeof(int)<<endl; return v; } }; /* 可以通过继承将特化类变为自己的一个普通的类,这就是特化类的工程意义 */ class Mytest : public Test<int> { }; int main(int argc, char** argv) { /* 编译器会优先选择特化类来生成对象 */ //Test<int> t2; Mytest t1; cout<<t1.test(4)<<endl; return 0; }
四.特化类模板的意义
当类模板在处理某种特定类型有缺陷时,可以通过类模板特化来克服处理这种特定类型带来的不足!
注意:编译器优先选择特化类生成对象