【C++技能树】一文看懂模板匹配

在这里插入图片描述
Halo,这里是Ppeua。平时主要更新C++,数据结构算法,Linux与ROS…感兴趣就关注我bua!

在这里插入图片描述

0.泛型编程

假设有下面这个场景:

需要获得一个swap函数可以交换整形.

void swap(int &a,int &b)
{
    
    
    int tmp=a;
    a=b;
    b=tmp;
}

如果这时候我有需要一个能够交换浮点型与浮点型、自定义类型与自定义类型…等函数呢

这些需求有一个共同点,他们的整体逻辑是一样的,但仅因为类型的改变就需要重构一份代码.这显然是效率低下不可取的.

所以C++引入了一个新的特性:泛型编程.

可以想象为:钢厂里需要制造钢具,他们的模子是一样的,但是颜色不同.所以我们可以 根据需求将相同的模子刷上不同的颜色

这里的模子就是代码逻辑,颜色就是类型.

1.模板

模板提供了多样的类型,这是泛型编程的基础.

template<class T>
// template<typename T>
code......

模板基础语法如上,其中class与typename没有什么大的差别.T为自定义名称,通常习惯为T(就如常量大写一样)

该模板的作用域是紧贴的那一段code(class或者function)

经过实例化之后,每一个函数都是不同的函数,每一个类也是不同的类,即使他们逻辑相同

将通过两个方面来解释模板:

  1. 函数模板

  2. 类模板

    a132a655bef4a4d4e93ec1857d03739

2 函数模板:

还是上面swap的例子.利用模板的方法是这么写:

template<class t>
void Swap(t t1,t t2)
{
    
    
    t tmp;
    tmp=t1;
    t1=t2;
    t2=tmp;
    
}
int main()
{
    
    
    int a=0;
    int b=2;
    Swap(a,b); 
}

此时就可以通过int去交换.

image-20230809160755635

可以直接将t看做成一个类如int double…等.所以正常写法上出现类型的地方都可以用t去代替

上文为隐式实例化:就是由编译器去自动推导需要刻画一个什么样的模板

但我们也可以自己去告诉编译器我们需要一个什么样的模板.也就是显式实例化

Swap<int>(a,b);

通常情况下,我们用隐式实例化即可.但如果有下面这样的函数,我们就需要显式实例化

T* Alloc(int n=10)
{
    
    
    return new T[n];
}
int main()
{
    
    
    auto array=Alloc<int>(2); 
}

该函数作用为创建一段默认为10大小的数组空间并返回.

这时候如果我们不告诉编译器我们需要什么样的数组,它又怎么会知道呢?

这就是显式实例化的意义所在

  1. 当调用时有一个函数与模板函数同名时,会优先去查找这个函数是否满足要求.若满足要求则会优先调用已有的函数,而不是用模板去刻画一个函数.

    int Add(int left, int right)
    {
          
          
     return left + right;
    }
    
    template<class T>
    T Add(T left, T right)
    {
          
          
     return left + right;
    }
    void Test()
    {
          
          
     Add(1, 2); 
     Add<int>(1, 2); 
    }
    

    输出结果为

image-20230808213538903

  1. 函数模板不可自动类型转换,但普通函数可以

这里可以理解为:函数模板会去适配参数类型,而普通函数需要参数类型去适配普通函数

2.1函数模板的特化

如果我想要设计一个函数根据不同的传入对象做不同的事情.就需要用到函数的特化这一概念

函数特化的步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
template<class T>
bool Less(T left,T right)
{
    
    
    return left<right;
}

template <>
bool Less<int*>(int *left,int *right)
{
    
    
    return *left<*right;
}

但这样写还不如直接函数重载,所以实用性不大

3. 类模板

这里放上之前提到过的Vector部分源代码演示,想要进一步了解vector的可以看这篇文章:vector的理解与使用

#pragma once
#include<iostream>
namespace H
{
    
    
    template<class T>
    class vector {
    
    
    public:
        typedef T* iterator;
        typedef const T* const_iterator;
        vector() 
        {
    
    
        }
        vector(size_t n, const T& val = T())
        {
    
    
            resize(n, val);
        }
        vector(int n, const T& val = T())
        {
    
    
            resize(n, val);
        }
        template<class InputIterator>
        vector(InputIterator begin, InputIterator end)
        {
    
    
            while (begin != end)
            {
    
    
                push_back(*begin);
                begin++;
            }
        }
        vector(const vector<T>& v)
        {
    
    
            _start = new T[v.capacity()];
            for (size_t i = 0; i < v.size(); i++)
            {
    
    
                _start[i] = v._start[i];
            }
            //_finish = v._finish; 只是令地址相等
            _finish = _start + v.size();
            _endofstorage = _start + v.capacity();
        }
        vector<T>& operator= (const vector<T>& v)
        {
    
    
            swap(v);
            return *this;
        }
        ~vector()
        {
    
    
            if (_start)
            {
    
    
                delete[]_start;
                _start = _finish = _endofstorage = nullptr;
            }
        }
        iterator begin()
        {
    
    
            return _start;
        }
        iterator end()
        {
    
    
            return _finish;
        }
        const_iterator begin()const
        {
    
    
            return _start;
        }
        const_iterator end()const
        {
    
    
            return _finish;
        }
        size_t capacity()const
        {
    
    
            return _endofstorage - _start;
        }
        size_t size()const
        {
    
    
            return _finish - _start;
        }
        void resize(size_t n, const T& val)
        {
    
    
            if (n < size())
            {
    
    
                _finish = _start + n;
            }
            else
            {
    
    
                reserve(n);
                while (_finish != _start + n)
                {
    
    
                    *_finish = val;
                    _finish++;
                }
            }
        }
        void reserve(size_t n)
        {
    
    
            if (n > capacity())
            {
    
    
                size_t sz = size();
                T* tmp = new T[n];
                if (_start)
                {
    
    
                    memcpy(tmp, _start, sizeof(T) * size());
                    delete[] _start;
                }
                _start = tmp;
                _finish = _start + sz;
                _endofstorage = _start + n;
            }
        }
        void push_back(const T& x)
        {
    
    
            if (_finish == _endofstorage)
            {
    
    
                reserve(capacity() == 0 ? 1 : capacity() * 2);
            }
            *_finish = x;
            _finish++;
        }
        void swap(vector<T>v)
        {
    
    
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstorage, v._endofstorage);
        }
        iterator insert(iterator pos, const T& val)
        {
    
    
            assert(pos < _finish&& pos >= _start);
            if (_finish == _endofstorage)
            {
    
    
                size_t newpos = pos - _start;
                reserve(capacity() == 0 ? 1 : capacity() * 2);
                pos = _start + newpos;
            }
            iterator end = _finish - 1;
            while (end >= pos)
            {
    
    
                *(end + 1) = end;
                end--;
            }
            *pos = val;
            ++_finish;
            return pos;

        }
        iterator erase(iterator pos)
        {
    
    
            assert(pos < _finish&& pos >= _start);
            iterator it = pos + 1;
            while (it != _finish)
            {
    
    
                *(it - 1) = *it;
                it++;
            }
            _finish;
            return pos;
        }
        T& operator[](size_t pos)
        {
    
    
            assert(pos < size());

            return _start[pos];
        }

        const T& operator[](size_t pos) const
        {
    
    
            assert(pos < size());

            return _start[pos];
        }
        void pop_back()
        {
    
    
            erase(--end());
        }
    private:
        iterator _start = nullptr;
        iterator _finish = nullptr;
        iterator _endofstorage = nullptr;


    };

}

这就是类代码的实例化,与函数模板的实例化大差不差,但是调用的时候需要显示实例化,这也很好理解,你不传参数他并不知道需要创造出一个什么样的类.

vector<int>v;

用到模板时:类名不再是类型,类型为vector,若实例化之后T为具体的类型

因为存在静态成员变量,所以使用泛型编程的时候,遇到使用模板参数去申明变量的时候.前面需要先申明该参数为类型而不是变量

template <class Container>
void Print(Container&v)
{
    
    
    //成分不明确 静态类型对象与类型 
    //typename 明确告诉编译器 这里是类型的模板实例化
    typename Container::const_iterator it=v.begin();
    //auto it=v.begin() auto为类型所以不需要加typename
    //vector<int>::const_iterator
    while(it!=v.end())
    {
    
    
        cout<<*it<<endl;
        it++;
    }
    cout<<endl;
}

例如假设Data类中有一个st静态成员变量.进行赋值的时候是

Data::st=10;

而假设Data类中有一个st类型需要定义时是这样的

Data::st stt=10;

这两明显意义不同但编译阶段容易被编译器混淆.所以需要加上typename关键字

typename Data::st stt=10;

上面的Data在泛型编程中,被替换为模板关键字,用来表示类的类型.

3.1 非类型模板参数

我们有时候想要动态的创建一个栈(若这个栈不扩容),所以需要我们在实例化的时候动态的给定一个大小.这时候就需要一个非类型模板参数来传递这个数据.

template <class T,size_t N=10>
class Stack{
    
    
public:
    
    T a[N];
    int _top;
    int capacity=N;
};

传入参数通过这样传入:

Stack<int,20> st20;

但注意,非类型模板参数只支持整形

这也很好理解,难道创建数组空间的大小可以为浮点型嘛.

3.2 类的模板刻画

与函数相同,刻画时仍然需要有原始类.

template<class T1,class T2>
class Date{
    
    
public:
    void print()
    {
    
    
        cout<<"Date<T1,T2>"<<endl;
    }
private:
    T1 a1=0;
    T2 a2=0;
};

3.2.1 全特化

顾名思义:是将全部的的函数参数都进行特化

template<>
class Date<int,double>
{
    
    
public:
    void print()
    {
    
    
        cout<<"Date<int,double>"<<endl;
    }
private:
    int a1=0;
    double a2=0;
};

3.2.2 偏特化

部分参数仍然使用模板,部分参数特化

template<class T>
class Date<T,double>
{
    
    
public:
    void print()
    {
    
    
        cout<<"Date<int,double>"<<endl;
    }
private:
    T a1=0;
    double a2=0;
};

猜你喜欢

转载自blog.csdn.net/qq_62839589/article/details/132190706