C++泛编程(5)

类模板高级(2)

  • 1.类模板与函数
  • 2.模板类与友元
    • 非模板友元
    • 约束模板友元
    • 非约束模板友元
  • 3.模板类的成员模板
  • 4.模板类作为参数

1.类模板与函数

上一节我们介绍了模板类的继承,希望大家已经掌握了。今天的内容从类作为函数的参数开始。假如有类模板:

template<class T1,class T2>
class AA 
{
    public:
        T1 m_a;
        T2 m_b;
        AA(T1 a,T2 b):m_a(a),m_b(b)
        {cout<<"这里是AA的构造函数"<<endl;}
        void show()
        {cout<<m_a<<" "<<m_b<<endl;}
};

那么这个类模板可以作为函数的参数吗?我们已经知道类模板在使用之前应当给出具体的通用类型,所以如果要把类的实例化作为函数的参数就应当这样写:

AA<T1,T2> test(AA<T1,T2> parameter)
{
    parameter.show();
    return parameter;
}

因为AA是我们自定义的模板类,这个函数就是以AA为返回值类型和参数类型。但是这样编译并不能通过,理由很简单,T1和T2没有定义。我们把它定义出来再试试:

template<typename T1,typename T2>
AA<T1,T2> test(AA<T1,T2> parameter)
{
    parameter.show();
    return parameter;
}

这样就没有问题了。然后试着调用:

int main()
{
    AA<int,string> aa(15,"ZhangSan");
    (test(aa)).show();
}
// 输出为:这里是AA的构造函数
//  	  15 ZhangSan
//		  15 ZhangSan

这样看起来就没问题了。但是这样做起来显得很臃肿。我们其实可以把返回值类型和参数类型全都作为一个需要自动推导的未知类型,做成精简的函数模板:

template<typename T>
T test(T parameter)
{
    parameter.show();
    return parameter;
}

换成这个模板再测试,效果也是一样的。实际上这样写函数的包容性也更强,参数完全可以是其他模板或者具体类型。有兴趣的小伙伴还可以debug一下,看看当两种test都存在的情况下,优先调用的是什么。根据函数模板越具体,满足调用条件时就越优先的现象,应当是优先调有template定义的test函数。

2.模板类与友元

除了友元类,C++还承认友元函数。类的友元函数可以直接访问类的所有内容,对于模板类也不例外:

template<class T1,class T2>
class AA 
{
    private:
        T1 m_a;
        T2 m_b;
    public:
        AA(T1 a,T2 b):m_a(a),m_b(b)
        {cout<<"这里是AA的构造函数"<<endl;}
    friend void show();
};
AA<int,double> a(5,5.5); // 可以在主函数外实例化AA

void show()
{
    cout<<a.m_a<<" "<<a.m_b<<endl;
}
int main()
{
    show();
}
// 输出为:这里是AA的构造函数
//        5 5.5

这种设置友元函数的方法在语法上没有问题,也能实现我们需要的功能。但是这需要用模板类创建全局对象,代码就显得笨重了。有没有能够让友元函数能灵活的搭配类模板的方法?

非模板友元

以上的任务直接把友元函数定义在类模板里,就可以实现了:

template<class T1,class T2>
class AA 
{
    private:
        T1 m_a;
        T2 m_b;
    public:
        AA(T1 a,T2 b):m_a(a),m_b(b)
        {cout<<"这里是AA的构造函数"<<endl;}
    friend void show(AA<T1,T2>& a)
    {
        cout<<a.m_a<<" "<<a.m_b<<endl;
    }
};

int main()
{
    AA<int,double> a(5,5.5);
    show(a);
    AA<char,string> b('w',"ZhangSan");
    show(b);
}
// 输出为:这里是AA的构造函数
//		  5 5.5
//		  这里是AA的构造函数
//		  w ZhangSan

虽然这样写看起来show像是AA类的一个方法,但从调用方式上可以看出它实际上是一个独立的函数。不过编译器会把这个show当做一个具体函数而非函数模板处理。在看一个例子,我们试图给show做一个具体化:

template<class T1,class T2>
class AA 
{
    private:
        T1 m_a;
        T2 m_b;
    public:
        AA(T1 a,T2 b):m_a(a),m_b(b)
        {cout<<"这里是AA的构造函数"<<endl;}
    friend void show(AA<T1,T2>& a)
    {
        cout<<a.m_a<<" "<<a.m_b<<endl;
    }
};
void show(AA<int,string>& a)
{
    cout<<a.m_a<<" "<<a.m_b<<endl;
}

编译器就会报错了,原因是show函数被重复定义。那么又该怎么解决这个问题呢?

约束模板友元

约束模板友元分三步实现,首先声明函数模板,然后在类内注明该模板为友元函数模板,第三部再定义函数模板:

void show(T &a); // 声明函数模板
template<class T1,class T2>
class AA 
{
    private:
        T1 m_a;
        T2 m_b;
    public:
        AA(T1 a,T2 b):m_a(a),m_b(b){}
    friend void show<>(AA<T1,T2> &a); // 声明为友元函数
};
template<typename T> // 定义友元函数模板
void show(T &a)
{
    cout<<"这里是show函数模板"<<endl;
    cout<<a.m_a<<" "<<a.m_b<<endl;
}
template<>       // 定义友元函数模板的具体化
void show(AA<char,string> &a)
{
    cout<<"这里是show函数模板具体化"<<endl;
    cout<<a.m_a<<" "<<a.m_b<<endl;
}

int main()
{
    AA<int,double> a(5,5.5);
    show(a);
    AA<char,string> b('w',"ZhangSan");
    show(b);
}
// 输出为:这里是show函数模板
//		  5 5.5
//		  这里是show函数模板具体化
//		  w ZhangSan

这种方式实现的友元函数模板虽然比较繁琐,但确实好用。此外,如果有定义与AA类似的模板类,show还可以同时作为这样模板类的友元函数。

非约束模板友元

非约束模板友元实现很简单,但是并不科学,所以实际开发中不常用。因为在模板实例化中,如果实例化了n个类,每个类都会实例化n个友元函数。非约束模板友元只需要在模板类中声明友元函数时使用template修饰即可,示例如下:

template<class T1,class T2>
class AA 
{
    private:
        T1 m_a;
        T2 m_b;
    public:
        AA(T1 a,T2 b):m_a(a),m_b(b){}
    template<typename T> friend void show(T &a);
};
template<typename T>
void show(T &a)
{
    cout<<"这里是show函数模板"<<endl;
    cout<<a.m_a<<" "<<a.m_b<<endl;
}
template<>
void show(AA<char,string> &a)
{
    cout<<"这里是show函数模板具体化"<<endl;
    cout<<a.m_a<<" "<<a.m_b<<endl;
}
int main()
{
    AA<int,double> a(5,5.5);
    show(a);
    AA<char,string> b('w',"ZhangSan");
    show(b);
}

这种友元方式从测试上看不出和约束模板友元的区别,我们只需要知道这种方式实际开发者不常用就好。

3.模板类的成员模板

虽然叫法很高级,但实现起来就是简单的模板嵌套。我们直接看一个例子:

template<class T1,class T2>
class AA 
{
    public:
        T1 m_a;
        T2 m_b;
        AA(T1 a,T2 b):m_a(a),m_b(b){}
        void show(){cout<<"这里是AA的show"<<m_a<<endl;}
    template<class T>
    class BB
    {
        public:
            T m_a;
            T2 m_b;
            void show(){cout<<"这里是BB的show"<<m_a<<" "<<m_b<<endl;}
    };
    BB<string> bb;
    template<typename T>
    void show(T bb)
    {
        cout<<"这里是函数show"<<m_a<<" "<<bb.m_a<<" "<<m_b<<endl;
    }
};
int main()
{
    AA<int,double>a(10,5.5);
    a.show();
    a.bb.m_a="ZhangSan";a.bb.m_b=23.5;
    a.bb.show();
    a.show(a.bb);
}

在这段代码中,我们定义了一个模板类AA,在AA类内,我们又定义了一个模板类BB和模板函数show。AA类的参数作用域是最大的,因此T1和T2在没有被覆盖(即BB或和show都没有定义名字为T1或T2的通用类型)时,T1和T2类型在BB和show中都可以应用。但是由于BB类和show函数作用域是彼此独立的,所以这两部分的T类型本质上没有任何关系。如果我们需要使用BB类,需要在AA类内实例化BB。另外,在主函数中要仔细地分析需要如何写才能调用到想用的内容。注意事项就这么多,下面我们再来看看如何将函数和内嵌类模板分开声明和定义:

template<class T1,class T2>
class AA 
{
    public:
        T1 m_a;
        T2 m_b;
        AA(T1 a,T2 b):m_a(a),m_b(b){}
        void show(){cout<<"这里是AA的show"<<m_a<<endl;}
    template<class T>
    class BB
    {
        public:
            T m_a;
            T2 m_b;
            void show();
    };
    BB<string> bb;
    template<typename T>
    void show(T bb);
};
template<class T1,class T2>
template<class T>
void AA<T1,T2>::BB<T>::show(){cout<<"这里是BB的show"<<m_a<<" "<<m_b<<endl;}
template<class T1,class T2>
template<typename T>
void AA<T1,T2>::show(T bb)
{cout<<"这里是函数show"<<m_a<<" "<<bb.m_a<<" "<<m_b<<endl;}

show也是AA的成员函数。

4.模板类作为参数

这部分比较抽象,我们需要结合代码来理解。先看一个例子:

template<class T,int len>
class Array
{
    public:
    void add()
    {cout<<"用于增加一条记录"<<endl;}
    void del()
    {cout<<"用于删除一条记录"<<endl;}
    void Long()
    {cout<<"线性结构的长度为"<<len<<endl;}
};

首先,我准备了一个Array的结构,他没有实际对数组的操作,仅仅为了展示代码如何运作。len作为一个非通用类参数,需要在实例化中填入指定类型的具体值。然后我们定义一个用于描述线性结构的模板类:

template<template<class,int>class T1,class T2,int len>
class Line
{
    public:
    T1<T2,len> my_table;
    void show_add()
    {my_table.add();}
    void show_del()
    {my_table.del();}
    void show_long()
    {my_table.Long();}
};

这个是一个用于接收模板类参数的模板类,其中templateclass T1这是在定义一个T1,T1可以接收一个拥有一个通用类和一个int类的类模板模板名,但是光有模板名是不能实例化模板类的,所以我们还需要一个通用参数(T2)和一个具体参数(int len)作为T1所代表的模板名实例化时应给出的通用参数和具体参数。光看文字可能不好理解,我们直接从代码上看看line是如何被调用的:

int main()
{
    Line<Array,int,10> aa;
    aa.show_add();
    aa.show_del();
    aa.show_long();
}
// 输出为:用于增加一条记录
//        用于删除一条记录
//        线性结构的长度为10

这段代码的运行逻辑是:Line aa换到line模板的定义就相当于:

templateclass T1,class T2,int len>

这里的T1成了array,T2成了int,len赋值为10,而

T1 my_table

这句话实际上就成了

Array my_table

再往后的代码应该就不需要我多说了吧?

这节课我们主要讲了类模板核函数、类模板与友元,类模板嵌套类模板或(和)函数模板,以及类模板作为类的参数。其中,类模板与函数、约束模板友元、模板类做参数需要我们重点掌握。还有一点需要注意,类模板作为参数多用于实现较为复杂的数据结构后中,而且类模板目前还不能作为函数模板的参数,即使可以,也没有什么必要。

你可能感兴趣的:(c++,c++,前端)