C++模板详解(template <class T>是什么?可以分为哪几类?能应用在什么地方)

张开发
2026/4/13 14:50:50 15 分钟阅读

分享文章

C++模板详解(template <class T>是什么?可以分为哪几类?能应用在什么地方)
假如现在要写很多个交换两个变量值的函数如下所示void Swap(int x, int y) { int tmp x; x y; y tmp; } void Swap(char x, char y) { char tmp x; x y; y tmp; } void Swap(double x, double y) { double tmp x; x y; y tmp; }使用函数重载虽然可以实现但是有一下几个不好的地方1. 重载的函数仅仅是类型不同代码复用率比较低只要有新类型出现时就需要用户自己增加对应的函数2. 代码的可维护性比较低一个出错可能所有的重载均出错像这样一个个的去写或者去改类型真的很麻烦那这个问题该如何去解决由此模板的概念就衍生出来了一.什么是模板在了解模板之前我们先了解一下什么是泛型编程。泛型编程编写与类型无关的通用代码是代码复用的一种手段。简单来说就是针对广泛的类型所给出的代码并不是针对某种具体的类型模板是泛型编程的基础。C模板是一种泛型编程工具允许编写与类型无关的代码。通过模板可以定义函数或类使其能够处理多种数据类型而无需重复编写代码。模板分为函数模板和类模板两种主要形式。简单来说就是给编译器一个模具你想使用什么类型就传给编译器什么类型的数据让编译器自己去识别类型如下所示template typename T//typename T是模板参数 void Swap(T x, T y) { T tmp x; x y; y tmp; }回到文章开头的问题有了模板这个便捷的工具我们不需要再去将这个函数重载多次更不用去一个个修改函数的类型只需要在主函数中传出我们想使用的数据类型编译器就会自动帮我们识别是不是方便了很多。注意模板参数关键字typename是可以替换为class的也就是template class T这种形式但是不能用struct来替换这样编译就会不通过-下面我们来看一下模板的两种主要形式函数模板和类模板二.函数模板1函数模板的概念函数模板其实就是C中的一种代码复用机制它允许你可以定义一个通用的函数并且你可以不用为每种不同类型的函数重复编写此代码。简单来说他就像是“函数模具”编译器可以根据你传入的实际数据的类型来自动推导出该使用什么类型的函数前面举得交换函数的例子就是模板函数的一种2函数模板的格式templatetypename T或者templateclass T返回值类型 函数名(参数列表){}例如上述交换函数的类型:templatetypename Tvoid Swap(T x,T y){}3函数模板的原理函数模板的原理可以概括为编译器根据调用时的实参类型自动生成具体类型的函数版本其实函数模板的核心原理就是模板的实例化。函数模板本身不是函数而是一个生成函数的蓝图。编译器会在需要的时候遇到调用或取地址时根据模板参数推导出具体类型然后实例化出真正的函数代码。注这个函数模板参数可以有很多个不一定就如上述函数里面只有一个如下​​​​​​template class T T Add(const T left,const T right) { return left right; } template class T1, class T2 T1 Func(const T1 x, const T2 y) { return x y; } int main() { int ret Func(1, 10); cout ret endl; int a 10, b 20; double c 10.1, d 20.1; cout Add(a, b) endl; cout Add(c, d) endl; return 0; }模板参数是按自己的需求决定去定义多少个4函数模板的实例化用不同类型的参数使用函数模板时称为函数模板的实例化。模板参数实例化分为隐式实例化和显式实例 化。1隐式实例化让编译器根据实参推演模板参数的实际类型​​​​​​template class T T Add(const T left,const T right) { return left right; } int main() { int a 10, b 20; double c 10.1, d 20.1; cout Add(a, b) endl; cout Add(c, d) endl; return 0;刚才的交换函数也是隐式实例化的一种2显示实例化在函数名后的中指定模板参数的实际类型如果在只给出了一个模板参数的情况下但是传入的实参类型不同那么编译就会报错如下所示那有没有什么方法来解决这个问题呢那么显式实例化就可以派上用场了如图所示我们可以通过在函数后加上在里面指定我们想要的数据类型就可以避免这种问题这就是显式实例化其实刚才这个例子并不是显式实例化的适用场景那既然研究出显式实例化这个东西一定有它比较适用的场景吧就比如下面这个例子如果有的函数并不能通过模板参数来自动推导类型呢template class T T* Alloc(int n) { return new T[n]; } int main() { //Alloc(10);//这样写是错误的 //因为Alloc函数不能通过编译器自动推导类型所以只能显示实例化来解决 double *p1 Allocdouble(10)//这种才是正确的写法还有一个要特别注意的点就是像刚才的Func函数和Add函数的参数都要加上const修饰否则编译会不通过这是因为涉及到了权限的问题因为不管是隐式类型转换还是显示类型转换都会产生临时变量并不是传实参本身而是这些临时变量而临时变量具有常性如果不加上const修饰则会涉及到权限放大的问题因为权限只能缩小或平移。5)函数模板的匹配原则1. 一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数//专门处理int类型的加法函数 int Add(int left, int right) { return left right; } // 通用加法函数 templateclass T T Add(T left, T right) { return left right; } void Test() { Add(1, 2); // Addint(1, 2); }这种写法编译器是允许的并不会报错2. 对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数 那么将选择模板三.类模板1类模板的概念类模板是 C 中用于生成类的蓝图它允许你定义一个通用的类其中某些成员的类型或行为可以参数化在实例化时再指定具体类型。2类模板的定义方式类模板的定义方式其实和函数模板极其相似如下template class T1,···class Tn class 类名 { 类内定义的内容 }3类模板的实例化类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的类型放在 中即可类模板名字不是真正的类而实例化的结果才是真正的类。咱们来用一个Stack类举例来说明一下// C 正常情况下如果需要不同类型的类int char double 等 typedef int DataType; class Stack { public: // 构造函数 Stack(int capacity 3) //初始化列表 :_array(new DataType[capacity]) // 开辟一个DateType的动态数组并进行初始化 , _capacity(capacity) ,_size(0) {} void Push(DataType data) { // CheckCapacity(); _array[_size] data; _size; } // 其他方法... ~Stack() { delete[]_array; _array nullptr; _size _capacity 0; } private: DataType* _array; int _capacity; int _size; }; int main() { Stack s1; return 0; }假如现在有这么一个场景我需要有一个Stack类是int类型一个是double类型还有一个是char类型该如何做到呢难道要像之前一样复制好多份代码出来然后挨个去改类型吗这样好像是能完成任务。但是将来如果发现这个类有bug呢那到时候要修改的话可就是一个大工程了。这时候类模板就派上用场了template class T class Stack { public: // 构造函数 Stack(size_t capacity 3) //初始化列表 :_array(new T[capacity]) , _capacity(capacity) , _size(0) { } void Push(T data) { _array[_size] data; _size; } ~Stack() { delete[]_array; _array nullptr; _size _capacity 0; } private: T _array; int _capacity; int _size; }; int main() { Stack ints1; Stack doubles2; Stack chars3; return 0; }有一个要注意的点是在类和对象中成员函数的定义和声明是要分开的但是在类模板中如果将某个成员函数的定义和声明编译器是会报错的这边有两个错误第一个错误在普通类中类名就是类型但是在类模板中类名和类型是不一样的Stack是类名而像Stackint这个才是类型第二个错误模板参数的作用范围就只是它下面的一个类或者那一个函数如果出了这个类或者这个函数就会报错出了这个类要想定义成员函数的话就必须要加上属于它自己的模板参数而且它的类型是StackT。总结以上就是我对C中的模板初阶的全部理解有什么错误或者需要补充的欢迎大家指出来我也会积极改正

更多文章