C++模板类(类模板)的定义和使用
类也可以像函数一样被不同的类型参数化,如 STL 中的 vector 容器就是典型的例子,使用 vector 不需要关心容器中的数据类型,就可以对数据进行操作。
类模板是针对成员数据类型不同的类的抽象,它不是一个具体实际的类,而是一个类型的类,一个类模板可以生成多种具体的类。
类模板的定义格式如下所示:
定义类模板示例代码如下所示:
定义了类模板就要使用类模板创建对象以及实现类中的成员函数,这个过程其实也是类模板实例化的过程,实例化出的具体类称为模板类。
如果用类模板创建类的对象,例如,用上述定义的类模板 A 创建对象,则在类模板 A 后面加上一个 <>,并在里面表明相应的类型,示例代码如下所示:
例如,定义一个有两个模板参数的类模板 B,然后用 B 创建类对象,示例代码如下所示:
【示例1】下面通过案例演示类模板的实例化,C++ 代码如下:
需要注意的是,类模板在实例化时,带有模板参数的成员函数并不会跟着实例化,这些成员函数只有在被调用时才会被实例化。
类模板的派生一般有三种情况:类模板派生普通类、类模板派生类模板、普通类派生类模板。这三种派生关系可以解决很多实际问题。
下面针对这三种派生关系进行讲解。
类模板派生普通类的示例代码如下所示:
例如,由类模板 Base 派生出一个类模板 Derive,示例代码如下:
类模板派生类模板技术可以用来构建类模板的层次结构。
普通类派生类模板示例代码如下所示:
接下来,将针对这三种友元函数进行详细讲解。
除此之外,还可以将带有模板类参数的函数声明为友元函数,示例代码如下:
调用带有模板类参数的友元函数时,友元函数必须显式具体化,指明友元函数要引用的参数的类型,例如:
【示例2】下面通过案例演示非模板友元函数的用法,C++ 代码如下:
函数模板的实例化类型取决于类模板被实例化时的类型,类模板实例化时会产生与之匹配的具体化友元函数。
在使用约束模板友元函数时,首先需要在类模板定义的前面声明函数模板。例如,有两个函数模板声明,示例代码如下:
在声明友元函数时,函数模板要实现具体化,即函数模板的模板参数要与类模板的模板参数保持一致,以便类模板实例化时产生与之匹配的具体化友元函数。示例代码如下所示:
当生成 A<int> 模板类时,会生成与之匹配的 func<int>() 函数和 show<int>() 函数作为友元函数。
需要注意的是,在上述代码中,func() 函数模板没有参数,必须使用 <> 指定具体化的参数类型。show() 函数模板有一个模板类参数,编译器可以根据函数参数推导出模板参数,因此 show() 函数模板具体化中<>可以为空。
【示例3】下面通过案例演示约束模板友元函数的用法,C++ 代码如下:
声明非约束模板友元函数示例代码如下所示:
函数模板 show() 的每个模板函数都是类模板 A 每个模板类的友元函数。
【示例4】下面通过案例演示非约束模板友元函数的用法,C++ 代码如下:
由此可知,非约束模板友元函数的模板参数与类模板的模板参数不相关,它可以接受任何类型的参数。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
类模板定义与实例化
函数可以定义函数模板,同样地,对于类来说,也可以定义一个类模板。类模板是针对成员数据类型不同的类的抽象,它不是一个具体实际的类,而是一个类型的类,一个类模板可以生成多种具体的类。
类模板的定义格式如下所示:
template<typename 类型占位符>
class 类名
{
}
定义类模板示例代码如下所示:
template<typename T> class A { public: T a; T b; T func(T a, T b); };上述代码中,在类 A 中声明了两个T类型的成员变量 a 和 b,还声明了一个返回值类型为 T 并带两个 T 类型参数的成员函数 func()。
定义了类模板就要使用类模板创建对象以及实现类中的成员函数,这个过程其实也是类模板实例化的过程,实例化出的具体类称为模板类。
如果用类模板创建类的对象,例如,用上述定义的类模板 A 创建对象,则在类模板 A 后面加上一个 <>,并在里面表明相应的类型,示例代码如下所示:
A<int> a;这样类 A 中凡是用到模板参数的地方都会被int类型替换。如果类模板有多个模板参数,创建对象时,多个类型之间要用逗号分隔开。
例如,定义一个有两个模板参数的类模板 B,然后用 B 创建类对象,示例代码如下所示:
template<typename T1, typename T2> class B { public: T1 a; T2 b; T1 func(T1 a, T2& b); }; B<int,string> b; //创建模板类B<int,string>的对象b使用类模板时,必须要为模板参数显式指定实参,不存在实参推演过程,也就是说不存在将整型值 10 推演为 int 类型再传递给模板参数的过程,必须要在
<>
中指定 int 类型,这一点与函数模板不同。【示例1】下面通过案例演示类模板的实例化,C++ 代码如下:
#include<iostream> using namespace std; template< typename T> //类模板的定义 class Array { private: int _size; T* _ptr; public: Array(T arr[], int s); void show(); }; template<typename T> //类模板外定义其成员函数 Array<T>::Array(T arr[], int s) { _ptr = new T[s]; _size = s; for (int i=0;i<_size; i++) { _ptr[i]=arr[i]; } } template<typename T> //类模板外定义其成员函数 void Array<T>::show() { for(int i=0;i<_size;i++) cout<<*(_ptr + i)<<" "; cout<<endl; } int main() { char cArr[] = { 'a', 'b', 'c', 'd', 'e' }; Array<char> a1(cArr, 5); //创建类模板的对象 a1.show(); int iArr[10] = { 1, 2, 3, 4, 5, 6 }; Array<int> a2(iArr, 10); a2.show(); return 0; }运行结果:
a b c d e
1 2 3 4 5 6 0 0 0 0
- 第 3~12 行代码定义了一个类模板 Array,Array 的构造函数有一个数组类型参数;
- 第 33 行代码在创建类对象 a1 时,用 char 类型的数组去初始化,调用 show() 函数输出数组元素;
- 第 36 行代码创建对象 a2 时,用 int 类型的数组去初始化,调用 show() 函数输出数组元素。
需要注意的是,类模板在实例化时,带有模板参数的成员函数并不会跟着实例化,这些成员函数只有在被调用时才会被实例化。
类模板的派生
类模板和普通类一样也可以继承和派生,以实现代码复用。类模板的派生一般有三种情况:类模板派生普通类、类模板派生类模板、普通类派生类模板。这三种派生关系可以解决很多实际问题。
下面针对这三种派生关系进行讲解。
1) 类模板派生普通类
在 C++ 中,可以从任意一个类模板派生一个普通类。在派生过程中,类模板先实例化出一个模板类,这个模板类作为基类派生出普通类。类模板派生普通类的示例代码如下所示:
template<typename T> class Base //类模板Base { private: T x; T y; public: Base(); Base(T x, T y); Base getx(); Base gety(); ~ Base(); }; class Derive:public Base<double> //普通类Derive公有继承类模板Base { private: double num; public: Derive(double a, double b, double c):num(c), Base<double>(a, b){} };在上述代码中,类模板 Base 派生出了普通类 Derive,其实在这个派生过程中类模板 Base 先实例化出了一个 double 类型的模板类,然后由这个模板类派生出普通类Derive,因此在派生过程中需要指定模板参数类型。
2) 类模板派生类模板
类模板也可以派生出一个新的类模板,它和普通类之间的派生几乎完全相同。但是,派生类模板的模板参数受基类模板的模板参数影响。例如,由类模板 Base 派生出一个类模板 Derive,示例代码如下:
template<typename T> class Base { public: T _a; public: Base(T n):_a(n) {} T get() const { return _a; } }; template<typename T, typename U> class Derive:public Base<U> { public: U _b; public: Derive(T t, U u):Base<T>(t), _b(u) {} U sum() const { return _b + U(Base::get()); } };上述代码中,类模板 Derive 由类模板 Base 派生,Derive 的部分成员变量和成员函数类型由类模板 Base 的参数 U 确定,因此 Derive 仍然是一个模板。
类模板派生类模板技术可以用来构建类模板的层次结构。
3) 普通类派生类模板
普通类也可以派生类模板,普通类派生类模板可以把现存类库中的类转换为通用的类模板,但在实际编程中,这种派生方式并不常用,本文只对它作一个简单示例,读者只需要了解即可。普通类派生类模板示例代码如下所示:
class Base { int _a; public: Base(int n):_a(n){} int get() const {return _a;} }; template<typename T> class Derive: public Base { T _b; public: Derive(int n, T t):Base(n), _b(t){} T sum() const {return _b + (T)get();} };在上述代码中,类 Base 是普通类,类模板 Derive 继承了普通类 Base。利用这种技术,程序设计者能够从现存类中创建类模板,由此可以创建基于非类模板库的类模板。
类模板与友元函数
在类模板中声明友元函数有三种情况:非模板友元函数、约束模板友元函数和非约束模板友元函数。接下来,将针对这三种友元函数进行详细讲解。
1) 非模板友元函数
非模板友元函数就是将一个普通函数声明为友元函数。例如,在一个类模板中声明一个友元函数,示例代码如下:template<typename T> class A { T _t; public: friend void func(); };在类模板 A 中,将普通函数 func() 声明为友元函数,则 func() 函数是类模板 A 所有实例的友元函数。上述代码中,func() 函数为无参函数。
除此之外,还可以将带有模板类参数的函数声明为友元函数,示例代码如下:
template<typename T> class A { T _t; public: friend void show(const A<T>& a); };在上述代码中,show() 函数并不是函数模板,只是有一个模板类参数。
调用带有模板类参数的友元函数时,友元函数必须显式具体化,指明友元函数要引用的参数的类型,例如:
void show(const A<int>& a); void show(const A<double>& a);上述代码中,模板参数为 int 类型的 show() 函数是 A<int> 类的友元函数,模板参数为 double 类型的 show() 函数是 A<double> 类的友元函数。
【示例2】下面通过案例演示非模板友元函数的用法,C++ 代码如下:
#include<iostream> using namespace std; template<typename T> class A { T _item; static int _count; //静态变量 public: A(const T& t) :_item(t){ _count++; } ~A(){ _count--; } friend void func(); //无参友元函数func() friend void show(const A<T>& a); //有参友元函数show() }; template<typename T> int A<T>::_count = 0; //初始化静态变量 void func() //func()函数实现 { cout<<"int count:"<<A<int>::_count<<";"; cout<<"double count:"<<A<double>::_count<<";"<<endl; } //模板参数为int类型 void show(const A<int>& a){cout<<"int:"<<a._item<<endl;} void show(const A<double>& a){cout<<"double:"<<a._item<<endl;} int main() { func(); //调用无参友元函数 A<int> a(10); //创建int类型对象 func(); A<double> b(1.2); show(a); //调用有参友元函数 show(b); return 0; }运行结果:
int count:0;double count:0;
int count:1;double count:0;
int:10
double:1.2
- 第 3~13 行代码定义了类模板 A,在类模板 A 中声明了两个友元函数 func() 和 show()。其中,func() 函数为无参友元函数,show() 函数有一个模板类对象作为参数。此外,类模板 A 还声明了静态成员变量 _count,用于记录每一种模板类创建的对象个数;
- 第 16~20 行代码是 func() 函数的定义,func() 函数的作用是输出 A<int> 类对象和 A<double> 类对象的个数;
- 第 22~23 行代码分别定义了 show(const A<int>&a) 函数和 show(const A<double>&a) 函数,用于分别输出 A<int> 类对象和 A<double> 类对象的值;
- 第 26 行代码调用 func() 函数,此时还未创建任何模板类对象;
- 第 27 行代码创建了 A<int> 模板类对象 a,初始化值为 10;
- 第 28 行再次调用 func() 函数;
- 第 29 行代码创建 A<double> 模板类对象b,初始化值为1.2;
- 第 30~31 行代码调用 show() 函数,分别传入对象a和对象b作为参数。
2) 约束模板友元函数
约束模板友元函数是将一个函数模板声明为类的友元函数。函数模板的实例化类型取决于类模板被实例化时的类型,类模板实例化时会产生与之匹配的具体化友元函数。
在使用约束模板友元函数时,首先需要在类模板定义的前面声明函数模板。例如,有两个函数模板声明,示例代码如下:
template<typename T> void func(); template<typename T> void show(T& t);声明函数模板之后,在类模板中将函数模板声明为友元函数。
在声明友元函数时,函数模板要实现具体化,即函数模板的模板参数要与类模板的模板参数保持一致,以便类模板实例化时产生与之匹配的具体化友元函数。示例代码如下所示:
template<typename U> //类模板的定义 class A { … //其他成员 friend void func<U>(); //声明无参友元函数func() friend void show<>(A<U>& a); //声明有参友元函数show() … //其他成员 };在上述代码中,将函数模板 func() 与 show() 声明为类的友元函数,在声明时,func() 与 show() 的模板参数受类模板 A 的模板参数约束,与类模板的模板参数相同。
当生成 A<int> 模板类时,会生成与之匹配的 func<int>() 函数和 show<int>() 函数作为友元函数。
需要注意的是,在上述代码中,func() 函数模板没有参数,必须使用 <> 指定具体化的参数类型。show() 函数模板有一个模板类参数,编译器可以根据函数参数推导出模板参数,因此 show() 函数模板具体化中<>可以为空。
【示例3】下面通过案例演示约束模板友元函数的用法,C++ 代码如下:
#include<iostream> using namespace std; template<typename T> //声明函数模板func() void func(); template<typename T> //声明函数模板show() void show(T& t); template<typename U> //类模板的定义 class A { private: U _item; static int _count; public: A(const U& u):_item(u){_count++;} ~A(){_count--;} friend void func<U>(); //声明友元函数func() friend void show<>(A<U>& a); //声明友元函数show() }; template<typename T> int A<T>::_count = 0; template<typename T> //函数模板func()的定义 void func() { cout<<"template size:"<<sizeof(A<T>)<<";"; cout<<"template func():"<<A<T>::_count<<endl; } template<typename T> //函数模板show()的定义 void show(T& t){cout<< t._item<<endl;} int main() { func<int>(); //调用int类型的函数模板实例,int类型,其大小为4字节 A<int> a(10); //定义A<int>类对象a A<int> b(20); //定义A<int>类对象b A<double> c(1.2); //定义A<double>类对象c show(a); //调用show()函数,输出类对象a的值 show(b); //调用show()函数,输出类对象b的值 show(c); //调用show()函数,输出类对象c的值 cout<<"func<int>output:\n"; func<int>(); //运行到此,已经创建了两个int类型对象 cout<<"func<double>()output:\n"; func<double>(); return 0; }运行结果:
template size:4;template func():0
10
20
1.2
func<int>output:
template size:4;template func():2
func<double>()output:
template size:8;template func():1
- 第 3~7 行代码声明函数模板 func() 和 show();
- 第 16~17 行代码分别将函数模板 func() 与 show() 声明为类的友元函数;
- 第 21~26 行代码是函数模板 func() 的定义,用于输出某一类型模板类的大小及对象个数;
- 第 27~28 行代码是函数模板 show() 的定义,用于输出模板类对象的值。
- 第 31 行代码调用 func<int>(),即输出A<int>类的大小及对象个数;
- 第 32~34 行代码分别定义 A<int> 类对象 a 和 b,A<double> 类对象 c;
- 第 35~37 行代码调用 show() 函数,分别传入对象 a、b、c 作为参数,输出各对象的值;
- 第 39 行代码调用 func<int>() 函数,由于此时已经创建了两个 A<int> 类对象 a 和 b,因此输出的对象个数应当为 2;
- 第 41 行代码调用 func<double>() 函数,由于此时已经创建了一个 A<double> 类对象 c,因此输出的对象个数应当为 1。
3) 非约束模板友元函数
非约束模板友元函数是将函数模板声明为类模板的友元函数,但函数模板的模板参数不受类模板影响,即友元函数模板的模板参数与类模板的模板参数是不同的。声明非约束模板友元函数示例代码如下所示:
template<typename T> class A { template<typename U, typename V> friend void show(U& u, V& v); };在上述代码中,类模板 A 将函数模板 show() 声明为友元函数,但 show() 的模板参数 U、V 不受类模板 A 的模板参数 T 影响,则函数模板 show() 就是类模板 A 的非约束友元函数。
函数模板 show() 的每个模板函数都是类模板 A 每个模板类的友元函数。
【示例4】下面通过案例演示非约束模板友元函数的用法,C++ 代码如下:
#include<iostream> using namespace std; template<typename T> //定义类模板A class A { private: T _item; public: A(const T& t) :_item(t){} template<class U, class V> //声明非约束模板友元函数 friend void show(U& u, V& v); }; template<typename U, typename V> //函数模板show()的定义 void show(U& u, V& v){cout<<u._item<<","<<v._item<<endl;} int main() { A<int> a(10); //定义A<int>类对象a A<int> b(20); //定义A<int>类对象b A<double> c(1.2); //定义A<int>类对象c cout<<"a,b: "; show(a, b); //调用show()函数,传入对象a、b作为实参 cout<<"a,c:"; show(a, c); //调用show()函数,传入对象a、c作为实参 return 0; }运行结果:
a,b: 10,20
a,c:10,1.2
- 第 3~12 行代码定义类模板 A,在类模板中将函数模板 show() 声明为非约束友元函数;
- 第 13~14 行代码是函数模板 show() 的定义;
- 第 17~19 行代码分别定义 A<int> 类对象 a 和 b,A<double> 类对象 c;
- 第 21 行代码调用 show() 函数,传入对象 a、b 作为实参;
- 第 23 行代码调用 show() 函数,传入对象 a、c 作为实参。
由此可知,非约束模板友元函数的模板参数与类模板的模板参数不相关,它可以接受任何类型的参数。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。