目录
模板
1.1 模板的概念
可以用来通过稍作修改(不可直接使用,只是框架)就可以进行使用的通用性事物,称之为模板。
生活中,就有很多模板,比如,常见的PPT模板、论文模板等等。
1.2 函数模板
1.2.1 语法
1.2.1.1 基本语法
建立一个通用的函数,其返回值类型和形参类型均可不做具体设置,只用虚拟的类型来代替。
语法:
template<typename T> //模板创建声明
一个函数声明或定义 //此函数(紧跟在模板声明之后的函数)就是函数模板
//其中,template为声明创建模板
//typename可用class代替,表示之后的T为数据类型
//typename和class有一个巨大的差异————他们拼写不一样
//以上代码含义为,设置一个虚拟的类型T,T可由其他字母代换,通常为大写
//使用T做虚拟数据类型,为大众化的习惯
接下来,通过一个简单的数值交换的函数来进行演示
template<typename T> //虚拟数据类型声明
void swap(T &a, T &b) //数值交换函数的模板(紧跟在虚拟数据类型声明之后)
{
T temp = a;
a = b;
b = temp;
}
1.2.1.2 在main中的使用方法
1. 自动类型推导
即,直接使用,编译器会自己推导T具体为什么类型。
如
int main()
{
int a = 10;
int b = 20;
swap(a, b);
//此时的T为int类型
}
注:其中,对T的推导必须是一致的才行,比如,当a为整型,b为浮点型时,编译器会报错。
2. 显示指定类型(一般都使用该方法)
以上文例子为例,显示int类型的方法如下,
swap<int>(a, b);
至于显示double等其他类型的方法,不能说和上文一摸一样吧,只能说是完全相同。
注:调用函数模板,则虚拟数据类型(T)必须有明确的数据类型, 否则会报错,比如
#include<iostream>
#include<string>
using namespace std;
template<class T>
void func()
{
cout << "aaa" << endl;
}
void test_()
{
func<int>(); //若无<int>则会报错
}
int main()
{
test_();
system("pause");
return 0;
}
1.2.2 普通函数与函数模板的区别
• 普通函数 调用时可以使用隐式转换,如下
int add01(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
char b = 'a';
cout << add01(a, b) << endl;
}
该代码是不会报错的,在调用 add01() 时传入的 char b 会自动根据ASCLL码值转换为整型(隐式转换),进行运算。
• 函数模板 使用"自动类型推导"调用时,不会发生隐式类型转换,如下
template<class T>
int add02(T a, T b)
{
return a + b;
}
void main()
{
int a = 10;
char b = 'a';
cout << add02(a, b) << endl; //此代码会报错
}
• 函数模板 使用"显示指定类型"调用时,可以发生隐式类型转换,如下
template<class T>
int add03(T a, T b)
{
return a + b;
}
void main()
{
int a = 10;
char b = 'a';
cout << add03<int>(a, b) << endl;
}
1.2.3 普通函数与函数模板的调用规则
1. 当函数模板与普通函数均可调用时,优先普通函数。
2. 函数模板可以发生重载。
3. 可以通过空模板参数列表来强制调用函数模板。
4. 如果函数模板可以发生更好的匹配,则会优先调用函数模板。
注:最好写了同名的函数模板和普通函数不要同时出现
void show(int a, int b)
{
cout << "普通函数调用" << endl;
}
template<class T>
void show(T a, T b)
{
cout << "函数模板调用" << endl;
}
void test01()
{
int a = 10;
int b = 2;
show(a, b); //调用普通函数
}
void test02()
{
char a = 10;
char b = 2;
show(a, b); //调用函数模板,因为如果调用普通函数,需要进行隐式转换,而函数模板可以进行直接
//推导
}
void test01()
{
int a = 10;
int b = 2;
show<>(a, b); //调用函数模板(强制调用)
}
1.2.4 函数模板的局限
对于一些自定义数据类型(class类等),函数模板自身并不清楚该如何处理(例如两类相等的比较依据是什么)
此时,就需要进行模板的具体化实现(重载),如下代码
//创建一个类,做例子
class Person
{
public:
//构造
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
string m_Name;
int m_Age;
};
//函数模板
template<class T>
bool compare(T& a, T& b)
{
if (a == b)
{
return true;
}
return false;
}
//函数模板Person版(具体化实现)
template<> bool compare(Person& p1, Person& p2)
{
if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
{
return true;
}
return false;
}
//用例子测试一下
void test_()
{
Person p1("张三", 10);
Person p2("李四", 20);
bool re = compare(p1, p2);
if (re)
{
cout << "相等" << endl;
}
else
{
cout << "不相等" << endl;
}
}
1.3 类模板
实际是,将类中的属性改变为虚拟数据类型。
1.3.1 语法
template<class NameType, class AgeType>
class Person
{
public:
//构造
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
//简单的输出函数
void showPerson()
{
cout << "Name:" << m_Name << "Age:" << m_Age << endl;
}
//属性
NameType m_Name;
AgeType m_Age;
};
//测试代码
void test()
{
Person<string, int> p1("张三", 10);
p1.showPerson();
}
其中的NameType和AgeType就相当于函数模板中的T。大体语法与函数模板相同。
1.3.2 类模板与函数模板的区别
1. 类模板只能使用“显示指定类型”,不可使用“自动类型推导”。(即,使用类模板必须指定数据类型)
2. 类模板的参数列表中,可以有参数。
template<class NameType, class AgeType = int> //此为模板参数列表,其中的int就是赋予的参数
class Person
{
public:
//有参构造
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
//用于测试的函数
void showPerson()
{
cout << "Name:" << m_Name << "\n" << "Age:" << m_Age << endl;
}
//属性
NameType m_Name;
AgeType m_Age;
};
void test_()
{
Person<string, int> p1("张三", 10); //类模板需要指定参数类型才可以使用
p1.showPerson();
}
void test_1()
{
Person<string> p2("李四", 100); //当模板参数列表中有赋值时,相应的那部分,
//就可以省略(这相当于AgeType有默认值)
}
拓:自定义的数据类型(类...)也可做模板参数,如下
//定义两个数据类型(名称不重要)
class Person1
{
public:
void showPerson1()
{
cout << "Person1" << endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2" << endl;
}
};
template<class T>
class Test
{
public:
void func1()
{
a.showPerson1();
}
void func2()
{
a.showPerson2();
}
//虚拟数据类型
T a;
};
void test_()
{
Test<Person1> t1;
t1.func1(); //调用Person1的showPerson1();
t1.func2(); //此代码会报错
}
1.3.3 类模板对象做函数参数
先来一个类示例
template<class T1, class T2>
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
void showPerson()
{
cout << "Name:" << m_Name << "\n" << "Age:" << m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
1. 指定参数传入类型
//1.指定传入类型
void printPerson1(Person<string, int>& p)
{
p.showPerson();
}
2. 参数模板化
//2.参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
p.showPerson();
cout << "T1 : " << typeid(T1).name() << "\n" << "T2 : " << typeid(T2).name() << endl;
}
拓:typeid(T).name() 可查看编译器推导出的数据类型,另外,string的原名是很长的。
3. 整个类模板化
//3.整个类模板化
template<class T>
void printPerson3(T& p)
{
p.showPerson();
cout << "T : " << typeid(T).name() << endl;
}
在这三种方法中,最常用的是第一种——指定传入类型。
1.3.4 类模板与继承
· 父类为类模板时,子类要想继承父类,需要在声明时,指定父类中T的类型
· 若想在子类中灵活指定父类的T的类型,可将子类也做成类模板
接下来,来看看具体的代码实现
//父类
template<class T>
class Base
{
public:
T m;
};
//子类,此子类的父类T只能是int类型,若想灵活改变T的类型,则需写成下文中 Son2 的形式
class Son1 :public Base<int> //指定父类类型的方式
{
};
//子类2.0版本,可灵活指定父类T的类型
template<class T1, class T2>
class Son2 :public Base<T1>
{
public:
//打印T1、T2的类型
void printType()
{
cout << "T1:" << typeid(T1).name() << endl << "T2:" << typeid(T2).name() << endl;
}
T2 n;
};
void test_()
{
Son2<int, double> s1;
s1.printType();
}
int main()
{
test_();
system("pause");
return 0;
}
1.3.4.1 关于类模板的成员函数类外实现
接下来将在上文的代码的基础上进行修改,对于类模板的成员函数类外实现, 只需记住,
(1).一旦变成了类模板,那么其声明时的名称就变成了 Base<...> 而不再是 Base ;
(2).在出现T1、T2 之类的虚拟函数类型时,一定伴随有 template
接下来,看看代码的实现
//本此代码只写出与上文代码的不同之处,未写之处,均与上文代码相同
//子类2.0版本,可灵活指定父类T的类型
template<class T1, class T2, class T3>
class Son2 :public Base<T1>
{
public:
Son2(T2 name, T3 age);
//打印T1、T2的类型
void printType()
{
cout << "T1:" << typeid(T1).name() << endl
<< "T2:" << typeid(T2).name() << endl
<< "T3:" << typeid(T3).name() << endl;
}
T2 m_Name;
T3 m_Age;
};
//成员函数的类外实现
template<class T1, class T2, class T3>
Son2<T1, T2, T3>::Son2(T2 name, T3 age)
{
m_Name = name;
m_Age = age;
cout << "姓名:" << m_Name << endl << "年龄:" << m_Age << endl;
}
void test_()
{
Son2<int, string, double> s1("张三", 18);
s1.printType();
}
细看代码,不难看出,与普通类外实现相比 ,多了template,并且,其作用域变为了类模板特有的形式,其他地方均相同。
1.3.4.2 类模板的分文件编写(类模板分文件,认准 .hpp )
分文件编写会出现的问题:成员函数在调用阶段才会创建,所以,在分文件编写的时候难以链接到。
先写个示例代码
在Person.h中
#pragma once
#include<iostream>
using namespace std;
#include<string>
template<class T1, class T2>
class Person
{
public:
//只进行函数声明
Person(T1 name, T2 age);
void showPerson();
T1 m_name;
T2 m_age;
};
在Person.cpp中
#include"Person.h"
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "Name: " << m_name << "Age: " << m_ age << endl;
}
在main中
#include"Person.h"
int main()
{
Person<string, int> p("张三", 18);
p.showPerson();
}
此时,如果运行代码,调用showPerson函数,编译器就会报错
解决方法一:
对main中的 #include"Person.h" 进行修改
#include"Person.cpp"
直接包含 .cpp (源文件),此法不推荐,因为不便于阅读,一般源文件都会比较繁杂。
解决方法二(一般都用此法):
将 .h 和 .cpp 的内容写到一起,后缀名改为 .hpp (约定俗成的名字)
上代码
在Person.hpp中
#pragma once
#include<iostream>
using namespace std;
#include<string>
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_name;
T2 m_age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "Name: " << m_name << endl << "Age: " << m_age << endl;
}
在main中
#include"Person.hpp"
void test01()
{
Person<string, int> p("张三", 18);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
注:.hpp 是头文件
一般都是用法二。
1.3.5 类模板与友元
1.3.5.1 全局函数类内实现
直接在类内声明友元
1.3.5.2 全局函数类外实现
需要让编译器提前知道它的存在,且在声明时,与普通函数做好区分
//类外实现的特殊操作,并且类外实现需要先让编译器看到,即放在上面
template<class T1, class T2>
class Person;
template<class T1, class T2>
void showPerson02(Person<T1, T2>& p)
{
cout << "友元类外实现——Age : " << p.m_age << endl;
}
template<class T1, class T2>
class Person
{
//虽然在类内声明,但都是全局函数
//类内实现的类内声明
friend void showPerson01(Person<T1, T2>& p)
{
cout << "友元类内实现——Name : " << p.m_name << endl;
}
//类外实现的类内声明,加一个<>告诉编译器,这是一个模板函数的声明
friend void showPerson02<>(Person<T1, T2>& p);
public:
Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
void showPerson()
{
cout << "Name: " << m_name << endl << "Age: " << m_age << endl;
}
private:
T1 m_name;
T2 m_age;
};
void test01()
{
Person<string, int> p("张三", 18);
showPerson01(p);
showPerson02(p);
}
int main()
{
test01();
system("pause");
return 0;
}