优化游戏的软件,优化方案化学,乌兰察布市建设工程造价网站,一站传媒seo优化目录 前言#xff1a;
本章学习目标#xff1a;
1.非类型模版参数
1.1使用方法
1.2注意事项
1.3 实际引用
2.模版特化 2.1概念
2.2函数模板特化 2.3类模板特化
2.3.1全特化
2.3.2偏特化 3.模版分离编译
编辑 3.1失败原因
编辑 3.2解决方案
4 总结 前言
本章学习目标
1.非类型模版参数
1.1使用方法
1.2注意事项
1.3 实际引用
2.模版特化 2.1概念
2.2函数模板特化 2.3类模板特化
2.3.1全特化
2.3.2偏特化 3.模版分离编译
编辑 3.1失败原因
编辑 3.2解决方案
4 总结 前言
本章节是在学习完STL之后对高阶模版进行的总结模板给泛型编程注入了灵魂模板提高了程序的灵活性模板包括非类型模版参数、全特化、偏特化等同时本文还会对模板声明和定义不能分离的问题做出介绍。
本章学习目标 1. 非类型模板参数 2. 类模板的特化 3. 模板的分离编译 1.非类型模版参数
模板参数分类 类型形参与非类型形参。 类型形参即 出现在模板参数列表中跟在class或者typename之类的参数类型名称。 非类型形参 就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。
1.1使用方法
非类型模版参数 既然是使用常量作为参数那么浮点数、类对象以及字符串是不允许作为非类型模版参数的 非类型模版参数必须在编译期就能确认结果。
利用非类型模版参数创建一个可以自由调整大小的整形数组代码如下
#include assert.h
using namespace std;template size_t N
class arr
{
public:int operator[](size_t pos){assert(pos 0 pos N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];
};
int main()
{arr5 a1;arr10 a2;arr15 a3;cout a1.size() endl;cout a2.size() endl;cout a3.size() endl;return 0;
}
定义一个模板类型的静态数组
template class T ,size_t N
class arr
{
public:T operator[](size_t pos){assert(pos 0 pos N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];
};
int main()
{arrint,5 a1;arrdouble,10 a2;arrchar,15 a3;cout a1.size() typeid(a1).name() endl;cout a2.size() typeid(a2).name() endl;cout a3.size() typeid(a3).name() endl;return 0;
}
1.2注意事项 非类型模板参数要求类型为 整型家族其他类型是不被编译器通过的如果我们使用非整形的家族成员就会报错代码如下
//浮点型非标准
templateclass T, double N
class arr
{/*……*/
};整形家族char short int bool long longlong 1.3 实际引用 在 C11 标准中引入了一个新容器 array它就使用了 非类型模板参数为一个真正意义上的 泛型数组这个数组是用来对标传统数组的。
注意 部分老编译器可能不支持使用此容器
新引入arry非类型模版参数的代码 如下
#include iostream
#include cassert
#include array //引入头文件using namespace std;int main()
{int arrOld[10] { 0 }; //传统数组arrayint, 10 arrNew; //新标准中的数组//与传统数组一样新数组并没有进行初始化//新数组对于越界读、写检查更为严格 优点arrOld[15]; //老数组越界读未报错arrNew[15]; //新数组则会报错arrOld[12] 0; //老数组越界写不报错出现严重的内存问题arrNew[12] 10; //新数组严格检查return 0;
}array 是泛型编程思想中的产物支持了许多 STL 容器的功能比如 迭代器 和 运算符重载 等实用功能最主要的改进是 严格检查越界行为。
需要提醒的是arry能做到严格的越界检查 得益于 []的重载对下标进行了严格检查。
2.模版特化 2.1概念
通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理比如使用 日期类对象指针 构建优先级队列后若不编写对应的仿函数则比较结果会变为未定义 代码如下
// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}
int main()
{cout Less(1, 2) endl; // 可以比较结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl; // 可以比较结果正确Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 可以比较结果错误return 0;
} 造成每次结果不一样是因为 我们每次都日期类对象比较的时候 系统每次分配的地址都是随机的我们对地址进行比较是不符合实际情况的。 此时就 需要对模板进行特化。即在原模板类的基础上针对特殊类型所进行特殊化的实现方式。模板特 化中分为 函数模板特化 与 类模板特化 2.2函数模板特化
1. 必须要先有一个基础的函数模板 2. 关键字template后面接一对空的尖括号 3. 函数名后跟一对尖括号尖括号中指定需要特化的类型 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误。
上述日期进行比较的函数模版进行特化之后代码如下
// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}
// 对Less函数模板进行特化
template
bool LessDate*(Date* left, Date* right)
{return *left *right;
}
int main()
{cout Less(1, 2) endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout Less(d1, d2) endl;Date* p1 d1;Date* p2 d2;cout Less(p1, p2) endl; // 调用特化之后的版本而不走模板生成了return 0;
} 2.3类模板特化 模板特化主要用在类模板中它可以在泛型思想之上解决大部分特殊问题并且类模板特化还可以分为全特化和偏特化适用于不同场景
后面用的日期类举例比较多先把日期类放出来
class Date
{
public:Date(int year 1970, int month 1, int day 1): _year(year), _month(month), _day(day){}bool operator(const Date d)const{return (_year d._year) ||(_year d._year _month d._month) ||(_year d._year _month d._month _day d._day);}bool operator(const Date d)const{return (_year d._year) ||(_year d._year _month d._month) ||(_year d._year _month d._month _day d._day);}private:int _year;int _month;int _day;
};
2.3.1全特化 全特化指 将所有的模板参数特化为具体类型将模板全特化后调用时会优先选择更为匹配的模板类
//原模板
templateclass T1, class T2
class Test
{
public:Test(const T1 t1, const T2 t2):_t1(t1),_t2(t2){cout templateclass T1, class T2 endl;}private:T1 _t1;T2 _t2;
};//全特化后的模板
template
class Testint, char
{
public:Test(const int t1, const char t2):_t1(t1), _t2(t2){cout template endl;}private:int _t1;char _t2;
};int main()
{Testint, int T1(1, 2);Testint, char T2(20, c);return 0;
}对模板进行全特化处理后实际调用时会优先选择已经特化并且类型符合的模板。 2.3.2偏特化 偏特化指 将泛型范围进一步限制可以限制为某种类型的指针也可以限制为具体类型
//原模板---两个模板参数
templateclass T1, class T2
class Test
{
public:Test(){cout class Test endl;}
};//偏特化之一限制为某种类型
templateclass T1
class TestT1, int
{
public:Test(){cout class TestT, int endl;}
};//偏特化之二限制为不同的具体类型
templateclass T
class TestT*, T*
{
public:Test(){cout class TestT*, T* endl;}
};int main()
{Testdouble, double t1;Testchar, int t2;TestDate*, Date* t3;return 0;
}偏特化尤其是限制为某种类型在 泛型思想 和 特殊情况 之间做了折中处理使得 限制范围式的偏特化 也可以实现 泛型
比如偏特化为 T*那么传 int*、char*、Date* 都是可行的
应用实例 有如下专门用来按照小于比较的类模板 Less
#includevector
#include algorithm
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};
int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vectorDate v1;v1.push_back(d1);
v1.push_back(d2);v1.push_back(d3);// 可以直接排序结果是日期升序sort(v1.begin(), v1.end(), LessDate());vectorDate* v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);// 可以直接排序结果错误日期还不是升序而v2中放的地址是升序// 此处需要在排序过程中让sort比较v2中存放地址指向的日期对象// 但是走Less模板sort在排序时实际比较的是v2中指针的地址因此无法达到预期sort(v2.begin(), v2.end(), LessDate*());return 0;
}通过观察上述程序的结果发现对于日期对象可以直接排序并且结果是正确的。但是如果待排序元素是指 针结果就不一定正确。因为 sort 最终按照 Less模板中方式比较所以只会比较指针而不是比较指针指 向空间中内容此时可以使用类版本特化来处理上述问题 // 对Less类模板按照指针方式特化
template
struct LessDate*
{bool operator()(Date* x, Date* y) const{return *x *y;}
}; 3.模版分离编译 早在 模板初阶 中我们就已经知道了 模板不能进行分离编译会引发链接问题 3.1失败原因 声明与定义分离后在进行链接时无法在符号表中找到目标地址进行跳转因此链接错误
下面是 模板声明与定义写在同一个文件中时具体的汇编代码执行步骤 test.h #pragma once//声明
templateclass T
T add(const T x, const T y);//定义
templateclass T
T add(const T x, const T y)
{return x y;
}main.cpp #include iostream
#include Test.husing namespace std;int main()
{add(1, 2);return 0;
}声明与定义在同一个文件中时可以直接找到函数的地址 编译器 生成可执行文件的四个步骤 预处理头文件展开、宏替换、条件编译、删除注释生成纯净的C代码编译语法 / 词法 / 语义 分析、符号汇总生成汇编代码汇编生成符号表生成二进制指令链接合并段表将符号表进行合并和重定位生成可执行程序 当模板的 声明 与 定义 分离时因为是 【泛型】所以编译器无法确定函数原型即 无法生成函数也就无法获得函数地址在符号表中进行函数链接时必然失败 3.2解决方案 解决方法有两种
在函数定义时进行模板特化编译时生成地址以进行链接模板的声明和定义不要分离直接写在同一个文件中
//定义
//解决方法一模板特化不推荐如果类型多的话需要特化很多份
template
int add(const int x, const int y)
{return x y;
}//定义
//解决方法二声明和定义写在同一个文件中
templateclass T
T add(const T x, const T y)
{return x y;
}这也就解释了为什么涉及 模板 的类其中的函数声明和定义会写在同一个文件中 .h著名的 STL 库中的代码的声明和定义都是在一个 .h 文件中 为了让别人一眼就看出来头文件中包含了 声明 与 定义可以将头文件后缀改为 .hpp著名的 Boost 库中就有这样的命名方式 4 总结 模板是 STL 的基础支撑假若没有模板、没有泛型编程思想那么恐怕 STL 会变得非常大
模板的优点
模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生 增强了代码的灵活性模板的缺点
模板会导致代码膨胀问题也会导致编译时间变长 出现模板编译错误时错误信息非常凌乱不易定位错误位置 总之模板 是一把双刃剑既有优点也有缺点只有把它用好了才能使代码 更灵活、更优雅