简洁网站欣赏,wordpress评论回复插件,常州低价网站建设公司,WordPress是静态的吗阅读导航 前言一、模版的概念二、函数模版1. 函数模板概念2. 函数模板定义格式3. 函数模板的原理4. 函数模版的实例化#x1f6a9;隐式实例化#x1f6a9;显式实例化 5. 函数模板的匹配原则 三、类模板1. 类模板的定义格式2. 类模板的实例化 四、非类型模板参数1. 概念2. 定义… 阅读导航 前言一、模版的概念二、函数模版1. 函数模板概念2. 函数模板定义格式3. 函数模板的原理4. 函数模版的实例化隐式实例化显式实例化 5. 函数模板的匹配原则 三、类模板1. 类模板的定义格式2. 类模板的实例化 四、非类型模板参数1. 概念2. 定义 五、模板的特化1. 概念2. 函数模版特化3. 类模版特化⭕全特化⭕偏特化 4. 模版特化应用示例 六、模板分离编译1. 什么是分离编译2. 模版的分离编译 七、模版的优缺点【优点】【缺点】 温馨提示 前言
前面我们讲了C语言的基础知识也了解了一些数据结构并且讲了有关C的命名空间的一些知识点以及关于C的缺省参数、函数重载引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ 以及学习了几个STL的结构也相信大家都掌握的不错接下来博主将会带领大家继续学习有关C比较重要的知识点—— 模版template。下面话不多说坐稳扶好咱们要开车了
一、模版的概念
模板是C中的一种编程工具它允许使用通用代码来定义函数和类以适应多种类型或值的需求从而实现代码的复用和泛化。模板实质上是一种参数化的类型或值的规范。通过模板的使用可以提高代码的复用性和拓展性使得代码更加通用并能适应不同类型或值的需求。模板可以在编译时生成针对不同类型或值的代码从而提高代码的效率和灵活性。在C中有两种类型的模板函数模板和类模板。下面博主来逐个介绍。
二、函数模版
1. 函数模板概念
函数模板允许定义一个通用的函数其中一些或全部的参数的类型可以是参数化的。使用函数模板时编译器根据实际使用的参数类型自动生成对应的函数代码。
2. 函数模板定义格式
templatetypename T1, typename T2,......,typename Tn
返回值类型 函数名(参数列表)
{
}注意typename是用来定义模板参数关键字也可以使用class(切记不能使用struct代替class)
使用模版定义一个交换函数
templatetypename T
void Swap( T left, T right)
{T temp left;left right;right temp;
}使用模版定义一个取较大值函数
template typename T
T getMax(T a, T b) {return a b ? a : b;
}int main() {int maxInt getMax(2, 5); // 使用函数模板实例化为 int 类型的函数double maxDouble getMax(3.14, 2.5); // 使用函数模板实例化为 double 类型的函数// ...
}3. 函数模板的原理
函数模板是一个蓝图它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。 在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码对于字符类型也是如此。
4. 函数模版的实例化
用不同类型的参数使用函数模板时称为函数模板的实例化。函数模板的实例化过程可以分为两个步骤模板参数推断和模板函数生成。 模板参数推断编译器根据实际传入的参数类型推导出模板参数的具体类型。编译器会尝试根据传递的参数类型来匹配模板参数并确定参数的具体类型。如果无法进行准确的匹配则可能会产生模板参数推断失败的错误。 模板函数生成根据推断出的模板参数类型编译器生成特定类型的函数代码。编译器使用推断出的参数类型来替换函数模板中的模板参数生成与传递的参数类型匹配的函数定义。
⭕函数模板的实例化是在编译时完成的它提前为不同的参数类型生成了不同的函数定义以提高代码的重用性和执行效率。
⭕模板参数实例化分为隐式实例化和显式实例化
隐式实例化
下面是一个示例展示函数模板隐式实例化的过程
template typename T
T getMax(T a, T b) {return a b ? a : b;
}int main() {int maxInt getMax(2, 5); // 实例化为 int 类型的函数参数推断为 intdouble maxDouble getMax(3.14, 2.5); // 实例化为 double 类型的函数参数推断为 doublechar maxChar getMax(a, b); // 实例化为 char 类型的函数参数推断为 char// ...
}在这个例子中编译器会根据传递的参数类型自动推断模板参数的类型并生成对应类型的函数代码。实例化后会生成 getMax 函数的具体定义其中的模板参数 T 被替换为相应的类型。
显式实例化
模板的显式实例化是指在编译时明确告诉编译器需要实例化的模板类型以生成对应的函数定义。
在函数名后的中指定模板参数的实际类型下面是一个示例展示函数模板显式实例化的过程
template typename T
T getMax(T a, T b) {return a b ? a : b;
}
int main() {int maxInt getMaxint(2, 5); // 显式实例化的 int 类型的函数int maxDouble getMaxdouble(1.1, 5.2)// 显式实例化的 double类型的函数// ...
}通过显式实例化可以在编译时生成特定类型的函数定义避免了模板参数推断和函数生成的开销提高了代码的执行效率。
5. 函数模板的匹配原则 最佳匹配原则编译器会尝试找到与调用参数最匹配的函数模板来实例化。在函数模板的候选函数中编译器会根据实际参数类型进行以下规则的匹配 a. 完全匹配如果有一个函数模板能够完全匹配实际参数的类型那么它将被选择为最佳匹配。 b. 类型转换匹配如果有多个函数模板能够通过一系列的类型转换如隐式类型转换匹配实际参数的类型那么转换次数最少的模板将被选择为最佳匹配。 c. 模板特化匹配如果存在与调用参数类型完全匹配的模板特化那么它将被选择为最佳匹配。 d. 不匹配如果没有找到合适的模板来匹配调用参数的类型那么将导致编译错误。 函数模板的特例化规则当函数模板的特化版本和常规模板同时存在时编译器会优先选择特化版本。
下面的代码展示了函数模板匹配原则的应用
template typename T
void print(T value) {std::cout value std::endl;
}template
void print(int value) {std::cout Specialized: value std::endl;
}void print(double value) {std::cout Non-template: value std::endl;
}int main() {print(5); // 调用特化版本的 print输出 Specialized: 5print(3.14); // 调用非模板函数 print输出 Non-template: 3.14print(Hello); // 调用普通模板函数 print输出 Helloreturn 0;
}在上述示例中当调用 print 函数时根据参数类型的不同编译器将根据匹配原则选择最佳匹配的函数版本。如果有特化版本将优先选择特化版本。如果没有特化版本会选择模板函数中最适合的版本来实例化。
三、类模板
1. 类模板的定义格式
定义多个类型
templateclass T1, class T2, ..., class Tn
class 类模板名
{// 类内成员定义
};定义单个类型
template typename T
class ClassName {// 类模板的成员和方法声明及定义
};在上述格式中template typename T 表示定义了一个类模板T 是一个类型参数它可以在类的成员和方法中使用。你可以根据需要使用其他的类型参数名称。
下面的代码展示了一个简单的类模板的定义
注意Pair 不是具体的类是编译器根据被实例化的类型生成具体类的模具template typename T
class Pair {
private:T first;T second;public:Pair(T f, T s) : first(f), second(s) {}T getFirst() const {return first;}T getSecond() const {return second;}void setFirst(T f) {first f;}void setSecond(T s) {second s;}
};Pair 是一个类模板拥有两个泛型成员变量 first 和 second以及一些泛型成员函数。通过类模板我们可以定义一个通用的配对(Pair)类用于存储任意类型的一对值。
2. 类模板的实例化
⭕类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的类型放在中即可类模板名字不是真正的类而实例化的结果才是真正的类。
例如
int main() {Pairint p(3, 4); // 实例化一个 Pair 类型对象其中 T 被替换为 intPairdouble q(1.5, 2.7); // 实例化一个 Pair 类型对象其中 T 被替换为 doubleint first p.getFirst(); // 获取 p 对象中的第一个值 3double second q.getSecond(); // 获取 q 对象中的第二个值 2.7p.setSecond(7); // 设置 p 对象的第二个值为 7return 0;
}在上面的代码中我们使用不同的具体类型参数实例化了 Pair 类模板并使用相应的对象进行操作。编译器会根据实际传递的类型参数替换类模板中的类型参数 T生成对应的类定义和对象实例化。
四、非类型模板参数
1. 概念
非类型模板参数是指在C中模板参数可以不仅仅是类型还可以是常量表达式。非类型模板参数允许在模板实例化时传递常量值作为参数并在编译时对其进行计算和使用。
⭕通过使用非类型模板参数可以实现在编译时生成特定类型或值的代码。
非类型模板参数必须是以下几种类型之一
整数类型包括整数、字符和枚举类型。指针类型。引用类型。
⭕浮点数、类对象和字符串是不允许作为非类型模板参数的。 因为非类型模板参数在编译时需要被计算和处理而浮点数、类对象和字符串类型的计算和处理是在运行时进行的无法在编译时确定。
2. 定义
在定义模板时可以使用非类型模板参数来指定一个或多个参数。例如
template typename T, int SIZE
class Array {T data[SIZE];// ...
};在这个例子中模板参数SIZE是一个非类型的整数参数用于指定数组的大小。在实例化Array模板时需要指定一个整数常量作为SIZE的值。
五、模板的特化
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;
}可以看到Less绝对多数情况下都可以正常比较但是在特殊场景下就得到错误的结果。上述示例中p1指向的d1显然小于p2指向的d2对象但是Less内部并没有比较p1和p2指向的对象内容而比较的是p1和p2指针的地址这就无法达到预期而错误。此时就需要对模板进行特化。即在原模板类的基础上针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
2. 函数模版特化
函数模板的特化步骤
必须要先有一个基础的函数模板关键字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()
{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;
}3. 类模版特化
⭕全特化
完全特化是指对于特定的类型或参数提供了一个完全定制的模板实现。在完全特化中给定的特化版本针对特定类型或参数提供了特定的实现这个特化版本将完全替代泛化模板。可以通过显式声明来完成完全特化使用 template 来指示特化版本。
下面是一个完全特化的示例
template typename T
struct MyClass {void doSomething() {// 泛化版本的实现}
};template
struct MyClassint {void doSomething() {// 针对 int 类型的完全特化实现}
};在上面的示例中我们定义了一个模板类 MyClass并对其进行了完全特化。在 MyClassint 的特化版本中我们针对 int 类型提供了一个特定的成员函数实现。
⭕偏特化
偏特化是指对部分类型或参数进行特化针对特定的形式或范围进行自定义处理。偏特化可以有多个参数并对其中一个或多个参数进行特化。相对于完全特化偏特化可以提供更灵活的定制需求。
下面是一个偏特化的示例
template typename T, typename U
struct MyClass {void doSomething() {// 泛化版本的实现}
};template typename T
struct MyClassT, int {void doSomething() {// 对于第二个参数为 int 的偏特化实现}
};在上面的示例中我们定义了一个模板类 MyClass并对其进行了偏特化。在 MyClassT, int 的特化版本中我们针对第二个参数为 int 的情况提供了一个特定的成员函数实现。
注意特化版本的成员函数可以是不同的甚至可以有不同的成员变量和特定的行为。
4. 模版特化应用示例
下面是一个使用函数模板特化的示例展示了如何实现针对特定类型的特定行为
#include iostream// 泛化版本的模板函数
template typename T
void showType(T value) {std::cout Value: value is of unknown type\n;
}// 特化版本的模板函数针对字符串类型
template
void showTypestd::string(std::string value) {std::cout Value: value is a string\n;
}// 特化版本的模板函数针对整型类型
template
void showTypeint(int value) {std::cout Value: value is an integer\n;
}int main() {showType(Hello); // 使用特化版本的模板函数输出 Value: Hello is a stringshowType(123); // 使用特化版本的模板函数输出 Value: 123 is an integershowType(3.14); // 使用泛化版本的模板函数输出 Value: 3.14 is of unknown typereturn 0;
}在上述示例中我们定义了一个模板函数 showType用于根据传入的参数类型显示该值的类型信息。
通过模板特化我们为特定类型std::string和int提供了特定的实现方式。在主函数中我们分别调用了 showType 函数并传入不同的参数类型从而分别调用了泛化版本和特化版本的模板函数。
这样我们可以根据不同的类型提供特定的处理方式以满足特定需求。运行结果为
Value: Hello is a string
Value: 123 is an integer
Value: 3.14 is of unknown type六、模板分离编译
1. 什么是分离编译
一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
2. 模版的分离编译
模板的分离编译可以将模板的声明和实现分离到不同的文件这样每个源文件只需要编译一次模板的实现减少了代码冗余和编译时间。
注意每次进行编译都要进行模版实例化如果不实例化或者参数与实例化的不匹配编译器在进行链接时会报错所以不建议进行分离编译
下面是一个使用模板的分离编译的示例
头文件 mytemplate.h 包含了模板的声明
#ifndef MYTEMPLATE_H
#define MYTEMPLATE_Htemplate typename T
class MyClass {
public:void print(T value);
};#endif源文件 mytemplate.cpp 包含了模板的实现
#include iostream
#include mytemplate.htemplate typename T
void MyClassT::print(T value) {std::cout Value: value std::endl;
}// 显式实例化模板以确保编译器生成该类型的代码
template class MyClassint;主函数所在的源文件 main.cpp 使用了模板但没有包含实现
#include mytemplate.hint main() {MyClassint obj;obj.print(42);return 0;
}在上述示例中mytemplate.h 包含了模板的声明mytemplate.cpp 包含了模板的实现并且通过使用 template class MyClassint 显式实例化了模板的 int 特化版本必须要实例化否则就会报错
七、模版的优缺点
【优点】 通用性模板提供了一种通用的编程方式可以在不同的类型上进行操作和实例化增强了代码的复用性和可扩展性。 静态类型检查模板在编译时进行类型检查可以捕获一些类型错误和逻辑错误提前发现问题并减少运行时错误。 高性能模板生成的代码在编译时会生成特定类型的实现避免了运行时的类型转换和动态分派提供了更高的执行效率。 泛化算法模板可以用于实现各种泛化算法无需为不同的数据类型编写不同的代码减少了重复劳动和代码维护成本。
【缺点】 长编译时间模板通常在编译时进行实例化和展开对于复杂的模板和大规模的代码库编译时间可能会显著增加。 可读性差模板的代码通常比非模板代码更复杂对于初学者或不熟悉模板编程的人来说理解和维护模板代码可能更加困难。 编译错误信息难以理解当模板出现编译错误时编译器生成的错误消息可能很难理解和定位给调试带来一定的困难。 扩展性受限对于已实例化的模板无法在运行时动态地添加新的类型支持如果需要支持新的类型或功能需要重新编译模板。
温馨提示
感谢您对博主文章的关注与支持另外我计划在未来的更新中持续探讨与本文相关的内容会为您带来更多关于C以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新不要错过任何精彩内容
再次感谢您的支持和关注。期待与您建立更紧密的互动共同探索C、算法和编程的奥秘。祝您生活愉快排便顺畅