C++泛型编程1 - 函数模板
C++函数模板完整教程
一、模板概述
模版是一种写程序的套路。是程序员写给编译器看的,程序员在编写时不用关心具体的数据类型,使用虚拟数据类型代替即可。只需要在使用时指定具体数据类型,编译器便会自动根据使用时的数据类型对模板进行实例化。即使使用时没有指定数据类型,编译器也会根据参数类型自动推导数据类型进行模板实例化。
特点:
- 模板不可以直接使用,它只是一个框架。
- 模板的通用并不是万能的。
分类
C++模板是泛型编程的基础,可以分为两大类:
- 函数模板:用于生成通用函数
- 类模板:用于生成通用类
1.1 模板核心优势
- 类型安全:比宏更安全
- 代码复用:避免为不同类型重写相同逻辑
- 性能无损:编译时实例化,没有运行时开销
- STL基础:标准模板库的构建基础
1.2 函数模板概念
函数模板(Function Templates)是C++泛型编程的重要特性,它允许我们编写与数据类型无关的通用函数。通过使用模板,我们可以用一套代码处理不同类型的数据,大大提高代码的重用性和灵活性。
1.3 函数模板的核心理念
- 将数据类型参数化
- 编译器在编译时自动生成特定版本的函数
- 提高代码复用性和可维护性
二、基本语法
1. 定义函数模板
template <typename T>
T func(parameters) {// 函数体
}
template <typename T>
或template <class T>
声明一个模板,T是类型参数T
可以用任何有效标识符代替,但习惯上用T(Type)- 模板参数可以有多个:
template <typename T1, typename T2>
T
可以作为参数类型、返回类型和函数体内用到的类型。
2. 简单示例
template <typename T>
T maximum(T a, T b) {return (a > b) ? a : b;
}
使用示例:
int main() {cout << maximum(10, 20) << endl; // 调用int版本cout << maximum(3.14, 2.71) << endl; // 调用double版本cout << maximum('A', 'B') << endl; // 调用char版本return 0;
}
三、模板的定义与使用
1. 多类型参数
template <typename T1, typename T2>
void printPair(T1 first, T2 second) {cout << first << ", " << second << endl;
}
2. 默认类型参数
函数模板的类型参数也可以像函数参数一样指定默认值。
// 定义函数模板,给 T2 指定默认类型
template <typename T1, typename T2 = std::string>
void printPair(T1 first, T2 second) {std::cout << "First: " << first << ", Second: " << second << std::endl;
}int main() {// 1. 不指定类型。自动推导printPair(42, "Hello, C++"); // First: 42, Second: Hello, C++// 2. 部分指定类型(第二个参数保持默认 std::string)printPair<double>(3.14, "PI"); // First: 3.14, Second: PI// 3. 显式指定两个类型printPair<int, double>(10, 20.5); // First: 10, Second: 20.5return 0;
}
默认类型参数不能在没有默认值的参数之前声明。
错误示范:template <typename T1 = int, typename T2>
。
3. 非类型参数模板
C++ 的模板不仅支持类型参数(typename T/class T),还支持非类型参数(如 int N、bool Flag、指针等)。我们可以用它们来:
- 在编译时传递固定大小(如数组长度)
- 控制代码生成(如开关优化)
- 结合默认参数简化调用
以下是一个数组打印模板。
template <typename T, int size>
void printArray(T (&arr)[size]) {for(int i = 0; i < size; ++i) {cout << arr[i] << " ";}cout << endl;
}
该模板函数的特点是:
template <typename T, int size>
声明了一个类型参数T和一个非类型参数sizeT (&arr)[size]
是一个数组引用参数,保留了数组的大小信息size
不用显式传参,编译时自动确定。
要使用这个模板,必须满足以下条件:
- 必须传入实际的数组(不能是指针或vector等其他容器)
- 数组必须是固定大小的(不能是动态分配的数组)
- 数组大小必须在编译时已知
- 数组不能退化成指针(保持数组类型)
使用方法示例
int main() {int intArr[] = {1, 2, 3, 4, 5};double doubleArr[] = {1.1, 2.2, 3.3};printArray(intArr); // 正确:T=int, size=5printArray(doubleArr); // 正确:T=double, size=3int* ptr = intArr;// printArray(ptr); // 错误:传入的是指针不是数组std::vector<int> vec = {1, 2, 3};// printArray(vec); // 错误:不适用于vector
}
技术原理
- 引用传递数组可以保留数组大小信息(
sizeof(arr)/sizeof(arr[0])
) - 模板会自动推导出数组的类型T和大小size
- 相比直接传指针,这种方式更安全,可以防止数组退化为指针
- 编译时就能确定数组大小,可以进行更好的优化
典型应用场景
- 需要处理原生数组的通用函数
- 需要知道数组大小的模板函数
- 避免数组到指针的隐式转换
- 保证类型安全的数组操作
这个模板是处理原生数组的一种类型安全方法,但只适用于编译时已知大小的真正数组。如果是动态数据结构,应考虑使用迭代器或容器方式。
4. 模板特化
作用:为特定类型提供特殊实现。
函数模板只有完全特化,不支持偏特化。
- 偏特化:函数模板的部分类型参数实例化。
- 全特化:函数模板的所有类型参数全部实例化。
定义方法:在函数名后加<>指定替代泛化类型的具体类型。
template <>具体返回类型 函数名<具体类型列表>(具体类型参数列表){}
示例:
// 通用模板
template <typename T>
bool isEqual(T a, T b) {return a == b;
}// 为const char*类型的特化版本
template <>
bool isEqual<const char*>(const char* a, const char* b) {return strcmp(a, b) == 0;
}
四、函数模板与普通函数
1. 名称区别
- 函数模板:有模板声明的函数,即使用
template
定义的函数。 - 模板函数:是函数模板的实例化,使用函数模板调用的函数就是模板函数。
- 普通函数: 非模版函数,没有模板声明,是有具体参数类型的函数。
2. 重载规则
当函数模板与普通函数同名时:
- 优先匹配普通函数
- 如果模板能产生更好的匹配,则选择模板
- 可以通过空模板参数列表强制使用模板:
function<>(args)
模板函数与普通函数的优先级:
普通函数 > 模板函数(完全特化 > 偏特化 > 通用)
3. 示例
void print(int a) {cout << "普通函数: " << a << endl;
}template <typename T>
void print(T a) {cout << "模板函数: " << a << endl;
}int main() {print(10); // 调用普通函数print<>(10); // 强制调用模板函数print(10.5); // 调用模板函数(更好的匹配)return 0;
}
五、注意事项与最佳实践
1. 注意事项
- 模板定义通常放在头文件中(因为编译时需要看到完整定义)
- 模板函数中使用的操作必须对所有可能的类型有效
- 模板编译错误通常在实例化时才会显现
2. 最佳实践
- 对于小型通用函数使用模板
- 遵循DRY原则(Don’t Repeat Yourself)
- 使用有意义的模板参数名(如T、U等)
- 当需要特殊处理某些类型时,使用模板特化
六、实际应用案例
1. 通用交换函数
template <typename T>
void swapValues(T &a, T &b) {T temp = a;a = b;b = temp;
}
2. 通用数组排序
template <typename T, int size>
void bubbleSort(T (&arr)[size]) {for(int i = 0; i < size-1; ++i) {for(int j = 0; j < size-i-1; ++j) {if(arr[j] > arr[j+1]) {swapValues(arr[j], arr[j+1]);}}}
}
3. 多类型组合函数
template <typename T1, typename T2>
auto multiply(T1 a, T2 b) -> decltype(a * b) {return a * b;
}
总结
模板定义
template <class T>
T func(T a);template <typename T, class E>
T func(T a, E b);template <typename T = int>
T func(T a);template <typename T, int size>
T func(T (&arr)[size]);
记忆重点:
template
声明该函数或类是一个模板typename
、class
声明一个泛化类型名称,可在类或函数中作为类型名使用,可以替代任意数据类型<>
中定义泛化类型名称。称作类型参数列表。一般用T
、E
、O
作为泛化类型名。typename T = int
指定默认类型,在调模板函数时可以不指定。
使用
// 根据参数自动推导类型(隐式实例化),不一定调用函数模板
func(param);
// 显式指定类型(显式实例化)
func<int>(param);
// 强制调用函数模板(隐式实例化)
func<>(param)
函数模板是C++中强大的泛型编程工具,通过将类型参数化,可以创建更加灵活和可重用的代码。掌握函数模板可以帮助我们:
- 减少代码重复
- 提高类型安全性
- 增强代码可维护性
- 实现更通用的算法
从简单类型比较到复杂容器操作,函数模板在现代C++编程中都有广泛应用,是每个C++开发者必须掌握的核心技能。