C++ 模板
模板并不是真正的函数或类,它仅仅是编译器用来生成函数或类的一张“图纸”。模板不会占用内存,最终生成的函数或者类才会占用内存。由模板生成函数或类的过程叫做模板的实例化,相应地,针对某个类型生成的特定版本的函数或类叫做模板的一个实例。
在学习模板以前,如果想针对不同的类型使用相同的算法,就必须定义多个极其相似的函数或类,这样不但做了很多重复性的工作,还导致代码维护困难,用于交换两个变量的值的 Swap() 函数就是一个典型的代表。而有了模板后,这些工作都可以交给编译器了,编译器会帮助我们自动地生成这些代码。从这个角度理解,模板也可以看做是编译器的一组指令,它命令编译器生成我们想要的代码。
模板的实例化是按需进行的,用到哪个类型就生成针对哪个类型的函数或类,不会提前生成过多的代码。也就是说,编译器会根据传递给类型参数的实参(也可以是编译器自己推演出来的实参)来生成一个特定版本的函数或类,并且相同的类型只生成一次。实例化的过程也很简单,就是将所有的类型参数用实参代替。
例如,给定下面的函数模板和函数调用:
template<typename T> void Swap(T &a, T &b){ T temp = a; a = b; b = temp; } int main(){ int n1 = 100, n2 = 200, n3 = 300, n4 = 400; float f1 = 12.5, f2 = 56.93; Swap(n1, n2); //T为int,实例化出 void Swap(int &a, int &b); Swap(f1, f2); //T为float,实例化出 void Swap(float &a, float &b); Swap(n3, n4); //T为int,调用刚才生成的 void Swap(int &a, int &b); return 0; }
编译器会根据不同的实参实例化出不同版本的 Swap() 函数。对于Swap(n1, n2)
调用,编译器会生成并编译一个 Swap() 版本,其中 T 被替换为 int:
void Swap(int &a, int &b){ int temp = a; a = b; b = temp; }
对于Swap(f1, f2)
调用,编译器会生成另一个 Swap() 版本,其中 T 被替换为 float。对于Swap(n3, n4)
调用,编译器不会再生成新版本的 Swap() 了,因为刚才已经针对 int 生成了一个版本,直接拿来使用即可。
另外需要注意的是类模板的实例化,通过类模板创建对象时并不会实例化所有的成员函数,只有等到真正调用它们时才会被实例化;如果一个成员函数永远不会被调用,那它就永远不会被实例化。这说明类的实例化是延迟的、局部的,编译器并不着急生成所有的代码。
通过类模板创建对象时,一般只需要实例化成员变量和构造函数。成员变量被实例化后就能够知道对象的大小了(占用的字节数),构造函数被实例化后就能够知道如何初始化了;对象的创建过程就是分配一块大小已知的内存,并对这块内存进行初始化。
请看下面的例子:
#include <iostream> using namespace std; template<class T1, class T2> class Point{ public: Point(T1 x, T2 y): m_x(x), m_y(y){ } public: T1 getX() const{ return m_x; } void setX(T1 x){ m_x = x; } T2 getY() const{ return m_y; }; void setY(T2 y){ m_y = y; }; void display() const; private: T1 m_x; T2 m_y; }; template<class T1, class T2> void Point<T1, T2>::display() const {cout<<"x="<<m_x<<", y="<<m_y<<endl;} int main(){ Point<int, int> p1(10, 20); //要指明类型 p1.setX(40); p1.setY(50); cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl; Point<char*, char*> p2("东180度", "北210度"); p2.display(); return 0; }
运行结果:
x=40, y=50 x=东180度, y=北210度
p1 调用了所有的成员函数,整个类会被完整地实例化。p2 只调用了构造函数和 display() 函数,剩下的 get 函数和 set 函数不会被实例化。
值得提醒的是,Point<int, int>
和Point<char*, char*>
是两个相互独立的类,它们的类型是不同的,不能相互兼容,也不能自动地转换类型,所以诸如p1 = p2;
这样的语句是错误的,除非重载了=
运算符。