文章目录
前言
模板就是建立通用的模具,大大提高复用性
模板的特点:
-
模板不可以直接使用,它只是一个框架
-
模板的通用并不是万能的
一、函数模板
1.函数模板基本语法
//交换整型函数
void swapInt(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
//交换浮点型函数
void swapDouble(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
上面定义两个交换函数分别用来交换int型数据和double型数据,但是c++中的数据类型是无数的,不可能定义出所有的交换函数。
可以利用模板的方法来简化这种操作。
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
template<typename T>
//这句话的作用相当于告诉编译器后面的代码中的“T”不要报错,要写一个模板
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
//两种方法实现模板
//1、自动类型推导,提前给出T的类型,编译器自动推导
mySwap(a, b);
//2、显示指定类型,直接告诉编译器T的类型
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
模板的目的是为了提高复用性,将类型参数化
2.函数模板注意事项
(1)自动类型推导,必须推导出一致的数据类型 T, 才可以使用。
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
mySwap(a, b); // 正确,可以推导出一致的T
mySwap(a, c); // 错误,推导不出一致的T类型
}
(2)模板必须要确定出T的数据类型,才可以使用。
//即使是无参函数也要让编译器知道T的数据类型
template<class T>
void func()
{
cout << "func 调用" << endl;
}
void test02()
{
func(); //错误,模板不能独立使用,必须确定出T的类型
func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}
3.普通函数与函数模板的区别
普通函数与函数模板区别:
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数
int myAdd01(int a, int b)
{
return a + b;
}
//函数模板,返回值也是T
template<class T>
T myAdd02(T a, T b)
{
return a + b;
}
//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码
myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换,编译器不知道T是a的int型还是c的char型
myAdd02<int>(a, c); //正确,如果用显示指定类型,编译器发生隐式类型转换,将char型的c转换为int型
}
4.模板的局限性
template<class T>
void f(T a, T b)
{
a = b;
}
在上面的函数模板中,如果a,b是数组或者是自定义数据类型的话,程序就不能正常运行了。
因此 C++ 为了解决这种问题,提供模板的重载,可以为这些 特定的类型 提供 具体化的模板
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//普通函数模板
template<class T>
bool myCompare(T& a, T& b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
//具体化优先于常规模板
template<> bool myCompare(Person& p1, Person& p2)
{
if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
{
return true;
}
else
{
return false;
}
}
void test()
{
Person p1("Tom", 10);
Person p2("Tom", 10);
//自定义数据类型,不会调用普通的函数模板
//可以创建具体化的Person数据类型的模板,用于特殊处理这个类型
bool ret = myCompare(p1, p2);
if (ret)
{
cout << "p1 == p2 " << endl;
}
else
{
cout << "p1 != p2 " << endl;
}
}
二、类模板
1.类模板基本语法
类模板作用:
建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
语法:template<typename T>
// 类模板
template<class NameType, class AgeType>
class Person
{
public:
Person(AgeType a, NameType b) :m_num(a), m_name(b) {}
AgeType m_num;
NameType m_name;
void showPerson()
{
cout << "name=" << m_name << " " << "age=" << m_num << " " << endl;
}
};
2.类模板与函数模板的区别
类模板在模板参数列表中可以有默认参数
类模板使用只能用显示指定类型方式
类模板中的模板参数列表可以有默认参数
template<class NameType, class AgeType = int>
普通类中的成员函数一开始就可以创建
类模板与函数模板区别主要有两点:
-
类模板没有自动类型推导的使用方式
-
类模板在模板参数列表中可以有默认参数
/*类模板与函数模板区别主要有两点:
1. 类模板没有自动类型推导的使用方式
2. 类模板在模板参数列表中可以有默认参数
*/
// 注意默认参数的写法
template<class NameType, class AgeType = int >
class Person {
public:
Person(NameType a, AgeType b) {
m_name = a;
m_age = b;
}
void showPerson() {
cout << "姓名:" << m_name << " "
<< "年龄:" << m_age << endl << endl;
}
NameType m_name;
AgeType m_age;
};
// 1.类模板没有自动推导类型
void test01() {
//Person p;
//p1("Tom", 36);报错
//Person p<int,string>; //这样也不行
//只能下面这样
// 如果传了参数就用传入的参数。没有传入参数就用默认数据类型
Person<string, int>p1("Tom", 36);
Person<int, string>p2(55, "JACK");
p1.showPerson();
p2.showPerson();
}
// 类模板在模板参数列表中可以有默认参数
void test02() {
// 如果传了参数就用传入的参数。没有传入参数就用默认数据类型
Person<string>p("FAZE", 556);
p.showPerson();
}
3.类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
正常情况下下面的两个函数的代码不能编译通过,因为obj的类型都无法确认,但因为类模板中的成员函数是在调用时才创建的,不去调用就不会创建。所以下面两个函数可以编译通过所以类模板中的成员函数可能有语法错误,但也能通过编译。
class Person1 {
public:
void showPerson1() {
cout << "Person1 " << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "Person2" << endl;
}
};
template<class T>
class Person3 {
public:
T obj;
// 类模板中的成员函数
// 正常情况下下面的两个函数的代码不能编译通过,因为obj的类型都 无法确认,showPerson()从哪来呢?
// 但因为类模板中的成员函数是在调用时才创建的,不去调用就不会创建。所以下面两个函数可以编译通过
// 所以类模板中的成员函数可能有语法错误,但也能通过编译
void func1() {
obj.showPerson1();
}
void func2() {
obj.showPerson2();
}
};
void test01() {
Person3<Person1>p1;
Person3<Person2>p2;
p1.func1();
// p1.func2();这里就会报错了,类模板调用时创建对象。showPerson2()不是Person1的成员函数
p2.func2();
}
4.类模板对象做函数参数
一共有三种传入方式:
- 指定传入的类型 — 直接显示对象的数据类型
void printPerson1(Person<string, int>& p) {
p.showPerson();
}
void test01() {
// 实例化出来的是Person<string,int>p1;那么参数就照着抄(记得传的是引用),也是Person<string,int>&p;
Person<string, int>p1("Tom", 36);
cout << "指定参数类型:" << endl;
printPerson1(p1); cout << endl;
}
- 参数模板化 — 将对象中的参数变为模板进行传递
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p) {
cout << "T1的类型为:" << typeid(T1).name() << endl
<< "T2的类型为:" << typeid(T2).name() << endl;
p.showPerson();
}
void test02() {
Person<string, int>p2("FAZE", 555);
cout << "参数模板化:" << endl;
printPerson2(p2);
cout << endl;
}
- 整个类模板化 — 将这个对象类型 模板化进行传递
template<class T>
void printPerson3(T& p) {
cout << "T的类型为:" << typeid(T).name() << endl;
p.showPerson();
}
void test03() {
Person<string, int>p3("NAVI", 686);
cout << "整个类模板化:" << endl;
printPerson3(p3);
cout << endl;
}
总结
以上是C++模板部分的内容。