函数模板

基本概念

重载与模板的核心区别

适用场景 技术选择 核心特征
同名函数,参数列表不同 + 算法不同 函数重载 逻辑差异化,需单独实现每个函数
同名函数,仅参数类型不同 + 算法一致 函数模板 逻辑统一,代码抽象化,避免冗余

函数模板的本质

  • 函数模板是一个设计蓝图,而非具体函数,不占用内存
  • 仅当发生具体调用并匹配成功后,编译器才会生成对应的函数实例(实例化过程)

典型场景示例

需求:设计“求两个数据最大值”的函数

  • 若用重载:需为int、float、double等每种类型写单独函数,算法完全重复,代码臃肿
  • 若用模板:仅需1个模板蓝图,编译器根据调用时的参数类型自动生成对应函数实例
函数模板实例化流程(用户可自行添加:模板蓝图 → 调用触发匹配 → 生成具体函数实例)

函数模板语法

函数模板的定义

核心关键字

  • template:声明后续为模板定义
  • typename:指定类型参数(旧版可用class替代,功能完全一致)

语法格式

// 类型参数列表(可多个,用逗号分隔)
template <typename T1, typename T2>  // T1、T2为类型参数(占位符)
void someFunc(T1 a, T2 b)  // 用类型参数定义数据参数
{
    // 算法逻辑(与类型无关,保持统一)
}

调用方式

函数模板有两个参数列表,调用时灵活选择:

  1. 显式指定类型参数:尖括号<>传递类型,圆括号()传递数据
    someFunc<const char *, int>("abcd", 100);  // 明确指定T1=const char*,T2=int
    
  2. 自动推导类型参数:省略尖括号,编译器根据实参类型推导
    someFunc("abcd", 100);  // 编译器自动推导T1=const char*,T2=int
    

函数模板的重载

重载起因

一个模板仅能覆盖“参数个数相同、算法一致”的场景,若需满足:

  • 参数个数不同
  • 算法逻辑不同

则需重载模板(得到另一堆同名函数集合)

语法示例

// 模板1:单参数函数集合(任意类型T)
template <typename T>
void f(T a)
{
    cout << "一堆接受一个任意参数的函数" << endl;
}

// 模板2:双参数函数集合(任意类型T1、T2)—— 重载模板1
template <typename T1, typename T2>
void f(T1 a, T2 b)
{
    cout << "另一堆接受两个参数的函数" << endl;
}

说明

  • 函数模板重载规则与普通函数一致:同名不同参数列表(个数/类型/顺序)
  • 重载后,编译器根据调用时的实参个数/类型匹配对应的模板

函数模板的特化

特化定义

当某个特定类型组合无法套用模板的统一算法时,将该类型的函数版本单独定义(特殊化实现)

特化起因(典型场景)

模板解决“类型不同但算法一致”的问题,如“喂养动物”:

// 通用模板:适用于大部分动物+饲料组合
template <typename T1, typename T2>
void feed(T1 animal, T2 food)
{
    // 通用算法:倒饲料到盆 → 叫动物来吃
}

但“喂养鲸鱼”的算法不同(无法套用通用逻辑),需特化模板

语法要点

  1. 保留template关键字,尖括号<>不可省略(即使无剩余类型参数)
  2. 将特化的类型从“类型参数列表”移除,在“数据参数列表”中写具体类型
  3. 特化版本的函数名与原模板一致

特化示例

// 1. 部分特化(仅特化部分类型参数:T1=Whale,T2仍为通用类型)
template <typename T2>
void feed(Whale w, T2 food)
{
    // 鲸鱼专属算法:将食物投入水池 → 引导鲸鱼进食
}

// 2. 全特化(所有类型参数都特化:T1=Whale,T2=string)
template <>  // 尖括号不可省略
void feed(Whale w, string food)
{
    // 鲸鱼+字符串类型饲料的专属算法(如特殊饲料处理)
}

匹配优先级

  • 非模板普通函数 > 模板特化版本 > 通用模板
  • 若特化版本与通用模板冲突,可定义非模板函数重载,直接覆盖模板匹配
函数模板匹配优先级(用户可自行添加:普通函数 → 特化模板 → 通用模板)

拓展

模板参数的默认值

C++11及以上支持为类型参数指定默认值,调用时可省略该参数的类型指定:

// 默认T2为int类型
template <typename T1, typename T2 = int>
void func(T1 a, T2 b = 0)
{
    // 逻辑实现
}

// 调用示例
func<double>(3.14);  // T1=double,T2默认int,b默认0
func<char, double>('a', 2.5);  // 显式指定所有类型

非类型模板参数

模板参数不仅可以是“类型占位符”,还可以是“常量值”(非类型参数),需指定具体类型(如int、char、指针等):

// N为非类型模板参数(int类型常量)
template <<int N>
int sum()
{
    return N * (N + 1) / 2;  // 计算1~N的和
}

// 调用:尖括号中传递常量值
cout << sum<100>();  // 输出5050(1~100求和)

实参推断规则

  1. 编译器仅根据实参类型推导模板参数,不考虑返回值类型
  2. 若实参类型不匹配(如隐式转换无法完成),需显式指定模板类型
  3. 引用/指针类型的模板参数,推导时会保留const/volatile限定符:
    template <typename T>
    void func(T& a);
    
    const int x = 10;
    func(x);  // T推导为const int(保留const属性)
    

函数模板的局限性

  1. 算法必须统一:若不同类型的核心逻辑差异过大,特化/重载会增加复杂度,不如直接用普通函数
  2. 类型支持限制:模板依赖类型支持的操作(如+>),若类型不支持该操作(如自定义类未重载运算符),会编译报错
  3. 调试难度高:模板实例化在编译期完成,错误信息通常较为复杂,需熟悉模板语法才能定位问题

实践练习

练习需求

使用函数模板实现“找出三个数中的最大值并返回”,要求支持任意可比较类型(int、float、double等)

参考框架

#include <iostream>
using namespace std;

// 定义函数模板:返回三个参数中的最大值
template <typename T>
T maxThree(T a, T b, T c)
{
    // 补全逻辑:先比较a和b,再与c比较,返回最大值
    T maxVal = a > b ? a : b;
    return maxVal > c ? maxVal : c;
}

// 测试代码
int main()
{
    cout << maxThree(10, 20, 15) << endl;        // 输出20(int类型)
    cout << maxThree(3.14, 2.5, 5.6) << endl;    // 输出5.6(double类型)
    cout << maxThree('c', 'a', 'b') << endl;     // 输出'c'(char类型)
    return 0;
}

核心总结

  1. 模板核心价值:代码复用(算法一致时,避免多类型重载的冗余)
  2. 关键语法:template <typename T> 声明模板,尖括号传类型,圆括号传数据
  3. 三大特性:
    • 重载:解决参数个数/算法不同的场景
    • 特化:解决特定类型算法差异的场景
    • 实例化:编译期根据调用生成具体函数
  4. 匹配优先级:普通函数 > 特化模板 > 通用模板
函数模板核心知识图谱(用户可自行添加:模板定义 → 调用 → 实例化/重载/特化 → 执行)

posted @ 2025-12-25 08:34  Jaklin  阅读(10)  评论(0)    收藏  举报