2.模板

模板:

模板的意义:对类型进行参数化

函数模板

template<typename T>
bool compare(T a,T b)
{
    cout<<"Template compare"<<endl;
    return a>b;
}
int main()
{
    compare<int>(10,20);
    //在函数的调用点,编译器用用户指定的类型,从原模版中实例化一份函数代码出来
    //compare(int a,int b)
    compare<double>(10.3,30.1);
}

模板的实参推演:

可以更具用户传入的实参类型,来推导出模板类型的参数

template<typename T>
bool compare(T a,T b)
{
    cout<<"Template compare"<<endl;
    return a>b;
}
int main()
{
    compare(10,20);           //未传入类型参数,编译器会进行实参推演,得到具体类型
    compare(10.3,30.1);       
    compare("alsdjflaf","asodf");  
    //自动类型推导为 const char* 这是没有意义的,因为两个指针比较大小实际是两个字符串的地址比大小
}

模板不参与编译:

模板不参与编译,因为没有办法编译,不知道具体类型,无法开辟正确的空间
但是,模板一旦实例化或推演,就会在符号表中产生相应的符号,对应的函数也就生成了

问题情况:
template<typename T>
bool compare(T a,T b)
{
    cout<<"Template compare"<<endl;
    return a>b;
}
int main()
{
    compare(10,20.1);
}

这样就会编译错误:因为,在进行类型推导的时候,第一次将T推导为int类型,第二次将T推到为double类型,引发二义性,导致错误

模板的特例化:

template<typename T>
bool compare(T a,T b)
{
    cout<<"Template compare"<<endl;
    return a>b;
}
int main()
{
    compare("alsdjflaf","asodf");  
    //自动类型推导为 const char* 这是没有意义的,因为两个指针比较大小实际是两个字符串的地址比大小
}

对于某些类型,依赖编译器默认实例化的模板代码逻辑是错误的(如上代码)。这时就要为模板提供特例化了。(不是编译器提供而是开发者提供)

template<typename T,typename E>
bool compare(T a,E b)
{
    cout<<"Template compare"<<endl;
    return a>b;
}
template<>    //一定要加,代表是模板函数
bool compare(const char* a,const char* b)
{
    cout<<"特例化"<<endl;
    return  srtcmp(a,b);
}
int main()
{
    compare("abcd","abcd");
}

特例化实际就是自实现

template<typename T,typename E>
bool compare(T a,E b)
{
    cout<<"Template compare"<<endl;
    return a>b;
}
template<>
bool compare(const char* a,const char* b)  //本质还是模板函数
{
    cout<<"特例化"<<endl;
    return  strcmp(a,b);
}

bool compare(const char* a,const char* b)  //就是一个普通函数
{
    cout<<"普通函数"<<endl;
    return  strcmp(a,b);
}
int main()
{
    compare("abcd","abcd");  //调用普通函数
    compare<const char*>("abc","def");  //调用模板函数
}

当特例化与函数的自实现同时存在时,优先调用普通函数

特例化与普通函数之间并不是重载的关系,因为不满足重载的定义(函数名相同),此例中模板函数的函数名为compare<const char*> 普通函数的函数名为compare

模板的非类型参数:

模板的非类型参数都是常量,即含有const属性,不可更改

template<typename T,const int SIZE>
void sort(T* arr)
{
    for(int i=0;i<SIZE;i++)
    {
        for(int j=0;j<SIZE-i-1;j++)
        {
            if(arr[j]<arr[j+1])
            {
                T tem = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tem;
            }
        }

    }
}
int main()
{
    int arr[] = {12,6,9,3,4,7,56};
    const int num = sizeof (arr)/sizeof (arr[0]);
    //传入模板的非类型参数,必须是 const类型
    sort<int,num>(arr);
    for(int i:arr){cout<<i<<"  ";}
}

非类型模板参数是有类型限制的。一般而言,它可以是常整数(包括enum枚举类型)或者指向外部链接对象的指针或引用

浮点数和类对象(class-type)不允许作为非类型模板参数

注意:

模板代码,不可以在一个文件中定义,在另一个文件中使用
因为,模板本身是不参加编译的,只有在实例化后才会编译。由于.cpp文件是分别进行编译之后在进行连接,但由于模板本身是不参与编译的使用也就不会产生对应的符号表,就导致链接错误

解决方法

  1. 将模板代码放在头文件中,在源文件中 include 包含 常用

  2. 可以将模板单独定义在一个文件中,但是要告诉编译器,进行模板指定类型的实例化,从而参与编译,生成对应的函数,即在模板文件中加上

    template bool compare<int>(int,int);
    

    在使用改模板的文件中,加上模板函数的声明

    template<typename T> bool compare(T a,T b);
    

    这种办法显然不好,因为不可能将所有可能的类型都枚举一编

类模板:

顺序栈的自实现

#include <iostream>
#include "string.h"
using namespace std;
template<typename T>
class SeqStack     //此时SeqStack是模板名,只有加上<T>才是类名
{
    SeqStack<T>(int size = 10)
            //模板是没有构造函数的,只有类才有构造函数,所以写成SeqStack<T>(),类的构造函数
            //由于这样看起来比较麻烦,所以c++规定,模板类的构造和析构函数可以不加<T>
        :top_(0)
        ,size_(size)
        ,pstack_(new T[size])
    {}
    ~SeqStack<T>()
    {
        delete[] pstack_;
        pstack_ = nullptr;
    }
    SeqStack(const SeqStack<T>&another)
        :top_(another.top_)
        ,size_(another.size_)
    {
        this->pstack_ = new T[size_];
        for(int i=0;i<top_;i++)
        {
            pstack_[i] = another.pstack_[i];
        }
    }
    SeqStack& operator=(const SeqStack<T>&another)
    {
        if(this == &another) return *this;  //防止自赋值
        top_ = another.top_;

    }
    void push(T val)
    {
        if(full()) expand();
        else
        {
            pstack_[top_] = val;
            ++top_;
        }
    }
    T pop()
    {
        top_--;
        return pstack_[top_];
    }
    bool full()
    {
        if(size_ == top_)
            return true;
        else return false;
    }
    bool empty()
    {
        if(top_ == 0)
            return true;
        else return false;
    }
private:
    T* pstack_;   //内存可增长
    int top_;
    int size_;
    void expand()  //顺序栈底层数组2倍的方式扩容
    {
        T* tem = new T[size_ * 2];
        for(int i=0;i<top_;i++)
        {
            tem[i] = pstack_[i];
        }
        pstack_ = tem;
        delete[] tem;
    }
};

stl::vector的自实现

class vector
{
public:
    vector<T>(int size = 10)
    {
        first_ = new T[size];
        last_  = first_;
        end_ = first_ + size + 1;
    }
    ~vector<T>()
    {
        delete[] first_;
        first_ = nullptr;
        last_ = nullptr;
        end_ = nullptr;
    }
    vector<T>(const vector<T>&another)
    {
        first_ = new T[end_ - first_];
        int len = another.last_ - another.first_;
        //len是有效元素的长度
        for(int i=0;i<len;i++)
        {
            first_[i] = another.first_[i];
        }
        last_ = first_ = len;
        end_ = first_ + (end - first_);
    }
    vector&operator=(const vector<T>&another)
    {
        if(this == &another)
            return *this;
        first_ = new T[end_ - first_];
        int len = another.last_ - another.first_;
        //len是有效元素的长度
        for(int i=0;i<len;i++)
        {
            first_[i] = another.first_[i];
        }
        last_ = first_ + len;
        end_ = first_ + (end - first_);
    }
    bool full() const
    {
        if(last_ == end_)
            return true;
        else
            return false;
    }
    bool empty() const
    {
        return !full();
    }
    void push_back(T val)
    {
        if(full())
            expand();
        *last_++ = val;  //++的优先级大于*解引用
    }
    void pop_back()
    {
        if (empty())
            return;
        last_--;
    }
    T back()  //返回容器末尾的元素值
    {
        return *(last_-1);
    }
private:
    T* first_;  //指向有效元素的第一个
    T* last_;   //指向最后一个有效元素的后继位置
    T* end_;    //指向数组空间的后继位置

    void expand()
    {
        int size = end_ - first_;
        T* tem = new T[size * 2];
        for(int i=0;i<size;i++)
        {
            tem[i] = first_[i];
        }
        first_ = tem;
        last_ = first_ + size;
        end_ = first_ + size * 2;
    }
};

这并不是STL中的实现,在STL中还有容器空间适配器allocator

理解容器空间适配器allovator

在之前自实现的vector有个很大的问题,就是内存的开辟与对象的构造时同步的,这极大的浪费了资源

需要把内存开辟与对象的构造分离开。需要把内存的释放和对象的析构分离开

template<typename T>
class Allocator
{
public:
    T* allocate(size_t size)  //负责内存开辟
    {
        return (T*) malloc(sizeof(T) * size);
    }
    void deallocate(void *p)  //负责内存释放
    {
        free(p);
    }
    void construct(T*p,const T&val)  //负责对象构造
    {
        new(p) T(val);  //定位new,在p的内存上调用拷贝构造
    }
    void destroy(T* p)
    {
        p->~T();
    }
};


template<typename T>
class vector
{
public:
    vector<T>(int size = 10)
    {
        first_ = allocator_.allocate(size);
        last_  = first_;
        end_ = first_ + size + 1;
    }
    ~vector<T>()
    {
        for(T* p=first_;p!=last_;p++)
        {
            allocator_.destroy(p);
        }
        allocator_.deallocate(first_);
        first_ = last_ = end_ = nullptr;
    }
    vector<T>(const vector<T>&another)
    {
        int len = another.last_ - another.first_;
        //len是有效元素的长度
        first_ = allocator_.allocate(len);
        for(int i=0;i<len;i++)
        {
            allocator_.construct(first_+i,another.first_[i]);
        }
        last_ = first_ = len;
        end_ = first_ + (end_ - first_);
    }
    vector&operator=(const vector<T>&another)
    {
        if(this == &another)
            return *this;
        for(T* p=first_;p!=last_;p++)
        {
            allocator_.destroy(p);
        }
        allocator_.deallocate(first_);
        int len = another.last_ - another.first_;
        //len是有效元素的长度
        first_ = allocator_.allocate(len);
        for(int i=0;i<len;i++)
        {
            allocator_.construct(first_+i,another.first_[i]);
        }
        last_ = first_ = len;
        end_ = first_ + (end_ - first_);
    }
    bool full() const
    {
        if(last_ == end_)
            return true;
        else
            return false;
    }
    bool empty() const
    {
        return !full();
    }
    void push_back(T val)
    {
        if(full())
            expand();
        allocator_.construct(last_,val);
    }
    void pop_back()
    {
        if (empty())
            return;
        else
        allocator_.destroy(--last_);
    }
    T back()  //返回容器末尾的元素值
    {
        return *(last_-1);
    }
private:
    T* first_;  //指向有效元素的第一个
    T* last_;   //指向最后一个有效元素的后继位置
    T* end_;    //指向数组空间的后继位置
    Allocator<T> allocator_;

    void expand()
    {
        int size = end_ - first_;
        T* tem = allocator_.allocate(size * 2);
        for(int i=0;i<size;i++)
        {
//            tem[i] = first_[i];
            allocator_.construct(tem+i, first_[i]);
        }
        for(int i=0;i<size;i++)
        {
            allocator_.destroy(first_ + i);
        }
        allocator_.deallocate(first_);
        first_ = tem;
        last_ = first_ + size;
        end_ = first_ + size * 2;
    }
};
posted @ 2022-10-18 20:18  satellite2002  阅读(25)  评论(0编辑  收藏  举报