概念
模板就是建立通用的摸具,大大提高复用性
特点
模板不可以直接使用,它只是一个框架
模板的通用并不是万能的
C++的另一种编程思想称为泛型编程,主要运用的技术就是模板
C++提供两种模板机制:函数模板和类模板
函数模板
语法
template <typename T>
函数声明或定义
解释:
template ---- 声明创建模板
typename ----表明其后面跟着的是一个数据类型,可以用class代替
T ----通用的数据类型,名称可以替换,通常为大写字母
作用
建立一个通用函数,其函数返回值类型和形参类型可以不做具体的制定,用一个虚拟的类型来代表
使用函数模板
关于函数模板的使用可以分为,自动推导(不传入数据类型,由编译器自动识别)和指定类型
关于指定类型的函数模板语法
函数名 <指定的数据类型>(参数列表);
案例代码:
#include<iostream>
using namespace std;
//声明要创建模板
template <typename T> //T表示任意的数据类型,可以替换成其他字母
void mySwap(T& a, T& b) {
T & temp = a;
a = b;
b = temp;
}
void test01() {
int a = 3;
int c = 2;
double b = 3.14;
//1.自动类型推导
//mySwap(a,c);
//2.指定类型 <>里面指定的是T
mySwap<int>(a, c);
cout << "a=" << a << endl;
cout << "c=" << c << endl;
}
int main() {
system("pause");
return 0;
}
目的:提高复用性,将类型参数化
函数模板使用的注意事项
- 自动类型推断必须推导至同一数据类型T才可以使用
- 模板必须要确定出T的数据类型才可以使用
如果使用的函数模板,并没有需要使用数据类型的地方,则可以直接指定T的数据类型,可以防止在声明是模板时却没有数据类型的参与导致的错误,不指定则直接在使用时报错。
函数模板案例
案件描述:
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则同大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
#include <iostream>
using namespace std;
//-利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
//- 排序规则同大到小,排序算法为选择排序
//- 分别利用char数组和int数组进行测试
template<typename T>
//交换模板
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
//排序模板
template<typename T>
void mySort(T myarry[],int len) {
for (int i = 0;i < len;i++) {
int max = i;//认定最大值的下标
for (int j = i + 1;j < len; j++) {
//认定最大值的下标值更小,说明j下标的才是最大值
if (myarry[max] < myarry[j]) {
max = j;
}
}
if (max != i) {
//如果下标不相等,直接交换max和i下的元素,这样i的位置始终是剩余元素中最大的
mySwap(myarry[max], myarry[i]);
}
}
}
//打印数组模板
template<typename T>
void printArry(T arry[],int len) {
for (int i = 0; i < len;i++) {
cout << arry[i] << " ";
}
cout << endl;
}
void test01() {
char cArry[] = "abdfagwhu";
int len1 = sizeof(cArry)/sizeof(char);
mySort(cArry, len1);
printArry(cArry,len1);
int iArry[] = { 3,23,45,1,4,49,10,4 };
int len2 = sizeof(iArry) /sizeof(int);
mySort(iArry, len2);
printArry(iArry, len2);
}
int main() {
test01();
system("pause");
return 0;
}
普通函数和函数模板的区别
- 普通函数调用时,可以自动发生类型转换,隐式类型转换
- 函数模板调用时,如果使用自动类型推导,不会发生隐式类型转换
- 函数模板调用时,如果用指定类型的方式,可以发生隐式类型转换
建议使用函数模板时,用显示指定函数类型的方式去调用
普通函数和函数模板调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表,强制调用函数模板
- 函数模板可以发生函数重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
模板的局限性
模板的通用性并不是万能的
有些特殊的数据类型,需要用具体化的方式实现
#include<iostream>
using namespace std;
//函数模板的局限性
//对比两个数据是否相等
template<class T>
bool myCompare(T& a, T& b) {
if (a == b) {
return true;
}
else {
return false;
}
}
//上面那个比较类型的模板,如果我们传入的数据类型是数组,或者是特定的自定义属性,则无法直接比较
//可以用运算符重载的方式,对此进行实现,但在函数模板中,有更简介的做法,就是具体化方式做具体实现
class Person {
public:
Person(string name, int age) {
m_Name = name;
m_Age = age;
}
string m_Name;
int m_Age;
};
//具体化方式做具体化实现
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 test01() {
Person p1("张大", 17);
Person p2("张大", 17);
Person p3("张大", 18);
cout << myCompare(p1, p2) << endl;
cout << myCompare(p1, p3) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
类模板
类模板作用
建立一个通用类,类中的成员数据类型可以不做具体制定,用一个虚拟的类型来代表
语法
template <typename T>
类
解释:
和函数模板一样
#include<iostream>
using namespace std;
//类模板
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
};
void test01() {
Person<long,int> p1(12538, 14);
Person<string,string> p2("肖晓", "二八年华");
cout << "p1的姓名为:" << p1.m_Name << "年龄为 " << p1.m_Age << endl;
cout << "p2的姓名为:" << p2.m_Name << "年龄为 " << p2.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
类模板和函数模板的区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
#include<iostream>
using namespace std;
//类模板
//类模板在函数参数列表中可以有默认参数
template<class NameType,class AgeType = int>
class Person {
public:
Person(NameType name, AgeType age ) {
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
};
void test01() {
Person<long> p1(12538, 14);
Person<string> p2("肖晓", 15);
cout << "p1的姓名为:" << p1.m_Name << "年龄为 " << p1.m_Age << endl;
cout << "p2的姓名为:" << p2.m_Name << "年龄为 " << p2.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
类模板中成员函数创建实际
类模板中的成员函数,并不是一开始就创建的,而是在模板调用后生成的
#include<iostream>
using namespace std;
class Person1 {
public:
void showPerson1() {
cout<< "我是Person1的方法" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "我是Person2的方法" << endl;
}
};
template<class P>
class Person {
public:
P person;
//类模板中的成员函数
void fun1() {
person.showPerson1();
}
void fun2() {
person.showPerson2();
}
};
void test01() {
Person<Person1> p;
p.fun1();
//p.fun2(); //报错 "showPerson2":不是"Person1"的成员
}
int main() {
test01();
system("pause");
return 0;
}
在上述代码中,在Person类中,person.showPerson2();并没有报错,但在test01()中,通过类模板创建p对象,p对象去调用fun2时,由于p的数据类型Person1,而fun2中调用了一个Person1中没有的成员,虽然代码并没有报错,但编译的时候直接报错。
上述说明了类模板中的成员函数并不是一开始就创建的,而是在我们调用模板时才创建。
类模板对象做函数参数
类模板示例化出的对象,向函数传参的方式
- 指定传入的类型 ----直接显示对象的类型
- 参数模板化 -----将对象中的参数变为模板传递
- 整个类模板化 ----将这个对象模板化传递数据
类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类再声明时,要指定父类T的数据类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需要变成类模板
类模板分文件书写
由于类模板中成员创建时期是在调用阶段,导致分文件时链接不到:
- 解决方案1:
普通类的分文件书写,只用分为.h和.cpp文件,在引用时,引用.h文件。
类模板不同,类模板如果采取类似普通类的写法,则需在引用时,引用.cpp文件(且此方法不建议使用) - 解决方案2
类模板分文件书写,将函数声明和实现写到一起,文件的后缀改为.hpp
使用时,引用.hpp文件即可
类模板与友元
全局函数类内实现,直接类内声明友元即可
全局函数类外实现,需让编译器提前知道全局函数的存在,在声明友元时需要加上<>表示类模板的空实现,在函数定义时,需在其上方有模板函数的标识
类模板案例
案例说明
实现一个通用的数组类,要求如下:
- 可以对内置的数据类型和自定义的数据类型进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法提供对数组的增加和删除
- 可以通过下标的方式访问数组的元素
- 可以获取数组中当前元素的个数及数组容量
MyArry.hpp
#pragma once
#include<iostream>
using namespace std;
template<class T>
class MyArry
{
public:
MyArry(int capacity) {
this->m_Capacity = capacity;
this->m_Size = 0;
this->myAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArry(const MyArry& arr) {
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->myAddress = new T[arr.m_Capacity];
//如果原有数组不为空,还需要将原本的数组元素拷贝过来
if (arr.myAddress != NULL) {
for (int i = 0;i < arr.m_Size;i++) {
this->myAddress[i] = arr.myAddress[i];
}
}
}
//operator= 防止浅拷贝问题
MyArry& operator=(const MyArry&arr) {
//先判断原来的堆区是否有数组,有就释放
if (this->myAddress != NULL) {
delete[] this->myAddress;
this->myAddress = NULL;
this->m_Capacity = NULL;
this->m_Size = 0;
}
//深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->myAddress = new T[arr.m_Capacity];
for (int i=0;i < arr.m_Size;i++) {
this->myAddress[i] = arr.myAddress[i];
}
return *this;
}
//尾插法
void Push_Back(const T&val) {
//判断数组是否还有空间进行插入,没有就直接返回
if (this->m_Capacity == this->m_Size) {
cout << "你的数组没有空间了" << endl;
return;
}
this->myAddress[this->m_Size] = val; //让数组末尾加一个位置,并指向我们插入的val
this->m_Size++; //更新数组元素个数
}
//尾删法
void Pop_Back() {
//让用户访问不到最后一个元素即为逻辑上的删除
//如果数组大小为0,就直接返回
if (this->m_Size == 0) {
return;
}
//如果数组不为0,直接让数组元素个数-1即可
this->m_Size--;
}
//通过下标的方式,访问数组的元素
T& operator[](int index) {
return this->myAddress[index];
}
//返回数组容量
int getmCapacity() {
return this->m_Capacity;
}
//返回数组大小
int getmSize() {
return this->m_Size;
}
~MyArry() {
if (this->myAddress != NULL) {
delete[] this->myAddress;
this->myAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
}
private:
T* myAddress;//指针要指向堆区开辟的真实数组
int m_Capacity; //数组容量
int m_Size; //数组大小
};
数组封装.cpp
#include<iostream>
using namespace std;
#include"MyArry.hpp"
//测试内置数据类型
void test01() {
MyArry<int>arr1(5);
/*MyArry<int>arr3(100);
arr3 = arr1;*/
for (int i = 0;i < 5;i++) {
arr1.Push_Back(i);
}
for (int i = 0;i < arr1.getmSize();i++) {
cout << arr1[i] << endl;
}
cout << "arr1的数组容量为" <<arr1.getmCapacity()<< endl;
MyArry<int>arr2(arr1);
cout << "尾删法之前" << endl;
for (int i = 0;i < arr2.getmSize();i++) {
cout << arr2[i] << endl;
}
cout << "尾删法之后" << endl;
arr2.Pop_Back();
for (int i = 0;i < arr2.getmSize();i++) {
cout << arr2[i] << endl;
}
}
//测试自定义数据类型
class Person {
friend void printPArry(MyArry <Person>& pArry);
public:
Person() {};
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
private:
string m_Name;
int m_Age;
};
void printPArry(MyArry <Person>& pArry) {
for (int i = 0;i < pArry.getmSize();i++) {
cout << "第" << i+1<< "个人的姓名为" << pArry[i].m_Name << " 的年龄为" << pArry[i].m_Age << endl;
}
}
void test02() {
Person p1("zz", 1);
Person p2("aa", 2);
Person p3("ez", 3);
Person p4("tz", 4);
Person p5("yz", 5);
Person p6("vz", 6);
MyArry <Person> pArry(10);
pArry.Push_Back(p1);
pArry.Push_Back(p2);
pArry.Push_Back(p3);
pArry.Push_Back(p4);
pArry.Push_Back(p5);
pArry.Push_Back(p6);
cout << "尾删前" << endl;
printPArry(pArry);
cout << "pArry的容量" << pArry.getmCapacity() << endl;
cout << "pArry的大小" << pArry.getmSize() << endl;
cout << "尾删后" << endl;
pArry.Pop_Back();
printPArry(pArry);
cout << "pArry的容量" << pArry.getmCapacity() << endl;
cout << "pArry的大小" << pArry.getmSize() << endl;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}