C++模板:template

发布时间 2023-08-30 22:26:21作者: 无夜千幕雪

1.引子

类的继承和包含并不总是能够满足重用代码的需要。

比如在一些类当中,仅有其中几个成员的类型发生变化,其他的部分都完全一样,因此我们需要为这样细小的不同而创建好几个这样的类。例如下面这段代码,两个test类当中的成员类型不同,因此可选的做法便是创建两个类。

class test1 {
    int m_member;
};
class test2 {
    double m_member;
};

这中做法会带来一个严重的问题:假如需求发生变化,这个变量类型再次发生改变,我们还需重新增加一个类。

C++的类模板(template)提供了一种更好的方法,模板提供参数化类型,能够将类型名作为参数传递到类当中,并以此来建立新的类或者函数。

template<typename T>
class test1 {
    T m_member;
};

2.函数模板

先介绍一下函数模板,函数也可以使用模板,格式方法和类模板一样。

举个例子,使用模板类来创建比较大小的函数max,再分别使用不同类型的输入。

template<typename T>
const T& max(const T& a,const T& b) {
    return a > b ? a : b;
}
class Test {

};

int main() {
    cout << max<int>(3, 2) << endl;        //1.显式转化和隐式转化
    cout << max('a', 'e') << endl;    //相当于max<char>
    //cout << max<float>(3.314, 1.2) << endl;    //2.输入类型为double,与模板函数不匹配
    //Test t1, t2;
    //cout << max(t1, t2) << endl;    // 3.Test类当中未重载<和>操作符,因此比较大小出错
    //cout << max("aaa","zzz") << endl;    //4.比较值不正确,aaa应当小于zzz,应输出zzz,实际输出aaa
    return 0;
}

2.1.模板函数可以显式也可以隐式使用

可以不写类型,调用模板函数时,系统会自动地填入对应的类型进行转化。

2.2.函数参数和模板参数必须保持一致

程序默认的小数是double类型,调用模板函数时使用float就会导致类型不一致,程序无法编译通过。

当然,如果输入类型是可以转化的情况下,也不会报错。

max<int>('a', 3);

2.3.模板输入的类型,必须能够支持模板函数运行

例如Test类当中不存在比较大小的重载,因此如果对Test进行比大小的话,系统就不支持这种操作。解决方案只能是更改当前类当中的成员或函数,以支持模板函数的运行,例如:

class Test {
public:
    friend bool operator>(const Test& t1,const Test& t2) {
        return true;
    }
    friend bool operator<(const Test& t1,const Test& t2) {
        return false;
    }
};

需要注意的是,必须要以友元的方式重载<和>符号,否则const Test& a就无法调用比较符号,会提示无法找到const T的左值运算符。

2.5.模板的参数不应当是指针

传入指针到模板类当中,很容易导致无法对指针进行赋值,从而产生内存问题,通常都不建议使用指针,对于模板类也同样适用。

3.类模板

类当中包含有函数,因此类模板要稍微复杂一些,有以下几点需要注意

3.1.类模板参数在整个类当中都能使用

例如下面这个例子,生成一个简单的栈,类型T在类当中都可以使用。

3.3.模板嵌套

模板作为成员时可以进行嵌套,例如下面这个例子,创建一个栈,这个栈的每一个成员都是一个向量,向量本身包含int元素。可以使用typename A = B<T>的方式进行嵌套,同时在使用时也输入对应的模板参数。

template<typename T>
class Stack {
public:
    explicit Stack(int maxSize = 10);
    ~Stack();

    void Push(const T& elem);
    void Pop();
    const T& Top() const;
    bool isEmpty() const;
private:
    T* elems_;
    int maxSize_;
    int top_;
};

template<typename T>
Stack<T>::Stack(int maxSize)
    :maxSize_(maxSize),
    top_(-1)
{
    elems_ = new T[maxSize_];
}
template<typename T>
Stack<T>::~Stack()
{
    delete[] elems_;
}

template<typename T>
void Stack<T>::Push(const T& elem) {
    if (top_ + 1 >= maxSize_)
        throw out_of_range("Stack<>::Push() stack full");

    elems_[++top_] = elem;
}

template<typename T>
void Stack<T>::Pop() {
    if (top_ + 1 <= 0)
        throw out_of_range("Stack<>::Pop() stack empty");
    --top_;
}

template<typename T>
const T& Stack<T>::Top() const {
    if (top_ + 1 == 0)
        throw out_of_range("Stack<>::Top() stack empty");

    return elems_[top_];;
}

template<typename T>
bool Stack<T>::isEmpty() const {
    return top_ + 1 == 0;
}
int main() {
    Stack<int> s;

    for (int i = 0; i < 5; ++i) {
        s.Push(i + 1);
    }

    while (!s.isEmpty()) {
        cout << s.Top() << endl;
        s.Pop();
    }
    return 0;
}

3.2.类模板可以传递参数

同理,传入的模板参数也可以在整个模板类当中使用,例如在构造时,自动将大小传递给maxSize。

template<typename T,int SIZE>
class Test{
public:
    explicit Stack(int maxSize = SIZE);
    ~Stack();

    void Push(const T& elem);
    void Pop();
    bool isEmpty() const;
private:
    T* elems_;
    int maxSize_;
    int top_;
};

3.3.模板嵌套

模板作为成员时可以进行嵌套,例如下面这个例子,创建一个栈,这个栈的每一个成员都是一个向量,向量本身包含int元素。可以使用typename A = B<T>的方式进行嵌套,同时在使用时也输入对应的模板参数。

template<typename T,typename CONT = deque<T> >
class Stack{
public:
    Stack()
    : c_()
    {
    }

    ~Stack3() {}

    void Push(const T& elem) {
        c_.push_back(elem);
    }

    void Pop() {
        c_.pop_back();
    }

    T& Top() {
        return c_.back();
    }

    const T& Top() const {
        return c_.back();
    }

    bool isEmpty() const {
        return c_.empty();
    }
private:
    CONT c_;
};


Stack<int, vector<int> > s

3.4.成员模板

在类模板的使用当中,不是所有的时候只用类的模板就可以达成效果的,例如下面这个例子,对模板类进行类型转换,不允许直接将int类型的模板类转化为double类型的模板类。

template<typename T>
class MmemberTemplate{
private:
    T value_;
public:
    void Assign(const MmemberTemplate<T>& x) {
        value_ = x.GetValue();
    }
    T GetValue() const {
        return value_;
    }
};
int main(){
    MmemberTemplate<double> d1;
    MmemberTemplate<int> i1;
    d1.Assign(d1);    // ok 
    //d1.Assign(i1);    // 不允许不同类型的转换
    return 0;
}

此时,可以使用成员模板,即便是类的成员,其也可以是模板函数。利用成员模板的特性,就可以解决不同模板之间赋值转换的问题。