C++ 函数模板完全指南

💡从泛型思想、模板特化到排序算法实战 ------ 基于现代C++深度剖析, 函数模板让算法脱离数据类型 ------ 真正实现 "Write once, use for any type".

✨ 一、为什么需要函数模板?

在C++中,如果需要实现功能相同但数据类型不同的函数(例如交换两个整数、两个浮点数,或者对不同类型数组排序),传统做法需要为每种类型重载函数,导致大量重复代码。函数模板允许编写通用的函数,编译器会根据实参类型自动生成对应的函数版本,实现了"一次编写,多类型使用",极大提高代码复用性与可维护性。

💡 核心思想:将数据类型参数化,使用 template <typename T> 或 template <class T> 定义类型占位符。

🔧 二、函数模板定义与基本语法

语法格式:

cpp 复制代码
template <typename T>
T 函数名(T 参数1, T 参数2) {
    // 函数体
}

基于示例代码中的交换模板和加法模板:

cpp 复制代码
// 交换模板
template<typename T>
void swap_T(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// 加法模板
template<class T>
T add_T(T a, T b) {
    return a + b;
}

🔍 调用方式

  • 自动类型推导:swap_T(a, b); 编译器根据实参推断 T 的类型。
  • 显式指定类型:swap_T<int>(a, b); 或 add_T<int>(a, d); 适用于需要类型转换或避免二义性。

📌 关键限制:自动推导时,所有模板参数必须保持一致的类型。例如 swap_T(a, d)(一个int一个double)会导致推导失败。但显式指定类型时允许隐式转换(值传递场景)。

🧩 三、无参数的模板函数 & 值初始化安全性

当模板参数无法从函数参数推导时,必须显式指定类型 。示例中 test_T<int>() 使用了 C++11 的 统一初始化 {},防止未初始化变量和窄化转换。

cpp 复制代码
template<typename T>
void test_T() {
    T temp{};        // 值初始化,int为0,double为0.0,自定义类调用默认构造
    T temp1{0};      // 安全
    // T temp2{0.5}; // 编译错误:窄化转换(若T为int)
    temp = 1.5;      // 隐式转换,int会截断
}

✅ 最佳实践:在模板中尽量使用 T var{} 保证变量总是被初始化,提高泛型代码的鲁棒性。

🎯 四、函数模板特化 (Explicit Specialization)

通用模板无法满足某些特殊类型的需求时(例如自定义类 MyPoint 无法直接用 << 输出,或者需要特殊格式),可以为特定类型提供特化版本。编译器会优先匹配特化版本。

cpp 复制代码
// 通用模板
template<typename T>
void printArr(T arr[], int len) { ... }

// 针对 int 类型的特化 (分隔符变为 " / ")
template<>
void printArr<int>(int arr[], int len) {
    for (int i = 0; i < len; i++) cout << arr[i] << " / ";
}

// 针对 MyPoint 类型的特化 (优雅输出坐标)
template<>
void printArr(MyPoint arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << "(" << arr[i].getX() << "," << arr[i].getY() << ")";
    }
}

调用时,若传入 MyPoint 数组,自动选择特化版本,实现定制行为。

⚖️ 五、普通函数 vs 函数模板 vs 模板特化 ------ 调用优先级

C++ 编译器选择调用哪个函数有一套明确的规则(优先级从高到低):

优先级 候选类型 示例
1️⃣ 最高 完全匹配的普通函数 void printArr(int arr[], int len)
2️⃣ 模板特化 template<> void printArr<int>(int arr[], int len)
3️⃣ 函数模板实例化 template<typename T> void printArr(T arr[], int len)
4️⃣ 最低 经过隐式转换的普通函数 例如 printArr(charArr, len) 不存在时可能会尝试转换
cpp 复制代码
// 普通函数 (打印int数组使用逗号分隔)
void printArr(int arr[], int len) { ... }

// 调用时:
printArr(int_arr, num);    // 匹配普通函数 (优先级最高)
printArr<>(int_arr, num);  // 空模板列表强制调用函数模板版本 (跳过普通函数)

⚠️ 注意:普通函数和函数模板最好不要提供完全相同的重载,容易引发二义性。若希望总是使用模板,使用空尖括号 <> 强制指定。

🚧 六、类型转换 & 引用的特殊规则

基于代码中的 test1 可总结:

  • 值传递:允许隐式类型转换(如 addInt(a, c) 将 char 提升为 int)。
  • 引用传递 :swapInt(a, c) 错误!引用不能绑定到不同类型转换后的临时变量。
    但是 const 引用 可以绑定到临时量(安全,只读)。
  • 模板引用参数:同样不支持隐式转换,保持类型严格一致。例如 swap_T(a, d) 编译失败。

📖 解决方案:需要处理不同类型时,可显式指定模板参数,或利用重载/函数重载决议。

📊 七、算法模板实战:选择排序 & 冒泡排序 (泛型排序)

以下两个排序模板可以处理 任意支持比较运算符 < 的类型数组(int, double, char, 甚至自定义类若重载了运算符)。

📌 1.选择排序模板 (不稳定,每轮选择最大/最小交换)

cpp 复制代码
template<typename T>
void mySelectionSort_T(T arr[], int len) {
    for (int i = 0; i < len-1; i++) {
        int max = i;
        for (int j = i+1; j < len; j++)
            if (arr[max] < arr[j]) max = j;   // 降序
        if (max != i) swap_T(arr[max], arr[i]);
    }
}

📌 2.冒泡排序模板 (稳定,提前退出优化)

cpp 复制代码
template<typename T>
void myBubbleSort_T(T arr[], int len) {
    for (int i = 0; i < len-1; i++) {
        bool swapped = false;
        for (int j = 0; j < len-1-i; j++) {
            if (arr[j] < arr[j+1]) {  // 降序排列
                swap_T(arr[j], arr[j+1]);
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}

在 test2 中对 "hello world" 字符数组进行降序排序,test3/test4 演示整型数组的排序过程,充分体现了模板对 char 和 int 的通用性。

🧪 八、完整测试模块说明

程序中的 test1~test5 覆盖了模板的所有关键特性,下面逐一说明:

  • test1():演示普通函数 vs 模板交换、加法、自动推导失败案例、显式指定类型 + 隐式转换、无参模板必须显式实例化。
  • test2() :字符数组选择排序 + printArr 重载测试,展示默认模板和自定义连接符模板的重载。
  • test3() :整型数组选择排序,并演示 printArr<>(int_arr, num) 强制调用模板版本。
  • test4():冒泡排序模板测试,验证提前退出优化。
  • test5():函数调用优先级测试 ------ 普通函数 printArr(int[],len) 被优先调用,特化版本处理 MyPoint,以及空模板参数强制使用模板。

💡 注意:test5 中通过 MyPoint point_arr[] 调用 printArr 会自动匹配模板特化版本,输出格式化为 (x,y)

📌 九、易错点 & 最佳实践总结

✔️ 模板定义通常放在头文件中 ,因为编译器需要在使用时看到完整定义才能实例化。

✔️ 函数模板不支持默认模板参数 (C++11 后支持,但函数模板常用默认类型参数较少见,类模板更常见)。

✔️ 模板特化时 template<> 必须前置,且参数列表必须匹配原模板。

✔️ 避免过度特化,优先使用重载或 if constexpr (C++17)。

✔️ 如果希望模板只适用于具有某种特性的类型,可以结合 SFINAEC++20 Concept

💎 十、关键代码与模板

cpp 复制代码
// 1. 泛型交换
template<typename T> void mySwap(T& a, T& b) { T t = a; a = b; b = t; }

// 2. 泛型打印数组(通用)
template<typename T>
void printArray(const T arr[], size_t len) {
    for (size_t i = 0; i < len; ++i) cout << arr[i] << " ";
}

// 3. 针对int的特化
template<> void printArray(const int arr[], size_t len) {
    for (size_t i = 0; i < len; ++i) cout << arr[i] << " | ";
}

// 4. 泛型排序算法框架(选择排序)
template<typename T>
void genericSelectionSort(T arr[], int n) {
    for (int i = 0; i < n-1; ++i) {
        int minIdx = i;
        for (int j = i+1; j < n; ++j)
            if (arr[j] < arr[minIdx]) minIdx = j;
        if (minIdx != i) mySwap(arr[i], arr[minIdx]);
    }
}

基于以上模板,可以轻松排序整数、浮点数、字符甚至自定义对象(需实现比较运算符或提供仿函数)。

Tips:仿函数 (也称为函数对象)是C++中一个行为类似函数对象 。它通过重载函数调用运算符operator()来实现,使得对象可以像函数一样被调用,这里就不多展开了。

cpp 复制代码
// 传统比较函数
bool compareInts(int a, int b) {
    return a < b;  // 从小到大
}

// 仿函数比较器
class CompareInts {
public:
    bool operator()(int a, int b) const {
        return a < b;  // 从小到大
    }
};

📈 十一、模板的优势与运行时开销

函数模板在编译期进行实例化,为每个类型生成独立函数,因此没有运行时额外开销,代码效率和手写特定类型函数相同。同时又能保证类型安全,堪称C++泛型编程的基石。

🔥 函数模板使得代码极度精简,同时又保留了极高的灵活性。在项目中,尽量将通用算法抽象为模板,减少重复劳动。

📝 十二、思考与扩展

1.如何修改选择排序模板使其支持升序/降序通过函数参数控制?

可以从几个层面来修改,使其功能更灵活:

  • 方案一:增加布尔参数 这是最直接的修改。在函数参数列表中添加一个bool ascending(默认为true表示升序),然后在内部比较时根据这个参数决定是使用<(找最小值,用于升序)还是>(找最大值,用于降序)。

    cpp 复制代码
    template<typename T>
    void mySelectionSort_T(T arr[], int len, bool ascending = true) {
        for (int i = 0; i < len-1; i++) {
            int targetIdx = i; // 目标位置应存放的元素索引
            for (int j = i + 1; j < len; j++) {
                bool condition = ascending ? (arr[j] < arr[targetIdx]) : (arr[j] > arr[targetIdx]);
                if (condition) {
                    targetIdx = j;
                }
            }
            if (targetIdx != i) {
                swap_T(arr[targetIdx], arr[i]);
            }
        }
    }

    使用示例mySelectionSort_T(arr, len); // 默认升序 mySelectionSort_T(arr, len, false); // 降序

  • 方案二:增加枚举参数(增强可读性) 定义一个enum SortOrder { ASCENDING, DESCENDING };,用枚举值代替布尔值,使调用意图更明确。

    cpp 复制代码
    mySelectionSort_T(arr, len, SortOrder::DESCENDING);
  • 方案三:增加仿函数参数(最灵活、最符合C++标准库风格,推荐) 这是最强大和通用的方法。在模板中增加一个比较器参数Compare comp,默认值可以设为std::less<T>()(表示升序)。在内部比较时,不再直接使用<>,而是调用比较器comp(arr[j], arr[targetIdx])。如果此调用返回true,则意味着arr[j]应该排在arr[targetIdx]之前 ,因此更新targetIdx

    cpp 复制代码
    template<typename T, typename Compare = std::less<T>>
    void mySelectionSort_T(T arr[], int len, Compare comp = Compare()) {
        for (int i = 0; i < len-1; i++) {
            int targetIdx = i;
            for (int j = i + 1; j < len; j++) {
                if (comp(arr[j], arr[targetIdx])) { // 使用比较器
                    targetIdx = j;
                }
            }
            if (targetIdx != i) {
                swap_T(arr[targetIdx], arr[i]);
            }
        }
    }

    使用示例

  • mySelectionSort_T(arr, len); // 默认使用std::less,升序

  • mySelectionSort_T(arr, len, std::greater<int>()); // 使用std::greater,降序

  • mySelectionSort_T(arr, len, [](int a, int b){ return a % 10 < b % 10; }); // 使用lambda表达式作为比较器自定义比较规则(按个位数升序排序)

  • mySelectionSort_T(arr, len, [](int a, int b){ return a % 10 > b % 10; }); // 使用lambda表达式作为比较器自定义比较规则(按个位数降序排序)

tips:无论是 std::lessstd::greater还是自定义的函数,在传递给像 std::sortmySelectionSort_T这样的排序算法时,都遵循同一个严格的语义约定

比较函数 comp(a, b)返回 true唯一含义 是:在算法所构建的最终有序序列中,元素 a必须排在元素 b前面

这个约定是排序算法能够工作的基础。算法并不关心 comp内部是实现了"小于"还是"大于",它只关心这个"前后顺序"的规则。

比较器 比较操作 返回 true的含义 在排序中产生的效果
**std::less<T>**​ a < b a(较小的) 应排在 b(较大的) 前面 升序​ (小的在前,大的在后)
**std::greater<T>**​ a > b a(较大的) 应排在 b(较小的) 前面 降序​ (大的在前,小的在后)

简单记忆std::less-> "小者在前" -> 升序。std::greater-> "大者在前" -> 降序。

2.如果希望模板只接受具有 operator< 的类型,应该怎么做?(C++20 Concept: template<typename T> requires std::totally_ordered<T>)

基于C++20的现代特性,我们可以这样实现:

std::totally_ordered<T> 是一个概念(Concept):它要求类型T必须支持<, <=, >, >=这四个完整的比较运算符。如果只需要operator<,使用std::totally_ordered是足够的,但可能约束"过强"。一个更精确的自定义概念可以是:

cpp 复制代码
template<typename T>
concept HasLessThan = requires(const T& a, const T& b) {
    { a < b } -> std::convertible_to<bool>;
};
  • template<typename T>:声明一个模板参数 T
  • concept HasLessThan = :定义一个名为 HasLessThan的概念
  • requires(const T& a, const T& b) :要求部分,定义需要满足的条件
  • { a < b } -> std::convertible_to<bool>; :具体的要求:a < b必须是一个有效的表达式,表达式的结果必须能转换为 bool类型

这个概念的语义是 :类型 T必须支持 <运算符,并且这个运算符的结果可以被用作布尔值。

概念(Concept)与模板函数的区别

特性 概念(Concept) 模板函数
本质 编译时类型约束 ,是谓词(返回true/false) 编译时代码生成 ,是可执行代码
作用 检查类型是否满足条件,不产生可执行代码 为特定类型生成函数实例,产生可执行代码
返回值 编译时布尔值(类型是否满足条件) 运行时返回值(函数执行结果)

然后,在函数模板声明中使用它进行约束:

cpp 复制代码
template <HasLessThan T> // 或者 template <std::totally_ordered T>
void mySelectionSort_T(T arr[], int len) {
    // ... 函数体内部可以安全地使用 arr[j] < arr[targetIdx] ...
}

这样,当用户尝试用不支持operator<的类型实例化此模板时,编译器会在调用处给出清晰易懂的错误信息,而不是在函数体内部遇到<符号时才报出令人困惑的错误。这是C++20引入Concepts的主要优势之一:将接口约束检查提前,并改善错误信息。

3.尝试实现一个模板函数 findMax,返回数组中最大元素的索引。

这个函数可以看作是mySelectionSort_T函数中"寻找最大值"步骤的独立版本,实现起来非常直观:

cpp 复制代码
/// 查找数组中最大元素的索引
/// @param arr 数组指针
/// @param len 数组长度
/// @return 最大元素的索引,如果数组为空(len<=0)则返回-1
template<typename T>
int findMax(T arr[], int len) {
    if (len <= 0) return -1; // 处理边界情况

    int maxIdx = 0; // 假设第一个元素最大
    for (int i = 1; i < len; ++i) {
        if (arr[maxIdx] < arr[i]) { // 如果发现更大的元素
            maxIdx = i; // 更新最大元素索引
        }
    }
    return maxIdx;
}

增强版本:可以像排序函数一样,为其增加一个比较器参数,使其更加通用,可以用于寻找"在某种比较规则下的极值"。

cpp 复制代码
template<typename T, typename Compare = std::less<T>>
int findMax(T arr[], int len, Compare comp = Compare()) {
    if (len <= 0) return -1;
    int extremeIdx = 0;
    for (int i = 1; i < len; ++i) {
        if (comp(arr[extremeIdx], arr[i])) { // 如果当前"极值"排在 arr[i] 之前
            extremeIdx = i; // 更新为新的"极值"
        }
    }
    return extremeIdx;
}
// 使用:findMax(arr, len) 找最大, findMax(arr, len, std::greater<int>()) 找最小。

4.模板特化和函数重载有何区别?何时优先使用特化?

这是一个核心概念,代码已经同时包含了两种用法(printArr的特化和重载),正好可以用来对比。

核心区别

特性 模板特化 函数重载
定义 一个已有的主模板特定类型参数提供一个完全特殊的实现。 定义多个同名但参数列表不同独立函数
关系 是主模板的"特例",语法上紧密绑定(需要template<>)。 函数之间是平等的"重载"关系,彼此独立。
匹配时机 模板参数推导/确定之后发生。编译器先决定调用哪个模板,再检查是否有特化。 重载决议阶段发生。编译器在所有候选函数(包括普通函数和可能推导出的模板)中选择最匹配的一个。
灵活性 主要是为特定类型定制行为,通常保持相同的函数签名和语义。 可以改变参数数量、类型,甚至语义(但通常不推荐改变语义)。

代码中的例子

  • template<> void printArr<int>(int arr[], int len)模板特化 。它是对主模板template<typename T> void printArr(T arr[], int len)T=int时的特殊实现。
  • void printArr(int arr[], int len) 是一个普通函数重载 。它是一个独立的函数,与模板无关。当调用printArr(int_arr, num1)时,根据您提到的优先级规则(完全匹配的普通函数 > 模板),它会优先于模板被调用。

何时优先使用特化?

  1. 定制类模板行为时 :这是最常见场景。函数重载不能用于类,只能对类模板进行(全/偏)特化。例如,为std::vector<bool>做特化优化。
  2. 希望行为变化对用户"透明"时 :当你想为某种类型提供特殊实现,但希望函数签名、名称和核心语义保持不变 ,对所有调用者来说,调用方式完全一样,只是内部效率或细节不同。例如,为std::swap特化自定义类,使其交换更高效。
  3. 针对一类类型(通过偏特化,仅限类模板) :类模板支持偏特化(如template<typename T> class Widget<T*>),可以为某一类类型(如所有指针)提供特殊实现,这是函数重载无法直接做到的。

总结建议 : 代码注释中"提供了函数模板,最好就不要提供普通函数,否则容易出现二义性"的观点非常重要。在函数层面,一个更通用的建议是:如果主模板的逻辑对大多数类型都有效,仅为少数类型需要特殊处理,且处理逻辑是"优化"或"微小调整",考虑使用特化。如果针对不同类型需要的是逻辑完全不同、甚至签名都不同的操作,那么应该使用函数重载。 在实际中,对于函数模板,使用"带约束的模板"或"标签分发"等技巧有时比特化更清晰。C++20的Concepts进一步增强了这种能力。

📖 附1:示例来自如下实际可运行的 .cpp 文件

cpp 复制代码
#include <iostream>

/// <summary>
/// 表示平面坐标系中的点的类
/// </summary>
class MyPoint {
    double x;
    double y;
public:
    MyPoint(double x, double y) { this->x = x; this->y = y; }
    double getX()const { return x; }
    double getY()const { return y; }
    void show()const { std::cout << '(' << x << ',' << y << ')'; }
};

/// <summary>
/// 打印数组函数(模板函数)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
template<typename T>
void printArr(T arr[], int len) {
    for (int i = 0; i < len; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

/// <summary>
/// 打印整型数组函数(模板特化)
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
template<>
void printArr<int>(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        std::cout << arr[i] << " / ";
    }
    std::cout << std::endl;
}

/// <summary>
/// 打印坐标点数组函数(模板特化)
/// 模板的通用性并不是万能的,比如两个数组直接赋值或者自定义数据类型操作,所以需要特化
/// 有参数可以自动推导为MyPoint类型,调用这个特化版本来打印坐标点数组
/// 也可以显式指定模板参数为MyPoint,void printArr<MyPoint>(MyPoint arr[], int len)
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
template<>
void printArr(MyPoint arr[], int len) {
    for (int i = 0; i < len; i++) {
        std::cout << "(" << arr[i].getX() << "," << arr[i].getY() << ")";
    }
    std::cout << std::endl;
}

/// <summary>
/// 打印数组函数,连接符自定义(模板函数)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
/// <param name="hyphen"></param>
template<typename T>
void printArr(T arr[], int len, char hyphen) {
    for (int i = 0; i < len; i++) {
        std::cout << arr[i] << hyphen;
    }
    std::cout << std::endl;
}

/// <summary>
/// 打印数组函数,连接符固定为逗号(普通函数)
/// 如果函数模板和普通函数都可以实现,优先调用普通函数
/// 但是提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
void printArr(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        std::cout << arr[i] << ",";
    }
    std::cout << std::endl;
}

/// <summary>
/// 交换整型函数
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
void swapInt(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

/// <summary>
/// 整型加函数
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
int addInt(int a, int b) {
	return a + b;
}

/// <summary>
/// 交换浮点型函数
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
void swapDouble(double& a, double& b) {
    double temp = a;
    a = b;
    b = temp;
}

/// <summary>
/// 交换函数(模板函数)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
template<typename T>
void swap_T(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

/// <summary>
/// 加函数(模板函数)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
template<class T>
T add_T(T a, T b)
{
    return a + b;
}

/// <summary>
/// 无参数模板函数测试
/// </summary>
/// <typeparam name="T"></typeparam>
template<typename T>
void test_T() {
	//初始化T类型的变量,使用{}值初始化
    //1. 安全性第一:防止未初始化变量
    //2. 防止窄化转换(编译时错误)
    //3. 统一性:一套语法适用于所有类型
    //4. 区分函数声明和对象构造
    //5. 清晰的语义表达
    //6. 模板的通用性:适用更多类型(例如自定义类、指针、仅有 explicit构造函数的类型等)
    T temp{};
    std::cout << "测试:" << temp << std::endl;
    T temp1{0};
    std::cout << "测试:" << temp << std::endl;
    //T temp2{0.5};//从"double"转换到"int"需要收缩转换
	//T的类型由显示指定,而不是自动类型推导,所以T的类型不是浮点型,而是int类型
    temp = 1.5;
    std::cout << "测试:" << temp << std::endl;
}

/// <summary>
/// 测试函数
/// </summary>
void test1() {
    std::cout << "==============================================================" << std::endl;
    std::cout << "函数模板封装交换函数测试:" << std::endl;
    int a = 10;
    int b = 20;
    char c = 'c';//'c' 对应 ASCII码 99
    double d = 0.5;
	std::cout << "交换前:a = " << a << ", b = " << b << ", c = " << c << ", d = " << d << std::endl;

    std::cout << "=== 普通函数 ===" << std::endl;
	//普通函数交换
    swapInt(a, b);
	std::cout << "交换后:a = " << a << ", b = " << b << std::endl;

	//隐式转换允许不同类型的变量交换
    //swapInt(a, c);// 引用传递 - 不允许隐式类型转换,引用应该对原变量的直接操作,而转换会产生临时变量
    //但是const引用允许隐式转换,const引用是只读的,绑定到临时变量是安全的,因为不会修改它。
    std::cout  << a << "+ " <<c << "= " << addInt(a, c) << std::endl;// 值传递 - 允许隐式类型转换

    std::cout << "=== 有参数版本(可以自动推导)===" << std::endl;
	//模板函数交换
    swap_T(a, b);
	std::cout << "交换后:a = " << a << ", b = " << b << std::endl;

	//显示指定类型
    swap_T<int>(a, b);
	std::cout << "交换后:a = " << a << ", b = " << b << std::endl;


    //swap_T<int>(a, d);//引用传递同样不允许隐式类型转换

    //隐式转换
    std::cout << "相加:a = " << a << ", d = " << d << std::endl;
    std::cout << a << "+ " << d << "= " << add_T<int>(a, d) << std::endl;//显式指定,可以隐式转换
    //std::cout << a << "+ " << d << "= " << add_T(a, d) << std::endl;//自动推导,不可以隐式转换

	//自动类型推导不允许不同类型的变量
    // 推导过程:
    //1. 看第一个参数a → T必须与a类型匹配 → T = int
    //2. 看第二个参数d → T必须与d类型匹配 → T = double
    //3. 矛盾!T不能既是int又是double
    //4. 推导失败,编译错误
    //swap_T(a, d);

    std::cout << "=== 无参数版本(必须显式指定)===" << std::endl;
	//必须显式指定类型
    //test_T();
    test_T<int>();
    std::cout << "==============================================================" << std::endl;
}

/// <summary>
/// 选择排序函数(模板函数)
/// 不稳定排序,可能改变相同元素相对顺序,因为会远距离交换
/// 时间复杂度:始终O(n²)
/// 空间复杂度:O(1)原地排序
/// 比较次数​:固定:n(n - 1) / 2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
template<typename T>
void mySelectionSort_T(T arr[],int len) {
    // 外层循环:控制放置位置
    for (int i = 0; i < len-1; i++) {
        int max = i;
        // 内层循环:从前往后在未排序部分寻找最大值
        for (int j = i + 1; j < len; j++) {
            if (arr[max] < arr[j]) {
                max = j;
            }
        }
        std::cout << "i = " << i << ", max = " << max << std::endl;
        // 内层循环结束后,交换一次,每次都挑出最大值
        if (max != i) {
            swap_T(arr[max], arr[i]);
            std::cout << "交换:" << std::endl;
            printArr(arr, len);
        }
    }
}

/// <summary>
/// 冒泡排序函数(模板函数)
/// 稳定排序,不改变相同元素相对顺序,因为只会相邻交换
/// 时间复杂度:平均O(n²),最优O(n)
/// 空间复杂度:O(1)原地排序
/// 比较次数​:平均n(n-1)/2,最优n-1
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
template<typename T>
void myBubbleSort_T(T arr[], int len) {
    // 外层循环:控制冒泡轮数
    for (int i = 0; i < len-1; i++) {
        bool swapped = false;//退出逻辑优化
        // 内层循环:从后往前每轮减少比较范围执行相邻比较
        for (int j = 0; j < len-1-i; j++) {
            //每轮内循环:让一个小气泡上浮到水面(从大到小排序)
            std::cout << "i = " << i << ", j = " << j << std::endl;
            if (arr[j] < arr[j+1]) {
                //立即交换
                swap_T(arr[j], arr[j+1]);
                swapped = true;
                std::cout << "交换:" << std::endl;
                printArr(arr, len);
            }
        }
        if (!swapped) {
            break;//如果没有交换,说明已经有序,提前退出
            std::cout << "已经有序,提前退出" << std::endl;
        }
    }
}

/// <summary>
/// 测试函数
/// </summary>
void test2() {
    std::cout << "==============================================================" << std::endl;
    std::cout << "函数模板封装选择排序函数,char数组测试:"<< std::endl;
	char char_arr[] = "hello world";//双引号括起的字符串会自动在末尾添加空字符'\0',所以长度12
    int num = sizeof(char_arr) / sizeof(char);
    std::cout << "输入数组元素数量:" << num << std::endl;
    //函数模板重载测试
    printArr(char_arr, num);
    printArr(char_arr, num,'-');
    mySelectionSort_T(char_arr, num);
    printArr(char_arr, num);
    std::cout << "==============================================================" << std::endl;
}

/// <summary>
/// 测试函数
/// </summary>
void test3() {
    std::cout << "==============================================================" << std::endl;
    std::cout << "函数模板封装选择排序函数,int数组测试:" << std::endl;
    //int int_arr[] = {7,5,3,1,5,9,4,5,6,8,5,2 };
    int int_arr[] = { 1,2,3,4 };
    int num = sizeof(int_arr) / sizeof(int);
    std::cout << "输入数组元素数量:" << num << std::endl;
    printArr<>(int_arr, num);//空模板参数列表来强制调用函数模板
    mySelectionSort_T(int_arr, num);
    printArr(int_arr, num);
    std::cout << "==============================================================" << std::endl;
}

/// <summary>
/// 测试函数
/// </summary>
void test4() {
    std::cout << "==============================================================" << std::endl;
    std::cout << "函数模板封装冒泡排序函数,int数组测试:" << std::endl;
    //int int_arr[] = {7,5,3,1,5,9,4,5,6,8,5,2 };
    int int_arr[] = { 1,2,3,4 };
    int num = sizeof(int_arr) / sizeof(int);
    std::cout << "输入数组元素数量:" << num << std::endl;
    printArr(int_arr, num);
    myBubbleSort_T(int_arr, num);
    printArr(int_arr, num);
    std::cout << "==============================================================" << std::endl;
}

/// <summary>
/// 测试函数
/// 函数调用优先级:
/// 1.完全匹配的普通函数(最高优先级)
/// 2.模板特化
/// 3.函数模板
/// 4.隐式转换的普通函数(最低优先级)
/// </summary>
void test5() {
    std::cout << "==============================================================" << std::endl;
    std::cout << "函数调用优先级测试:" << std::endl;

    int int_arr[] = { 1,2,3,4 };
    int num1 = sizeof(int_arr) / sizeof(int);
    std::cout << "输入数组元素数量:" << num1 << std::endl;
    printArr(int_arr, num1);
    printArr<>(int_arr, num1);//空模板参数列表来强制调用函数模板

	MyPoint point_arr[] = { MyPoint(1,2), MyPoint(3,4), MyPoint(5,6) };
    int num2 = sizeof(point_arr) / sizeof(MyPoint);
    std::cout << "输入数组元素数量:" << num2 << std::endl;
    printArr(point_arr, num2);
    std::cout << "==============================================================" << std::endl;
}

int main()
{
	//调用测试函数
    test1();
    test2();
    test3();
    test4();
    test5();
    
	//等待用户输入
    system("pause");
	//返回0表示程序正常结束
    return 0;
}

🎬 附2:程序运行输出

复制代码
==============================================================
函数模板封装交换函数测试:
交换前:a = 10, b = 20, c = c, d = 0.5
=== 普通函数 ===
交换后:a = 20, b = 10
20+ c= 119
=== 有参数版本(可以自动推导)===
交换后:a = 10, b = 20
交换后:a = 20, b = 10
相加:a = 20, d = 0.5
20+ 0.5= 20
=== 无参数版本(必须显式指定)===
测试:0
测试:0
测试:1
==============================================================
==============================================================
函数模板封装选择排序函数,char数组测试:
输入数组元素数量:12
h e l l o   w o r l d
h-e-l-l-o- -w-o-r-l-d--
i = 0, max = 6
交换:
w e l l o   h o r l d
i = 1, max = 8
交换:
w r l l o   h o e l d
i = 2, max = 4
交换:
w r o l l   h o e l d
i = 3, max = 7
交换:
w r o o l   h l e l d
i = 4, max = 4
i = 5, max = 7
交换:
w r o o l l h   e l d
i = 6, max = 9
交换:
w r o o l l l   e h d
i = 7, max = 9
交换:
w r o o l l l h e   d
i = 8, max = 8
i = 9, max = 10
交换:
w r o o l l l h e d
i = 10, max = 10
w r o o l l l h e d
==============================================================
==============================================================
函数模板封装选择排序函数,int数组测试:
输入数组元素数量:4
1 / 2 / 3 / 4 /
i = 0, max = 3
交换:
4,2,3,1,
i = 1, max = 2
交换:
4,3,2,1,
i = 2, max = 2
4,3,2,1,
==============================================================
==============================================================
函数模板封装冒泡排序函数,int数组测试:
输入数组元素数量:4
1,2,3,4,
i = 0, j = 0
交换:
2,1,3,4,
i = 0, j = 1
交换:
2,3,1,4,
i = 0, j = 2
交换:
2,3,4,1,
i = 1, j = 0
交换:
3,2,4,1,
i = 1, j = 1
交换:
3,4,2,1,
i = 2, j = 0
交换:
4,3,2,1,
4,3,2,1,
==============================================================
==============================================================
函数调用优先级测试:
输入数组元素数量:4
1,2,3,4,
1 / 2 / 3 / 4 /
输入数组元素数量:3
(1,2)(3,4)(5,6)
==============================================================
相关推荐
·心猿意码·2 小时前
C++ 线程安全单例模式的底层源码级解析
c++·单例模式
故事和你912 小时前
洛谷-入门4-数组3
开发语言·数据结构·c++·算法·动态规划·图论
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——原型模式
c++·笔记·设计模式
玉树临风ives2 小时前
atcoder ABC 451 题解
c++·算法·atcoder
南境十里·墨染春水2 小时前
C++传记 详解单例模式(面向对象)
开发语言·c++·单例模式
扶摇接北海1762 小时前
洛谷:B4488 [语言月赛 202602] 甜品食用
数据结构·c++·算法
cui_ruicheng2 小时前
C++智能指针:从 RAII 到 shared_ptr 源码实现
开发语言·c++
共享家95272 小时前
实现简化的高性能并发内存池
开发语言·数据结构·c++·后端
千里马学框架2 小时前
aospc/c++的native 模块VScode和Clion
android·开发语言·c++·vscode·安卓framework开发·clion·车载开发