c++---模板

c++—目录索引(知识小渠道)


模板是泛型编程的基础,不支持分离编译(定义和声明只能在一个文件中)
 泛型编程就是编写与类型无关的逻辑代码,是一种复用的方式
编译器调用模板参数函数时,编译器会根据传递的参数自动推演出模板的形参的类型,自动生成对应代码。
模板的分离编译
模板实现在一个.cpp文件,模板的调用在main函数的.cpp文件,main函数去其他.cpp找模板实现,但实现函数的.cpp因为没有在文件内调用并不会向.o文件生成代码,因此会找不到,造成报错

模板函数

template<class 形参名1,class 形参名2...,形参名n>
//关键词只能是class、typename
//不同的形参名表示不同的模板类型,当调用函数时,实参类型相同,那模板只需定义一个形参名
  • 模板函数:模板内具体类型由编译器推演,根据传递的参数自动推演出模板类型,并自动生成对应代码,且只有在生成对象的时候,编译器才会编译,否则,只对函数的外壳进行检查
  • 使用模板函数并不会减少最终可执行程序的大小,因为在调用模板函数时,编译器都根据调用时的参数类型进行隐式实例化,只不过使用模板函数可以减小代码的复杂度,便于修改
  • 模板函数的调用方式:没有显示实例化时,一般函数调用默认模板函数;显示实例化后,会调用最匹配的模板函数,不再调用默认的模板函数;若找不到匹配的模板函数,则会调用默认模板函数
  • 模板函数和模板类,非类型模板类和模板函数都适用的特性:它们的模板参数都支持设置参数缺省值,如果模板参数都使用缺省值的haul,则可以不用显示实例化。如果设置默认缺省值且显示实例化的话,会使用显示实例化内的模板参数而不是模板参数的默认缺省参数。它们不必遵循普通类函数的从右向左定义规则,模板参数应尽量少使用默认参数,显示实例化后使用模板可能会使模板类或函数相同,造成调用不明确
template<typename T1,typename T2>
//template<typename T1,typename T2>,如果是这样,会报错:未找到匹配的重载函数和未能为T2推导模板参数
bool IsEqual(const T1& a,const T2& b)
{
    return a==b;
}
int main()
{
    cout<<IsEqual(1,1)<<end1;
    cout<<IsEqual(1,2)<<end1;
    cout<<IsEqual("asdf","asdfg")<<end1;
    cout<<IsEqual(1.1111,2.2222)<<end1;
    cout<<IsEqual(1.1111,1)<<end1;
}

模板函数,用几个类型就定义几个模板类型,少用也不行。也可以直接进行显示实例化处理,此刻不在依赖模板函数类型,每定义一个模板函数,都要在前面定义一次自己的模板,各个模板函数直接不能通用模板

  • 模板函数之间的重载
    重载函数之间的选择顺序:
       1.普通函数,如果类型匹配,优先选中,重载解析结束
       2.如果没有普通函数匹配,那么所有的基础函数模板进入候选,编译器开始平等挑选,类型最匹配的,则被选中,注意,此时才会进入第三步继续筛选;
       3.如果第二步里面选中了一个模板基础函数,则查找这个模板基础函数是否有全特化版本,如果有且类型匹配,则选中全特化版本,重载解析结束,否则使用第二里面选中的模板函数,重载解析依然结束
       4.如果第二步里面没有选中任何函数基础模板,那么匹配失败,编译器会报错,程序员需要检查下代码
void Test(const int a,const int b)
{
    cout<<"普通函数"<<end1;
}
template<class T>
void Test(const T&a,const T&b)
{
    cout<<"一个模板类型"<<end1;
}
template<class T1,class T2>
void Test(const T1&a,const T2&b)
{
    cout<<"两个模板函数"<<end1;
}
int main()
{
    Test(10,'a');//两个
    Test('a',10);//两个
    Test(10,20);//普通
    Test<int>(100,200);//一个
    Test('a','b');//一个
    return 0;
}

模板类

只能显示实例化,系统不会推演,类内将成员定义为模板参数类型
类名<类型>对象名,此时对象类型不再是类名,是类名<类型>

template<class T>
class Person
{
public:
    T* a;
    int b;
};
int main()
{
    Person<char> p;//显示实例化
    p.a = "phonycat";
    cout<<p.a<<end1;
}

上例如果类内不使用模板类型指明类内成员的类型,不显示初始化对象的话,会报错:

  • 对象p大小不明确
  • Person类没有合适的默认构造函数
  • Person类使用类模板需要模板参数列表
  • 当深拷贝内置类型(int double等),应使用memcpy函数,效率较高
  • 当深拷贝自定义类型(string等),应使用fro+operate=,如果用memcpy拷贝时,指针指向会出现错误

容器适配器

在计算机编程体系中就相当于两个不兼容的接口间的桥梁,这样就能让一个接口的内容通过适配器转换,然后从另一个接口的得到我们能用的内容
- 模板参数

template<typename T>
class SeqList
{
public:
    void PushFront()
    {
        cout<<"PushFront"<<end1;
    }
private:
    int _size;
    int _capacity;
    T* _data;
};
//Stack内对SeqList做的处理,我就认为是适配的适配行为
template<class T,class Container>
class Stack
{
public:
    void Push(const T& x)
    {
            _con.PushFront();//此处调用的就是SeqList类的PushBack函数
    }
private:
    Container _con;//模板类类型定义类对象,形同SeqList<int> _con;
};
void Test()
{
    Stack<int ,SeqList<int>> s1;//显示实例化,SeqList<int>是模板类类型
    //Stack<int ,SeqList<char>> s2;
    //如果用char显示初始化,则插入数据值超过char的取值范围显示会出错
    s1.Push(1);
}
int main()
{
    Test();
}
  • 模板的模板参数
template<typename T>
class SeqList
{
public:
    void PushFront()
    {
        cout<<"PushFront"<<end1;
    }
private:    
    int _size;
    int _capacity;
    T* _data;
};
template<class T,template<class>class Container>//模板的模板参数
class Stack
{
public:
    void Push(const T& x)
    {
        _con.PushFront();
    }
private:
    Container<T> _con;//此处_con是一个模板类型的模板类的对象
};
void Test()
{
    Stack<int,SeqList> s1;
    s1.Push(1);
}

非类型模板参数

不是一个类型,是一个固定类型的常量,模板类和模板函数都可以用非类型做形参

  • 非类型形参的局限
     1.浮点数不可以作为非类型形参,包括float,double
     2.类不可以作为非类型形参
     3.字符串不可以作为非类型形参
     4.只有整形,指针,引用可以作为非类型形参,而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果

  • 非类型的类模板参数

template<class T,size_t N>
class Stuent
{
public:
    T _name[N];
};
int main()
{
    Student<char,10> s1;
    Student<char ,100> s2;
}
  • 非类型的模板函数参数
template<size_t N=10>
bool IsEqual(size_t a)
{
    return a==N;
}
int main()
{
    cout<<IsEqual<100>(100)<<end1;//尽管模板参数是10,但依据实例化的模板参数进行传值
    return 0;
}

特化

  • 特化是在模板类和模板函数的基础上进行模板化,因此要先定义一个主模板再对其进行特化。特化是为了更好的处理一件事,不再自动让编译器选择,而是指定用哪个

  • 模板特化分为:全特化和偏特化
    特化对象就是模板类和模板函数,注意:模板函数只能全特化
    全特化:就是对全部模板参数进行解析
    偏特化,特化部分模板参数

  • 为啥模板函数不能偏特化?
    因为已经有了函数重载,再去进行偏特化就显得不必要了,所以规定c++不支持函数偏特化,函数模板的全特化不参与重载,并且优先级低于函数基础模板参与匹配。原因是:c++标准委员会认为如果因为程序员随意写了一个函数模板的全特化版本,而使得原先的重载函数模板匹配结果发生改变(也就是改变了约定的重载解析规则)是不能接受的

  • 全特化和偏特化

template<class T1,class T2>
class Test//普通模板类
{};

template<>
class Test<int,double>//全特化类
{
public:
    Test()
    {
        cout<<"全特化"<<end1;
    }
};

template<class T1>
class Test<T1,char>//偏特化类
{
public:
    Test()
    {
        cout<<"偏特化"<<end1;
    }
};

template<class T1,class T2>
int IsEqual<T1 a,T2 b>//普通模板函数
{};

template<>
int IsEqual<int,int>(int a,int b)//全特化模板函数
{
    cout<<"全特化模板函数"<<end1;
    return a==b;
}
int main()
{
    Test<int,double> t1;
    Test<int,char> t2;
    cout<<IsEqual<int,int>(10,10)<<end1;
}

如果有什么不对的地方,可以评论告诉我,望指导!

猜你喜欢

转载自blog.csdn.net/phonycat/article/details/80294528