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 知识共享协议,转载请附上原文出处链接和本声明。


...全文
19 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
因文件超过20M不能上传,所以拆分为两个文件分次上传 第1章 COM背景知识 1.1 COM的起源 1.1.1 软件业面临的挑战 1.1.2 传统解决方案 1.1.3 面向对象程序设计方法 1.1.4 最终解决方案:组件软件 1.1.5 面向对象的组件模型——COM 1.2 COM的发展历程 1.2.1 COM以前的对象技术:DDE、OLE 1、VBX控件 1.2.2 COM首次亮相:OLE2 1.2.3 Microsoft拥抱Internet:ActiveX 1.2.4 更多的新名词:Windows DNA和COM+ 1.2.5 远程对象:ORBs和DCOM 1.2.6 COM的最新版本:COM+ 1.3 COM技术现状 1.3.1 COM与CORBA 1.3.2 COM与Enterprise Java Beans 1.3.3 Windows之外的COM 小结 第2章 从C++到COM 2.1 C++客户重用C++对象——例程DB 2.1.1 C++对象 2.1.2 客户程序 2.2 将C++对象移进DLL中——例程DB_cppdll 2.2.1 成员函数的引出 2.2.2 内存分配 2.2.3 Unicode/ASCII兼容 2.2.4 例程实现 2.2.4.1 修改接口文件 2.2.4.2 修改对象程序 2.2.4.3 修改客户程序 2.3 C++对象使用抽象基类——例程DB_vtbl 2.3.1 问题:私有数据成员被暴露 2.3.2 解决方案:抽象基类 2.3.2.1 什么是抽象基类(Abstract Base Class) 2.3.2.2 实现秘诀:虚函数(Virtual Functions) 2.3.3 使用抽象基类 2.3.4 例程实现 2.3.4.1 修改接口文件 2.3.4.2 修改对象程序 2.3.4.3 修改客户程序 2.4 改由COM库装载C++对象——例程dbalmostcom 2.4.1 COM库 2.4.2 对象创建的标准入口点 2.4.3 标准对象创建API 2.4.4 标准对象注册 2.4.5 例程实现 2.4.5.1 修改接口文件 2.4.5.2 修改对象程序 2.4.5.3 修改客户程序 2.5 将C++对象变成COM对象 2.5.1 引用计数 2.5.2 多接口 2.5.3 IUnknown接口 2.5.4 标准类厂接口:IClassFactory 2.5.5 对象代码的动态卸载 2.5.6 自动注册 2.5.7 例程实现 2.5.7.1 修改接口文件 2.5.7.2 修改对象程序 2.5.7.3 修改客户程序 2.6 为COM对象添加多接口支持 2.6.1 多接口 2.6.2 DEFINE_GUID 2.6.3 例程实现 2.6.3.1 修改接口文件 2.6.3.2 修改对象程序 2.6.3.3 修改客户程序 小结 第3章 COM基础知识 3.1 对象与接口 3.1.1 COM对象 3.1.2 COM接口 3.1.3 IUnknown接口 3.1.3.1 生存期控制:AddRef和Release 3.1.3.2 接口查询:QueryInterface 3.1.4 全球唯一标识符GUID 3.1.5 COM接口定义 3.1.6 接口描述语言IDL 3.2 COM应用模型 3.2.1 客户/服务器模型 3.2.2 进程内组件 3.2.3 进程外组件 3.2.4 COM库 3.2.5 HRESULT返回值 3.2.6 COM与注册表 3.3 COM组件 3.3.1 实现类厂对象 3.3.2 类厂对象的创建 3.3.3 实现自动注册 3.3.4 实现自动卸载 3.4 COM客户 3.4.1 COM对象创建函数 3.4.1.1 CoGetClassObject 3.4.1.2 CoCreateInstance 3.4.1.3 CoCreateInstanceEx 3.4.2 如何调用进程内组件 3.4.3 COM客户调用进程外组件 3.5 进一步认识COM 3.5.1 可重用机制:包容和聚合 3.5.2 进程透明性 3.5.3 安全性机制 小结 第4章 COM扩展技术 4.1 可连接对象机制 4.1.1 客户、接收器与可连接对象 4.1.1.1 接收器 4.1.1.2 可连接对象 4.1.1.3 客户 4.1.2 实现可连接对象 4.1.3 实现接收器 4.1.4 建立接收器与连接点的连接 4.1.5 获得出接口的类型信息 4.2 结构化存储 4.2.1 什么叫结构化存储和复合文件 4.2.2 存储对象和IStorage接口 4.2.2.1 IStorage接口 4.2.2.2 获得IStorage指针 4.2.2.3 释放STATSTG内存 4.2.2.4 枚举存储对象中的元

5,178

社区成员

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

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

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

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