非类型模板参数

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

namespace bite
{template<class T, size_t N>class array{public:void push_back(constT& data){//N=10;_array[_size++] = data;}T& operator[](size_t){assert(index < _size)return _array[index];}bool empty()const{return 0 == _size;}size_t size()const{return _size;}private:T _array[N];size_t _size;};
}

注意事项

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  2. 非类型的模板参数必须在编译期就能确认结果。

模板的特化

模板特化的概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如

class Date
{public:Date(int year, int month, int day):_year(year), _month(month), _day(day){}bool operator>(const Date&d)const{return _day > d._day;}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "/" << d._month << "/" << d._day << endl;return _cout;}
private:int _year;int _month;int _day;
};
template<class T>
T& Max(T& left, T& right)
{return left > right ? left : right;
}char *p1 = "world";char *p2 = "hello";cout << Max(p1, p2) << endl;cout << Max(p2, p1) << endl;

这个代码他就无法比较字符串类型的变量的大小
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式

函数模板特化

如果不需要通过形参改变外部实参加上const
例如

template<class T>
const T& Max(const T& left, const T& right)
{return left > right ? left : right;
}
//函数模板的特化
template<>
char *& Max<char*>(char*& left, char*& right)
{//>0大于 =0等于 <0小于if (strcmp(left, right) > 0){return left;}return right;
}

注意事项

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

一般函数模板没必要特化,直接把相应类型的函数给出就行

char* Max(char *left, char* right)
{if (strcmp(left, right) > 0){return left;}return right;
}

类模板的特化

全特化

//全特化-----对所有类型参数进行特化
template<class T1, class T2>
class Data
{public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};template<>
class Data<int, int>
{public:Data() { cout << "Data<int, int>" << endl; }
private:int _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;return 0;
}

偏特化

部分特化
//偏特化,将模板参数列表中的参数部分参数类型化
template<class T1>
class Data<T1,int>
{public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;Data<double, int>d3;system("pause");return 0;
}
参数更进一步的限制
//偏特化:让模板参数列表中的类型限制更加的严格
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<int, int*>d2;Data<int*, int*>d3;//特化Data<int*, double*>d4;//特化system("pause");return 0;
}

模板特化的作用之类型萃取

编写一个通用类型的拷贝函数

template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}

上述代码虽然对于任意类型的空间都可以进行拷贝,但是如果拷贝自定义类型对象就可能会出错,因为自定义类型对象有可能会涉及到深拷贝(比如string),而memcpy属于浅拷贝。如果对象中涉及到资源管理,就只能用赋值。

class String
{public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str ( new char[strlen(s._str)+1]){strcpy(this->_str, s._str);}String& operator=(const String &s){if (this != &s){char *str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[]_str;        _str = s._str;}}~String(){delete[]_str;}private:char* _str;
};//写一个通用的拷贝函数,要求:效率尽量高
template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}void TestCopy()
{int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int array2[10];Copy(array2, array1, 10);String s1[3] = { "111", "222", "333" };String s2[3];Copy(s2, s1, 3);
}

如果是自定义类型用memcpy,那么会存在

  1. 浅拷贝----导致代码崩溃
  2. 内存泄露—S2数组中每个String类型对象原来的空间丢失了

增加一个拷贝函数处理浅拷贝

template<class T>
void Copy2(T* dst, T*src, size_t size)
{for (size_t i = 0; i < size; ++i){dst[i] = src[i];}
}
  1. 优点:一定不会出错
  2. 缺陷:效率比较低,让用户做选择,还需要判断调用哪个函数

让函数自动去识别所拷贝类型是内置类型或者自定义类型

bool IsPODType(const char* strType)
{//此处将所有的内置类型枚举出来const char * strTypes[] = { "char", "short", "int", "long", "long long", "float", "double" };for (auto e : strTypes){if (strcmp(strType, e) == 0)return true;}return false;
}template<class T>
void Copy(T* dst, T*src, size_t size)
{//通过typeid可以将T的实际类型按照字符串的方式返回if (IsPODType(typeid(T).name()){//T的类型:内置类型memcpy(dst, src, sizeof(T)*size);}else{//T的类型:自定义类型----原因:自定义类型种可能会存在浅拷贝for (size_t i = 0; i < size; ++i){dst[i] = src[i];}}
}

在编译期间就确定类型—类型萃取

如果把一个成员函数的声明和定义放在类里面,编译器可能会把这个方法当成内联函数来处理

class String
{public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str(new char[strlen(s._str) + 1]){strcpy(this->_str, s._str);}String& operator=(const String& s){if (this != &s){char* str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[] _str;_str = str;}return *this;}~String(){delete[]_str;}private:char* _str;
};//确认T到底是否是内置类型
//是
//不是
//对应内置类型
struct TrueType
{static bool Get(){return true;}
};//对应自定义类型
struct FalseType
{static bool Get(){return true;}
};template<class T>
struct TypeTraits
{typedef FalseType PODTYPE;};template<>
struct TypeTraits<char>
{typedef TrueType PODTYPE;
};template<>
struct TypeTraits<short>
{typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<int>
{typedef TrueType PODTYPE;
};//........还有很多内置类型template<class T>
void Copy(T* dst, T* src, size_t size)
{// 通过typeid可以将T的实际类型按照字符串的方式返回if (TypeTraits<T>::PODTYPE::Get()){// T的类型:内置类型memcpy(dst, src, sizeof(T)*size);}else{// T的类型:自定义类型---原因:自定义类型中可能会存在浅拷贝for (size_t i = 0; i < size; ++i)dst[i] = src[i];}
}

STL中的类型萃取

// 代表内置类型
struct __true_type {};
// 代表自定义类型
struct __false_type {};template <class type>
struct __type_traits
{typedef __false_type is_POD_type;
};// 对所有内置类型进行特化
template<>
struct __type_traits<char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<signed char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<unsigned char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<int>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<float>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<double>
{typedef __true_type is_POD_type;
};
// 注意:在重载内置类型时,所有的内置类型都必须重载出来,包括有符号和无符号,比如:对于int类型,必
须特化三个,int -- signed int -- unsigned int
// 在需要区分内置类型与自定义类型的位置,标准库通常都是通过__true_type与__false_type给出一对重载
的
// 函数,然后用一个通用函数对其进行封装
// 注意:第三个参数可以不提供名字,该参数最主要的作用就是让两个_copy函数形成重载
template<class T>
void _copy(T* dst, T* src, size_t n, __true_type)
{memcpy(dst, src, n*sizeof(T));
}
template<class T>
void _copy(T* dst, T* src, size_t n, __false_type)
{for (size_t i = 0; i < n; ++i)dst[i] = src[i];
}
template<class T>
void Copy(T* dst, T* src, size_t n)
{_copy(dst, src, n, __type_traits<T>::is_POD_type());
}

分离编译

预处理----->编译---->汇编----->链接

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

没有模板的函数,没有问题
有模板的函数,编译可以过,但是链接会出错

函数模板编译:

  1. 实例化之前:编译器只做一些简答的语法检测,不会生成处理具体类型的代码。并不会确认函数的入口地址
  2. 实例化期间:编译器会推演形参类型来确保模板参数列表中T的实际类型,在生成具体类型的代码
  3. 不支持分离编译

解决分离编译

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实是可以的。
  2. 模板定义的位置显式实例化。

模板总结

优点

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

缺点

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

更加深入学习参考这个链接

深入理解c++中的函数模板相关推荐

  1. 计算机函数模式的用处是啥,请问怎么理解计算机中的函数?

    你的理解有点外行看热闹的意思,呵呵. 代码本身就是抽象的,所以"计算机中的函数是一种对代码进行抽象的方式"不能说不对,但是也和没说一样.至于"我们使用抽象出来的函数,而不 ...

  2. 彻底理解JavaScript中回调函数 (推荐)

    在javascript中回调函数非常重要,它们几乎无处不在.像其他更加传统的编程语言都有回调函数概念,但是非常奇怪的是,完完整整谈论回调函数的在线教程比较少,倒是有一堆关于call()和apply() ...

  3. 初识c++中的函数模板

    函数模板 函数模板概念 函数模板:编译器生成代码的一个规则.函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本. 函数模板格式 //要让这个函数与类 ...

  4. c 语言 模板函数参数,深入解析C++中的函数模板和函数的默认参数

    C++函数模板 我们知道,数据或数值可以通过函数参数传递,在函数定义时它们是未知的,只有在发生函数调用时才能确定其值.这就是数据的参数化. 其实,数据类型也可以通过参数来传递,在函数定义是可以不指明具 ...

  5. 深入理解MATLAB中contour函数

    1 contour函数 语法: [c,h] = contour(___); % 返回等高线矩阵c和等高线对象h 等高线矩阵,返回为二行矩阵.此矩阵包含等高线层级(高度)和每个层级上各顶点的坐标.对于 ...

  6. 如何理解函数式中纯函数

    函数式编程中的函数,指的就是纯函数,纯函数的概念就是对于一个函数来说,使用相同的输入始终会得到相同的输出,而且没有可观察到的副作用.关于副作用我们后面在解释.这里我们只讨论相同的输入始终会得到相同的输 ...

  7. matlab中ode45函数的用法_带你理解Excel中COUNTIF函数的简单用法

    每天5分钟,每天学一点. COUNTIF函数是Excel中最常用的统计函数之一,它的作用主要是用于根据特定条件对数据进行统计.假如,你想统计一下本周总共做了几次健身/瑜伽,或者统计上了几次培训课,那么 ...

  8. 如何理解python中的函数_如何理解“python中函数是一等公民”?

    python.js.scala等支持函数式编程的语言中,是如何体现"函数是一等公民(first class)"的?而在c/c++.java等静态语言中的一等公民又是什么?如何体现的 ...

  9. 理解javascript中的函数模式

    1.回调函数 将一个函数作为参数传给另一个函数,我们称它为回调函数 function test1(callback){//执行脚本//执行回调函数 callback();} 2.即时函数(自执行匿名函 ...

最新文章

  1. Composer PHP依赖管理
  2. Java处理split分割【for循环】
  3. python找与7相关的数字_C++和python实现阿姆斯特朗数字查找实例代码
  4. 微信小程序自定义组件,提示组件
  5. jenkins的历史
  6. 【Python爬虫】Windows环境下wxpy不需每次登陆重新扫描
  7. LeetCode 链表的插入排序
  8. Ubuntu上通过FinalShell或Asbru访问CentOS虚拟机
  9. Python语言importError:cannot import name ‘InvalidArgumentException‘报错的解决方法:
  10. 华为根本没有鸿蒙系统,【图片】你看不明白的鸿蒙系统,才是华为缔造未来的“伟大”!华为并没有把系统划分为手机操作系统,我们就能知道华为想的并不是那么简单【手机吧】_百度贴吧...
  11. 详解Transformer
  12. TestNg测试框架使用
  13. 【krpano】KRPano测试开发专用浏览器
  14. lisp角度转换弪度_角度与弧度之间的换算(rad与度的换算)
  15. win10底部任务栏无响应解决办法
  16. 【转】ADW_Launcher
  17. 管理经济学 知识点总结(一)
  18. 上门家教app开发的前景
  19. (免费分享)基于jsp,javaweb银行柜员业务绩效考核系统(带论文)
  20. RGB接口屏和SPI接口屏的引脚

热门文章

  1. C# 判断txt文件编码格式
  2. Python操作Redis(转)
  3. 牛客网NOIP赛前集训营-提高组(第六场)B-选择题[背包]
  4. (扩展)欧几里德快速幂
  5. add separator in the sessionmenu
  6. 前端项目难点及解决方法_预埋件施工重点难点的解决方法
  7. netapp管理地址_NetApp常用管理命令总结
  8. 台达b3伺服modbus通讯_【数控系统】台达伺服压机控制灵活 精准压合满足各种工序需求...
  9. 视觉平衡与物理平衡_设计中的平衡理论为什么这么重要?
  10. ubantu下安装Nginx