C++———模板

目录

1. 什么是模板

2.函数模板

2.1 函数模板的作用

2.2 函数模板语法

2.3 函数模板注意事项

2.4 函数模板案例

2.5 普通函数与函数模板的区别及调用规则

2.6 模板的局限性

3. 类模板

3.1 类模板与函数模板的区别

3.2 类模板中成员函数的创建时机

3.3 类模板对象做函数参数

3.4 类模板与继承

3.5 类模板成员函数类外实现

3.6 类模板与友元

4 总结


1. 什么是模板

C++另一种编程思想称为泛型编程,主要利用的技术就是模板。

模板就是建立通用的摸具,大大提高复用性。

模板不能直接使用。

C++提供两种模板机制:函数模板和类模板。

2.函数模板

2.1 函数模板的作用

函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体确定,用一个虚拟的类型来代表。

2.2 函数模板语法

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

template------声明创建模板

typename------表明其后面的符号是一种数据类型,可以用class代替。

T------通用数据类型,名称可以替换,通常为大写字母。

//函数模板
template<typename T>  //声明一个函数模板,T为一个通用数据类型
void mySwap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

void test()
{
    int a = 100;
    int b = 200;
    //两种方式适应函数模板
    //方式1:自动类型推导
    mySwap(a, b);

    //方法2:显示指定类型
    mySwap<int>(a, b);   //<int>指定T为int型

}

总结:

  • 函数模板用关键字template。
  • 函数模板两种使用方式:自动类型推导、显示指定类型。
  • 模板的目的是为了提高复用性,将类型参数化。

2.3 函数模板注意事项

注意事项:

  • 自动类型推导必须要推导出一致的数据类型T,即推出的参数类型要一致。
  • 模板必须要有确定的数据类型,不能有二义性。
//函数模板注意事项
template<typename T>   //typename也可以替换成class
void mySwap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = a;
}

//自动类型推导必须要推导出一致的数据类型T,即推出的参数类型要一致,不能有二义性。
void test1()
{
    int a = 100;
    int b = 200;
    char c = 'c';
    mySwap(a,b);  //正确!
    //mySwap(b,c);  //错误!推导不出一致的T类型(一个是int,一个是char)
    
}

//模板必须要有确定的数据类型。
template<typename T>
void func()
{
    cout<<"func的调用"<<endl;
}
void test2()  
{
    //func();  //错误!若去掉template<typename T>使func变为普通函数则正确
    func<int>(); //正确!不管有没有使用到,都必须将其模板函数的数据类型确定
}

2.4 函数模板案例

案列描述:

  • 利用函数模板封装一个排序函数,可以对不同类型的数据进行排序
  • 排序规则由大到小,排序算法为选择排序
template<typename T>     //交换模板
void mySwap(T &a,T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

template<typename T>   //打印模板
void myPrint(T arr,int len)
{
    for(int i=0;i<len;i++)
    {   
       cout<<arr[i]<<" "; 
    }
    cout<<endl;
}

template<typename T>    //排序模板
void mySort(T arr[], int len)
{    //选择排序
    for(int i=0;i<len;i++)
    {    
        int max = i;
        for(int j=i+1;j<len;j++)
        {
            if(arr[max]<arr[j])
            {
               max=j; 
            }
        }
        if(max!=i)
        {
            mySwap(arr[max],arr[i]);  //交换
        }
    }
}

void test1()//测试char类型数组
{
    //char类型数组
    char arr[]="abcdefghi";
    //计算char数组长度
    int len = sizeof(arr)/sizeof(char)  
    
    mySort(arr,len);  //对char类型数组进行排序
    myPrint(arr,len);    //打印输出排序后的char数组

}

void test2() //测试double类型数组
{
    //double类型数组
    double doubleArr[]={1,3,2,6,8,3,9};
    //double数组长度
    int num = sizeof(doubleArr)/sizeof(double);

    mySort(doubleArr,num);    //对double类型数组进行排序
    myPrint(doubleArr,num);    //打印输出排序后的double类型数组
}

2.5 普通函数与函数模板的区别及调用规则

普通函数与函数模板的区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)。
  • 使用函数模板时,如果利用自动类型推导,则不会发生隐式类型转换。
  • 如果利用显示指定类型的方式,则可以发生隐式类型转换。
//普通函数调用时可以发生自动类型转换(隐式类型转换)。
int myAdd1(int a,int b)  //普通函数
{
    return a+b;
}
//使用函数模板
template<class T>
T myAdd2(T &a, T &b)
{
    return a+b;
}

void testAdd()
{
    int a = 100;
    int b = 200;
    char c = 'c';
    cout<<myAdd1(a,b)<<endl;  //普通函数调用
    cout<<myAdd1(a,c)<<endl;  //普通函数调用 会发生隐式类型转换 将c转换为其对应的ASCII c的ASCII为99

    //函数模板--自动类型转换
    //使用函数模板时,如果利用自动类型推导,则不会发生隐式类型转换。
    cout<<myAdd2(a,b)<<endl;  //正确
    cout<<myAdd2(a,c)<<endl;  //错误

    //函数模板--显示指定类型
    //如果利用显示指定类型的方式,则可以发生隐式类型转换。
    cout<<myAdd2<int>(a,c)<<endl;  //正确
}

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

1. 如果函数模板与普通函数都可以实现,优先调用普通函数。

2. 可以通过空模板参数列表来强制调用函数模板。

3. 函数模板也可以发生重载。

4. 如果函数模板可以产生更好的匹配(如不用发生隐式转换),优先调用函数模板。

注:空模板 例如:myAdd1<>(a,b)

2.6 模板的局限性

模板的通用性并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。

//模板局限性

//自定义数据类型
class Person
{
public:
    string m_name;  //姓名
    int m_age;    //性别

    Person(string name,int age)
    {
        this.m_name = name;
        this.m_age = age;
    }
}


template<typename T>   //模板
bool myCompare(T &a, T &b)   //比较函数 比较两个数据是否相等
{
    if(a == b)
    {
        return true;
    }
    else
    {
        return false;
    }
}

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 test()
{
    int a = 100;
    int b = 200;

    bool ret = myCompare(a,b);
    if(ret)
    {
        cout<<"a == b"<<endl;
    }
    else
    {
        cout<<"a != b"<<endl;
    }
}

void testPerson()
{
    Person per1("Tony",19);
    Person per2("Tony",19);

    bool ret = myCompare(p1,p2);   //编译器不会报错,但是运行时会发生错误!
    if(ret)
    {
        cout<<"a == b"<<endl;
    }
    else
    {
        cout<<"a != b"<<endl;
    }
}

3. 类模板

类模板的作用:建立一个通用类,类中的成员、数据类型可以不具体制定,用一个虚拟的类型来代表。

语法:template<typename T>

//类模板
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;
    }
    void showPerson()
    {
        cout<<"name: "<<this->m_name<<endl;
        cout<<"age: "<<this->m_age<<endl;
    }
}

void test()
{
    Person<string,int> p1("yujingtian","19");  //<string,int>为模板参数列表
    p1.showPerson();
}

3.1 类模板与函数模板的区别

类模板与函数模板的区别:

1. 类模板没有自动类型推导的使用方式,只有显示指定类型。

2. 类模板在模板模板参数列表中可以有默认参数。

//类模板与函数模板的区别

//类模板
template<class NameType, class AgeType=int>   //可以在此默认
class Person
{
public:
    NameType m_name;
    AgeType m_age;

    Person(NameType name, AgeType age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    void showPerson()
    {
        cout<<"name: "<<this->m_name<<endl;
        cout<<"age: "<<this->m_age<<endl;
    }
}

//1.类模板没有自动类型推导的使用方式:
void test1()
{
    Person p("YuTony",19);  //错误!无法用自动类型推导
    Person<string,int> p1("YuTony",19)    //正确!只能用显示指定类型
    
    p1.showPerson();
}

//2.类模板在模板的参数列表中可以有默认:

void test2()
{
    Person<string> p2("xuchangjin",29);
    
    p2.showPerson();
}


3.2 类模板中成员函数的创建时机

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

1. 普通类中的成员函数在开始就创建。

2.类模板中的成员函数在调用时才创建。

3.3 类模板对象做函数参数

类模板实例化出一个对象,向函数传参的方式。

三种传入方式:

1. 指定传入类型:直接显示对象的数据类型。

2. 参数模板化:将对象中的参数变为模板进行传递。

3.整个类模板化:将这个对象类型、类模板进行传递。

template<class T1,class T2>
class Person
{
public:
    T1 m_name;
    t2 m_age;

    Person(T1 name,T2 age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    void showPerson
    {
        cout<<"name: "<<this->m_name<<endl;
        cout<<"age: "<<this->age<<endl;
    }
}

//1. 指定传入类型
void printPerson1(Peison<string,int> &p)
{
    p.showPerson();
}
void test1()
{
    Person<string,int> p1("孙悟空",99);
    printPerson1(p1);
}

//2. 参数模板化
template<class T1,class T2>
void printPerson2(Peison<T1,T2> &p)  //将string和int模板化
{
    p.showPerson();
    cout<<"T1的类型为:"<<typeid(T1).name()<<endl;
    cout<<"T2的类型为:"<<typeid(T2).name()<<endl;
}
void test2()
{
    Person<string,int> p2("猪八戒",199);
    printPerson2(p2);
}

//3. 整个类模板
template<class T>
void printPerson3(T &p)
{
    p.showPerson();
}
void test3()
{
    Person<string,int> p3("唐僧",39);
    printPerson3(p3);
}

3.4 类模板与继承

注意:

1. 当子类继承的父类是模板时,子类在声明的时候要指定出父类T的类型。

2. 不指定编译器将无法给子类分配内存。

3. 如想灵活指出父类中的T的类型,则需将子类变成类模板。

//类模板与继承
template<class T>
class Base
{
public:
    T m;
}
class Son : public Base  //错误!必须要知道父类中T的数据类型才能继承

class Son : public Base<int>  //正确
{} 

//子类变为类模板
template<class T1, calss T2>
class Son2 : public Base<T2>  //正确
{
    T s;    
} 
void test()
{
    Son2<int,char> S2;    //将char类型传给T2--父类,int类型传给T1--子类
}

3.5 类模板成员函数类外实现

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

//类模板成员函数的类外实现
template<class T1,class T2>
class Person
{public:
    T1 m_name;
    T2 m_age;

    Person(T1 name,T2 age);
    //类内实现
    //{
    //    this->m_name=name;
    //    this->m_age=age;
    //}
    void showPerson();
    //类内实现
    //{
    //    cout<<"name: "<<this->m_name<<endl;
    //    cout<<"age: "<<this->m_age<<endl;
    //}
}

//构造函数的类外实现:
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)  //要在作用域后加上模板参数列表
{
    this->m_name=name;
    this->m_age=age;
}

//成员函数的类外实现:
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
    cout<<"name: "<<this->m_name<<endl;
    cout<<"age: "<<this->m_age<<endl;
}

3.6 类模板与友元

全局函数类内实现:直接在类内声明友元。

全局函数类外实现:需要提前让编译器知道全局函数的存在。

//类模板与友元

//通过全局函数来打印Person的信息

//全局函数类外实现:需要提前让编译器知道全局函数的存在。
//(提前让编译器知道Person类的存在,类外实现才不会出错!)
template<class T1,class T2>
class Person;
//(要提前知道全局函数的存在)
template<class T1,class T2>
void printPerson2(Person<T1,T2> p)
    {
        cout<<"类外实现: "<<endl;
        cout<<"name: "<<this->m_name<<endl;
        cout<<"age: "<<this->m_age<<endl;
    }   

template<class T1,class T2>
class Person
{
        //全局函数类内实现
    //全局函数类内实现:直接在类内声明友元。
    friend void printPerson(Person<T1,T2> &p)
    {
        cout<<"类内实现:"<<endl;
        cout<<"name: "<<this->m_name<<endl;
        cout<<"age: "<<this->m_age<<endl;
    }   

        //全局函数类外实现

     friend void printPerson2<>(Person<T1,T2> &p);  //类内仅声明!但需加空模板的参数列表

public:
    Person(T1 name,T2 age)
    {
        this->m_name=name;
        this->m_age=age;
    }
private:
    T1 m_name;
    T2 m_age;
}

void test1()  //全局函数类内实现测试
{
    Person<string,int> p1("Tom",25);
    printPerson(p1);
}


void test2()   //全局函数类外实现测试
{
    Person<string,int> p2("Jack",21);
    
    printPerson2(p2);
}

4 总结

        尝试先对所学知识点进行总结,之后针对知识点再利用具体的案例来实现,感觉这样理解更深刻些。此外,还有一个类模板的分文件编写注意事项:建议将.h文件和.cpp文件写在一起,并命名为.hpp文件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值