鞍山制作网站的公司,有没有专业做淘宝网站吗,临沂网站建设推荐,做网站网页的人是不是思维目录
一、前言
二、什么是C模板#xff1f;
#x1f4a6;泛型编程的思想
#x1f4a6;C模板的分类
三、非类型模板参数
⚡问题引入⚡
⚡非类型模板参数的使用⚡
#x1f525;非类型模板参数的定义
#x1f525;非类型模板参数的两种类型
#x1f52…目录
一、前言
二、什么是C模板
泛型编程的思想
C模板的分类
三、非类型模板参数
⚡问题引入⚡
⚡非类型模板参数的使用⚡
非类型模板参数的定义
非类型模板参数的两种类型
非类型模板参数的使用规则
⚡问题的解决⚡
⚡非类型模板参数的实例应用⚡
四、模板的特化 概念 函数模板特化 类模板特化
全特化
偏特化
模板特化的应用示例
五、总结
六、共勉 一、前言 在我们学习C时常会用到函数重载。而函数重载通常会需要我们编写较为重复的代码这就显得臃肿且效率低下。重载的函数仅仅只是类型不同代码的复用率比较低只要有新类型出现时就需要增加对应的函数。此外代码的可维护性比较低一个出错可能会导致所有的重载均出错。 那么模板的出现就让这些问题有了解决方案在之前的文章中已经详细的讲解了C的 ----- 模板初阶所以本次博客将为大家详细的讲解C的模板进阶 二、什么是C模板 程序设计中经常会用到一些程序实体它们的实现和所完成的功能基本相同不同的仅 仅是所涉及的数据类型不同。而模板正是一种专门处理不同数据类型的机制。 模板------是泛型程序设计的基础泛型generic type——通用类型之意。 函数、类以及类继承为程序的代码复用提供了基本手段还有一种代码复用途径——类属类型泛型利用它可以给一段代码设置一些取值为类型的参数注意这些参数 的值是类型而不是某类型的数据通过给这些参数提供一些类型来得到针对不同类 型的代码。 泛型编程的思想 首先我们来看一下下面这三个函数如果学习过了C函数重载 和 C引用 的话就可以知道下面这三个函数是可以共存的而且传值会很方便 void Swap(int left, int right)
{int temp left;left right;right temp;
}
void Swap(double left, double right)
{double temp left;left right;right temp;
}
void Swap(char left, char right)
{char temp left;left right;right temp;
}
但是真的很方便吗这里只有三种类型的数据需要交换若是我们需要增加交换的数据呢再CV然后写一个函数吗 这肯定是不现实的所以很明显函数重载虽然可以实现但是有一下几个不好的地方
重载的函数仅仅是类型不同代码复用率比较低只要有新类型出现时就需要用户自己增加对应的函数代码的可维护性比较低一个出错可能所有的重载均出错
那是否能做到这么一点告诉编译器一个模子让编译器根据不同的类型利用该模子来生成代码 ⭐总结 所以总结上面的这么一个技术C的祖师爷呢就想到了【模版】这个东西告诉编译器一个模子然后其余的工作交给它来完成根据不同的需求生成不同的代码 这就是泛型编程编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础 C模板的分类 1️⃣: 函数模板function tempalte使用泛型参数的函数function with generic parameters2️⃣:类模板class template使用泛型参数的类class with generic parameters 更加具体 模板初阶知识 大家这一去看看之前的文章 ----- 模板初阶
本篇文章主要讲解 模板的高阶操作非类型模板参数、全特化、偏特化等以及关于模板声明与定义不能分离在两个不同的文件中的问题。 三、非类型模板参数 之前所使用的模板参数都是用来匹配不同的类型如 int、double、Date 等模板参数除了可以匹配类型外还可以匹配常量非类型完成如数组、位图等结构的大小确定 ⚡问题引入⚡ 问题 假设我现在自定义了一个静态栈栈的大小设置为100。然后我构建了一个int 的类型的栈st1,和一个double 类型的栈st2。那么我希望stl 的大小为100st2 的大小为500能不能实现呢? -------------- 肯定是不能的! ! ! 如下面这个例子所示
#define N 100// 静态栈
templateclass T
class Stack
{
private:int _a[N];int _top;
};int main()
{Stackint st1;Stackdouble st1;return 0;
}从上图 可以发现栈 的两个对象 的大小都为 100。 那有什么办法 可以解决这个问题呢 这个时候就要引出 ---- 非类型模板参数 ⚡非类型模板参数的使用⚡ 我们知道模板参数分为 : 类型形参 与 非类型形参 类型模板形参 : 出现在模板参数列表中跟在 class 或者 typename 类之后的参数类型名称。
template class T // T 为模板参数中的 ---------- 类型模板形参
非类型模板形参 : 就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。
template size_t N // N 为模板参数中的 ------- 非类型模板形参 非类型模板参数的定义 在定义模板参数时可以不再使用 class 或 typename而是直接使用具体的类型如 size_t此时称为 非类型模板参数 注非类型模板参数必须为常量即在编译阶段确定值 非类型模板参数的两种类型
1️⃣: 利用 非类型模板参数 定义一个大小可以自由调整的 整型数组 类
templatesize_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]; //创建大小为 N 的整型数组
};int main()
{arr10 a1; // 大小为 10arr20 a2; // 大小为 20arr100 a3; // 大小为 100cout a1.size(): a1.size() endl;cout a2.size(): a2.size() endl;cout a3.size(): a3.size() endl;
} 2️⃣:可以再加入一个模板参数类型此时就可以得到一个 泛型、大小可自定义 的数组
templateclass 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]; //创建大小为 N 的整型数组
};int main()
{arrint , 10 a1; // 大小为 10arrdouble , 20 a2; // 大小为 20arrchar , 100 a3; // 大小为 100// 输出它们的 类型cout typeid(a1).name() endl;cout typeid(a2).name() endl;cout typeid(a3).name() endl;
} 非类型模板参数支持缺省因此写成这样也是合法的
templateclass T, size_t N 10 //缺省大小为10非类型模板参数的使用规则 非类型模板参数要求类型为 整型家族其他类型是不行的 比如下面这些 非类型模板参数 都是标准之内的
//整型家族部分
templateclass T, int N
class arr1 { /*……*/ };templateclass T, long N
class arr2 { /*……*/ };templateclass T, char N
class arr3 { /*……*/ };而一旦使用其他家族类型作为 非类型模板参数就会引发报错
//浮点型非标准
templateclass T, double N
class arr4 { /*……*/ };因此可以总结出非类型模板参数 的使用要求为
只能将 整型家族 类型作为非类型模板参数其他类型不在标准之内非类型模板参数必须为常量不可被修改且需要在编译阶段确定结果 整型家族char、short、bool、int、long、long long 等 ⚡问题的解决⚡ 此时我们已经知道的非类型模板参数的用法只需要给上面的栈添加非类型模板参数这样就实现了 st1 和 st2 构造不同的大小。 // 静态栈
templateclass T, size_t N
class Stack
{
private:int _a[N];int _top;
};int main()
{Stackint, 100 st1;Stackdouble, 500 st2;return 0;
}⚡非类型模板参数的实例应用⚡ 在 C11 标准中引入了一个新容器 array它就使用了 非类型模板参数为一个真正意义上的 泛型数组这个数组是用来对标传统数组的 array 的第二个模板参数就是 非类型模板参数
#include iostream
#include cassert
#include arrayusing 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 容器的功能比如 迭代器 和 运算符重载 等实用功能最主要的改进是 严格检查越界行为 实际开发中很少使用 array因为它对标传统数组连初始化都没有vector 在功能和实用性上可以全面碾压并且 array 使用的是 栈区 上的空间存在栈溢出问题可以说 array 是一个鸡肋的容器 四、模板的特化 概念 通常情况下模板可以帮我们实现一些与类型无关的代码但在某些场景中【泛型】无法满足调用方的精准需求此时会引发错误。 比如使用 日期类 实现一个专门用来进行小于比较的函数模板
// 日期类
class Date
{//友元函数friend std::ostream operator(std::ostream out, const Date d); // 标准流输出 -- printffriend std::istream operator(std::istream in, Date d); // 标准流插入 -- scanfpublic:// 构造函数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);}// 打印日期void Printf(){cout _year / _month / _day endl;}
private:int _year;int _month;int _day;
};
// 流插入
std::ostream operator(std::ostream out, const Date d) {out d._year - d._month - d._day endl;return out;
}// 流提取
std::istream operator(std::istream in, Date d) {in d._year d._month d._day;return in;
}// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}int main()
{cout Less(1, 2) endl;Date d1(2024, 7, 6);Date d2(2024, 7, 8);cout Less(d1, d2) endl;return 0;
}
可以看到不管是内置类型,还是自己实现的日期类都可以通过Less函数模板来比较大小而且结果都是正确的 那如果我们要比较指针类型呢
// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}int main()
{Date* p1 new Date(2024, 7, 6);Date* p2 new Date(2024, 7, 8);cout Less(p1, p2) endl; return 0;
}
我们运行发现结果是正确滴呀6 确实小于 8 哦 如果我们再运行一次可以看到竟然变成了 0 了也就是说 6 小于 8 为 false 也就是说Less 绝对多数情况下都可以正常比较但是在特殊场景下就得到错误的结果。 上述示例中p1 指向的对象显然小于p2指向的对象但是Less 内部并没有比较p1和p2指向的对象内容而比较的是pl和p2指针地址这就无法达到预期而错误。 此时就需要对 -------------- 模板进行特化处理。 即 : 在原模板类的基础上 , 针对特殊类型所进行特殊化的实现方式。
模板特化中分为 函数模板特化 与 类模板特化。 函数模板特化 函数模板的特化步骤: 必须要先有一个基础的函数模板关键字 template 后面接 一对空的尖括号『函数名后跟一对尖括号』尖括号中指定需要特化的类型函数形参表必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一 些奇怪的错误。 代码示例
// 函数模板 -- 参数匹配
templateclass T
bool Less(T left, T right)
{return left right;
}// 对Less函数模板进行特化
template
bool LessDate*(Date* left, Date* right)
{return *left *right;
}int main()
{Date* p1 new Date(2024, 7, 6);Date* p2 new Date(2024, 7, 8);cout Less(p1, p2) endl;Date* p3 new Date(2024, 7, 8);Date* p4 new Date(2022, 7, 6);cout Less(p3, p4) endl;return 0;
} 此时就会调用特化之后的版本而不走模板生成得啦 注意般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该函数直接给
bool Less(Date* left, Date* right)
{return *left *right;
}该种实现简单明了代码的可读性高容易泻。因为对于一些参数类型复 杂的函数模板特化时才会特别给出因此函数模板不建议特 类模板特化 模板特化主要用在 --- 类模板它可以在泛型思想之上解决大部分特殊问题并且类模板特化还可以分为全特化 和 偏特化适用于不同场景 全特化 全特化指 将所有的模板参数特化为具体类型将模板全特化后调用时会优先选择更为匹配的模板类。 简单一点来说全特化 就是将模板参数列表中 所有的参数都确定话 全特化的特化步骤
首先必须要有一个基础的类模板 关键字template后接一对空的尖括号『类名后跟一对尖括号』尖括号中指定需要特化的类型 假设有下面这样一个 Data 类我希望 构造函数 打印出来的 d2 对象里面 Tl 是 int , T2 是 double有什么办法吗?
templateclass T1, class T2
class Data
{
public:Data(){cout DataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};int main()
{Dataint, int d1;Dataint, double d2;return 0;
}我们实例化 dl 和 d2 对象时编译器会自动调用其默认构造函数当我们打印的时候可以看到实际上d2 对象里面还是 T1 和 T2 并不是我们想要的 int 和 double。 那么这个时候 我们就可以对 T1 和 T2 进行模板的特化
templateclass T1, class T2
class Data
{
public:Data(){cout DataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};// 全特化
template
class Dataint, double
{
public:Data(){cout Dataint, double endl;}
private:int _d1;double _d2;
};int main()
{Dataint, int d1;Dataint, double d2;return 0;
}当我们运行以后可以看到 d2 对象就去调用刚刚写好的特化类模板 总结 对模板进行全特化处理后实际调用时会优先选择已经特化并且类型符合的模板这就好比虽然你家冰箱里有菜但你还是想点外卖因为外卖对于你来说更加合适 偏特化 偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本。 偏特化又可分为以下两种表现形式『 部分特化』、『 参数更进一步的限制』。 偏特化的特化步骤
首先必须要有一个基础的类模板 关键字template后接一对尖括号尖括号中指定特定类型『类名后跟一对尖括号』尖括号中指定需要特化的类型 『 部分特化』 将模板参数类表中的一部分参数特化。
比如我们对 Tl 类型进行特化处理固定其类型为 double
templateclass T1, class T2
class Data
{
public:Data(){cout DataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};// 部分特化 -- 将第一个参数特化为double
templateclass T2
class Datadouble, T2
{
public:Data(){cout Datadouble, T2 endl;}
private:double _d1;T2 _d2;
};int main()
{Dataint, int _d1;Datadouble, double _d2;Datadouble, char _d3;return 0;
}可以看到当我们指定T1 为 double 的时候才会调用这个部分特化的类模板。 『 参数更进一步的限制』 偏特化并不仅仅是指特化部分参数而是针对模板参数更进一步的条件限 制所设计出来的一个特化版本。
// 基础模板
templateclass T1, class T2
class Data
{
public:Data(){cout DataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};// 部分特化 -- 将第一个参数特化为double
templateclass T2
class Datadouble, T2
{
public:Data(){cout Datadouble, T2 endl;}
private:double _d1;T2 _d2;
};//两个参数偏特化为指针类型
template typename T1, typename T2
class Data T1*, T2*
{
public:Data() { cout DataT1*, T2* endl; }private:T1 _d1;T2 _d2;
};//两个参数偏特化为引用类型
template typename T1, typename T2
class Data T1, T2
{
public:Data(const T1 d1, const T2 d2): _d1(d1), _d2(d2){cout DataT1, T2 endl;}private:const T1 _d1;const T2 _d2;
};// 主函数
int main()
{Dataint, int d1; // 调用基础的版本Datadouble, double d2; // 调用部分特化的double版本Dataint*, int* d3; // 调用特化的指针版本Dataint, int d4(2, 4); // 调用特化的引用版本return 0;
}运行以后可以看到当我们实例化的对象为指针类型或者引用类型的时候就会去调用这两个特化模板。 模板特化的应用示例 我们还是拿日期类来举例假设我现在要对3个实例化对象进行排序 // Less模板 --- 比较小于
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};int main()
{Date d1(2024, 7, 7);Date d2(2024, 7, 6);Date d3(2024, 7, 8);vectorDate v1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);// 排序sort(v1.begin(), v1.end(), LessDate());// 打印for (auto e : v1){cout e;}return 0;
}可以看到此时是能直接排序的结果是日期升序。 那 如果我将 vector 里面存放的是 Date* 类型的数据还能排序吗
// Less模板 --- 比较小于
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};int main()
{Date d1(2024, 7, 7);Date d2(2024, 7, 6);Date d3(2024, 7, 8);vectorDate* v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);// 排序sort(v2.begin(), v2.end(), LessDate*());// 打印for (auto e : v2) {cout *e endl;}return 0;
}因为v2 当中存放的地址所以我们打印的时候要解引用打印以后看到日期还不是升序呀,那么我们排序的到底是什么呢? 如果我们不解引用直接打印v2的每个元素可以看到v2 中放的地址是升序的。因为此处需要在排序过程中让sort 比较v2中存放地址指向的日期对象但是走了Less模板sort 在排序时实际比较的是v2中指针的地址因此无法达到预期。 通过观察上述程序的结果发现对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针结果就不一定正确。
因为: sort 最终按照Less模板中的方式比较所以只会比较指针而不是比较指针指向空间中内容。
那么此时可以使用类版本特化来处理上述问题: 全特化处理 // Less模板 --- 比较小于
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};// 对Less类模板按照指针方式特化
template
struct LessDate*
{bool operator()(Date* x, Date* y) const{return *x *y;}
};int main()
{Date d1(2024, 7, 7);Date d2(2024, 7, 6);Date d3(2024, 7, 8);vectorDate* v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);// 排序sort(v2.begin(), v2.end(), LessDate*());// 打印for (auto e : v2) {cout *e;}return 0;
}特化之后再运行就可以得到正确的排序结果了 偏特化处理 // Less模板 --- 比较小于
templateclass T
struct Less
{bool operator()(const T x, const T y) const{return x y;}
};//偏特化后的比较模板
templateclass T
class LessT*
{
public:bool operator()(T* x, T* y) const{return *x *y;}
};int main()
{Date d1(2024, 7, 7);Date d2(2024, 7, 6);Date d3(2024, 7, 8);vectorDate* v2;v2.push_back(d1);v2.push_back(d2);v2.push_back(d3);// 排序sort(v2.begin(), v2.end(), LessDate*());// 打印for (auto e : v2) {cout *e;}return 0;
} 特化之后再运行就可以得到正确的排序结果了 五、总结 模板是 STL 的基础支撑假若没有模板、没有泛型编程思想那么恐怕 STL 会变得非常大 模板的优点
模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生增强了代码的灵活性
模板的缺点
模板会导致代码膨胀问题也会导致编译时间变长出现模板编译错误时错误信息非常凌乱不易定位错误 总之模板 是一把双刃剑既有优点也有缺点只有把它用好了才能使代码 更灵活、更优雅 六、共勉
以下就是我对 【模板进阶】 的理解如果有不懂和发现问题的小伙伴请在评论区说出来哦同时我还会继续更新对 C 的理解请持续关注我哦