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.5
    return 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;
}

该模板函数的特点是:

  1. template <typename T, int size> 声明了一个类型参数T和一个非类型参数size
  2. T (&arr)[size] 是一个数组引用参数,保留了数组的大小信息
  3. size不用显式传参,编译时自动确定。

要使用这个模板,必须满足以下条件:

  1. 必须传入实际的数组(不能是指针或vector等其他容器)
  2. 数组必须是固定大小的(不能是动态分配的数组)
  3. 数组大小必须在编译时已知
  4. 数组不能退化成指针(保持数组类型)

使用方法示例

int main() {
    int intArr[] = {1, 2, 3, 4, 5};
    double doubleArr[] = {1.1, 2.2, 3.3};
  
    printArray(intArr);    // 正确:T=int, size=5
    printArray(doubleArr); // 正确:T=double, size=3
  
    int* ptr = intArr;
    // printArray(ptr);    // 错误:传入的是指针不是数组
  
    std::vector<int> vec = {1, 2, 3};
    // printArray(vec);    // 错误:不适用于vector
}

技术原理

  1. 引用传递数组可以保留数组大小信息(sizeof(arr)/sizeof(arr[0])
  2. 模板会自动推导出数组的类型T和大小size
  3. 相比直接传指针,这种方式更安全,可以防止数组退化为指针
  4. 编译时就能确定数组大小,可以进行更好的优化

典型应用场景

  1. 需要处理原生数组的通用函数
  2. 需要知道数组大小的模板函数
  3. 避免数组到指针的隐式转换
  4. 保证类型安全的数组操作

这个模板是处理原生数组的一种类型安全方法,但只适用于编译时已知大小的真正数组。如果是动态数据结构,应考虑使用迭代器或容器方式。

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. 重载规则

当函数模板与普通函数同名时:

  1. 优先匹配普通函数
  2. 如果模板能产生更好的匹配,则选择模板
  3. 可以通过空模板参数列表强制使用模板: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. 注意事项

  1. 模板定义通常放在头文件中(因为编译时需要看到完整定义)
  2. 模板函数中使用的操作必须对所有可能的类型有效
  3. 模板编译错误通常在实例化时才会显现

2. 最佳实践

  1. 对于小型通用函数使用模板
  2. 遵循DRY原则(Don’t Repeat Yourself)
  3. 使用有意义的模板参数名(如T、U等)
  4. 当需要特殊处理某些类型时,使用模板特化

六、实际应用案例

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 声明该函数或类是一个模板
  • typenameclass 声明一个泛化类型名称,可在类或函数中作为类型名使用,可以替代任意数据类型
  • <> 中定义泛化类型名称。称作类型参数列表。一般用TEO作为泛化类型名。
  • typename T = int 指定默认类型,在调模板函数时可以不指定。

使用

// 根据参数自动推导类型(隐式实例化),不一定调用函数模板
func(param);	
// 显式指定类型(显式实例化)	
func<int>(param);	
// 强制调用函数模板(隐式实例化)
func<>(param)    

函数模板是C++中强大的泛型编程工具,通过将类型参数化,可以创建更加灵活和可重用的代码。掌握函数模板可以帮助我们:

  1. 减少代码重复
  2. 提高类型安全性
  3. 增强代码可维护性
  4. 实现更通用的算法

从简单类型比较到复杂容器操作,函数模板在现代C++编程中都有广泛应用,是每个C++开发者必须掌握的核心技能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

念致达

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值