C++ —— 模板

C++知识总结目录索引

本文目录:

一、模板的基本概念

1. 什么是模板

  模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。

  模板是泛型编程的基础。所谓泛型编程就是编写与类型无关的逻辑代码,是一种复用的方式。模版可以分为两类,一个是函数模版,另外一个是类模版。


2. 模板的分类

(1)模板函数

①书写格式

Template <class 形参名1, class 形参名2, 形参名n>
返回类型 函数名(形参表)
{
    // DoSomething();
}

  templateclass是关键字,其中class可以用typename关键字代替,此处typenameclass没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。
  
  模板内具体类型由编译器推演,根据传递的参数自动推演出模板类型,并自动生成对应代码。且只有在生成对象,调用函数才会编译。否则,只对函数的外壳进行检查。


②代码示例

template<class T>
bool IsEquals(T& a, T& b)
{
    return a == b;
}

int main()
{
    int a = 10; 
    int b = 20;
    cout << IsEquals(a, b)<< endl;

    char c1 = 'a';
    char c2 = 'b';
    cout << IsEquals(c1, c2) << endl;

    return 0;
}

③调用规则

  1. 没有显示实例化时,一般函数调用默认模板函数;

  2. 显示实例化后,会调用最匹配的模板函数,不再调用默认的模板函数。

  3. 若找不到匹配的模板函数,则会调用默认模板函数。

  4. 模板参数支持设置参数缺省值,如果模板参数都使用缺省值的话,则可以不用显示实例化。

  5. 如果设置默认缺省值且显示实例化的话,会使用显示实例化内的模板参数而不是模板参数的默认缺省参数。

  6. 模板参数不必遵循普通类函数的从右向左定义规则

  7. 模板参数应尽量少使用默认参数,显示实例化后使用模板可能会使模板类或函数相同,造成调用不明确。


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


(2)模板类

①书写格式

Template < class或者也可以用typename T >
class类名{

    // DoSomething();
};

  template是声明各模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个。


②代码示例

template <class T>
class A
{
public:
    A();
    ~A();
protected:
    T a;
};

template <class T>  //类外实现必须声明模板
A<T>::A()
{
    //DoSomething();
}

int main()
{
    A<int> a; //必须指定类内成员的类型,不然就不知道创建的对象a的大小。
    return 0;
}

  模板类只能显示实例化,系统不会推演。类里面会将成员定义为模板参数类型。


三、非类型模板参数

  非类型模板参看,顾名思义,模板参数不限定于类型,普通值也可作为模板参数。在基于类型的模板中,模板实例化时所依赖的是某一类型的模板参数,你定义了一些模板参数(template<typename T>)未加确定的代码,直到模板被实例化这些参数细节才真正被确定。而非类型模板参数,面对的未加确定的参数细节是指(value),而非类型。当要使用基于值的模板时,你必须显式地指定这些值,模板方可被实例化。

1. 非类型类模板参数

  非类型类模板参数就是类的模板参数不是类型,而是一个值。

template <class T, int MAXSIZE>
class List
{
private:
    T elems[MAXSIZE];
public:
    void Print()
    {
        cout << "The maxsize of list is " << MAXSIZE << endl;
    } 
};

(1)使用规则

  1. 只有整形,指针和引用才能作为非类型形参,而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。
  2. 实参必须是编译时常量表达式,不能使用非const的局部变量、局部对象地址及动态对象。
  3. 非const的全局指针,全局对象,全局变量都不是常量表达式。
  4. 由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参。
  5. 常量表达式基本上是字面值以及const修饰的变量
template<char const* name>
class pointerT{
    int len = strlen(name);
};
char a[] = "a1aaa";//全局变量  
char a2[] = "a2aaa";//局部变量,写在main函数里面  
char *b = "bbbb";//全局变量  
char *const c = "cccc";//全局变量,顶层指针,指针常量  

pointerT<"testVarChar"> p1;//错误 
pointerT<a> p2;//正确  
pointerT<a2> p22;//错误,局部变量不能用作非类型参数  
pointerT<b> p3;//错误,error C2975:“pointerT”的模板参数无效,应为编译时常量表达式  
pointerT<c> p4;//错误,error C2970: “c”: 涉及带有内部链接的对象的表达式不能用作非类型参数  

(2)为什么字符串不能作为实参?

  我们看到上面p1的模板实参是”testVarChar”,然而当我们在另一个编译单元(.cpp文件)同样声明这么一个模板实例时,这两个”testVarChar”的地址可能是不同的,编译器传递给模板时就会传递传递不同的地址,从而导致这两个模板实例是两个不同且不兼容的类型。
  
②变量b和c作为模板实参为什么错误
  答:首先解释b实参,b在这里看做是一个指针,是一个全局指针,但是他不是一个常量表达式,所以b不对。我们再看看c相比于b对了一个const修饰符,表示这个指针是一个常量。然而const是一个比较特别的关键字,他具有内部链接属性,也就是说仅在定义这个变量的文件内可见,不会造成不同编译单元的混编时的链接错误。
  这个特性对于模板来说可是有问题的,就像问题①所描述的,由于每个编译单元可能都有一个c变量,导致在编译时,实例化多个c,而且c的地址还不同,这就造成两个模板的实例是两个不同且不兼容的类型。
  


2. 非类型函数模板参数

(1)代码示例

template <class T, int value>
T Add(const T& x)
{
    return x + value;
}

int main()
{
    int a = 10;
    const int val = 10;
    int ret = Add<int, val>(a);

    return 0;
}

四、特化(类型萃取 )

1. 特化

//原始版本
template<class T1, class T2>
class Data
{
public:
    Data()
    {
        cout << "<T1, T2>原始版本" << endl;
    }
private:
    T1 d1;
    T2 d2;
};

//半特化——特化一部分属性(指针、引用等)
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
    Data()
    {
        cout << "<T1*, T2*>半特化(指针)" << endl;
    }
private:
    T1 d1;
    T2 d2;
};

//半特化——特化一部分属性(指针、引用等)
template<class T1, class T2>
class Data<T1&, T2*>
{
public:
    Data()
    {
        cout << "<T1&, T2*>半特化(引用、指针)" << endl;
    }
private:
    //T1 d1; //=========================引用必须在参数列表初始化
    T2 d2;
};


//全特化
template<>
class Data<int, int>
{
public:
    Data()
    {
        cout << "<int, int>全特化" << endl;
    }
private:
};

//半特化
template<class T1>
class Data<T1, int>
{
public:
    Data()
    {
        cout << "<T1, int>半特化" << endl;
    }
private:
};

int main()
{
    //seq<int>s1;
    //s1.expand();

    //seq<string> s2;
    //s2.expand();
    Data<int, int>d1;      //全特化
    Data<double, char>d2;  //原始版本
    Data<char, int>d3;     //半特化
    Data<char*, int*>d4;   //半特化
    Data<char&, int*>d5;   //半特化

    system("pause");
    return 0;
}

这里写图片描述


2. 类型萃取

#pragma once  
#include <string>
using namespace std;

struct __TrueType    //类
{
    bool Get()  //写法二
    {
        return true;
    }
};

struct __FalseType   //类
{
    bool Get() //写法二
    {
        return false;
    }
};

template<class T>
struct TypdeTraits  //模板
{
    typedef __FalseType IsPodType;
};

template<>    //特化
struct TypdeTraits<int>
{
    typedef __TrueType IsPodType;
};

//====================================写法一:(STL库中写法)=============================

//template<class T>
//T* __TypeCopy(const T* src, T*des, size_t n, __TrueType)  //pod类型,采取浅拷贝
//{
//  return (T*)memcpy(des, src, sizeof(T)*n);
//}
//
//template<class T>
//T* __TypeCopy(const T* src, T*des, size_t n, __FalseType)  //非pod类型,采取深拷贝
//{
//  for (size_t i = 0; i < n; ++i)
//  {
//      des[i] = src[i];
//  }
//  return des;
//}
//
//template<class T>
//T* TypeCopy(const T* src, T*des, size_t n)
//{
//  return __TypeCopy(src, des, n, TypdeTraits<T>::IsPodType());
//}


//======================================写法二:(代码更简洁)============================

template<class T>
T* TypeCopy(const T* src, T*des, size_t n)
{
    if (TypdeTraits<T>::IsPodType().Get())
    {
        memcpy(des, src, sizeof(T)*n);
    }
    else
    {
        for (size_t i = 0; i < n; ++i)
        {
            des[i] = src[i];
        }
        return des;
    }
}


void testCopy()
{
    string s1[10] = { "1", "222222222222222222", "3", "44" };
    string s2[10] = { "11", "22", "33" };
    TypeCopy(s1, s2, 10);

    int a1[10] = { 1, 2, 3 };
    int a2[10] = { 0 };
    TypeCopy(a1, a2, 10);
}

五、模板的分离编译

参靠链接:为什么模板不支持分离编译


猜你喜欢

转载自blog.csdn.net/tianzez/article/details/80298290