模板/STL|【c++模板笔记二】类模板的介绍以及使用方法

2015年2月16日 周一雨
很久没有更新笔记,上一篇博客介绍了什么是模板,主要是告诉大家函数模板。今天我们就再来一起看一看什么是类模板。
函数模板就是把函数作为模板去使用,类模板当然也就是把类作为模板去使用。
——————————————————————分割线————————————————————————
一、什么是类模板? 模板的目的就是为了淡化数据类型的要求,作出通用数据类型的模板。类中的成员变量和成员函数都牵涉到了数据类型。
在成员函数、成员变量以及基类中包含有类型参数的类称为类模板。

【模板/STL|【c++模板笔记二】类模板的介绍以及使用方法】和函数模板一样,类模板只是一个模板,并不是真正的类。
二、类模板的定义和使用 1.定义语法:
template
class 类模板名 [: public 基类] { ... };
和函数模板一样,都在上方加上一句template声明模板参数列表。然后在类中就可以使用这些类型形参了。 2.使用语法:
类模板名<类型实参1, 类型实参2, ...> 对象 (构造实参);
类模板名<类型实参1, 类型实参2, ...>& 对象引用 = ...;
类模板名<类型实参1, 类型实参2, ...>* 对象指针 = ...;

我们一起来看个例子大家就懂了:

#include #include using namespace std; template class A{ T m_data; public: void show (){ cout< a1, *a2; a1.show(); a2->show(); A& a3 = a1; a3.show(); A a4; a4.show(); return 0; }

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片


A就是一个类模板,a1为A实例化(int)的对象,a2则是一个对象指针,a3是一个对象引用。a4是double实例化A模板的对象。

三、类模板与函数模板的异同
1.类模板和函数模板都需要二次编译
请大家参看: 【c++模板笔记一】。我们看到函数模板的原理是二次编译。同样的类模板也需要二次编译。 类模板 -- 实例化为 ->类 -- 实例化为 -> 对象
类模板先在编译期的时候实例化为类,再在运行的时候实例化为真正的对象。其实类中的这个二次编译也叫二次实例化。
2.类不能隐式推断
我们在上一篇中讲到了函数模板的隐式推断,但是类模板却不能隐式推断。 模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片


我们可以看到,如果我们给类模板不提供模板参数列表的话,我们可以看见比编译器报错了(a之前缺少模板参数)
四、类模板的实例化 1.用类去实例化模板
上面说过,类模板的二次编译,其实也是二次实例化。但是如果用一个类类型去实例化一个类模板的话,又要注意什么呢? 用类去实例化一个类模板,规则如下: 1)该类型需要满足模板中对类型化参数的调用规则。
2)对于那些不使用的调用规则,实例化类型可以不予支持。

#include #include using namespace std; class Student{ int m_age; string m_name; public: Student(int age=0,string name=""):m_age(age),m_name(name){} }; template class A{ T m_data; public: void show (){ cout<<"show()"< a1; a1.show(); a1.fun(); A a2; a2.show(); //a2.fun(); return 0; }

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片

我发现我们用int去实例化类模板A的话,调用show和fun成员函数都没有问题。如果我们用Student这个类去实例化模板的话可不可以呢?我们发现,我们调用show函数时没有问题的。如果我们把30行的注释去掉的话,又会怎样? 模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片

如果我们用Student类去实例化类模板A的话,如果调用成员函数fun的话, 程序就开始报错 了。m_data这个类的类型是Student类型的,如果要cout<>”运算符进行重载。 这就是我们上面说的原则。
2.递归实例化
我们一起用模板做一个数组模板类,这种模板能存储任何数据类型。
#include using namespace std; template class Array{ T m_size[3]; public: T& operator[](int flag){ return m_size[flag]; } }; int main() { Array arr; arr[0]=10; arr[1]=20; arr[2]=30; for(int i=0; i<3; ++i) cout<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片

Array是一个类模板,可以实例化为多种类型的类,成员变量为含有3个元素的数组,可以把这个实例化后的类用来当做含有3个元素的数组使用。我们实例化了一个int类型的Array类,定义了一个对象。这个对象就是一个含有3元素的一维数组。 那我们如何去实例化一个3x3的二维数组类,就需要用到递归实例化,如:Array >。这就是递归实例化。 我们把来一起用代码来实现:
#include using namespace std; template class Array{ T m_size[3]; public: T& operator[](int flag){ return m_size[flag]; } }; int main() { Array > arr; for(int i=0; i<3; ++i) for(int j=0; j<3; ++j) arr[i][j]=(i+1)*3+(j+1); for(int i=0; i<3; ++i){ for(int j=0; j<3; ++j) cout<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片


这种实例化还是很实用的,可以适用于很多种递归的数据结构(如二位数组,树等等递归结构)。
五、类模板中的静态成员变量 我们都知道类中可以包含静态数据(静态成员变量和静态成员函数),不懂的请看我的: 【c++笔记七】。那类模板能不能也包含静态数据呢?当然没有问题。 但是我们还是要注意点一点,模板不是真正的类,仅仅是模板而已。类模板的静态成员变量既不是一个对象一个,也不是一个模板一个,而是一个实例化类一个。我们一起来看代码:
#include using namespace std; template class A{ public: static T m_data; }; class B{ public: static int num; }; int B::num = 0; int main() { cout<<"B::num : "<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片
B类是一个普通类,有一个静态成员变量。静态成员变量需要初始化的,所以需要在类外进行初始化,初始化的语法如第13行。但是,A却是一个模板类,但是我们要如何去初始化这个模板类中的静态成员变量呢?
我们前面说过,类模板只是模不是类,不能当类去使用它。只有类才有作用域,所以无法直接在A后面跟上“::”作用域限定符。并且,声明m_data的类型是T,在类外的话没有这个T。所以,初始化模板的静态成员变量应该这么去写:
#include using namespace std; template class A{ public: static T m_data; }; template T A::m_data = https://www.it610.com/article/100; class B{ public: static int num; }; int B::num = 0; int main() { cout<<"A::m_data : "<::m_data<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片



六、局部特化 记得我们上一篇提到过模板重载吧?我们说过函数模板可以和普通函数构成重载关系。如:
template T Max(T a,T b){ return a>b?a:b; } const char* Max(const char* a,const char* b){ return strcmp(a,b)>0?a:b; }

下面的那个普通函数Max和上面函数模板Max构成了重载关系。如果在类模板中也进行这样的重载,就会出现什么情况呢? 模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片

编译器报错了,说13行的普通成员函数不能构成重载。那么怎么去解决这个问题呢?一个方法就是 全部特化。
#include #include using namespace std; template class A { T m_a; T m_b; public: A (T a, T b) : m_a (a), m_b (b) {} T max (){ return m_a > m_b ? m_a : m_b; } }; template<> class A{ const char* m_a; const char* m_b; public: A (const char* a, const char* b) : m_a (a), m_b (b) {} const char* max () { return strcmp (m_a, m_b) > 0 ? m_a : m_b; } }; int main() { A a("hello","world"); cout<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片
我们可以看第14行到23行,就是模板A的 全局特化。用const char*去 特例化模板类A。 另外一种方法就是 局部特化。
#include #include using namespace std; template class A { T m_a; T m_b; public: A (T a, T b) : m_a (a), m_b (b) {} T max (){ return m_a > m_b ? m_a : m_b; } }; template<> const char* A::max(){ return strcmp(m_a,m_b)>0?m_a:m_b; } int main() { A a("hello","world"); cout<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片
第14行到17行,就是 局部特化。相比全部特化,省去很多事情。


七、类模板的缺省模板参数 我们经常看见函数有缺省参数,如int max(int a=10,int b=20)。 类模板其实也有缺省的模板参数,但是c++的98标准并没有这一东西,是在c++11标准中新引入的。我们看看代码就行:
#include #include using namespace std; template void fun(){ T a; cout<

模板/STL|【c++模板笔记二】类模板的介绍以及使用方法
文章图片

我们给类模板的模板参数指定默认的参数类型为double型。
————————————————————分割线———————————————————————— 类模板的知识就是这些,没有什么比较难的知识点,主要是大家懂了之后要会去使用,为以后的STL打个基础。 总结一下:首先我们介绍什么是类模板,怎么去定义和使用类模板。其次我们比较了类模板和函数模板的异同,然后我们讲解了类模板的实例化。再者我们讲了类模板的静态成员变量和局部特化。特别是要会使用局部特化。最后还讲了类模板的缺省参数,是一个非常有用的东西。


    推荐阅读