C++之模板

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HaloTrriger/article/details/80299033

模板是泛型编程的基础,不支持分离编译(定义和声明只能在一个文件中)。

模板实现在一个.cpp文件,模板的调用在main函数的.cpp文件,main函数去其他.cpp找模板实现代码,但实现函数的.cpp因为没有在文件内调用并不会向.o文件生成代码,因此会找不到,造成报错。

泛型编程就是编写与类型无关的逻辑代码,是一种复用的方式。

一、模板分为模板函数和模板类

模板格式:template

①模板函数:模板内具体类型由编译器推演,根据传递的参数自动推演出模板类型,并自动生成对应代码。且只有在生成对象,调用函数才会编译。否则,只对函数的外壳进行检查。

==注意:使用模板函数并不会减少最终可执行程序的大小,因为在调用模板函数时,编译器都根据调用时的参数类型进行了隐式实例化,只不过使用模板函数可以减小代码的复杂度,也便于修改。==

②模板函数调用方式:

没有显示实例化时,一般函数调用默认模板函数;
显示实例化后,会调用最匹配的模板函数,不再调用默认的模板函数。
若找不到匹配的模板函数,则会调用默认模板函数。
模板函数和模板类,非类型模板类和模板函数都适用的特性,后面不再赘述
它们的模板参数都支持设置参数缺省值,如果模板参数都使用缺省值的话,则可以不用显示实例化。
如果设置默认缺省值且显示实例化的话,会使用显示实例化内的模板参数而不是模板参数的默认缺省参数。 
它们不必遵循普通类函数的从右向左定义规则
模板参数应尽量少使用默认参数,显示实例化后使用模板可能会使模板类或函数相同,造成调用不明确。
template<typename T1, typename T2>

bool IsEqual(const T1& a, const T2& b)
{
    return a == b;
}

int main()
{
    cout << IsEqual(1, 1) << endl;
    cout << IsEqual(1, 2) << endl;
    cout << IsEqual("asdas", "sdad") << endl;
    cout << IsEqual(1.222, 2) << endl;

}

上例,若函数模板改为

bool IsEqual(const T1& a, const T1& b)
则会报错“IsEqual”: 未找到匹配的重载函数和“bool IsEqual(const T1 &,const T1 &)”:未能为“T2”推导 模板 参数

模板函数,用几个类型就定义几个模板类型,少用也不行。也可以直接进行显示实例化处理,此刻不在依赖模板函数类型,而是依靠显示实例化的类型传递类型。

每定义一个模板函数,都要在前面定义一次自己的模板,各个模板函数之间不能通用模板。

③模板函数之间的重载

==重载函数之间的选择顺序==

  1. 普通函数,如果类型匹配,优先选中,重载解析结束;
  2. 如果没有普通函数匹配,那么所有的基础函数模版进入候选,编译器开始平等挑选,类型最匹配的,则被选中,注意,此时才会进入第(3)步继续筛选;
  3. 如果第(2)步里面选中了一个模版基础函数,则查找这个模版基础函数是否有全特化版本,如果有且类型匹配,则选中全特化版本,重载解析结束,否则使用(2)里面选中的模版函数,重载解析依然结束。
  4. 如果第(2)步里面没有选中任何函数基础模版,那么匹配失败,编译器会报错,程序员需要检查下代码。
     
void Test(const int a, const int b)
{
    cout << "普通函数的Test " << endl;
}

template<class T>
void Test(const T& a, const T& b)
{
    cout << "一个模板类型的Test " << endl;
}

//造成Test<int>(100, 200);调用不明确
//template<class T=char>
//void Test(int a, const T& b)
//{
//  cout << "一个模板类型的Test " << endl;
//}

template<class T1, class T2>
void Test(const T1& a, const T2& b)
{
    cout << "两个模板函数的Test " << endl;
}

int main()
{
    Test(10, 'a');//两个
    Test('a', 10);//两个
    Test(100, 200);//普通
    Test<int>(100, 200);//一个
    Test('a', 'b');//一个

    return 0;
}

==符合第3条规则的例子还没想出来==。

二、模板类

只能显示实例化,系统不会推演。类内将成员定义为模板参数类型。
注意:类名<类型>对象名,此时对象类型不再是类名,而是类名<类型>。
template<class T>
class Person
{
public:
    T* a;
    int b;
    int c;
};

int main()
{
    Person<char> p;
    p.a = "wùwùde";
    cout << p.a << endl;
}

模板的定义可能关联了类内的函数,上例如果类内不使用模板类型指明类内成员的类型,不显示初始化对象的话,会报错。如下:\
==第一个:对象p大小不明确。\
第二个:Person类没有合适的默认构造函数。\
第三个:Person类使用类 模板需要模板 参数列表。\
当深拷贝内置类型(int,double等)值,应用memcpy函数,效率较高;当深拷贝自定义类型(string等)值,应用for+operate= ,如果用memcpy拷贝时,指针指向会出现错误。==

使用memcpy进行深拷贝

三、容器适配器

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

Vector在C++中就是一个容器,通过适配器,我们就能让Vector实现栈,队列,这样我们就不需用数据结构去新写栈和队列了。

①模板参数

template <typename T> 
class SeqList 
{
public:
    void PushFront()
    {
        cout << "PushFront" << endl;
    }
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>> s3 ;如果用char显示初始化,则插入数据值超过char的取值范围显示会出错
    s1.Push(1);
}

int main()
{
    Test();
}

模板参数适配器

②模板模板的参数

template <typename T>
class SeqList 
{ 
public: 
    void PushFront()
    {
        cout << "PushFront" << endl;
    }
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);
}

int main()
{
    Test();
}

模板模板参数适配器

四、非类型

字面意思,不是一个类型,是一个固定类型的常量。
模板类和模板函数都可以用非类型做形参。
当然只有整形,指针和引用才能作为非类型形参,而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。

①非类型形参的局限

  1. 浮点数不可以作为非类型形参,包括float,double。
  2. 类不可以作为非类型形参。
  3. 字符串不可以作为非类型形参
  4. 整形,可转化为整形的类型都可以作为形参,比如int,char,long,unsigned,bool,short(enum声明的内部数据可以作为实参传递给int,但是一般不能当形参)
  5. 指向对象或函数的指针与引用(左值引用)可以作为形参

②非类型的类模板参数

//N是一个常量
template<class T, size_t N>
class Student
{
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) << endl;//尽管模板参数N=10,但依然根据实例化的模板参数进行传值

    return 0;
}

五、特化

①为什么要特化?

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

②模板特化分为:

全特化和偏特化,特化对象就是类模板和函数模板,但注意的是函数模板只能全特化。

③为什么模板函数不能偏特化?

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

④全特化和偏(半)特化

全特化,就是对全部模板参数进行特化。
偏特化,特化部分模板参数
template<class T1, class T2>
class Test {};

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

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

template<class T1,class T2>
int IsEqual(T1 a, T2 b){}

template<>
int IsEqual<int ,int>(int a, int b)
{
    cout << "全特化模板函数" << endl;
    return a == b;

}

int main() 
{ 
    Test<int ,double> t1;
    Test<int, char> t2;
    cout << IsEqual<int,int>(10, 10) << endl;;
}

⑤类型萃取

根据类型不同萃取不同的实现方法,提高程序效率。
就比如,我们要对内置类型进行拷贝时,使用memcyp函数进行拷贝效率最高。但遇到了自定义类型,如果使用memcpy进行拷贝的话,对象内有指针,进行memcpy后原对象释放后会造成现对象访问错误。因此,遇到这种新情况一般会使用for + operate= 进行拷贝。
这时候,为了根据传入对象类型,选择合适的拷贝方法,这就是类型萃取的思想。

六、模板声明中typename和class的小区分

在模板中,模板参数用class和typename来修饰,但有些情况只能用typename

template<typename A>
class Attribute
{
    A::B s;
    int *name = s;
};
s是A::B类型,依赖A::B,s被称为从属名称。
name是内置类型定义的,不依赖模板参数,称为非从属名称。
这里A::B被称为嵌套从属类型(B嵌套于A,从属于模板参数A),编译器看到A::B时不懂B是A的内部类型还是成员,因此默认其不是类型。而此时只需在A::B前加入typename即可。

总结一点就是遇到从属类型,前面要加typename。
当然要除下情况

  1. 在类定义的基类列表中出现的嵌套从属类型之前,不能写typename。
    2.在成员初值列表中,不能使用typename。
struct Person{};

template<class T>
class Student
{
public:
    T* name;
    typedef Person _P;
};

class Test:public  Student<char>::_P//不可以
{
public:
    Test(int x) :  Student<char>::_P()//不可以
    {
        typename Student<char>::_P s;//可以
    }
};

猜你喜欢

转载自blog.csdn.net/HaloTrriger/article/details/80299033