第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;
}
四.特化类模板的意义
当类模板在处理某种特定类型有缺陷时,可以通过类模板特化来克服处理这种特定类型带来的不足!
注意:编译器优先选择特化类生成对象