大纲 C++ 模板与泛型 泛型编程
泛型编程是一种以独立于任何特定类型的方式编写代码的编程方式。在使用泛型编程时,需要提供具体程序实例所需的类型、习惯或值。 在 C++ 中,泛型编程(基于模板)可以用于实现静态多态(编译期多态),即在编译阶段根据具体类型生成对应的代码 ;与之对应,虚函数机制用于实现动态多态(运行时多态),通过基类指针或引用在运行时决定调用哪个派生类的实现。模板的作用
模板是泛型编程的基础。它是一种用于创建类或函数的蓝图或公式,开发者只需为这些蓝图提供足够的信息,模板便能真正执行并生成具体的代码。 模板的类型支持
模板支持将类型作为参数进行程序设计,从而直接支持泛型编程过程。也就是说,C++ 的模板机制允许在定义类或函数时,将类型作为参数传递,实现代码的通用性。 模板的两种分类
函数模板函数模板是用于生成特定类型函数的蓝图。它允许在定义函数时,将参数或返回值的类型作为模板参数,从而实现通用操作(如比较、交换等)。 比如,基于函数模板编写一个比较两个值大小的函数,可以适用于 int、double、string 等多种类型。 类模板类模板是用于生成特定类型类的蓝图。它允许在定义类时,将成员变量或成员函数的类型作为模板参数,从而实现通用数据结构或容器(如栈、队列、数组等)。 比如,基于类模板实现一个可存储任意类型元素的栈类,使用时再指定具体类型(如 Stack<int>、Stack<string>)。 模板基础 函数模板 函数模板的概述 模的板定义以 template 关键字开头
后面紧跟 <>,其中称为模板参数列表(或模板实参列表)。 模板参数列表规则
至少包含一个模板参数。 多个参数用逗号分隔。 每个模板参数前需使用 typename 或 class 关键字(此处 class 非类定义用途)。 常见写法示例
单个模板参数template<class T>或者 template<typename T> 多个模板参数:template<class T, class Q>或者 template<typename T, typename Q> 模板参数的含义
表示函数定义中使用的类型或值,类似于函数参数列表的作用。 模板实参的指定方式
显式指定:使用 <> 包裹模板实参,比如 max<int>(3, 5);。 隐式推断:编译器根据函数调用上下文自动推断模板实参,无需显式指定。 类型参数 T 的作用
T 是模板的类型参数,本身只是一个占位符。在模板被使用时,编译器会进行模板实例化,根据调用上下文推导出 T 的具体类型。这个过程通常发生在编译期,而不是运行时 。函数模板的使用 关键点
函数模板的调用与普通函数的调用区别不大。在调用函数模板时,编译器会根据调用时提供的实参,去推断模板参数列表中的参数(即模板形参)的类型。有些时候,光凭函数实参是无法准确推断出模板参数的,这时候就需要使用 <> 来主动提供模板参数了,比如 max<int>(3, 5);。值得注意的是,模板参数有时是由编译器推断出来的,推断的依据是调用函数时传入的实参。当编译器推断出模板的形参类型后,编译器会实例化一个特定版本的函数。函数模板只有被调用(实例化)时,编译器才会生成对应的代码,仅定义模板编译器是不会生成任何代码的。由于编译器在生成代码的时候,需要找到函数的函数体,所以函数模板的定义通常是写在 .h 头文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;template <typename T>T maxNum (T a, T b) { return (a > b) ? a : b; } int main () { int result1 = maxNum (10 , 20 ); double result2 = maxNum (3.14 , 2.71 ); double result3 = maxNum<double >(10 , 3.14 ); return 0 ; }
非类型模板参数 关键点
在定义函数模板时,因为 T 前边有一个 typename / class,这表示 T 代表一个类型,是一个类型参数。那么在模板参数列表里面,还可以定义非类型参数,这里的非类型参数代表的是一个值。既然非类型参数代表一个值,那么就不能用 typename / class 这种关键字来修饰这个值,而是必须要用具体的类型名称来指定非类型参数。比如,非类型参数 S 如果是个整型,就可以写成 template<typename T, int S>。当函数模板实例化时,这种非类型参数的值通常需要由用户显式指定(一般情况下编译器不会自动推断),并且这些值都必须是常量表达式,因为函数模板的实例化是在编译阶段完成的。另外,无论是函数模板还是类模板,非类型模板参数都可以指定默认值,比如 template <typename T, int A = 5>,但需要注意非类型模板参数的默认值必须从右向左连续定义。特别注意,浮点型不能作为非类型模板参数,比如 float、double。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <cstring> #include <iostream> using namespace std;template <int A, int B>int sum () { return A + B; } template <unsigned L1, unsigned L2>int charscomp (const char (&c1)[L1], const char (&c2)[L2]) { return strcmp (c1, c2); } template <typename T, int A = 5 >void repeatPrint (T value) { for (int i = 0 ; i < A; ++i) { cout << value << " " ; } cout << endl; } void test01 () { cout << "========== test01() ======" << endl; int result1 = sum<3 , 5 >(); cout << result1 << endl; } void test02 () { cout << "========== test02() ======" << endl; int result1 = charscomp ("test2" , "test" ); cout << result1 << endl; } void test03 () { cout << "========== test03() ======" << endl; repeatPrint<int , 2 >(10 ); repeatPrint<double , 3 >(3.14 ); repeatPrint<const char *, 4 >("Hi" ); repeatPrint<float >(6.7f ); } int main () { test01 (); test02 (); test03 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 ========== test01() ====== 8 ========== test02() ====== 50 ========== test03() ====== 10 10 3.14 3.14 3.14 Hi Hi Hi Hi 6.7 6.7 6.7 6.7 6.7
类模板 类模板的概述 类模板可以用来实例化一个特定的类。由于编译器不能为类模板自动推断模板参数类型,因此为了使用类模板,必须在模板名后边使用 <> 来提供额外的信息,这些信息其实就是对应着模板参数的具体类型或值。类模板的核心优势在于:同一套代码,可以应付不同的数据类型,代码精简多了,避免了为每种类型重复编写类似的类。在 C++ 模板中,只要一个名称依赖于模板参数,编译器在第一阶段无法判断它是类型还是变量,默认按非类型解析;因此无论是在类模板还是函数模板中,都必须使用 typename 显式声明它是类型。
对比项 函数模板 类模板 模板实参推断 支持自动推断 不支持(C++17 之前),需显式指定 使用方式 max(1, 2)vector<int>实例化时机 调用时实例化(编译期实例化) 使用类型时实例化(编译期实例化) 函数 / 成员函数生成 调用时生成 成员函数按需实例化(懒生成)
类模板的使用 关键点
C++ 类模板的成员函数是指在类模板中定义的函数,这些函数同样依赖于模板参数,因此在类外实现类模板的成员函数时,需要保留模板参数声明(比如 template<typename T>)并使用作用域限定符(如 MyVector<T>::func())进行定义。由于模板是在编译期进行实例化的,成员函数的实现通常需要放在头文件中,以便编译器在使用时能够生成具体类型的代码。此外,在成员函数中如果使用了依赖于模板参数的类型(如 MyVector<T>::iterator),编译器默认无法判断其是类型还是变量,此时必须使用 typename 进行显式说明(如 typename MyVector<T>::iterator)。总体来说,类模板的成员函数与普通类成员函数在语法上类似,但需要额外注意模板参数、依赖名称解析以及编译期实例化机制。
MyVector.hpp,简单模拟实现 STL 的 vector 容器,不考虑扩容、移动语义等实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #pragma once #include <iostream> using namespace std;template <typename T>class MyVector {public : using iterator = T*; MyVector (); ~MyVector (); MyVector (const MyVector& v); MyVector& operator =(const MyVector& v); iterator begin () ; iterator end () ; bool push_back (const T& t) ; private : T* m_data; size_t m_size; size_t m_capacity; }; template <typename T>MyVector<T>::MyVector () { m_size = 0 ; m_capacity = 10 ; m_data = new T[m_capacity]; } template <typename T>MyVector<T>::~MyVector () { if (m_data) { delete [] m_data; m_data = nullptr ; } } template <typename T>MyVector<T>::MyVector (const MyVector<T>& v) { m_size = v.m_size; m_capacity = v.m_capacity; m_data = new T[m_capacity]; for (size_t i = 0 ; i < m_size; ++i) { m_data[i] = v.m_data[i]; } } template <typename T>MyVector<T>& MyVector<T>::operator =(const MyVector<T>& v) { if (this == &v) { return *this ; } T* new_data = new T[v.m_capacity]; for (size_t i = 0 ; i < v.m_size; ++i) { new_data[i] = v.m_data[i]; } delete [] m_data; m_data = new_data; m_size = v.m_size; m_capacity = v.m_capacity; return *this ; } template <typename T>typename MyVector<T>::iterator MyVector<T>::begin () { return m_data; } template <typename T>typename MyVector<T>::iterator MyVector<T>::end () { return m_data + m_size; } template <typename T>bool MyVector<T>::push_back (const T& t) { if (m_size == m_capacity) { return false ; } m_data[m_size] = t; ++m_size; return true ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include "MyVector.hpp" using namespace std;void show (MyVector<int >& v) { for (MyVector<int >::iterator it = v.begin (); it != v.end (); ++it) { cout << *it << " " ; } cout << endl; } int main () { MyVector<int > v1; v1.push_back (1 ); v1.push_back (3 ); v1.push_back (5 ); show (v1); MyVector<int > v2 = v1; show (v2); v2 = v1; show (v2); return 0 ; }
程序运行输出的结果如下:
非类型模板参数 关键点
在定义类模板时,因为 T 前边有一个 typename / class,这表示 T 代表一个类型,是一个类型参数。那么在模板参数列表里面,还可以定义非类型参数,这里的非类型参数代表的是一个值。既然非类型参数代表一个值,那么就不能用 typename / class 这种关键字来修饰这个值,而是必须要用具体的类型名称来指定非类型参数。比如,非类型参数 S 如果是个整型,就可以写成 template<typename T, int S>。当类模板实例化时,这种非类型参数的值通常需要由用户显式提供(一般情况下编译器不会自动推断),并且这些值都必须是常量表达式,因为类模板的实例化是在编译阶段完成的。另外,无论是函数模板还是类模板,非类型模板参数都可以指定默认值,比如 template <typename T, int A = 5>,但需要注意非类型模板参数的默认值必须从右向左连续定义。特别注意,浮点型不能作为非类型模板参数,比如 float、double。
MyVector.hpp,简单模拟实现 STL 的 vector 容器,不考虑扩容、移动语义等实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #pragma once #include <iostream> using namespace std;template <typename T, int C = 10 >class MyVector { public : using iterator = T*; MyVector (); ~MyVector (); MyVector (const MyVector& v); MyVector& operator =(const MyVector& v); iterator begin () ; iterator end () ; bool push_back (const T& t) ; private : T* m_data; size_t m_size; size_t m_capacity; }; template <typename T, int C>MyVector<T, C>::MyVector () { m_size = 0 ; m_capacity = C > 0 ? C : 10 ; m_data = new T[m_capacity]; } template <typename T, int C>MyVector<T, C>::~MyVector () { if (m_data) { delete [] m_data; m_data = nullptr ; } } template <typename T, int C>MyVector<T, C>::MyVector (const MyVector<T, C>& v) { m_size = v.m_size; m_capacity = v.m_capacity; m_data = new T[m_capacity]; for (size_t i = 0 ; i < m_size; ++i) { m_data[i] = v.m_data[i]; } } template <typename T, int C>MyVector<T, C>& MyVector<T, C>::operator =(const MyVector<T, C>& v) { if (this == &v) { return *this ; } T* new_data = new T[v.m_capacity]; for (size_t i = 0 ; i < v.m_size; ++i) { new_data[i] = v.m_data[i]; } delete [] m_data; m_data = new_data; m_size = v.m_size; m_capacity = v.m_capacity; return *this ; } template <typename T, int C>typename MyVector<T, C>::iterator MyVector<T, C>::begin () { return m_data; } template <typename T, int C>typename MyVector<T, C>::iterator MyVector<T, C>::end () { return m_data + m_size; } template <typename T, int C>bool MyVector<T, C>::push_back (const T& t) { if (m_size == m_capacity) { return false ; } m_data[m_size] = t; ++m_size; return true ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include "MyVector.hpp" using namespace std;template <int C>void show (MyVector<int , C>& v) { for (typename MyVector<int , C>::iterator it = v.begin (); it != v.end (); ++it) { cout << *it << " " ; } cout << endl; } int main () { MyVector<int , 5 > v1; v1.push_back (1 ); v1.push_back (3 ); v1.push_back (5 ); show (v1); MyVector<int , 5 > v2 = v1; show (v2); v2 = v1; show (v2); return 0 ; }
程序运行输出的结果如下:
模板进阶 typename 的使用场合 在 C++ 中,typename 常见的三种使用场合:
(1) 函数模板的定义:
1 2 3 4 5 template <typename T>T maxNum (T a, T b) { ...... }
(2) 类模板的定义:
1 2 3 4 5 template <typename T>class MyVector { ...... }
(3) 类外访问类模板的类型成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename T>class MyVector {public : using iterator = T*; iterator begin () ; ...... } template <typename T>typename MyVector<T>::iterator MyVector<T>::begin () { ...... }
函数指针的使用方式 函数指针作为其他函数的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;typedef int (*FunType) (int , int ) ;int sum (int a, int b) { return a + b; } int testFunc (int i, int j, FunType func) { return func (i, j); } int main () { int result = testFunc (3 , 5 , sum); cout << result << endl; return 0 ; }
程序运行输出的结果如下:
函数模板的趣味写法 在函数模板中,使用函数指针和函数对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include <iostream> using namespace std;typedef int (*FunType) (int , int ) ;template <typename T, typename F>int testFunc (const T &i, const T &j, F func) { return func (i, j); } int sum (int a, int b) { return a + b; } class Test {public : Test () { cout << "Test()" << endl; } ~Test () { cout << "~Test()" << endl; } Test (const Test &t) { cout << "Test(const Test&)" << endl; } int operator () (int i, int j) const { return i + j; } }; void test01 () { cout << "------------ test01() ---------------" << endl; int result = testFunc (1 , 3 , sum); cout << result << endl; } void test02 () { cout << "------------ test02() ---------------" << endl; Test obj; int result2 = testFunc (5 , 6 , obj); cout << result2 << endl; } void test03 () { cout << "------------ test03() ---------------" << endl; int result3 = testFunc (7 , 8 , Test ()); cout << result3 << endl; } int main () { test01 (); test02 (); test03 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 ------------ test01() --------------- 4 ------------ test02() --------------- Test() Test(const Test&) ~Test() 11 ~Test() ------------ test03() --------------- Test() ~Test() 15
函数指针与函数对象的简单介绍
对比 函数指针 函数对象 是否能存状态 ❌ ✅ 是否可内联优化 一般不行 ✅ 灵活性 低 高
默认模板参数的使用 函数模板使用默认模板参数
模板参数设置默认类型(typename T = int) 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> using namespace std;template <typename T = int >T add (T a, T b) { return a + b; } int main () { cout << add (1 , 2 ) << endl; cout << add<double >(1.5 , 2.3 ); }
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std;template <typename T>T add (T a, T b = 10 ) { return a + b; } int main () { cout << add (5 , 3 ) << endl; cout << add (5 ) << endl; }
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> using namespace std;template <typename T = int >T add (T a, T b = 10 ) { return a + b; } int main () { cout << add (5 ) << endl; cout << add<double >(5.5 ) << endl; }
类模板使用默认模板参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <exception> #include <iostream> using namespace std;template <typename T, size_t size = 10 >class MyArray { public : T& operator [](size_t index) { if (index < 0 || index > size - 1 ) { throw out_of_range ("index out of range" ); } return array[index]; } const T& operator [](size_t index) const { if (index < 0 || index > size - 1 ) { throw out_of_range ("index out of range" ); } return array[index]; } private : T array[size]; }; int main () { MyArray<int > array; array[0 ] = 10 ; array[1 ] = 20 ; cout << array[0 ] << ", " << array[1 ] << endl; MyArray<int , 5 > array2; array2[0 ] = 40 ; array2[1 ] = 50 ; cout << array2[0 ] << ", " << array2[1 ] << endl; return 0 ; }
程序运行输出的结果如下:
成员函数模板的使用 在 C++ 中,无论是普通类还是类模板,其成员函数(包括构造函数)都可以声明为函数模板(称为成员函数模板)。但由于虚函数(virtual)机制依赖于运行时多态,而函数模板是在编译期间实例化的,因此成员函数模板不能被声明为虚函数(virtual)。
特别注意
对于类模板,其成员函数(包括普通成员函数和成员函数模板)通常采用 "按需实例化"(Lazy Instantiation)机制。只有当程序中实际使用(例如发生函数调用)某个成员函数时,编译器才会对该函数进行实例化。如果某个成员函数从未被使用,则不会被实例化。
普通类的成员函数模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;class Test {public : template <typename T> T sum (T v1, T v2) { return v1 + v2; } }; int main () { Test obj; int result = obj.sum (3 , 5 ); cout << result << endl; return 0 ; }
类模板的成员函数模板 第一种写法(类的声明与定义写在一起)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;template <typename A>class Test {public : template <typename B> void func (A v1, B v2) { cout << v1 << ", " << v2 << endl; } }; int main () { Test<int > obj; obj.func (3 , 5.3 ); return 0 ; }
第二种写法(类的声明与定义分开写)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;template <typename A>class Test {public : template <typename B> void func (A v1, B v2) ; }; template <typename A>template <typename B>void Test<A>::func (A v1, B v2) { cout << v1 << ", " << v2 << endl; } int main () { Test<int > obj; obj.func (3 , 5.3 ); return 0 ; }
模板显式实例化的使用 为了避免在多个 .cpp 文件中重复实例化同一个模板(包括类模板和函数模板),C++ 提供了 “显式实例化”(Explicit Instantiation)机制。通过在某一个 .cpp 源文件中进行显式实例化,可以将模板实例化集中到单一个翻译单元,从而避免重复生成代码并减少编译和链接开销。实际上,编译器和链接器通常会对重复的模板实例进行合并(如 COMDAT 机制),但显式实例化仍然可以用于控制模板实例化的位置,并在一定程度上减少编译时间和代码膨胀。
特别注意
显式实例化(Explicit Instantiation)既适用于类模板,也适用于函数模板,其本质是为指定类型生成对应的模板实例代码。 函数模板的显式实例化:其本质是在某个 .cpp 源文件中为指定类型(如 int)生成对应的函数实现,其他翻译单元通过 extern template 声明避免函数模板重复实例化。 类模板的显式实例化:其本质是在某个 .cpp 源文件中为某个具体类型(如 MyClass<int>)生成完整的实例代码(包括类结构及其所有已定义的成员函数实现),其他翻译单元通过 extern template 声明避免类模板重复实例化。 类模板的显式实例化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef MYCLASS_H #define MYCLASS_H #include <iostream> using namespace std;template <typename T>class MyClass {public : void print (T value) ; }; extern template class MyClass <int >;#endif
1 2 3 4 5 6 7 8 9 10 #include "MyClass.h" template <typename T>void MyClass<T>::print (T value) { cout << "value: " << value << endl; } template class MyClass <int >;
1 2 3 4 5 6 7 #include "MyClass.h" int main () { MyClass<int > obj; obj.print (42 ); return 0 ; }
函数模板的显式实例化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef FUNC_H #define FUNC_H #include <iostream> using namespace std;template <typename T>void printValue (T value) ;extern template void printValue<int >(int );#endif
1 2 3 4 5 6 7 8 9 10 #include "func.h" template <typename T>void printValue (T value) { cout << "value: " << value << endl; } template void printValue<int >(int );
1 2 3 4 5 6 #include "func.h" int main () { printValue (42 ); return 0 ; }
使用 using 定义别名模板 在 C++ 中,使用 using 定义别名模板(也叫模板别名)是一种比 typedef 更强大且语法更清晰的特性。其核心形式为 template<typename ...> using 别名 = 原类型;,它允许为一个依赖于模板参数的类型表达式创建一个新的名称。与只能定义具体类型别名的 typedef 不同,using 可以直接定义别名模板(Alias Template),从而简化复杂的模板类型书写。例如,template<typename T> using Vec = std::vector<T>; 为 std::vector 创建了别名,之后 Vec<int> 就等价于 std::vector<int>。这一特性极大地提升了泛型编程中类型代码的可读性和可维护性。
特别注意
在 C++ 中,using 除了可以用于定义类型别名,还可以定义别名模板,它包含了 typedef 的所有功能。
定义类型别名 使用 typedef 定义类型别名的写法(C 语言风格) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <map> using namespace std;int main () { typedef unsigned int uint_t ; uint_t a; typedef map<string, int > map_s_i; map_s_i m; m.insert ({"first" , 1 }); m.insert ({"second" , 2 }); typedef map<string, string> map_s_s; map_s_s m2; m2.insert ({"first" , "one" }); m2.insert ({"second" , "two" }); return 0 ; }
使用 using 定义类型别名的写法(现代 C++ 风格) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <map> using namespace std;int main () { using uint_t = unsigned int ; uint_t a; using map_s_i = map<string, int >; map_s_i m; m.insert ({"first" , 1 }); m.insert ({"second" , 2 }); using map_s_s = map<string, string>; map_s_s m2; m2.insert ({"first" , "one" }); m2.insert ({"second" , "two" }); return 0 ; }
定义别名模板 在 C++ 中,使用 using 定义别名模板
这里假设需要定义一个 map 类型,其中 key 的类型固定不变,但 value 的类型支持自定义,实现方式有两种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <map> using namespace std;template <typename st>struct map_s { typedef map<string, st> type; }; int main () { map_s<int >::type m; m.insert ({"first" , 1 }); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <map> using namespace std;template <typename T>using str_map_t = std::map<string, T>; int main () { str_map_t <int > m; m.insert ({"first" , 1 }); return 0 ; }
在 C++ 中,使用 using 定义别名模板 + 函数指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;template <typename T>using funcType = T (*)(T, T);int sum (int a, int b) { return a + b; } int main () { funcType<int > func; func = sum; int result = func (1 , 3 ); cout << result << endl; return 0 ; }
显式指定模板实参 在 C++ 中,” 显式指定模板实参” 是指在调用函数模板或实例化类模板时,由程序员在模板名后紧跟的尖括号内明确写出模板参数所对应的具体类型或值,而不是依赖编译器进行模板实参推导。这一语法通常用于解决编译器无法自动推导实参的场景,例如函数模板的返回类型与参数列表无关,或者存在多个模板参数但仅有部分能推导时,也可以显式指定部分实参,其余由编译器补全。通过显式指定,程序员可以精确控制模板实例化的行为,消除因类型推导可能产生的歧义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;template <typename T1, typename T2, typename T3>T1 sum (T2 i, T3 j) { T1 result = i + j; return result; } int main () { auto result = sum<double , double , double >(2000000000 , 2000000000 ); cout << result << endl; return 0 ; }
程序运行输出的结果如下:
模板高级 模板特例化 类模板特例化 C++ 的模板特例化(Template Specialization)是一种允许为特定类型或特定值提供模板自定义实现的机制,用于处理泛型模板无法满足特殊需求的场景。它主要分为全特化(Full Specialization)和偏特化(Partial Specialization)两种形式:全特化为所有模板参数指定具体类型或值,生成一个完全确定的独立版本;偏特化则仅针对部分参数进行限制(如将泛型指针特化为特定指针类型),从而生成更特殊的模板版本。通过模板特例化,程序员可以在保持泛型接口统一的同时,为某些特定类型优化算法效率或修复语义问题,是模板元编程中实现条件分支和类型萃取的核心手段之一。值得注意的是,类模板全特化后是一个具体类(不再接受模板参数),而类模板偏特化后本质上仍然是类模板(仍然需要实例化) 。
特别注意
在 C++ 中,类模板必须先存在泛化版本(至少是声明),才能为其定义任何特化版本(全特化或偏特化);特化是建立在泛化基础之上的特殊实现,不能独立存在 。泛化版本是指定义模板时最通用、最基础的版本,其模板参数保持抽象(如 typename T),不针对任何具体类型或值进行特化;它是所有特化版本的原始模板,为后续可能的特例化提供了基础框架。
类模板全特化 常规全特化(特化类模板)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <iostream> using namespace std;template <typename T, typename U>class TestClass {public : TestClass () { cout << "TestClass() 泛化版本" << endl; } void print (T t, U u) { cout << "printf(T, U) 泛化版本" << endl; } }; template <>class TestClass <int , int > {public : TestClass () { cout << "TestClass() 全特化版本" << endl; } void print (int t, int u) { cout << "printf(int, int) 全特化版本" << endl; } }; template <>class TestClass <double , double > {public : TestClass () { cout << "TestClass() 全特化版本" << endl; } void print (double t, double u) { cout << "printf(double, double) 全特化版本" << endl; } }; int main () { TestClass<int , double > t; t.print (3 , 5.61 ); TestClass<int , int > t2; t2.print (7 , 9 ); TestClass<double , double > t3; t3.print (2.45 , 6.7 ); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 TestClass() 泛化版本 printf(T, U) 泛化版本 TestClass() 全特化版本 printf(int, int) 全特化版本 TestClass() 全特化版本 printf(double, double) 全特化版本
特化成员函数,而不是类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> using namespace std;template <typename T, typename U>class TestClass {public : TestClass () { cout << "TestClass() 泛化版本" << endl; } void print (T t, U u) { cout << "printf(T, U) 泛化版本" << endl; } }; template <>void TestClass<int , int >::print (int t, int u) { cout << "printf(int, int) 全特化版本" << endl; } template <>void TestClass<double , double >::print (double t, double u) { cout << "printf(double, double) 全特化版本" << endl; } int main () { TestClass<int , double > t; t.print (3 , 5.61 ); TestClass<int , int > t2; t2.print (7 , 9 ); TestClass<double , double > t3; t3.print (2.45 , 6.7 ); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 TestClass() 泛化版本 printf(T, U) 泛化版本 TestClass() 泛化版本 printf(int, int) 全特化版本 TestClass() 泛化版本 printf(double, double) 全特化版本
类模板偏特化 特别注意
C++ 的类模板偏特化(局部特化)允许程序员在不放弃所有模板参数的情况下,对模板参数的数量子集进行具体化,或对参数的类型 / 值模式施加更具体的约束(如指针、数组、常量等),从而生成更匹配特定使用场景的模板定义。它不同于全特化(所有参数固定),也不同于函数模板的重载机制。
类模板从参数数量上进行偏特化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <iostream> using namespace std;template <typename T, typename U, typename W>class TestClass {public : TestClass () { cout << "TestClass() 泛化版本" << endl; } void print (T t, U u, W w) { cout << "printf(T, U, W) 泛化版本" << endl; } }; template <typename U>class TestClass <int , U, int > {public : TestClass () { cout << "TestClass() 偏特化版本" << endl; } void print (int t, U u, int ) { cout << "printf(int, U, int) 偏特化版本" << endl; } }; template <typename U>class TestClass <double , U, double > {public : TestClass () { cout << "TestClass() 偏特化版本" << endl; } void print (double t, U u, double ) { cout << "printf(double, U, double) 偏特化版本" << endl; } }; int main () { TestClass<int , int , double > t1; t1.print (1 , 2 , 3.14 ); TestClass<int , double , int > t2; t2.print (1 , 2.55 , 3 ); TestClass<double , int , double > t3; t3.print (1.0 , 2 , 3.14 ); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 TestClass() 泛化版本 printf(T, U, W) 泛化版本 TestClass() 偏特化版本 printf(int, U, int) 偏特化版本 TestClass() 偏特化版本 printf(double, U, double) 偏特化版本
类模板从参数范围上进行偏特化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include <iostream> using namespace std;template <typename T>class TestClass {public : TestClass () { cout << "TestClass() 泛化版本" << endl; } void print (T t) { cout << "printf(T) 泛化版本" << endl; } }; template <typename T>class TestClass <const T> {public : TestClass () { cout << "TestClass() 偏特化版本" << endl; } void print (T t) { cout << "printf(const T) 偏特化版本" << endl; } }; template <typename T>class TestClass < T *> {public : TestClass () { cout << "TestClass() 偏特化版本" << endl; } void print (T *t) { cout << "printf(T *) 偏特化版本" << endl; } }; template <typename T>class TestClass < T &> {public : TestClass () { cout << "TestClass() 偏特化版本" << endl; } void print (T &t) { cout << "printf(T &) 偏特化版本" << endl; } }; template <typename T>class TestClass < T &&> {public : TestClass () { cout << "TestClass() 偏特化版本" << endl; } void print (T &&t) { cout << "printf(T &&) 偏特化版本" << endl; } }; int main () { int num1 = 10 ; const int num2 = 20 ; int &num3 = num1; TestClass<int > t1; t1.print (1 ); TestClass<const int > t2; t2.print (num2); TestClass<int *> t3; t3.print (&num1); TestClass<int &> t4; t4.print (num3); TestClass<int &&> t5; t5.print (move (num3)); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 TestClass() 泛化版本 printf(T) 泛化版本 TestClass() 偏特化版本 printf(const T) 偏特化版本 TestClass() 偏特化版本 printf(T *) 偏特化版本 TestClass() 偏特化版本 printf(T &) 偏特化版本 TestClass() 偏特化版本 printf(T &&) 偏特化版本
类模板从参数范围上进行偏特化,这里的参数范围是指什么?
偏特化允许对类模板参数施加更具体的模式或约束(即缩小模板参数的范围),而不是仅仅减少参数个数。常见形式包括: 指针类型:template <typename T> struct A<T*> 引用类型:template <typename T> struct A<T&> 常量类型:template <typename T> struct A<const T> 数组类型:template <typename T, int N> struct A<T[N]> 模板的模板参数:template <typename T, template<typename> class C> struct A<C<T>> 常量值限制:template <int N> struct A<N>; 再偏特化 template <> struct A<0> 函数模板特例化 特别注意
函数模板的全特化,本质上是为该模板的某个特定参数组合提供一个显式的、独立的定义;它并不是一个新模板,也不是一个普通函数,但它参与重载决议时与普通函数有本质区别。全特化不引入重载,而是替换模板实例化时生成的版本。值得一提的是,编译器优先调用普通函数;没有普通函数时,才考虑函数模板。在同一个函数模板中,优先调用全特化版本,最后才用模板自动生成的通用版本(泛化版本) 。
特性 函数重载 函数模板全特化 引入新函数 ✅ 是 ❌ 否,依附于泛化版本(主模板) 独立参与重载决议 ✅ 是 ❌ 否 参数类型可完全不同 ✅ 是 ❌ 必须匹配泛化版本(主模板)的模板参数模式 可被显式实例化引用 ❌ 否 ✅ 是
函数模板全特化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std;template <typename T, typename U>void func (T t, U u) { cout << "func(T, U) 泛化版本" << endl; } template <>void func (int t, int u) { cout << "func(int, int) 全特化版本" << endl; } template <>void func (double t, double u) { cout << "func(double, double) 全特化版本" << endl; } int main () { func (3 , 5.6 ); func (5 , 8 ); func (6.5 , 3.5 ); return 0 ; }
程序运行输出的结果如下:
1 2 3 func(T, U) 泛化版本 func(int, int) 全特化版本 func(double, double) 全特化版本
函数模板偏特化 特别注意
函数模板没有偏特化的概念,只有类模板、变量模板(C++ 14 起)支持偏特化。函数模板的 "类似偏特化" 效果通常是通过函数重载来实现 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;template <typename T, typename U>void func (T t, U u) { cout << "func(T, U) 泛化版本" << endl; } template <typename U>void func<int , U>(int i, U u) { cout << "func(int, U) 偏特化版本" << endl; } int main () { return 0 ; }
模板特例化使用建议 在 C++ 中,模板特化版本的书写位置建议:
模板的声明与实现都放在该 .h 文件中,不分离到 .cpp 文件中 模板的泛化版本(主模板)与特化版本应放在同一个 .h 文件 中 .h 文件内的顺序:先写泛化版本,后写特化版本 可变参模板 可变参函数模板 可变参函数模板的概述 在 C++ 中,可变参函数模板允许函数模板接受任意数量、任意不同类型的模板参数,其核心特点如下:
1 2 3 4 5 6 template <typename ... Args>void print (Args... args) { cout << sizeof ...(args) << endl; }
语法:用 template <typename... Args> 或 template <class... Args> 声明参数包 参数数量:参数包可容纳 0 到多个任意类型的参数 可以通过 sizeof... 在编译期获取参数包大小(即参数数量),如 sizeof...(args) 参数包展开: 递归实例化: 常见用途: 注意事项:对于 template <typename... Args>Args(可变参数类型)代表一包不同的类型(不是单一的类型)Args 不是表示某种类型,而是表示 0 到多种不同的类型 对于 void print(Args... args) {}args(可变参数)代表一包形参各个形参的类型可以各不相同 可变参函数模板的使用 可变参函数模板的使用案例一(获取可变参数的数量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;template <typename ... Args>void print1 (Args... args) { cout << sizeof ...(args) << endl; } template <typename T, typename ... Args>void print2 (T first, Args... args) { cout << sizeof ...(Args) << endl; } int main () { print1 (); print1 (1 , 33 , 44.5 ); print2 (1.2 , "hello" ); print2 (2.3 , "hello" , 5 ); return 0 ; }
程序运行输出的结果如下:
可变参函数模板的使用案例二(通过递归调用展开参数包,即获取参数包里的参数值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> using namespace std;void print () { cout << "递归终止" << endl; } template <typename T, typename ... Args>void print (T first, Args... args) { cout << first << endl; print (args...); } int main () { print (2 , 3.14 , "hello" ); return 0 ; }
程序运行输出的结果如下:
可变参函数模板的使用案例三(通过数组初始化时的逗号表达式配合 { } 来展开参数包,即获取参数包里的参数值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std;template <typename ... Args>void print (Args... args) { using expander = int []; (void )expander{0 , (std::cout << args << std::endl, 0 )...}; } int main () { print (2 , 3.14 , "hello" ); return 0 ; }
程序运行输出的结果如下:
(std::cout << args << std::endl, 0) 输出后返回 0expander{0, 返回值1, 返回值2...} 保证参数包的展开顺序(void) 用于避免编译器警告可变参函数模板的使用案例四(通过数组初始化时的逗号表达式 + 完美转发来展开参数包,即获取参数包里的参数值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std;template <typename ... Args>void print (Args&&... args) { using expander = int []; (void )expander{0 , (std::cout << std::forward<Args>(args) << std::endl, 0 )...}; } int main () { print (2 , 3.14 , "hello" ); return 0 ; }
程序运行输出的结果如下:
可变参类模板 可变参类模板的概述 在 C++ 中,可变参类模板允许类模板接受任意数量、任意不同类型的模板参数。
1 2 3 4 5 6 template <typename ... Types>class MyClass { static constexpr std::size_t size = sizeof ...(Types); };
语法:
用 template <typename... Types> 或 template <class... Types> 声明参数包 参数数量:
参数包可容纳 0 到多个任意类型的参数 可以通过 sizeof... 在编译期获取参数包大小(即参数数量),如 sizeof...(Types) 参数包展开:
使用 ... 展开参数包,常用于继承或成员变量声明,如:std::tuple<Types...> 递归继承:
常见用途:
元组:std::tuple<int, double, string> 变体:std::variant<int, double, string> 类型容器:存储或操作多个类型 继承展开:多重继承参数包中的每个类型 注意事项:
对于 template <typename... Types>Types(可变参数类型)代表一包不同的类型(不是单一的类型)Types 不是表示某种类型,而是表示 0 到多种不同的类型 与函数模板区别:类模板的参数包通常放在模板参数列表末尾(函数模板无此限制) 可变参类模板的使用 可变参类模板的使用案例一(通过递归继承方式展开参数包,即获取参数包里的参数值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> using namespace std;template <typename ... Others>class myclasst ;template <>class myclasst < > {public : myclasst () { cout << "特例化终止, address = " << this << endl; } }; template <typename First, typename ... Others>class myclasst < First, Others...> : private myclasst<Others...> { public : myclasst () { cout << "address = " << this << endl; } myclasst (First first, Others... others) : m_first (first), myclasst<Others...>(others...) { cout << "address = " << this << endl; cout << "size = " << sizeof ...(others) << endl; cout << "value = " << m_first << endl; } public : First m_first; }; int main () { myclasst<int , float , double > myc (2 , 1.2f , 3.45 ) ; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 特例化终止, address = 0x7ffef2def920 address = 0x7ffef2def920 size = 0 value = 3.45 address = 0x7ffef2def920 size = 1 value = 1.2 address = 0x7ffef2def920 size = 2 value = 2
可变参类模板的使用案例二(通过递归组合方式展开参数包,即获取参数包里的参数值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> using namespace std;template <typename ... Others>class myclasst ;template <>class myclasst < > {public : myclasst () { cout << "特例化终止, address = " << this << endl; } }; template <typename First, typename ... Others>class myclasst < First, Others...> {public : myclasst () { cout << "address = " << this << endl; } myclasst (First first, Others... others) : m_first (first), m_obj (others...) { cout << "address = " << this << endl; cout << "size = " << sizeof ...(others) << endl; cout << "value = " << m_first << endl; } public : First m_first; myclasst<Others...> m_obj; }; int main () { myclasst<int , float , double > myc (2 , 1.2f , 3.45 ) ; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 9 10 特例化终止, address = 0x7fff0b10b308 address = 0x7fff0b10b300 size = 0 value = 3.45 address = 0x7fff0b10b2f8 size = 1 value = 1.2 address = 0x7fff0b10b2f0 size = 2 value = 2
可变参类模板的使用案例三(通过 std::tuple 和递归调用展开参数包,即获取参数包里的参数值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <tuple> using namespace std;template <int curCount, int argsTotal, typename ... T>class myclasst {public : static void print (const tuple<T...>& t) { cout << get<curCount>(t) << endl; myclasst<curCount + 1 , argsTotal, T...>::print (t); } }; template <int curCount, typename ... T>class myclasst < curCount, curCount, T...> {public : static void print (const tuple<T...>& t) { cout << "特化终止" << endl; } }; template <typename ... T>void print (const tuple<T...>& t) { myclasst<0 , sizeof ...(T), T...>::print (t); } int main () { tuple<int , float , double > t (2 , 2.5f , 3.14 ) ; print (t); return 0 ; }
程序运行输出的结果如下:
模板模板参数 模板模板参数的概述
在 template <typename T> 中,T 叫做模板参数,因为前面都有 typename 修饰,所以又称为类型模板参数 模板模板参数是指:模板的参数本身又是一个模板,也就是说 T 这模板参数又是一个模板第一种写法:template <template <typename> class Container> 第二种写法:template <template <typename> typename Container> 模板模板参数的特点:用 template <typename> 取代 typename 或 class 参数名(如 Container)本身是一个模板,需要进一步实例化 从 C++ 17 开始可以用 typename 替代 class,比如:template <template <typename> typename Container> 1 2 3 4 5 6 7 8 9 10 11 template <typename T> class A { };template <template <typename > class Container > class MyClass {public : Container<int > c; };
模板模板参数的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <list> #include <vector> using namespace std;template <typename T, template <typename > typename Container>class MyClass {public : MyClass () { for (int i = 0 ; i < 10 ; ++i) { m_c.push_back (i); } } public : Container<T> m_c; }; template <typename T>using MyList = list<T, allocator<T>>;template <typename T>using MyVector = vector<T, allocator<T>>;int main () { MyClass<int , MyList> m1; MyClass<int , MyVector> m2; return 0 ; }