C++——深入理解模板类

首先,栈可以使用指针压栈吗?

当然可以,这里写一个简单的栈

提到指针,可能会想到字符串,我们可以有以下几种传入

string str; 最简单的传入,得益于C++提供的强大的string类,我们可以轻松的完成这个操作

char str[size]; C风格的字符串,但对某些模板类方法可能不可用

char * str = new char[size];

对于我们实现的这个类来说,至少对输入数据不做修改是不可行的

template<class Type>
bool Stack<Type>::push(const Type &t)
{
    if(now < MAX)
    {
        items[now++] = t;
        return true;
    }else{
        return false;
    }
}

我们可以看见,这样压栈元素的是一个指针,在没有人为的干预下,这个指针是指向的内存是不会改变的,我们重复对str输入是无意义的;

妥当的做法还是让程序提供一个指针数组(一般是二维数组),其中每个指针都指向不同的字符串。栈的任务是管理指针,而不是创建指针。

//
// Created by JAN on 2022/1/26.
//

#ifndef C___STACK_H
#define C___STACK_H
template <class T>
class Stack
{
private:
    enum { MAX = 10};
    T items[MAX];
    int now;
public:
    Stack() : now(0) {}
    bool push(T & t);
    bool pop();
    bool empyt() const;
    const T& top() const;
};

template <class T>
bool Stack<T>::push(T &t)
{
    if(now < MAX){
        items[now++] = t;
        return true;
    }else
        return false;
}

template<class T>
bool Stack<T>::empyt() const
{
    if(now == 0)
        return true;
    else
        return false;
}

template<class T>
const T &Stack<T>::top() const
{
    //int temp = now-1;
    return items[now-1];
}

template<class T>
bool Stack<T>::pop()
{
    if(now > 0){
        now--;
        return true;
    }else
        return false;
}
#endif //C___STACK_H
//
// Created by JAN on 2022/1/25.
//

#ifndef C___STACK_H
#define C___STACK_H

template<class Type>
class Stack
{
private:
    enum { MAX = 5 };
    Type items[MAX+1];
    //也可以写成这样的形式,但是别忘了在构造函数中new一块空间出来,和一个析构函数释放空间
    //Type *items;
    int now;
public:
    Stack();
    bool empty();
    bool push(const Type & t);
    bool pop();
    Type top();
};
template<class Type>
Stack<Type>::Stack()
{
    now = 0;
}
template<class Type>
bool Stack<Type>::empty()
{
    return now == 0;
}

template<class Type>
bool Stack<Type>::push(const Type &t)
{
    if(now < MAX)
    {
        items[++now] = t;
        return true;
    }else{
        return false;
    }
}

template<class Type>
bool Stack<Type>::pop()
{
    if(now > 0)
    {
        now--;
        return true;
    }else
        return false;
}

template<class Type>
Type Stack<Type>::top()
{
    if(now > 0){
        return items[now];
    }
}

#endif //C___STACK_H

运行结果

可以看见重复输出了eee


模板的递归使用

我们知道有array这个模板类,所谓的递归也就是模板套模板罢了

array< array<int, 3>, 5> arr;

外层的array中包含 五个 包含 三个int 的array 模板

描述成语言可能不好理解,不过看一下源码就懂了 

#include <iostream>
#include <valarray>
#include <array>
using namespace std;

int main()
{
    array< array<int, 3>, 5> arr;
    for(int i=0;i<5;i++){
        for(int j=0;j<3;j++){
            cin >> arr[i][j];
        }
    }
    for(int i=0;i<5;i++){
        for(int j=0;j<3;j++){
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

可像二维数组那样进行操作


接受多个类型的模板

假设我们要用一个二元组存放数据,这两个元类型可能是相同的,也可能是不同的,那么我们如何编写模板类

简单模拟一下二元组pair,非常简单,可能动动手就写出来了

pair.h

#ifndef PAIR_H
#define PAIR_H
//二元组
//template_name <class type_name, class type_name ... some argument>
//我们可以将上面的称为模板类原型
template <class Type1, class Type2>
class Pair
{
private:
    Type1 t1;
    Type2 t2;
public:
    Pair() = default;
    Pair(const Type1 & _t1, const Type2 & _t2) : t1(_t1), t2(_t2) {}
    const Type1 & first() const;
    const Type2 & second() const;
};

template <class Type1, class Type2>
const Type1 & Pair<Type1, Type2>/*这里的作用域就是模板原型,这样写,这里容易忘记*/::first() const
{
    return t1;
}

template <class Type1, class Type2>
const Type2 & Pair<Type1, Type2>::second() const
{
    return t2;
}

#endif

main.cpp

#include <iostream>
#include "pair.h"
using namespace std;

int main()
{
    Pair<int, double> p(3,4.12);
    cout << p.first() << endl;
    cout << p.second() << endl;
    return 0;
}

默认类型模板

就像默认函数参数那样,如果没声明可以自动就变为默认值,模板类如果没有声明类型,就会自动变为默认类型

template <class Type1=int, class Type2=int>

...

Pair <> p; //Pair<int, int> p;

模板类的具体化

1.隐式实例化

就像模板函数一样,模板类在没有指出实例化对象时编译器会帮助我们自动实例化我们传入的类型。在编译器需要对象之前,不会生成隐式的实例化。

array <int,3> * parr;

这只是声明一个模板类指针,并没有实例化。

2.显示实例化

使用关键字template并指出所需的类型来声明类时,编译器将会生成类声明的显式实例化。声明必须位于模板定于的名称空间中。

下面显式声明一个<string,int>的类

template class class_name <string,int>

在这种情况下,虽然没有创建或者提及对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。

3.显式具体化

显式具体化是特定类型的定义。有些类型我们需要有特定的模板类方法处理它。这时候就要用到显式具体化。显式具体化是模板类通用性的进一步补充。

定义格式如下

template class class_name <sprcialized-type-name>
{
    do sometings
};

4.部分具体化

即限制模板的通用性,假如 我们写了只可以处理整数的模板类,不想让浮点类型使用这个模板,我们可以这样做

template <class Type, int> 
class class_name
{
    some things;
}

这样模板类的第二个类型只能是整形

如果有多个模板可供选择,编译器将会选择具体化程度最高的

特别注意为指针提供的模板具体化

template <class T>
class A
{
    ...
};

template <class T*>
class A
{
    ...   
};
------------------------------------------------------
A<char *> a;

若没有第二个模板声明,编译器会将T变为char *

而第二个不是将T变为char **而是变为char


 成员模板

模板可用作结构体,类或者模板的成员。要完全实现STL的设计,必须使用这项特性。下面写一个简单的模板类的嵌套

#include <iostream>

using std::endl;
using std::cout;
//定义模板类

template <class T>
class beat
{
private:
    template<class V>
    class hold
    {
    private:
        V val;    //创建一个type variable
    public:
        //构造函数
        hold(V v=0) : val(v) {}

        //class method`
        void show() const
        {
            cout << val << endl;
        }
        V value() const
        {
            return val;
        }
    };
    //定义内层模板之后我们便可以创建两个模板类内的模板类
    //beat类把这两个模板类当作成员
    hold<T> q; //和最外层类实例化一个数据类型
    hold<int> n; //显式实例化int类型的

public:
    //class method
    //构造函数,实际上是调用hold内的构造函数
    beat(T t, int i) : q(t), n(i) {}

    template<class U>
    //这里是模板函数
    //U u是这个模板函数独有的, T t和大模板类的T实例化一种类型
    U blab (U u, T t)
    {
        return (n.value()+q.value()) * u / t;
    }
    void show() const
    {
        q.show();
        n.show();
    }
};
int main()
{
    //beat(T t, int i) : q(t), n(i)
    beat<double> guy(3.5, 3);
    cout << "T was set to double\n";
    guy.show();
    cout << "V was set to T, which is double, then V was set to int\n";
    //由于是T is double 所以 下面的2.3 若将其变为2也是double 类型
    cout << guy.blab(10,2.3) << endl;
    cout << "U was set to int\n";
    cout << guy.blab(10.0,2.3) << endl;
    cout << "U was set to double\n";
    return 0;
}

  将模板类作为参数

模板类可以包含类型参数(type)也可以包含非类型参数(int a),同样的,模板类也可以包含本身就是模板的参数,是C++11的特性,用于实现STL

其实,模板类的递归就是将模板类作为了参数

语法形式很好理解

注意中间没有逗号

template < /**/template <typename type_name> class/**/ type_name>
class class_name
{
    do something;
};

其中 /**/template <typename type_name> class/**/是类型,后面的type_name是参数

假设我们要对二元组进行栈处理,那我们之前的栈就会不起作用,因为二元组也是一个模板类,之前的栈不支持模板类作为类型参数,可以对栈进行改造,使其能接收二元组模板类,在这基础上,可以写一个新的模板类,把栈和二元组集成进去就可以,这个类接受两个模板类为对像,还可以写一个另外的模板类,接受一个栈模板类,在这个模板类中实例化两个模板栈类对象,代表二元组。

第二种办法看似麻烦,但是他的通用性可能会更好一点,第三种方法既简单又方便实现

这里我们用最后一种形式实现

#include <iostream>
#include "stack.h"
using namespace std;

template <template<class T> class S, class T1, class T2>
class IDE
{
private:
    S<T1> stack1;
    S<T2> stack2;
public:
    IDE() = default;

    //class method
    bool push(T1 & t1, T2 & t2);
    bool pop();
    void top(T1 & t1, T2 & t2) const;
};

template <template<class T> class S, class T1, class T2>
bool IDE<S, T1, T2>::push(T1 &t1, T2 &t2)
{
    return stack1.push(t1) && stack2.push(t2);
}

template <template<class T> class S, class T1, class T2>
bool IDE<S, T1, T2>::pop()
{
    return stack1.pop() &&stack2.pop();
}

template <template<class T> class S, class T1, class T2>
void IDE<S, T1, T2>::top(T1 &t1, T2 &t2) const
{
    t1 = stack1.top();
    t2 = stack2.top();
}
int main()
{
    IDE<Stack/*no Stack <argument>*/,int ,double> stk;
    int a;
    double b;
    cin >> a >> b;
    stk.push(a,b);
    cin >> a >> b;
    stk.push(a,b);
    int c;
    double d;
    stk.top(c,d);
    cout << c << " " << d << endl;
    stk.pop();
    stk.top(c,d);
    cout << c << " " << d;
    stk.pop();
    return 0;
}

很明显,我们可以发现,我们无法实现top这个函数,我们无法返回两个元素,若返回只能返回一个结构体,或者改变top的做法。但用其他方法可直接返回一个pair,因为pair模板类是直接压入栈中的。


 模板类和友元

如同类可以声明友元一样,模板类也可以声明友元函数。模板的友元分为三类。

1.非模板友元

2.约束模板友元

即友元的类型取决于类被实例化时的类型

3.非约束模板友元

即友元的所有具体化都是类的每一个具体化的友元

读不懂不重要,理解了才重要。


1.非模板友元

在模板中声明一个常规的函数做友元

正因为他不是模板函数,所以模板函数的每一种实例化都要单独写一个友元函数出来

我的友元是我的友元,你的友元是你的友元,虽然我们像兄弟一样都是从模板类具体化出来的,虽然我们很像,但我的朋友不是我兄弟的朋友,我的友元不是我兄弟的友元——模板类如是说道。

template <class T>
class A
{
    friend void func();
    ...
};

可以这样吗

template <class T>
class A
{
    friend void func(A &);
    ...
};

当然不行,因为不存在A这样的对象,只有特定的具体化才能这样做

我们可以这样

template <class T>
class A
{
    friend void func(A<T>);
    ...
};

因为func并不是类方法,而是友元函数,他不属某个类,也不是模板函数,所以在使用时要显式具体化,在编译时期,我们假设用户传入int类型,那么编译器就会把T换为int,对于func来说,这就是个显式具体化。

重载版本

friend void func(A<int>);
friend void func(A<double>);

一个小示例

#include <iostream>
using namespace std;

template<class T>
class A
{
private:
    T data;
    static int total;
public:
    A(T a) : data(a) { total++; }
    friend void report();
    friend void show(const A<T> &);
};

template<class T>
int A<T>::total = 0;
//这里必须显式具体化
void show(const A<int> &t)
{
    cout << t.data << endl;
}

//这里也是
void show(const A<double> &t)
{
    cout << t.data << endl;
}
void report()
{
    cout << " <int> is " << A<int>::total << endl;
    cout << " <double> is " << A<double>::total << endl;
}
int main()
{
    report();
    A<int> a(1);
    report();
    A<double> b(3.14);
    report();
    A<int> c(10);
    report();
    show(a);
    show(b);
    show(c);
    return 0;
}

别忘了初始化静态变量哦!

运行结果

由此也可见A<int>,A<double> ,是两个类!!!并且他们是出自一个模子的。


2.约束模板友元

因为<int>, <double>是不同的类,所以应该让类的每一种具体化都获得一个与友元匹配的具体化

说白了就是模板函数做友元,正好对应上面的如是说道。

可以修改前一个示例,使友元函数本身称为模板

包含以下三补

首先在类前面定义每个友元函数的模板

这里的T是模板函数的T,不是模板类的T,T实例化后是某个模板类如<int>,<string>
template <class T> void show(T &);
template <class T> void report();

然后,在模板类中再次将函数声明为友元

template <T>
class A
{
    friend void show <> (A<T> &);
    friend void report <T> ();
    ...
};

 声明中的<>指出模板具体化。对于show,可以为空,因为可以从函数参数推断模板类型参数

相当于

void show <A<T> (A<T> &);

最后再像定义模板函数那样定义友元函数即可。

这就正好是模板函数的具体化。

最好将友元的Type 和模板类的Type区分开

#include <iostream>

using std::cout;
using std::endl;
template <class T> void counts();
template <class T> void report(T &);

template<class TT>
class HasFriend
{
private:
	TT item;
	static int ct;
public:
	HasFriend(const TT& i) : item(i) { ct++; }
	~HasFriend() { ct--; }
	friend void counts <TT>();
	//这里是HasFriend<TT>& 而不是T&,因为这里实现的是友元函数的实例化
	friend void report <>(HasFriend<TT>&);
};

template <class T>
void counts()
{
	cout << "template size: " << sizeof(HasFriend<T>) << ";";
	cout << "template counts(): " << HasFriend<T>::ct << endl;
}

template<class T>
void report(T & t)
{
	cout << t.item << endl;
}

//注意初始化也要用模板,代表他是这个模板类下的静态成员
template <class TT>
int HasFriend <TT>::ct= 0;
int main()
{
	counts<int>();
	HasFriend <int> hfi1(10);
	HasFriend <int> hfi2(20);
	HasFriend <double> hfi3(10.5);
	report(hfi1);
	report(hfi2);
	report(hfi3);
	cout << "counts<int>() output:\n";
	counts<int>();
	cout << "counts<double>() output:\n";
	counts<double>();
	return 0;
}

 正如程序所见,两个counts报告的大小不同,证明了每个类型都有他自己的友元函数。

正对应每个counts的具体化。


3.非约束模板友元

很简单

class A
{
public:
    template<class C, class D> friend void show(C & c, D & d);
};

类外只需要将声明的分号去掉,friend去掉,接花括号定义即可。


模板别名

为了简化代码,可能会用typedef为模板类起一个别名,在C++11中也有类似的功能

template <class T>
  using arrtype = std::array<T,12>;

arrtype就是std::array<T,12>的别名了。功能比typedef更加强大一些。

//using arrtype = std::array<T,12>;
arrtype<int>; //等价于std::array<int,12>

arrtype<string>; //等价于std::array<string,12>

using 还可以用于非模板类。当用于非模板时,这种语法与typedef等价 

最后,关于具体化和实例化的一些说明

类定义(实例化)在声明类对象并指定特定类型时生成。例如,下main的声明将导致编译器声成类声明,用声明中的实际类型sotr替换模板类中的T

class IC <sotr> sic;

这里,类名为IC <sotr>,而不是IC,IC<sotr>被称为模板具体化

在这种情况下,编译器将使用通用模板生成一个int具体化——CI<int>,虽然尚未请求这个类的对象

可提供显式具体化——覆盖模板类定义的具体类声明,假如模板类不能很好的处理一个字符数组时,我们可以显式具体化一个

方法时template <>打头(就是语法),,然后是模板类名称,在加上尖括号(其中包含要具体化的类型)。

template <> class IC(char *)
{
    // T variable_name;
    char * str;
public:
    IC(const char * s) : str(s) {}
    ...
}

 这样,就可以获得专门处理字符数组的模板,而不是再用通用模板类方法,和模板函数的具体化也很像。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值