
一:模板初识
模板是C++中实现泛型编程的核心工具。它允许你编写与类型无关的代码,从而定义一族类型、函数或变量。模板由一个或多个模板形参参数化,这些形参在实例化时会被具体的模板实参替换。
语法如下:
template <class/typename T> 声明·template:模板声明关键字。
·class/typename:类型形参关键词。
·T:数据类型。也可以写成其它字母,通常为大写。

#include <iostream>using namespace std;// 定义一个加法函数intadd(int a, int b){int sum = a + b;return sum;}intmain(){int a = 10;int b = 20;int sum = add(a, b); // 30cout << "a + b = :" << sum << endl;return 0;}
#include <iostream>using namespace std;// 定义一个加法函数模板template<typename T>T add(T a, T b){T sum = a + b;return sum;}intmain(){// int类型的加法int a = 10;int b = 20;int sum = add(a, b); // 30cout << "a + b = :" << sum << endl;// float类型的加法float c = 1.8;float d = 2.2;float sum1 = add(c, d); // 4.0cout << "c + d = :" << sum1 << endl;// double类型的加法double e = 3.1;double f = 2.5;double sum2 = add(e, f); // 5.6cout << "e + f = :" << sum2 << endl;return 0;}
二:模板形参与模板实参
参考:https://zh.cppreference.com/cpp/language/template_parameters
模板以一个或多个模板形参参数化,形参有三种:类型模板形参、非类型模板形参和模板模板形参。
1:模板形参
模板形参:在模板定义中声明的占位符,可以是:
·类型形参(typename T 或 class T):代表一个未知类型。
·非类型形参(int N):代表一个具体的值(如整数、指针等)。
·模板形参(template <typename> class C):表示一个未知的类模板。
2:模板实参
模板实参:在实例化时传递给模板形参的具体类型或值。
示例程序如下:
#include<iostream>using namespace std;#include<vector>// 1. 类型模板形参: T// 2. 非类型模板形参: int N// 3. 模板模板形参: template<typename> class Containertemplate<typename T, int N, template<typename> class Container>class MyData{private:// 使用类型形参 T 和非类型形参 NT data[N]; // 数组 data 用于存储 N 个类型为 T 的元素// 使用模板模板形参 ContainerContainer<T> backup; // 备份容器,用于存储类型为 T 的元素public:voidsetValue(int index, T value){if (index < N){data[index] = value; // 如果索引小于 N,将值存储在数组中}}T getValue(int index){return data[index];}};intmain(){// 1. 类型实参: double// 2. 非类型实参: 5 (一个具体的整数值)// 3. 模板模板实参: std::vectorMyData<double, 5, std::vector> myObj;myObj.setValue(0, 3.14);cout << "Value: " << myObj.getValue(0) << endl;return 0;}
输出结果为:Value:3.14
三:函数模板
作用:定义一系列接受不同类型参数的函数。
示例程序如下:
#include<iostream>using namespace std;// 定义一个函数模板,用于交换两个变量的值template <typename T> // T 是类型形参voidmySwap(T& a, T& b){T temp = a;a = b;b = temp;}intmain(){int x = 1, y = 2;mySwap(x, y); // 实例化为 mySwap<int>(int&, int&)cout << "x = " << x << ", y = " << y << endl; // 输出: x=2, y=1double u = 1.5, v = 2.5;mySwap(u, v); // 实例化为 mySwap<double>(double&, double&)cout << "u = " << u << ", v = " << v << endl; // 输出: u=2.5, v=1.5return 0;}
输出结果为:

四:类模板
作用:定义一系列操作相同但数据类型不同的类。
示例程序如下:
#include<iostream>using namespace std;// 定义一个类模板,用于存储一个“盒子”template <typename T> // T 是类型形参class Box{private:T content;public:Box(T value) : content(value) {}T getContent()const{return content;}};intmain(){Box<int> intBox(123); // 实例化为存储 int 的盒子Box<std::string> strBox("Hello"); // 实例化为存储 string 的盒子cout << intBox.getContent() << endl; // 输出: 123cout << strBox.getContent() << endl; // 输出: Helloreturn 0;}
输出结果为:123,Hello
五:别名模板
作用:为模板化的类型创建一个别名,简化代码。
示例程序如下:
#include<iostream>using namespace std;#include<vector>#include<string>// 为“存储 T 类型元素的向量”这个模板族起一个别名template <typename T>using Vec = std::vector<T>; // 等同于 std::vector<T>// 为“存储字符串的向量”这个具体特化起一个别名using StrVec = Vec<std::string>; // 等同于 std::vector<std::string>intmain(){Vec<int> intVec; // 等同于 std::vector<int>StrVec strVec; // 等同于 std::vector<std::string>strVec.push_back("Hello");cout << strVec[0] << endl;cout << strVec.at(0) << endl;return 0;}
输出结果为:Hello,Hello
六:变量模板
作用:定义一系列变量,例如数学常数在不同类型下的值。
示例程序如下:
#include<iostream>using namespace std;// 定义一个变量模板,表示π的值template <typename T> // T 是类型形参constexpr T pi = T(3.1415926535897932385L);intmain(){cout << pi<float> << endl; // 以 float 类型实例化,输出: 3.14159cout << pi<double> << endl; // 以 double 类型实例化,输出: 3.14159cout << pi<int> << endl; // 以 int 类型实例化,输出: 3return 0;}
输出结果为:

七:约束与概念
作用:为模板形参添加编译期约束,使错误信息更清晰,并允许函数重载和模板特化基于类型特征。
示例程序如下:
#include<iostream>using namespace std;#include<concepts>// 包含标准概念// 定义一个概念:类型 T 必须是整数类型template <typename T>concept Integral = std::is_integral_v<T>;// 约束:函数 add 只接受满足 Integral 概念的类型template <Integral T> // T 是类型形参,受 Integral 约束T add(T a, T b){return a + b;}intmain(){cout << add(1, 2) << endl; // 3,OK,int 满足 Integral// cout << add(1.5, 2.5) << endl; // 错误!double 不满足 Integral,编译期报错return 0;}
输出结果为:3
总结:

注意:
·模板并不是万能的,不要为了写模板而写模板。
·用好模板可以解决自定义类型的通用化。
八:奇异递归模板
参考:https://zh.cppreference.com/cpp/language/crtp
运行时多态(动态多态)使用传统继承+虚函数,会引入虚表(vtable),每次调用虚函数都要通过间接寻址,影响性能。
奇异递归模板模式(Curiously Recurring Template Pattern,CRTP)是一种C++编译期多态技术(静态多态),其核心思想为:把派生类作为基类的模板参数。即CRTP就是让子类把自己作为模板参数传递给父类。
基本语法结构如下:
template <typename Derived>classBase{// 基类定义};// 关键点:派生类将自己作为模板参数传递给基类classDerived : publicBase<Derived>{// 派生类定义};
Derived继承自Base<Derived>,也就是说,子类 把自己“递归”地传给了父类。这就是“奇异递归”的由来。
CRTP在C++中主要有两种用途:
·静态多态(static polymorphism)。
·添加方法,同时精简代码。
所谓静态多态,是指CRTP通过派生类对基类模板实例化,也可以实现类似动态多态的效果。最鲜明的特点就是:
1:继承自模板类;
2:使用派生类作为模板参数特化基类;静态多态实现原理:编译时编译器会为模板生成一份实例化代码,根据对应实例调用对应函数。
静态多态与和动态的区别是:多态是动态绑定(运行时绑定),CRTP是静态绑定(编译时绑定)。
其中,动态多态在实现多态时,需要重写虚函数,这种运行时绑定的操作往往需要查找虚表等,效率低。而template的核心技术在于编译期多态机制,与运行期多态相比,这种机制提供编译期多态性,给了程序运行期无可比拟的效率优势。示例程序如下:
#include<iostream>using namespace std;template<typename T>class Base{public:voidRun(){static_cast<T*>(this)->Run();}};class Derived : public Base<Derived>{public:voidRun(){cout << "Derived::Run()" << endl;}};intmain(){Base<Derived>* ptr = new Derived();ptr->Run();return 0;}
输出结果为:Derived::Run()
使用派生类作为模板参数特化基类,这样做的目的是为了基类中使用派生类,从基类的角度来看,派生类其实也是基类,通过向下转换。因此,基类可以通过static_cast把其转换到派生类,从而使用派生类的成员,形式如下:
·T* derived = static_cast<T*>(this)
·T& derived = static_cast<T&>(*this)
这两种方式都是可以使用的。
注意:这里不使用dynamic_cast,因为dynamic_cast一般是为了确保在运行期向上向下转换的正确性。CRTP的设计是:派生类就是基类的模板参数,因此static_cast就可以了。
夜雨聆风