什么是模板?
模板就是建立通用规则,提高复用性,例如Java里面的抽象泛型类,C++的泛型类
模板就是一个框架,其实你直接理解为泛型,更加可以。
首先模板的定义template<typename T> 函数声明或定义
C++ 中提供两种模板机制,函数模板和类模板
函数模板
看代码
template<typename T> 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是通用数据//template 可以替换成classtemplate<class T>void mySwap(T &a, T &b){T temp = a;a = b;b = temp;}//===========================调用方式===================int a= 10;int b= 20;调用方式//1.1自动推导mySwap(a,b);//1.2显示指定类型mySwap<int>(a,b);//2.模板必须要确定出T的数据类型,才可以使用template<class T>void func(){cout << "func 调用" << endl;}void test02(){func(); //报错 func<int>();func<int>();//调用正确}
再例如我们写一个通用的排序函数
template<class T>void sort(T * arr, int len){for(int i = 0;i < len - 1;i++){bool isSorted = true;for(int j = 0;j < len - 1 - i; j++){if(arr[j] > arr[j+1]){int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;isSorted = false;}}if(isSorted){break;}}}
普通函数和函数模板区别
有啥区别?函数模板不就多了一个泛型,搞得比较通用么?区别就在于调用规则
//普通函数int add(int a,int b){return a + b;}//函数模板tepmlate<class T>T add02(T a, T b){return a + b;}int main(){int a = 10;int b = 20;cout << add(a, b) << endl;//这里如果传char ,只有在运行时会把char 转成 整型cout << add02(a, b) <<endl;//okchar c = 'c';cout << add(a, c) << endl;//正确cout << add02(a, c) << endl;//报错, 无法推导出一致的类型,一个是int 一个是char,没有隐私类型转换cout << add02<int>(a, c) << endl;//正确,只能显示指定return 0;}
普通函数与函数模板的调用规则
一般不会存在普通函数和函数模板名称一样的吧,开发规范必须有约束的
- 函数模板和普通函数都可以调用,优先调用普通函数
- 可以通过空模板参数列表 强制调用 函数模板
- 函数模板可以发生函数重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
既然写了函数模板,就不要写普通函数,避免歧义
void myPrint(int a, int b){
cout << "普通函数" << endl;
}
template<class T>
void myPrint(T a, T b){
cout << "模板函数" << endl;
}
void test01(){
int a = 10;
int b = 20;
char c1 = 'c';
char c2 = 'd';
myPrint(a, b); //普通函数
//通过空模板参数列表强制调用模板函数
myPrint<>(a, b);
myPrint(c1, c2);//调用的模板函数,因为普通函数需要隐式转换成int,编译器会认为模板函数是更好的匹配方
}
优先调用普通函数,如果要调用模板函数,必须强制显示指定,即使是指定空模板参数列表
模板的局限性
其实就是泛型使用规则,泛型的局限性
例如T a, T b
a > b ,根本涉及不到所有类型数据,比较不了
template<class T>
bool myCompare(T t1,T t2){
//t1 和 t2 如果传的是对象呢,所以无法约束
}
如果我们比较的是Person类
那么还需要添加具体化Person的比较,调用的时候就会走下面的代码
template<> bool myCompare(Person &p1, Person &p2){
if(p1.age == p2.age){
return true;
}
return false;
}
类模板
//类模板
template<class NameType, class AgeType>
class Person{
public:
NameType m_Name;
AgeType m_Age;
Person(NameType name, AgeType age){
this->m_Name = name;
this->m_Age = age;
}
};
int main(){
Person<string ,int > p1("张三",20);
cout << "name = " << p1.m_Name << " age = " << p1.m_Age << endl;
return 0;
}
类模板和函数模板区别
类模板没有自动推导使用方式,必须显示指定
在模板参数列表可以设置默认参数
Person<string,int> p1 ("", 1);
类模板没有自动类型推导使用方式
Person p1("", 1); 调用错误
类模板在模板参数列表中可以有默认参数 int
template<class NameType, class AgeType = int>
class Person{
xxxx
}
Person<string> p("", 99); //调用正确
类模板对象是可以作为参数的,和Java一样,有三种方式
//1.指定传入类型
void printPerson1(Person<string,int> p1){}
//2.参数模板化
template<class T1, class T2>
void printPerson2(Person<T1,T2> p2){
typeid(T1).name() 查看T1 是什么类型
}
//3.整个类模板化
template<class T>
void printPerson3(T &p){}
类模板和继承
#include<iostream>
using namespace std;
template<class T>
class Base{
T m;
};
//1.方式一
class Son:public Base<int>{ //必须指定T类型,否则运行报error: expected class-name before //'{' token
//class Son:public Base{
// ^
};
//2.方式二
template<class T1, class T2>
class Son2:public Base<T2>{
T1 obj;
};
int main(){
Son2<string,int> son2;
return 0;
}
类模板类外实现
template<class NameType,class AgeType>
class Person{
public:
NameType m_Name;
AgeType m_Age;
Person(NameType name,AgeType age);
};
//类外实现
template<class T1, class T2>
Person<T1,T2>::Person(T1 name,T2 age){
}
类模板分文件编写
一般类太多的情况下才会分文件编写
类模板中成员函数创建时机是在运行调用阶段,分文件编写需要注意,避免出现链接不到的问题
#pragma once
#include<iostream>
using namespace std;
template<class T1,class T2>
class Person{
public:
Person(T1 name,T2 age);
void showPerson();
T1 name;
T2 age;
};
#include "person.h"
//类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age){
this->name = name;
this->age = age;
}
//成员函数类外实现
templage<class T1,class T2>
void Person<T1,T2>::showPerson(){
}
#include<iostream>
#include "person.h"
using namespace std;
void test(){
//include "person.h"改成 "person.cpp"
Person<string,int> p("",20);
p.showPerson();
}
int main(){
test();
return 0;
}
上述运行结果会报动态链接找不到,编译器去person.h中找到成员方法后,由于编译期间这两个成员函数都不会创建,person.cpp文件编译器又没有被发现。
将 person.h 改成 include person.cpp ,编译器会发现cpp文件中头文件包含了person.h,会去看.h文件,然后编译下面的文件。结果正常
当然,你也可以将person类实现和定义放在一个新的文件里面,可以定义为.hpp文件,一看.hpp就知道是类模板的相关方法了
类模板和友元
直接看代码吧
#include<iostream>
using namespace std;
//类外实现 函数模板实现
template<class T1,class T2>
class Person;
template<class T1,class T2>
void printPerson2<>(Person<T1,T2> p){
cout << "姓名" << p.m_Name << " 年龄" << p.m_Age << endl;
}
template<class T1,class T2>
class Person{
//全局函数 类内实现
friend void printPerson(Person<T1,T2> p){
cout << "姓名" << p.m_Name << " 年龄" << p.m_Age << endl;
}
//全局函数 类外实现 <>需要加入<> 说明不是普通函数, 而是函数模板函数声明
//还需要让编译器知道有这个函数存在
friend void printPerson2<>(Person<T1,T2> p);
private:
T1 m_Name;
T2 m_Age;
public:
Person(T1 name,T2 age){
this->m_Name = name;
this->m_Age = age;
}
};
//类内实现
void test(){
Person<string,int> p("test", 18);
printPerson(p);
}
void test02(){
Person<string,int> p("test", 18);
printPerson2(p);
}
int main(){
test();
return 0;
}
全局函数类内实现,直接在类内声明友元即可
全局函数类外实现,需要提前让编译器知道全局函数的存在
数组类模板
关键点在于拷贝构造以及数组 = 时候赋值判断。
class MyArray{
public:
MyArray(int capacity){
this->mCapacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
~MyArray(){
if(this->pAddress != NULL){
delete[] this->pAddress;
this->pAddress = NULL;
}
}
//拷贝构造
MyArray(const MyArray& arr){
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Capacity];
//将数据拷贝出来
for(int i = 0;i < this->m_Size;i++){
this->pAddress[i] = arr.pAddress[i];
}
}
//operator 防止浅拷贝 a = b = c
MyArray& operator=(const MyArray& arr){
if(this->pAddress != NULL){
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity= 0;
this->m_Size = 0;
}
//深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Size];
for(int i = 0;i<this->m_Size;i++){
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//通过下标方式访问数组中的元素
T& operator[](int index){
return this->pAddress[index];
}
private:
T * pAddress;//指针指向堆区开辟的真实数据
}
