模板进阶

  • 非类型模板参数
  • 模板的特化
  • 类模板特化应用之类型萃取
  • 模板分离编译
  • 模板总结、

1. 非类型模板参数
类型形参即:出现在模板参数列表中,跟在class或者typename之后的参数类型名称。
非类型形参:是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

类型模板参数
template <class T1, typename T2>
T1 Add(const T1& a, const T2& b)
{
    return a + b;
}
//非类型模板参数,作为常量使用
//
template <class T,  size_t N>
class Array
{
private:
    T a[N];
};
int main()
{
    Array<int, 10> arr1;
    Array<double, 100> arr2;
    return 0;
}
  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  2. 非类型的模板参数必须在编译期就能确认结果。

2. 模板的特化
2.1 概念
在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特
化中分为函数模板特化与类模板特化。

2.2 函数模板特化::定制指定类型数据的处理逻辑

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template <>
bool isGreater<char*&>(char*& const a, char*& const  b)
{
    if (strcmp(a, b) > 0)
         return true;
    return false;
}
//5. 一般不去写函数模板的特化版本,函数模板的特化比较复杂,会报出一些奇怪的错误
    建议对于函数模板处理不了或者报错的类型,去实现一个对应类型的普通函数
bool isGreater(char* a, char* b)
{
    if (strcmp(a, b) > 0)
         return true;
    return false;
}
int main()
{
    int a = 10, b = 20;
    
    char* str2 = "world";
    char* str1 = "hello";
    
    cout << isEqual(a, b) << endl;
    cout << isEqual(str1, str2) << endl;  // bool  isEqual(char*& a, char*& b)
    cout << isGreater(a, b) << endl;
    cout << isGreater(str1, str2) << endl;
    return 0;
}

2.3 类模板特化

2.3.1 全特化:是将模板参数类表中所有的参数都确定化

//全特化:template<>
             class 类<类型1,类型2>
template<class T1, class T2>
class Data
{
public:
    Data() { cout << "Data<T1, T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};
//全特化
template<>
class Data<int, char>
{
public:
    Data() { cout << "Data<int, char>" << endl; }
private:
    int _d1;
    char _d2;
};

2.3.2 偏特化(半特化):任何针对模版参数进一步进行条件限制设计的特化版本。

// 1. 部分特化:只特化部分类型, template<class T>
                              class Data<T,int>{};
template<class T>
class Data<T, char>
{
public:
    Data() { cout << "Data<T, char>" << endl; }
private:
    int _d1;
    char _d2;
};
// 2. 对模板参数做进一步限制,template<class T1,class T2>;      
                           class Data<T1*,T2*>{};
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
    Data() { cout << "Data<T1*, T2*>" << endl; }
private:
    T1* _d1;
    T2* _d2;
};
// 2. 对模板参数做进一步限制  template<class T1,class T2>;      
                           class Data<T1&,T2&>{};
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
    Data() { cout << "Data<T1&, T2&>" << endl; }
private:
    T1* _d1;
    T2* _d2;
};
int main()
{
    Data<int, int> d1;  // Data<T1, T2>
    Data<int, char> d2; // Data<int, char>
    Data<char, char> d3;// Data<T, char>
    Data<int*, int*> d4; //Data<T1*, T2*>
    Data<int&, int&> d5;  // Data<T1&,T2&>
    return 0;
}

3. 类模板特化应用之类型萃取

3.1 容器增容: 拷贝
STL 拷贝分两路:
1. 内置类型: memcpy ( 一片内存拷贝,效率高 : O(1))
2. 自定义类型:赋值拷贝 ( 每个元素依次拷贝,效率较低 : O(n))

内置类型: memcpy

template<class T>
void Copy(T* dst, const T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
int main()
{
// 试试下面的代码
string strarr1[3] = {"11", "22", "33"};
string strarr2[3];
Copy(strarr2, strarr1, 3);
}

自定义类型:赋值拷贝

template<class T>
void Copy(T* dst, const T* src, size_t size)
{
for(size_t i = 0; i < size; ++i)
{
dst[i] = src[i];
}
}

3.2 增加bool类型区分自定义与内置类型

void Copy(T* dst, const T* src, size_t sz, bool  isPODtype)
{
    if (isPODtype)
    {
         memcpy(dst, src, sizeof(T)* sz);
    }
    else
    {
         for (int i = 0; i < sz; i++)
         {
             dst[i] = src[i];
         }
    }
}

通过多增加一个参数,就可将两种拷贝的优势体现结合起来。但缺陷是:用户需要根据所拷贝元素的类型去传递第三个参数,那出错的可能性就增加。那能否让函数自动去识别所拷贝类型是内置类型或者自定义类型呢

3.3 使用函数区分内置与自定义类型
因为内置类型的个数是确定的,可以将所有内置类型集合在一起,如果能够将所拷贝对象的类型确定下来,在内置类型集合中查找其是否存在即可确定所拷贝类型是否为内置类型

// RTTI :  Run Time Type Information /  Identification 进行时判断类型
// RTTI:   typeid   typeid(a).name
// bool isPODType(const char* tp): 缺陷:效率较低,时间复杂度O(n^2)
template <class T>
bool isPODType(const char* tp)
{
    static char* typyArr[] = { "int", "double",  "float", "char", "..." };
    for (int i = 0; i < sizeof(typyArr) /  sizeof(typyArr[0]); i++)
    {
         if (strcmp(tp, typyArr[i]))
             return true;
    }
    return false;
}
template <class T>
void Copy(T* dst, const T* src, size_t sz)
{
    if (isPODtype(typeid(T).name()))
    {
         memcpy(dst, src, sizeof(T)* sz);
    }
    else
    {
         for (int i = 0; i < sz; i++)
         {
             dst[i] = src[i];
         }
    }
}

3.4 类型萃取typetraits:类模板的一个应用场景

1.使用类模板和模板特化技术定义类型萃取类typetraits,内部定义类型falsetype
2.对于内置类型,通过模板特化自定义类型typetraits,内部定义类型truetype
3.编译期通过输入的类型生成对应的typetraits,调用truetype或者falsetype的 get()方法确定类型是否为自定义类型,根据返回结果决定拷贝的方式
4.优点:效率高,编译时确定类型,不占运行时间

struct TrueType
{
    static bool Get()
    {
         return true;
    }
};
struct FalseType
{
    static bool Get()
    {
         return false;
    }
};
//类型萃取
template <class T>
struct TypeTraits
{
    typedef FalseType _isPodType;
};
//模板特化
template <>
struct TypeTraits<char>
{
    typedef TrueType _isPodType;
};
template <>
struct TypeTraits<int>
{
    typedef TrueType _isPodType;
};
template <>
struct TypeTraits<double>
{
    typedef TrueType _isPodType;
};
template <>
struct TypeTraits<float>
{
    typedef TrueType _isPodType;
};
//.........

/*
T为int:TypeTraits<int>已经特化过,程序运行时就会使用已经特化过的TypeTraits<int>, 该类中的
IsPODType刚好为类TrueType,而TrueType中Get函数返回true,内置类型使用memcpy方式拷贝

T为string:TypeTraits<string>没有特化过,程序运行时使用TypeTraits类模板, 该类模板中的IsPODType
刚好为类FalseType,而FalseType中Get函数返回false,自定义类型使用赋值方式拷贝
*/
template <class T>
void Copy(T* dst, const T* src, size_t sz)
{
    if (TypeTraits<T>::_isPodType::Get())
    {
         cout << typeid(T).name() << ": memcpy()"  << endl;
         memcpy(dst, src, sizeof(T)* sz);
    }
    else
    {
         cout << typeid(T).name() << ":  operator=()" << endl;
         for (int i = 0; i < sz; i++)
         {
             dst[i] = src[i];
         }
    }
}
int main()
{

    int arr[] = { 1, 2, 3, 4, 5, 6, 7 };
    int arr2[7];
    Copy(arr2, arr, 7);
    string str1[] = { "123", "345", "hello",  "world" };
    string str2[4];
    Copy(str2, str1, 4);
    return 0;
}

在这里插入图片描述
4. 模板的分离编译
概念:

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

模板不支持分离编译:因为链接时找不到实际的指令
解决方法:将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的
分离编译会导致链接错误,如下图
在这里插入图片描述
将声明和定义放在一个.h文件中可避免错误,如下图
在这里插入图片描述
5. 模板总结
【 优点】:

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

【缺陷】:

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
乐播投屏是一款简单好用、功能强大的专业投屏软件,支持手机投屏电视、手机投电脑、电脑投电视等多种投屏方式。 多端兼容与跨网投屏:支持手机、平板、电脑等多种设备之间的自由组合投屏,且无需连接 WiFi,通过跨屏技术打破网络限制,扫一扫即可投屏。 广泛的应用支持:支持 10000+APP 投屏,包括综合视频、网盘与浏览器、美韩剧、斗鱼、虎牙等直播平台,还能将央视、湖南卫视等各大卫视的直播内容一键投屏。 高清流畅投屏体验:腾讯独家智能音画调校技术,支持 4K 高清画质、240Hz 超高帧率,低延迟不卡顿,能为用户提供更高清、流畅的视觉享受。 会议办公功能强大:拥有全球唯一的 “超级投屏空间”,扫码即投,无需安装。支持多人共享投屏、远程协作批注,PPT、Excel、视频等文件都能流畅展示,还具备企业级安全加密,保障会议资料不泄露。 多人互动功能:支持多人投屏,邀请好友加入投屏互动,远程也可加入。同时具备一屏多显、语音互动功能,支持多人连麦,实时语音交流。 文件支持全面:支持 PPT、PDF、Word、Excel 等办公文件,以及视频、图片等多种类型文件的投屏,还支持网盘直投,无需下载和转格式。 特色功能丰富:投屏时可同步录制投屏画面,部分版本还支持通过触控屏或电视端外接鼠标反控电脑,以及在投屏过程中用画笔实时标注等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值