
目录
模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板分为函数模板和类模板,函数模板针对参数类型不同的函数;类模板针对数据成员和成员函数类型不同的类。
模板支持与了类型无关的代码的编写。
编写与类型无关的通用代码,是代码复用的一种手段。
比如说,我们定义一个交换intl类型元素值的函数。
- void swap(int& a, int& b)
- {
- int temp = 0;
- temp = a;
- a = b;
- b = temp;
- }
这个函数可以交换int类型的变量值,但是只能操作int类型。如果我们需要交换double类型的变量,按照C++的逻辑就需要再次定义一个新的重载函数。
- void swap(double& a, double& b)
- {
- double temp = 0;
- temp = a;
- a = b;
- b = temp;
- }
实现所有类型变量的交换需要写太多逻辑相同的函数,过于冗余。那么,如果我们不指定这个参数的类型,用一个泛型去替代这些类型,让编译器去推导这个参数的具体类型,那么我们就只需要一个函数定义了。
这样的泛型函数就叫做模板,内部变量的类型叫做泛型。
- template<typename T1, typename T2,......,typename Tn>//typename也可以换成class
- 返回值类型 函数名 ( 参数列表 )
- {}
- template<typename T1>//用template关键字定义泛型
- void swap(T1& a, T1& b)//内部变量也用泛型
- {
- T1 temp = 0;
- temp = a;
- a = b;
- b = temp;
- }
可以定义多个泛型T1、T2、T3等等,一个泛型只能推导出一个具体类型。T1可以推导为int类型,但是不能又推导为int,又推导为其他类型。
我们定义一个没有完全写好的栈
- #define TYPE int
- class Stack
- {
- public:
- Stack()
- :_a((TYPE*)malloc(sizeof(TYPE)* 4))
- ,_size(0)
- , _volume(0)
- {}
- void push(TYPE data)
- {
- _a[_size++] = data;
- }
- private:
- TYPE* _a;
- int _size;
- int _volume;
- };
虽然通过改变TYPE对应的类型可以改变存储数据的类型,但是如果我们想同时使用一个储存int类型元素的栈和一个储存double类型元素的栈,那么还需要定义另一个栈的类,依旧冗余。所以类似于函数,类也可以使用泛型。这种使用泛型的类也叫做类模板,与函数模板相似。
- template<typename T1>
- class Stack
- {
- public:
- Stack()
- :_a((T1*)malloc(sizeof(T1)* 4))
- ,_size(0)
- , _volume(0)
- {}
- void push(T1 data)
- {
- _a[_size++] = data;
- }
- private:
- T1* _a;
- int _size;
- int _volume;
- };
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数和类的声明,相当于建筑的图纸,只有使用到这个函数和类时才会定义。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
简单说就是本来我们应该多去写的Swap函数和Stack类的重复工作去给编译器做了。
这是我们之前的函数模板
- template<typename T1>//用template关键字定义泛型
- void swap(T1& a, T1& b)//内部变量也用泛型
- {
- T1 temp = 0;
- temp = a;
- a = b;
- b = temp;
- }
对于不同类型的变量进行swap,当编译器遇到符合模板但没有定义的函数,编译器就会自己生成一个对应的重载函数,如果这个函数之前定义过那就使用之前定义过的函数。
- template<typename T1>
- void swap(T1& a, T1& b)
- {
- T1 temp = 0;
- temp = a;
- a = b;
- b = temp;
- }
- int main()
- {
- int a = 1;
- int b = 2;
- swap(a,b);
- //交换两个int变量a和b,编译器会根据模板定义一个int类型的交换函数,如下:
- //void swap(int& a, int& b)
- double c = 1.0;
- double d = 2.0;
- swap(c,d);
- //交换两个double变量c和d,编译器会根据模板再次定义一个double类型的交换函数,如下:
- //void swap(double& a, double& b)
- int e = 1;
- int f = 2;
- swap(e,f);
- //再次交换两个int变量e和f,编译器会继续使用之前定义的int类型交换函数
- //void swap(int& a, int& b)
- return 0;
- }
最后的结果还是定义了很多不同类型的重载函数,与我们自己定义许多不同类型的函数的结果是一样的。但是这时不再需要程序员去一个一个类型定义函数,大大简化了程序的代码量。
模板对于类也是一样的效果,也会自己定义许多不同类型的类去使用,但是类只支持显式实例化。
我们不光可以让计算机自己推断类型,而且也可以自己指定生成哪一种类型参数的函数和类。
- template<typename T1>//定义泛型T1,在这里也表示下面的函数中会用到T1
- void Swap(T1& a, T1& b)
- {
- T1 temp = 0;
- temp = a;
- a = b;
- b = temp;
- }
- template<typename T1, typename T2>//定义泛型T2,T1在前面已经定义了表示会用到T1和T2
- void print(T1& a, T2& b)//注意,这个T1经过推导只能对应一个类型,不能在上面的函数中是int下面的是double
- {
- cout << a << ' ' << b << endl;
- }
- int main()
- {
- int a = 1;
- int b = 2;
- double c = 1.0;
- Swap<int>(a, b);//在函数后面加<类型>就可以指定定义和使用对应类型的函数
- print<int, double>(a, c);//<>内部多个类型用,隔开对应上面的template
- cout << a << ' ' << b << endl;
- return 0;
- }
- //输出:
- //2 1
- //2 1
之前说过,类只支持显式实例化,不能通过编译器推断。类模板也是一个声明或者说是一个蓝图,只有使用了这个类型的函数或者类才会被定义。
- template<typename T>
- class Stack
- {
- public:
- Stack()
- :_a((T*)malloc(sizeof(T)* 4))
- , _size(0)
- , _volume(0)
- {}
- void push(T data)
- {
- _a[_size++] = data;
- }
- private:
- T* _a;
- int _size;
- int _volume;
- };
-
- int main()
- {
- Stack<int> s1;//定义一个储存int类型的类,类型为Stack
- Stack<double> s2;//定义一个储存double类型的类,类型为Stack
- s1.push(1);//int类型栈可以插入数据
- s2.push(1.0);//double类型栈也可以插入数据
- return 0;
- }
我们可以清楚地看到T会推导出两个变量类型int和double,此时T还是会推导为int,但是b参数会隐式转化为int,我们知道这个转换的临时参数是有常性的,所以需要用const修饰。
- template<typename T>
- T Add(const T& left, const T& right)
- {
- return left + right;
- }
-
- int main()
- {
- int a = 10;
- double b = 20.2;
- cout << Add<int>(a, b) << endl;//调用int类型Add函数,b生成一个临时int常量传递给函数
- return 0;
- }
在不能完成隐式类型转换时,程序就会报错。