C++模板

概念

模板就是建立一个通用的模具,大大提高复用性
函数模板和类模板

函数模板

泛型编程主要利用的技术就是模板

函数模板的作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表
提高复用性,使类型参数化

语法

template<typename T>
函数声明或定义

/*
template--声明创建模板
typename--表明其后面的符号是一种数据类型,可以用class代替
T -- 通用的数据类型  名称可以替换
*/

基本使用

//函数模板
template <typename T>
void swap(T &t1,T &t2){
    T temp = t1;
    t1 = t2;
    t2 = temp;
}


    int a=5,b=10;
    /*有两种使用函数模板
     * 1.自动类型推导  swap(a,b); 即可
     * 2.显示指定类型  swap<int>(a,b);
     * */
    swap<int>(a,b);

注意事项

  • 自动类型推导,必须推导出一致的数据类型T才可以使用
template <typename T>
void swap(T &t1,T &t2){
    T temp = t1;
    t1 = t2;
    t2 = temp;
}


int a = 5;
char b = 'b';

swap(a,b); //错误的 不是一致的数据类型
  • 模板必须要确定出T的数据类型才可以用
template <typename T>
void func(){
	cout<<"aaa"<<endl;
}


func();//不确定类型无法调用函数

函数模板案例

从小到大排序


template<class T>
void sort(T arr[],int len){
    for (int i = 0;i<len;++i){
        int minIndex = i;
        for (int j = i+1; j < len; ++j) {
            if (arr[minIndex]>arr[j])
                minIndex = j;
        }
        if (minIndex!=i){
            swap<T>(arr[i],arr[minIndex]);
        }

    }
}

    int arr[] = {1,45,20,3,9,5};
    sort<int>(arr,6);
    for (int i = 0; i < 6; ++i) {
        std::cout<<arr[i]<<" ";
    }
    std::cout<<std::endl;
    char arr0[] = {'a','c','z','r','f','z'};
    sort<char>(arr0,6);
    for (int i = 0; i < 6; ++i) {
        std::cout<<arr0[i]<<" ";
    }

普通函数和函数模板的区别

  • 普通函数调用时可以发生隐式类型转化(自动类型转化)
int add(int a,int b){
    return a+b;
}

    int a=10,b=20;
    char c='c';
    std::cout<<add(a,b)<<std::endl;//30
    std::cout<<add(a,c)<<std::endl;//109  'c'的ASCII码为97,自动转化
  • 函数模板使用自动推导不可以发生隐式类型转化,使用显示指定可以发生隐式类型转化
template<class T>
T myAdd(T a,T b){
    return a+b;
}

    std::cout<<myAdd(a,b)<<std::endl;//30
    std::cout<<myAdd(a,c)<<std::endl;//报错 不能隐式类型转化

    std::cout<<myAdd<int>(a,b)<<std::endl;//30
    std::cout<<myAdd<int>(a,c)<<std::endl;//109  'c'的ASCII码为97,自动转化

建议使用函数模板使用显式指定

普通函数和函数模板调用规则

  1. 如果普通函数和函数模板都都可以实现,优先调用普通函数
  2. 可以通过空模板强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产生更好的匹配优先调用函数模板
void myPrint(int a,int b){
    std::cout<<"ordinary:"<<"a="<<a<<"&&b="<<b<<std::endl;
}

template <class T>
void myPrint(T a,T b){
    std::cout<<"template:"<<"a="<<a<<"&&b="<<b<<std::endl;
}

template <class T>
void myPrint(T a,T b,T c){
    std::cout<<"template:"<<"a="<<a<<"&&b="<<b<<"&&c="<<c<<std::endl;
}


int a=10,b=20,c=30;
    myPrint(a,b);//ordinary:a=10&&b=20
    // 调用的是普通函数 优先普通函数,如果普通函数只有声明,就会报错不会找函数模板

    myPrint<>(a,b);//空模板  强制调用模板函数

    myPrint<>(a,b,c);//函数模板重载

    char h = 'h',g = 'g';
    myPrint(h,g);//模板产生更好的匹配 优先函数模板

总结:如果存在函数模板了,最好就不要提高同名的普通函数了,否则容易出现二义性

模板的局限性

模板通用性并不是万能的

template <class T>
void f(T a,T b){
	a=b;
}

在上述代码中,如果传入的a和b是数组,那么无法实现了

C++为了解决这种问题,提供模板的重载,可以对特定的数据类型实现具体化

两个方法解决:
1、运算符重载 在类内部
2、重载函数模板 具体化模板

class P{
public:
    int age;
    std::string name;

    P(int a,std::string s):age(a),name(s){}
};
template <class T>
bool myCompare(T &a,T &b){
    return a==b;
}
//利用具体化P的版本实现代码  具体化优先调用
template<> bool myCompare(P &a,P &b){
    if (a.age==b.age && a.name == b.name)
        return true;
    return false;
}


    int a=10,b=10;
    std::cout<<myCompare<int>(a,b)<<std::endl;

    P p1(18,"tom");
    P p2(18,"tom");
    std::cout<<myCompare(p1,p2)<<std::endl;

实际上,学习模板并不是为了使用模板,而是为了STL中使用系统提供的模板

函数模板具体化

语法template <>函数返回值类型 函数模板名 <数据类型>(参数列表){}

template<typename T>
void print(T t){...}

template<> 
void print<int>(int t){...}

分文件编写

普通函数和普通函数模板声明和定义放在头文件中
函数模板具体化声明在头文件,定义在源文件

类模板

建立一个通用类,类中的成员 数据类型可以不具体指定,使用一个虚拟的类型来代表

语法

//class可由typename代替  意思是一样的  其余意思和函数模板一样的含义
template <class T,class R>template<class NameType,class AgeType>
class PER{
public:
    NameType name;
    AgeType age;
};

类模板和函数模板的区别

  • 类模板没有自动类型推导(只有函数)
  • 类模板在模板参数列表中可以有默认参数(只有类,C++11函数模板也可有默认参数)

默认参数之后必须都是默认参数

template<class NameType=std::string ,class AgeType=int>
class PER{
public:
    NameType name;
    AgeType age;

    PER():name("孙悟空"),age(999){}
    PER(std::string n,int a):name(n),age(a){}
};

//但是还是要有 <>  不然编译器认为为自动类型推导 报错的
    PER<>p("猪八戒",9999);
    std::cout<<p.name<<std::endl;

类模板成员函数创建时机

类模板中的成员函数和普通成员函数创建时机是有区别:

  • 普通成员函数在编译的时候就可以创建
  • 类模板成员函数在调用的时候才会创建

类模板作为函数参数

有三种方法:推荐第三个

template <class T1,class T2>
class Person1{
public:
        T1 name;
        T2 age;
        Person1(T1 n,T2 a):name(n),age(a){}

        void printInformation(){
            std::cout<< this->name<<"&&"<< this->age<< std::endl;
        }
    };

//1.指定传入类型(最常用)
void fun1(Person1<std::string,int>&p){
    p.printInformation();
}
//2.参数模板化
template <class T1,class T2>
void fun2(Person1<T1,T2> &p){
    p.printInformation();

    //模板的类型名称
    std::cout<<"T1的类型:"<< typeid(T1).name()<<std::endl;
    std::cout<<"T2的类型:"<< typeid(T2).name()<<std::endl;
}

//3.整个类模板化 推荐第三个
template <class T>
void fun3(T &p){
    p.printInformation();

    std::cout<<"T的类型:"<< typeid(T).name()<<std::endl;
}


int main() {

    Person1<std::string,int>p1("孙悟空",5252);
    fun1(p1);

    Person1<std::string,int>p2("猪八戒",1289);
    fun2(p2);

    Person1<std::string,int>p3("沙和尚",8289);
    fun3(p3);
}

类模板与继承

注意事项

  • 当子类继承的父类是一个类模板时,子类在声明的时候要指定父类的T的类型
  • 如果不指定类型,编译器无法给子类分配内存
  • 如果想要灵活的指定父类的T类型 子类也要变成模板
template<class T>
class Father{
public:
    T name;
};

//子类必须知道父类的T类型
class Son:public Father<std::string>{

};

//如果想要灵活的指定父类的T类型 子类也有变成模板
template <class T1,class T2>
class Son2:public Father<T1>{
public:
    T2 age;
};

int main() {
    Son2<std::string,int>s;
    s.name = "孙悟空";
    s.age = 888;
}
//模板类继承模板参数给出的基类(不能是模板类)
template<class T>
class AAA:public T{
    
};

类模板成员函数类外实现

类模板中成员函数类外实现时,需要加上模板参数列表

template<class T1,class T2>
class An{
public:
    T1 name;
    T2 age;
    An(T1 n,T2 a);

};

template<class T1,class T2>
An<T1,T2>::An(T1 n,T2 a){
    this->name = n;
    this->age=a;
}

类模板分文件编写

问题:类模板中的成员函数创建时期是在调用阶段,导致分文件编写链接不到

解决方法

  • 直接包含.cpp文件(使用的比较少)
  • 将声明和实现写一起,改变后缀名为.hpp hpp是约定的后缀 而不是强制
//hpp
#ifndef DU_H
#define DU_H
#include <iostream>
#include "string"
using namespace std;
template<class T1,class T2>
class Du {
public:
    T1 myName;
    T2 myAge;
    Du(T1 name,T2 age);
    void printInformation();
};





template<class T1,class T2>
Du<T1,T2>::Du(T1 name,T2 age) {
    myName = name;
    myAge = age;
}


template<class T1,class T2>
void Du<T1,T2>::printInformation() {
    cout<< this->myName<<endl;
    cout<< this->myAge<<endl;
}
#endif //DU_H

类模板和友元

  • 非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数。只能在类内实现
template<class T1,class T2>
class AAAA{
private:
    T1 m_x;
    T2 m_y;
public:
    AAAA(const T1 x,const T2 y):m_x(x),m_y(y){}
    friend void showAAAA(const AAAA<T1,T2>&a){//该函数不是模板函数
        cout<<a.m_x<<"--"<<a.m_y<<endl;
    }
};
//void showAAAA(const AAAA<int,string>&a){
//    cout<<a.m_x<<"--"<<a.m_y<<endl;
//}
int main() {
    AAAA<int,string>a(18,"duy");
    showAAAA(a);
    AAAA<char,string>b('X',"duy");
    showAAAA(b);
  • 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数(更有使用价值)
template<typename T>
void showAAAA(T&t); //第一步

template<class T1,class T2>
class AAAA{
private:
    T1 m_x;
    T2 m_y;
public:
    AAAA(const T1 x,const T2 y):m_x(x),m_y(y){}
    friend void showAAAA<>(AAAA<T1,T2>&a);//第二步
};

template<typename T>//第三步、通用 友元函数模板
void showAAAA(T&t){
    cout<<t.m_x<<"--"<<t.m_y<<endl;
}

template<>
void showAAAA(AAAA<int,string>&t){//第三步、具体化版本
    cout<<"AAAA<int,string>&a"<<endl;
    cout<<t.m_x<<"--"<<t.m_y<<endl;
}
int main() {
    AAAA<int,string>a(18,"duy");
    showAAAA(a);
    AAAA<char,string>b('X',"duy");
    showAAAA(b);
  • 非约束模板友元:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数(不科学)
template<typename T>
void showAAAA(T&t); //第一步

template<class T1,class T2>
class AAAA{
private:
    T1 m_x;
    T2 m_y;
public:
    AAAA(const T1 x,const T2 y):m_x(x),m_y(y){}
//    friend void showAAAA<>(AAAA<T1,T2>&a);//第二步
    template<typename T>friend void showAAAA(T&t);
};

template<typename T>//第三步、通用 友元函数模板
void showAAAA(T&t){
    cout<<t.m_x<<"--"<<t.m_y<<endl;
}

template<>
void showAAAA(AAAA<int,string>&t){//第三步、具体化版本
    cout<<"AAAA<int,string>&a"<<endl;
    cout<<t.m_x<<"--"<<t.m_y<<endl;
}
int main() {
    AAAA<int,string>a(18,"duy");
    showAAAA(a);
    AAAA<char,string>b('X',"duy");
    showAAAA(b);
//类模板与友元
#include<iostream>
using namespace std;
template <class T1, class T2>
class Person;
//函数模板的实现
template <class T1, class T2>
void printPerson1(Person<T1, T2> p)
{
        cout << p.id << p.age << endl;

}
template <class T1, class T2>
class Person
{
        //类内友元  加上friend就是全局函数
        friend void printPerson0(Person<T1, T2> p)
        {
                cout << p.id << p.age << endl;
        }
    //类外实现
        //普通函数的声明  friend void printPerson1(Person<T1, T2> p);
        //如果全局函数是类外实现,需要编译器提前知道模板函数的存在
        friend void printPerson1<>(Person<T1, T2> p);
public:
        Person(T1 a,T2 b)
        {
                this->id = a;
                this->age = b;
        }
private:
        T1 id;
        T2 age;
};
int main()
{
        Person<int, int> p(1,2);
        printPerson1(p);
        return 0;
}

类内实现简单,如果没有特殊需求使用类内实现就可以了

模板类的具体化

分为完全具体化和部分具体化

template <>
class A<xxx>{}


template<class T>
class A{}

template<class T>
class A<T*>{}

具体化程度高的类优先于具体化程度低的类,具体化的类优先于没有具体化的类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值