1. 模板
模板可以让类或者函数支持一种通用类型,这种通用类型在实际的运行过程中可以使用任何数据类型,因此可以让程序员写出一些与类型无关的代码,这种编程方式也被称为"泛型编程"。
模板——是一段带有类型参数的程序代码,可以通过给这些参数提供一些类型来得到针对不同类型的具体代码。
- 值和类型是数据的两个主要特征,它们在C++中都可以被参数化。
- 数据的值可以通过函数参数传递,在函数定义时数据的值是未知的,只有等到函数调用 时接收了实参才能确定其值。——这就是值的参数化。
- 数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当函数 调用时,编译器根据传入的实参自动推断数据类型。——这就是类型的参数化(把类型 定义为参数)
模板通常有两种形式:
● 函数模板
● 类模板
1.1 函数模板
使一个函数支持模板编程,使函数支持通用数据类型。
函数模板——实际上是定义一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)均被作为参数:不指定具体类型,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位)。凡是函数体相同的函数都可以用 这个模板来代替,在函数调用时根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板。
通过函数模板实现类型参数化,即把类型定义为参数,从而实现代码复用。
可见:函数模板并不是一个可以直接使用的函数,它是可以产生多个函数的模板。
用法:
- template是声明模板的关键字,告诉编译器这是一个模板,开始泛型编程。
- 尖括号<>中的class是定义形参的关键字,用来说明其后的形参名为类型参数,(模板形参)。class可以用Typename关键字代替,两者没有区别。
- 模板形参(类属参数)不能为空(俗成约定用一个大写英文字母表示),且在函数定义部分的参数列表中至少出现一次。与函数形参类似,可以用在函数定义的各个位置:返回值、形参列表和函数体。
- 函数定义部分:与普通函数定义方式相同,只是参数列表中的数据类型要使用 尖括号<>中的模板形参名来说明。
实例化:
定义好的函数模板不可以直接使用,只相当于一个模具、规则,可使用不同类型的参数来调用,可减少代码的书写,提高代码复用性。
编译器会根据调用时的参数类型进行相应的实例化,即用具体的类型参数去替换函数模板中的模板参数,生成一个确定的具体类型的真正函数,才能实现运算操作。
比如:template<class T>
T add(T a,T b)
比如当调用 add(1,2) 时,形参T被替换成 int
当调用 add(1.5,2.2) 时,形参T被替换成 float
注意事项:
- 函数模板中的每一个类型参数在函数参数表中必须至少使用一次。
- 在全局域中声明的与模板参数同名的对象、函数或类型,在函数模板中将被 隐藏。
- 函数模板中定义声明的对象或类型不能与模板参数同名。
- 模板参数名在同一模板参数表中只能使用一次,但可在多个函数模板声明或 定义之间重复使用。
- 模板的定义和多处声明所使用的模板参数名不一定要必须相同。
- 函数模板如果有多个模板参数,则每个模板类型前都必须使用关键字typename 或class修饰。
函数模板实例:
#include <iostream>
using namespace std;
template<class T> // 可以是class 也可以是typename
T add(T a,T b)
{
return a+b;
}
//T add2(T a,T2 b)
//{
// return a+b;
//}
int main()
{
cout << "Hello World!" << endl;
string a = "hello";
string b = "world";
cout << "add(1,2)= " << add(1,2) << endl;
cout << "add(1.2,2.5)= " << add(1.2,2.5) << endl;
cout << "add(a,b)= " << add(a,b) << endl;
// cout << "add2(1,2)= " << add2(1,2) << endl;
// cout << "add2(1,2.5)= " << add2(1,2.5) << endl;
// cout << "add2(1.2,2)= " << add2(1.2,2) << endl;
return 0;
}
1.2 函数模板注意事项说明
(1)函数模板中的每一个类型参数在函数参数表中必须至少使用一次。例如:下 面的函数模板声明是不正确的:函数模板声明了两个参数T1与T2,但在使用 时 只使用了T1,没使用T2。
(2)在全局域中声明的与模板参数同名的对象、函数或类型,在函数模板中将被 隐藏。例如:在函数体内访问num是访问的T类型的num,而不是全局变量num
(3)函数模板中定义声明的对象或类型不能与模板参数同名。例如:
(4)模板参数名在同一模板参数表中只能使用一次,但可在多个函数模板声明或 定义之间重复使用。例如:
(5)模板的定义和多处声明所使用的模板参数名不一定要必须相同。例如:
(6)函数模板如果有多个模板参数,则每个模板类型前都必须使用关键字typename 或class修饰。例如:
1.3 类模板
使一个类支持模板编程,可以使一个类支持通用数据类型。
除了函数模板外,C++中还支持类模板。类模板是对成员数据类型不同的类的抽象,它说明了类的定义规则,一个类模板可以生成多种具体的类。与函数模板的定义形式类似, 类模板也是使用template关键字和尖括号“<>”中的模板形参进行说明,类的定义形式与普通类相同。
类模板并不能直接使用,需要对其进行实例化,实例化的方法是:
类名<具体类型> 对象名。
类与类模板的关系:
由于类模板包含类型参数,因此也称为参数化类,如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。
用法注意:
- 使用类模板创建对象时,必须指明具体的数据类型。
如 A<int> a; //类A中凡是用到模板形参的地方都会被int类型所代替
- 当类模板有两个模板形参时,创建对象时,类型之间要用逗号分隔开。
类模板实例1:
构造函数直接在类内实现
#include <iostream>
using namespace std;
template<class T>
class Test
{
private:
T val;
public:
Test(T val):val(val){}
T get_val()const
{
return val;
}
T set_val(const T& val)
{
this->val = val;
}
};
int main()
{
cout << "Hello World!" << endl;
Test<int> t1(20);
cout << t1.get_val() << endl;
t1.set_val(30);
cout << t1.get_val() << endl;
Test<double> t2(2.2);
cout << t2.get_val() << endl;
t2.set_val(3.3);
cout << t2.get_val() << endl;
return 0;
}
类模板实例2:
构造函数在类内声明,类外实现。此时,类外实现的构造函数时候,都必须在函数前加上类模板标志 template<class T> 。
#include <iostream>
using namespace std;
template<class T> // 可以是class 也可以是typename
T add(T a, T b)
{
return a+b;
}
template<class T>
class Test
{
private:
T val;
public:
Test(T val);
T get_val()const;
void set_val(const T& val);
};
template<class T>
Test<T>::Test(T val):val(val)
{
}
template<class T>
T Test<T>::get_val()const
{
return val;
}
template<class T>
void Test<T>::set_val(const T& val)
{
this->val = val;
}
int main()
{
Test<int> t1(20);
cout << t1.get_val() << endl;
t1.set_val(23);
cout << t1.get_val() << endl;
Test<double> t2(2.2);
cout << t2.get_val() << endl;
t2.set_val(2.32);
cout << t2.get_val() << endl;
return 0;
}