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笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。