文章目录
1 C++中的类模板简介
我们知道,有些类主要用于存储和组织数据元素,类中数据组织的方式和数据元素的具体类型无关,如:数组类、链表类、Stack类、Queue类等。C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。
C++中的类模板:
- 以相同的方式处理不同的类型。
- 在类声明前使用template进行标识。
- <typename T>用于说明类中使用的泛指类型T。
- 声明的泛指类型T可以出现在类模板的任意地方。

类模板的应用:
- 只能显示指定具体类型,无法自动推导。
- 使用具体类型<Type>定义对象。

编译器对类模板的处理方式和函数模板相同:
- 从类模板通过具体类型产生不同的类。
- 在声明的地方对类模板代码本身进行编译。
- 在使用的地方对参数替换后的代码进行编译(成员函数也是分步编译,调用了哪个成员函数就对哪个成员函数进行编译)。
编程实验:类模板初探
#include <iostream>
#include <string>
using namespace std;
template < typename T >
class Operator
{
public:
T add(T a, T b)
{
return a + b;
}
T minus(T a, T b)
{
return a - b;
}
T multiply(T a, T b)
{
return a * b;
}
T divide(T a, T b)
{
return a / b;
}
};
string operator-(string& l, string& r)
{
return "Minus";
}
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
Operator<string> op2;
cout << op2.add("D.T.", "Software") << endl;
cout << op2.minus("D.T", "Software") << endl;
return 0;
}
2 继承中类模板的使用
2.1 父类一般类、子类是模板类
class B
{
public:
B(int b)
{
this->b = b;
}
private:
int b;
};
template <typename T>
class A : public B
{
public:
//函数的参数列表使用虚拟类型
A(T t) : B (0)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
父类一般类,子类是模板类, 和普通继承的玩法类似。
2.2 子类一般类、父类是模板类
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
class B : public A<int>
{
public:
B(int b)
{
this->b = b;
}
private:
int b;
};
子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数。
2.3 父类和子类均为模板类
template <typename T>
class A
{
public:
//函数的参数列表使用虚拟类型
A(T t)
{
this->t = t;
}
//成员函数返回值使用虚拟类型
T &getT()
{
return t;
}
private:
//成员变量使用虚拟类型
T t;
};
template <typename Tb>
class B: public A<Tb>
{
public:
B(Tb b):A<Tb>(b)
{
this->b = b;
}
private:
Tb b;
};
父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中。
3 类模板的工程应用
类模板的工程应用:
- 类模板必须在头文件中定义。
- 类模板不能分开实现在不同的文件中。
- 类模板外部定义的成员函数需要加上模板<>声明,成员函数的内部加不加都是可以的。
编程实验:模板类的工程应用
operator.h:
#ifndef _OPERATOR_H_
#define _OPERATOR_H_
template < typename T >
class Operator
{
public:
T add(T a, T b);
T minus(T a, T b);
T multiply(T a, T b);
T divide(T a, T b);
};
template < typename T >
T Operator<T>::add(T a, T b)
{
return a + b;
}
template < typename T >
T Operator<T>::minus(T a, T b)
{
return a - b;
}
template < typename T >
T Operator<T>::multiply(T a, T b)
{
return a * b;
}
template < typename T >
T Operator<T>::divide(T a, T b)
{
return a / b;
}
#endif
main.cpp:
#include <iostream>
#include <string>
#include "Operator.h"
using namespace std;
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
cout << op1.multiply(4, 5) << endl;
cout << op1.minus(5, 6) << endl;
cout << op1.divide(10, 5) << endl;
return 0;
}
我们在有些地方也会看到类模板的声明和实现是放在两个不同的文件中,但是因为类模板的特殊实现,我们应在使用类模板时使用#include 包含 实现部分的.cpp 或.hpp文件(如果一个文件是hpp结尾,那么代表当前文件是类模板的实现)。这样的作法显然有点过于繁琐,因此还是定义在同一个文件中比较合适!
4 多参数类模板和类模板的特化
4.1 多参数类模板
类模板可以定义任意多个不同的类型参数。

4.2 类模板的特化
类模板可以被特化:
- 指定类模板的特化实现。
- 部分类型参数必须显示指定。
- 根据类型参数分开实现类模板。
类模板的特化类型:
- 部分特化:用特定规则约束类型参数。
- 完全特化:完全显示指定类型参数。
部分特化:

完全特化:

类模板特化注意事项:
- 特化只是模板的分开实现,本质上是同一个类模板。
- 特化模板的使用方式是统一的,必须显示指定每一个类型参数。
编程实验:类模板的特化
#include <iostream>
#include <string>
using namespace std;
template
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
template
< typename T1, typename T2 >
class Test < T1*, T2* > // 关于指针的特化实现
{
public:
void add(T1* a, T2* b)
{
cout << "void add(T1* a, T2* b)" << endl;
cout << *a + *b << endl;
}
};
template
< typename T >
class Test < T, T > // 当 Test 类模板的两个类型参数完全相同时,使用这个实现
{
public:
void add(T a, T b)
{
cout << "void add(T a, T b)" << endl;
cout << a + b << endl;
}
void print()
{
cout << "class Test < T, T >" << endl;
}
};
template
< >
class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
int main()
{
Test<int, float> t1;
Test<long, long> t2;
Test<void*, void*> t3;
t1.add(1, 2.5);
t2.add(5, 5);
t2.print();
t3.add(NULL, NULL);
Test<int*, double*> t4;
int a = 1;
double b = 0.1;
t4.add(&a, &b);
return 0;
}
4.3 重定义和特化的不同
重定义:
- 一个类模板和一个新类(或者两个类模板)。
- 使用的时候需要考虑如何选择的问题。
特化:
- 以统一的方式使用类模板和特化类。
- 编译器自动优先选择特化类。
5 模板类的特殊用法
5.1 模板类遇到友元函数
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t=0);
//声明一个友元函数,实现对两个A类对象进行加法操作
template <typename T>
friend A<T> addA(const A<T> &a, const A<T> &b);
/* 也可以写成如下形式:
friend A<T> addA <T>(const A<T> &a, const A<T> &b);
*/
T &getT();
A operator +(const A &other);
void print();
private:
T t;
};
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T &A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<<this->t<<endl;
}
//A 类的友元函数,就是它的好朋友
template <typename T>
A<T> addA(const A<T> &a, const A<T> &b){
A<T> tmp;
cout<<"call addA()..."<<endl;
tmp.t = a.t + b.t;
return tmp;
}
int main(void){
A<int> a(666), b(888);
//cout<<a.getT()<<endl;
A<int> tmp = a + b;
A<int> tmp1 = addA<int>(a, b);
tmp.print();
tmp1.print();
system("pause");
return 0;
}
结论:
(1) 类内部声明友元函数,必须写成一下形式
template<typename T>
friend A<T> addA (A<T> &a, A<T> &b);
(2) 友元函数实现 必须写成
template<typename T>
A<T> add(A<T> &a, A<T> &b)
{
//......
}
(3)友元函数调用 必须写成A<int> c4 = addA<int>(c1, c2);。
5.2 类模板遇上静态成员
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
A(T t=0);
T &getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
T t;
};
template <typename T> int A<T>::count = 666;
template <typename T>
A<T>::A(T t)
{
this->t = t;
}
template <typename T>
T &A<T>::getT()
{
return t;
}
template <typename T>
A<T> A<T>::operator+(const A<T> &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
template <typename T>
void A<T>::print(){
cout<<this->t<<endl;
}
/*
//当我们的虚拟的类型T被 int 实例化以后,模板类如下:
class A
{
public:
A(int t=0);
int &getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
int t;
};
int A::count = 666;
A::A(int t)
{
this->t = t;
}
int &A::getT()
{
return t;
}
A A::operator+(const A &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
void A::print(){
cout<<this->t<<endl;
}
*/
/*
//当我们的虚拟的类型T被 float 实例化以后,模板类如下:
class A
{
public:
A(float t=0);
float &getT();
A operator +(const A &other);
void print();
public:
static int count;
private:
float t;
};
int A::count = 666;
A::A(float t)
{
this->t = t;
}
float &A::getT()
{
return t;
}
A A::operator+(const A &other){
A tmp; //类的内部类型可以显示声明也可以不显示
tmp.t =this->t + other.t;
return tmp;
}
void A::print(){
cout<<this->t<<endl;
}
*/
int main(void){
A<int> a(666), b(888);
A<int> tmp = a + b;
//A a(666), b(888);
//A tmp = a + b;
A<float> c(777), d(999);
a.count = 888;
cout<<"b.count:"<<b.count<<endl;
cout<<"c.count:"<<c.count<<endl;
cout<<"d.count:"<<d.count<<endl;
c.count = 1000;
cout<<"修改后, d.count:"<<d.count<<endl;
//tmp.print();
system("pause");
return 0;
}
总结:
- 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员。
- 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化。
- static 数据成员也可以使用虚拟类型参数T。
参考资料:
967

被折叠的 条评论
为什么被折叠?



