模板(Template)指C++程序设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
使用模板的目的就是能够让程序员编写与类型无关的代码。
I. 函数模板
函数模板的调用:
- 1. 编译器自己推导数据类型
- 2. 显式指出数据类型
template<typename T>
void TemplateTest::MyAdd(T &a, T &b)
{
T temp = a + b;
cout << a << " + " << b << " = " << temp << endl;
}
int main()
{
TemplateTest test;
int a = 10;
int b = 3;
//显式指定类型
test.MyAdd<int>(a, b);
float c = 2.1;
float d = 2.2;
//自动推导数据类型
test.MyAdd(c, d);
system("pause");
return 0;
}
一些注意事项:
- 模板推导出的数据类型必须一致
- 模板必须要确定T的数据类型才可以使用(1. 在模板函数编写的时候就已经使用到T,因此编译器可以自己推到;2. 函数编写的时候没有使用T,调用该函数的时候需要显式指出T的数据类型)
普通函数和函数模板的区别:
- 普通函数可以发生隐式类型转换(如把char通过ASCII码转换成int类型);
- 模板函数,如果是使用自动类型推导,不可以发生隐式类型转换(因为自动类型推导要求推导出的所有数据类型保持一致);
- 模板函数,如果显式指定类型,则可以发生隐式类型转换。
普通函数和函数模板的调用规则:
- 如果普通函数和函数模板都可以实现(相同的函数名),优先调用普通函数;
- 如果想要强制调用函数模板,可以使用空模板参数列表( myAdd<>(a, b) ,通过加<>这样的一个空的参数列表,来强制调用函数模板);
- 函数模板也可以发生重载(同样的函数名,参数列表不一样);
- 如果函数模板可以产生更好的匹配(参数匹配更好),则优先调用函数模板;
模板的局限性:
- 函数模板的调用不是万能的(例如对于赋值操作,在两个数组间无法实现),对于有些特定的数据类型,需要用具体化方式做特殊实现(即实例化该具体的函数:template<> 函数声明,使用具体的数据类型)。(利用具体化的模板,可以解决自定义类型的通用化)
学习模板并不是为了写模板,而是为了可以在STL中运用系统提供的模板。
II. 类模板
template<typename nameType, typename ageType>
class Person
{
public:
Person(nameType name, ageType age)
{
this->m_name = name;
this->m_age = age;
}
void PersonPrint()
{
cout << "name:" << this->m_name << endl;
cout << "age:" << this->m_age << endl;
}
nameType m_name;
ageType m_age;
};
int main()
{
Person<string, int> p1("Jerry", 20);
p1.PersonPrint();
system("pause");
return 0;
}
类模板与函数模板的区别:
1. 类模板没有自动类型推导的使用方式;
2. 类模板在模板参数列表中可以有默认参数(可以指定默认的参数类型,如指定其中一个参数为int型。如果后面确实使用的是int型,那么在显式指定类模板的参数列表的时候可以不用写该已被默认指定的。如果不是int型,则还是需要特别指定的)
类模板中成员函数和普通类中成员函数的创建时机是有区别的:
1. 普通类中的成员函数一开始就可以创建;
2. 类模板中的成员函数调用时才创建;
类模板对象做函数参数:
类模板实例化出的对象,向函数传参的方式,一共三种:
1. 指定传入类型 --直接显示对象的数据类型;
template<typename nameType, typename ageType>
class Person
{
public:
Person(nameType name, ageType age)
{
this->m_name = name;
this->m_age = age;
}
nameType m_name;
ageType m_age;
};
//1. 指定传入类型
void PersonPrint(Person<string, int> &p)
{
cout << "name:" << p.m_name << endl;
cout << "age:" << p.m_age << endl;
}
int main()
{
Person<string, int> p1("Jerry", 20);
PersonPrint(p1);
system("pause");
return 0;
}
2. 参数模板化 -- 将对象中的参数变为模板进行传递;
//2. 参数模板化
template<Typename T1, Typename T2>
void PersonPrint(Person<T1, T2> &p)
{
cout << "name:" << p.m_name << endl;
cout << "age:" << p.m_age << endl;
}
3. 整个类模板化 -- 将这个对象类型模板化进行传递;
//3. 整个类模板化
template<Typename T>
void PersonPrint(T &p)
{
cout << "name:" << p.m_name << endl;
cout << "age:" << p.m_age << endl;
}
类模板与继承:
1. 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型。如果不指定,编译器无法给子类分配内存;
2. 如果想灵活指定出父类中T的类型,子类也需要变为类模板;
template <typename T>
class Base
{
T name;
};
//指定传入类型
class Son1 :public Base<int>
{
};
//子类作为类模板时
template <typename T, typename T1>
class Son2 :public Base<T>
{
public:
Son2()
{
cout << "父类的数据类型为:" << typeid(T).name() << endl;
cout << "子类的数据类型为:" << typeid(T1).name() << endl;
}
T1 kk;
};
int main()
{
Son1 s1;
Son2<char, int> s2;
system("pause");
return 0;
}
类模板成员函数的类外实现
成员函数的类外实现,需要在作用域后面加上参数列表
template <typename T>
class animal
{
public:
animal(T name);
void animalPrint();
T m_name;
};
template <typename T>
animal<T>::animal(T name)
{
this->m_name = name;
}
template <typename T>
void animal<T>::animalPrint()
{
cout << "动物的名字是:" << this->m_name << endl;
}
int main()
{
animal<string> a("CAT");
animal<string> b("dog");
b.animalPrint();
system("pause");
return 0;
}
类模板分文件编写
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。
解决办法:
1. 方法1:直接包含.cpp源文件
2. 方法2:将声明和实现写到同一个文件中,并更改后缀为hpp,hpp是约定的名称,不是强制的
note:对于类模板函数,一般我们采用第二种方法。
类模板与友元
全局函数类内实现:直接在类内声明友元即可;
全局函数类外实现:需要提前让编译器知道全局函数的存在;