C++ 面向对象(类和对象)—— 函数模板

微软技术分享 微软全球最有价值专家
全栈领域优质创作者
博客专家认证
2025-06-07 07:56:18

文章目录

模板

  • 本阶段主要针对于==C++范式编程STL技术==进行详细讲解,探讨 C++ 更深层的应用 template<class T> — 类模板
    template<typename T> — 函数模板

这两种模板的形式,在作用上是无差别的,唯一用法可能只是在于对函数模板和类模板的区分


一、模板简介

  • 类型安全:模板提供了类型安全,因为编译器会在编译时检查类型错误。
  • 代码重用:通过使用模板,你可以编写一次代码,然后对多种类型重用它,从而减少代码重复,提高复用性。
  • 性能:模板在编译时实例化,这意味着没有运行时开销,性能通常比使用虚函数或动态类型识别(如 typeid)更好。

模板就是建立通用的摸具,大大提高复用性

https://i-blog.csdnimg.cn/direct/62a0079f586547469ff78d463a6292a9.png

  • 模板的特点:

  • 模板不可以直接使用,它只是一个框架

  • 模板的通用并不是万能的


二、函数模板

  • C++的另一种编程思想叫做泛型编程,主要利用的技术就是模板
  • **C++提供两种模板机制:函数模板 ** 和 类模板

2.1函数模板的基本语法

  • 函数模板的作用: ​ 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟类型来代表
C++
    void func(int a);
     |         |
 返回值类型   形参类型
     T         T   使用虚拟类型T来代表

语法:template <typename T> + 后紧跟 + 函数声明或定义
紧跟的这一部分(函数声明或定义)就叫做函数模板

  • template ---> 声明创建模板
  • typename ---> 表示其后面的符号是一种数据类型,也可以使用class代替
  • T ---> 通用的数据类型,名称可替换

模板存在的意义:数据类型的参数化


  • 当我们打算实现两数交换,但我们并不明确两数的数据类型,我们可以写出无数种方式
C++
    内置数据类型
    void SwapInt(int& x, int& y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    void SwapDouble(double& x, double& y)
    {
        double temp = x;
        x = y;
        y = temp;
    }
    
    自定义数据类型
    void SwapPerson(Person& x, Person& y);...
  • 但是,我们发现,除返回值类型与参数的数据类型不同外,其余代码逻辑都是相同的
  • 于是函数模板就实现了

2.1.1函数模板的调用方式
C++
    //声明一个模板,告诉编译器T是一个通用的数据类型,及告诉编译器模板T后紧跟着的那段代码不要报错!
    template<typename T>
    void My_Swap(T& x, T& y)
    {
        T temp = x;
        x = y;
        y = x;
    }

--->模板的使用方式:
    int main()
    {
        //1.自动类型推导
        My_Swap(参数1, 参数2);
        
        //2.显示指定类型(推荐 - 隐式类型转换) - 明确告诉编译器你想要的数据类型
        My_Swap<数据类型>(参数1, 参数2);---><int>(a, b);
    }

2.2函数模板的注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T
  • 模板必须要确定T的数据类型

1.自动类型推导,必须推导出一致的数据类型T

C++
    template<typename T>
    void My_Swap(T& x, T& y)
    {
        ...    
    }

int main()
{
    int a = 1, b = 2;
    char c = 'a';
    My_Swap(a, b);--->true
    My_Swap(a, c);--->false
    return 0;
}

https://i-blog.csdnimg.cn/direct/8d897db83b3f4429861f902941622488.png


2.模板必须要确定T的数据类型

C++
    template<typename T>
    void func()--->函数体 func() 是函数模板,但没有指定模板参数  T 的具体类型
    {
        cout<< "func() 的调用" << endl;
    }
    int main()
    {
        func();--->也没有在调用 func 时提供类型参数
            
//如果想调用func(),那么可以给模板显示指定一个数据类型
        func<int>();--->这样,就可以调用到func()
        return 0;
    }

https://i-blog.csdnimg.cn/direct/3181fe45afd341c4b96d2195d631b283.png


2.4函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同的数据类型进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别用char数组和int数组进行排序

1.逐一解决模板,首先我们需要先建立一个排序模板

#include<iostream>
using namespace std;

template<typename T>
void My_Sort(T arr[], int len)
{
    for (int i = 0; i < len; i++)
    {
        int pos = i;//默认当前i位置下标所对应的元素为最大元素
        for (int j = i + 1; j < len; j++)
        {
            if (arr[pos] < arr[j]) pos = j;
        }
        
        //当发现arr[i]不是最大值时,进行交换
        if (pos != i) My_Swap(arr[pos], arr[i]);
    }
}
void test01()
{
    char chArr[] = "udoijqwadcknzx";
    My_Sort(chArr, sizeof(chArr) / sizeof(char));
    
}
int main(){test01();return 0;}

2.在我们搭建排序模板的过程中,我们发现我们还需要一个支持两数交换的模板

C++
    template<typename T>
    void My_Swap(T& x, T& y)
    {
        T temp = x;
        x = y;
        y = temp;
    }

3.当我们所有的算法步骤解决后,我们就可以再次利用模板的方式来输出结果了

C++
    //输出模板
    template<typename T>
    void My_Print(T arr[], int len)
    {
        for (int i = 0; i < len; i++) cout << arr[i] << " ";
    }

4.总代码:

#include<iostream>
using namespace std;

//交换模板
template<typename T>
void My_Swap(T& x, T& y)
{
    T temp = x;
    x = y;
    y = temp;
}

//排序模板 - 从大到小
template<typename T>
void My_Sort(T arr[], int len)
{
    for (int i = 0; i < len; i++)
    {
        int pos = i;//默认当前i位置下标所对应的元素为最大元素
        for (int j = i + 1; j < len; j++)
        {
            if (arr[pos] < arr[j]) pos = j;
        }

        //当发现arr[i]不是最大值时,进行交换
        if (pos != i) My_Swap(arr[pos], arr[i]);
    }
}

//输出模板
template<typename T>
void My_Print(T arr[], int len)
{
    for (int i = 0; i < len; i++) cout << arr[i] << " ";
}

void test01() {
    char charArr[] = "dasjhdaxaodnqb";
    My_Sort(charArr, sizeof(charArr) / sizeof(char));
    My_Print(charArr, sizeof(charArr) / sizeof(char));
}

void test02()
{
    int intArr[] = { 4,8,4,3,1,3,1,3,13,3,21,56,46 };
    My_Sort(intArr, sizeof(intArr) / sizeof(int));
    My_Print(intArr, sizeof(intArr) / sizeof(int));
}
int main()
{
    test01();
    cout << endl;
    test02();
    return 0;
}

2.5普通函数和函数模板的区别

普通函数与 函数模板 的区别(是否会发生隐式类型转换):

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

2.5.1隐式类型转换

  • 在编程语言中,隐式类型转换 是指在程序运行过程中,由编译器自动进行的类型转换,而不需要程序员显式地指定。例如,在C++中,如果将一个整数赋值给一个浮点数变量,编译器会自动将整数转换为浮点数。这种转换是隐式的,因为它是在程序员不知情的情况下由编译器完成的。

隐式类型转换的常见情况:

  • 数值类型之间的转换C++中,当将一个较小范围的整数类型(如charshort)赋值给一个较大范围的整数类型(如int)时,会发生隐式类型转换。例如:
C++
    char c = 'a';
    int i = c; // char类型隐式转换为int类型
    输出结果97
C++
    int Add(int x, int y) return x + y;

    void test()
    {
        int a = 10;
        char c = 'a';
        //发生隐式类型转换
        cout << Add(a, c) << endl;--->109
    }

  • 函数模板 - 无法推导出一致的数据类型
C++
    template<typename T>
    void My_Add(T& x, T& y)
    {
        return x + y;
    }
    int main()
    {
        int a = 10;
        char c = 'c';
        //1.自动类型推导
        cout << My_Add(a, c);--->false

        //2.显示指定类型
        cout << My_Add<int>(a, c);--->true,会发生隐式类型转换
        return 0;
    }

https://i-blog.csdnimg.cn/direct/5d504a7322474ba5a25b94d2c674c3dc.png

  • 总结:建议使用显示类型的方式,调用函数模板,因为可以自己确定通用类型

2.6普通函数和函数模板的调用规则

  • 调用规则如下:

​ 1.如果函数模板和普通函数都可以实现,优先调用普通函数

​ 2.可以通过空模板参数列表来强制调用函数模板

​ 3.函数模板也可以发生重载

​ 4.如果函数模板可以发生更好的匹配,优先调用函数模板

C++
    void My_Print(int a, int b)--->哪怕只有函数声明 void My_Print(int a, int b);
    {                                 也无法直接调用不加空模板参数列表修饰的函数模板
        cout << "调用普通函数" << endl;
    }

    template<typename T>
    void My_Print(T& a, T& b)
    {
        cout << "调用函数模板" << endl;
    }
    int main()
    {
        int a = 10, b = 20;
        My_Print(a, b);
        return 0;
    }

https://i-blog.csdnimg.cn/direct/48f03561a2754c0d8014be685b471c34.png


  • 空模板参数列表:强制调用函数模板
C++
    My_Print<>(a, b);

https://i-blog.csdnimg.cn/direct/650984c46c3d4f6cbd0cbaa8e4d89194.png


  • 函数模板也可以发生函数重载
C++
    template<typename T>
    void My_Print(T& a, T& b)
    {
        cout << "调用函数模板" << endl;
    }

    template<typename T>
    void My_Print(T& a, T& b, T& c)
    {
        cout << "调用重载的函数模板" << endl;
    }

  • 函数模板可以产生更好的匹配时,编译器会优先调用函数模板
C++
    void My_Print(int a, int b)
    {
        cout << "调用普通函数" << endl;
    }

    template<typename T>
    void My_Print(T& a, T& b)
    {
        cout << "调用函数模板" << endl;
    }

    int main()
    {
        char c1 = 'a', c2 = 'b';
        My_Print(c1, c2);
        return 0;
    }

https://i-blog.csdnimg.cn/direct/5d17b7dea27a46a69d21bb5c4f3ceeed.png

https://i-blog.csdnimg.cn/direct/a18cb86ea73046809cb6c97a43977612.png

  • 因为编译器认为,调用普通函数时还需要进行隐式类型转换,那就过于麻烦了,所以就不去调用普通函数了。

总结:当我们使用函数模板时,最好减少使用普通函数,否则容易出现二义性


2.7模板的局限性

局限性:

  • 模板不是万能的

例如:

C++
    template<typename T>
    void f(T& a, T& b)
    {
        a = b;
    }

如上述代码中,提供一个赋值操作,如果 a 和 b 是一个数组,就无法实现了

再例如:

C++
    template<typename T>
    void f(T& a, T& b)
    {
        if(a > b){ ... }
    }

如上述代码中,如果 T 的数据类型传入的是像 Pereson 这样的自定义数据类型,也无法正常运行

因此,C++为了解决这种问题,提供了模板的重载,可以为这些特定的数据类型提供具体化的模板

C++
class Person
{
public:
    Person(string Name, int Age)
    {
        this->Name = Name;
        this->Age = Age;
    }

public:
    string Name;
    int Age;
};

template<class T>
bool My_Compare(T& x, T& y)
{
    return x == y;
}

int main()
{
    Person p1("啦啦啦", 20);
    Person p2("呦呦呦", 3);

    cout << (My_Compare(p1, p2) ? "p1 == p2" : "p1 != p2") << endl;

    return 0;
}

https://i-blog.csdnimg.cn/direct/5f67a89bfcd64d5aa63cb1dd4a50d8d2.png


2.7.1运算符重载
  • 解决方法一:运算符重载
C++
    class Person
    {
      public:
        bool operator== (const Person& other)
        cosnt{
            return Name == other.Name && Age == other.Age;
        }
    };

2.7.2具体化模板
  • 利用 Person具体化的模板实现代码,且这种基于具体化实现的模板会优先被调用
C++
    template<typename T>
    bool My_Compare(T& p1, T& p2)--->函数模板声明
    {
        return p1 == p2;
    }

    template<> bool My_Compare(Person& p1, Person& p2)
    {
        return p1.Name == p2.Name && p1.Age == p2.Age;
    }

https://i-blog.csdnimg.cn/direct/32ad2363b0844f74a56b9f5aedc00b39.png

  • 相比于运算符重载模板具体化的优点:可以省去对每一个运算符都进行重载的过程,大大降低了代码量

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化
  • 学习模板并不是为了写模板,而是为了在 STL 中能够运用系统提供的模板

文章来源: https://blog.csdn.net/2401_87692970/article/details/147032677
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
49 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

5,585

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 振翅碎流霞
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧