什么是模板?
模板就是建立通用规则,提高复用性,例如Java里面的抽象泛型类,C++的泛型类
模板就是一个框架,其实你直接理解为泛型,更加可以。
首先模板的定义
template<typename T> 函数声明或定义
C++ 中提供两种模板机制,函数模板和类模板

函数模板

看代码

  1. template<typename T> 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是通用数据
  2. //template 可以替换成class
  3. template<class T>
  4. void mySwap(T &a, T &b){
  5. T temp = a;
  6. a = b;
  7. b = temp;
  8. }
  9. //===========================调用方式===================
  10. int a= 10;
  11. int b= 20;
  12. 调用方式
  13. //1.1自动推导
  14. mySwap(a,b);
  15. //1.2显示指定类型
  16. mySwap<int>(a,b);
  17. //2.模板必须要确定出T的数据类型,才可以使用
  18. template<class T>
  19. void func(){
  20. cout << "func 调用" << endl;
  21. }
  22. void test02(){
  23. func(); //报错 func<int>();
  24. func<int>();//调用正确
  25. }

再例如我们写一个通用的排序函数

  1. template<class T>
  2. void sort(T * arr, int len){
  3. for(int i = 0;i < len - 1;i++){
  4. bool isSorted = true;
  5. for(int j = 0;j < len - 1 - i; j++){
  6. if(arr[j] > arr[j+1]){
  7. int temp = arr[j];
  8. arr[j] = arr[j+1];
  9. arr[j+1] = temp;
  10. isSorted = false;
  11. }
  12. }
  13. if(isSorted){
  14. break;
  15. }
  16. }
  17. }

普通函数和函数模板区别

有啥区别?函数模板不就多了一个泛型,搞得比较通用么?区别就在于调用规则

  1. //普通函数
  2. int add(int a,int b){
  3. return a + b;
  4. }
  5. //函数模板
  6. tepmlate<class T>
  7. T add02(T a, T b){
  8. return a + b;
  9. }
  10. int main(){
  11. int a = 10;
  12. int b = 20;
  13. cout << add(a, b) << endl;//这里如果传char ,只有在运行时会把char 转成 整型
  14. cout << add02(a, b) <<endl;//ok
  15. char c = 'c';
  16. cout << add(a, c) << endl;//正确
  17. cout << add02(a, c) << endl;//报错, 无法推导出一致的类型,一个是int 一个是char,没有隐私类型转换
  18. cout << add02<int>(a, c) << endl;//正确,只能显示指定
  19. return 0;
  20. }

普通函数与函数模板的调用规则

一般不会存在普通函数和函数模板名称一样的吧,开发规范必须有约束的

  1. 函数模板和普通函数都可以调用,优先调用普通函数
  2. 可以通过空模板参数列表 强制调用 函数模板
  3. 函数模板可以发生函数重载
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板

既然写了函数模板,就不要写普通函数,避免歧义


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;//指针指向堆区开辟的真实数据

}