C++的进阶泛型编程学习(1):函数模板的基本概念和机制

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、模板
    • 1.1 模板的概念
      • 1.1.1 形象的解释:模板就是通用的模具,目的是提高通用性
      • 1.1.1 模板的特点:
      • 1.1.2 综述模板的作用
    • 1.2 模板的使用机制
      • 1.2.1 函数模板
  • 二、函数模板的深入学习及注意机制
    • 2.1 函数模板的自动类型推导
      • 2.1.1 ①自动类型推导,必须使得推导出的数据类型T是一致的
    • 2.2 typename为什么可以替换为class?
    • 2.3 函数模板的功能及使用时机
  • 三、函数模板具体案例——数组排序
    • 3.1 目的:测试char数组和int数组的排序
    • 3.2 思路:
    • 3.3 全部代码
  • 四、普通函数与函数模板的区别
    • (1)定义方式:
    • (2)参数类型:
    • (3)重载:
    • (4)代码生成:
    • (5)使用方式:
  • 五、普通函数和函数模板的调用规则
    • 5.1 如果函数模板和普通函数都可以实现,优先调用普通函数
    • 5.2 可以通过空模板参数列表来强制调用函数模板
    • 5.3 函数模板也可以发生重载
    • 5.4 如果函数模板可以产生更好的匹配优先调用函数模板
  • 六、函数模板的局限性:不是万能的
    • 6.1 问题提出:函数模板无法解决数组、结构体这类特殊传参的比较
    • 6.2 解决办法
  • 七、学习模板并不是为了写模板,而是在STL(标准模板库)能够运用系统提供的模板


前言

回顾一下C++的面向对象特性有哪些?
在这里插入图片描述
在这里插入图片描述
我用这两个表来简要的总结了一下,接下来要接触的,是C++中更加复杂,更加进阶的内容

一、模板

1.1 模板的概念

C++的模板是一种通用编程工具,它允许在编写代码时定义通用的数据类型或函数,以便在不同的上下文中重复使用。模板可以用于类、函数和类成员函数。

1.1.1 形象的解释:模板就是通用的模具,目的是提高通用性

在这里插入图片描述
另外比如说PPT模板、实验报告模板等等。

1.1.1 模板的特点:

①模板不可以直接使用,他只是一个框架
②模板的通用不是万能的,只在一个或者几个方面发挥作用
所以C++里面的模板的作用也是大同小异

1.1.2 综述模板的作用

C++中的模板是一种通用编程工具,它允许在编写代码时定义通用的数据类型或函数,以便在不同的上下文中重复使用。模板的作用包括代码重用、泛型编程、类型安全、高性能代码生成以及提供容器和算法库等功能。通过使用模板,可以编写通用的代码,适应不同的数据类型,提高代码的灵活性和可重用性。模板在编译时进行类型检查,提供类型安全性,并通过编译时代码生成来提高性能。

1.2 模板的使用机制

C++提供两种模板机制:函数模板类模板

1.2.1 函数模板

函数模板是C++中的一种特殊函数,它可以用于定义通用的函数,可以在不同的数据类型上进行操作。函数模板通过使用模板参数来表示通用的数据类型,从而实现代码的重用和泛型编程。
即告诉编译器,我要声明一个暂时未知函数返回类型与未知形参的函数

函数模板的语法如下所示:

template <typename T>
返回类型 函数名(参数列表) {
    // 函数体
}

template:声明创建了一个模板
typename :表明其后面的符号是一种数据类型
T:一种通用的数据类型,通常为大写字母,可以替换

在上面的语法中,template 表示这是一个函数模板,并且T是一个模板参数,可以是任意类型。返回类型表示函数的返回类型,函数名是函数的名称,参数列表是函数的参数列表。

写一个实例:

template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int num1 = 10, num2 = 20;
    int maxNum = maximum(num1, num2);
    cout << "Maximum number is: " << maxNum << endl;

    double num3 = 3.14, num4 = 2.71;
    double maxDouble = maximum(num3, num4);
    cout << "Maximum double is: " << maxDouble << endl;

    return 0;
}

二、函数模板的深入学习及注意机制

2.1 函数模板的自动类型推导

在C++中,函数模板可以使用自动类型推导来推断模板参数的类型。通过使用auto关键字作为函数模板的参数类型,编译器可以根据函数调用时的实参类型来推导出模板参数的类型

以下是一个使用自动类型推导的函数模板示例:

template <typename T>
void print(T value) {
    std::cout << value << std::endl;
}

int main() {
    print(10);  // 推导为int类型
    print(3.14);  // 推导为double类型
    print("Hello");  // 推导为const char*类型

    return 0;
}

在上面的示例中,print函数模板使用了自动类型推导,它的模板参数类型使用了auto关键字。在main函数中,我们分别调用了print函数,并传递了不同类型的参数。编译器会根据实参的类型推导出模板参数的类型,并实例化相应的函数。

2.1.1 ①自动类型推导,必须使得推导出的数据类型T是一致的


需要注意的是,自动类型推导仅适用于函数模板的参数类型,而不适用于函数模板的返回类型。

2.2 typename为什么可以替换为class?

在C++中,typename和class在函数模板中都可以用来表示模板参数的类型。它们在函数模板中的使用是等效的,可以互相替换使用

使用typename关键字来表示模板参数的类型是C++标准的做法,特别是在模板的嵌套和依赖名称的情况下。例如,在模板内部使用嵌套类型时,需要使用typename来指示编译器该名称是一个类型。

使用class关键字来表示模板参数的类型是C++早期版本的做法,但在C++标准化过程中,为了更好地表达模板参数是类型的概念,引入了typename关键字。

因此,typename和class在函数模板中的使用是等效的,可以根据个人喜好和代码风格选择使用其中之一。但在一些特定的情况下,如模板的嵌套和依赖名称时,使用typename是必需的

2.3 函数模板的功能及使用时机

函数模板是一种通用的函数定义,可以用于处理多种不同类型的数据。它允许编写一次函数定义,然后根据需要在不同的上下文中使用不同的数据类型。

函数模板的主要功能是实现代码的重用和泛化。通过使用函数模板,可以编写一次通用的函数定义,然后在需要时根据具体的数据类型进行实例化。这样可以避免重复编写相似的代码,提高代码的可维护性和可读性

函数模板的使用时机包括但不限于以下情况:

处理不同类型的数据:当您需要编写一个函数来处理多种不同类型的数据时,可以使用函数模板。例如,您可以编写一个通用的排序函数,可以用于排序整数数组、浮点数数组或字符串数组。

泛化算法:当您需要编写一个通用的算法,可以适用于不同类型的数据结构时,可以使用函数模板。例如,您可以编写一个通用的搜索函数,可以在不同类型的容器中查找特定的元素。

类型安全的操作:函数模板可以提供类型安全的操作,因为它们在编译时进行类型检查。这意味着如果您在函数模板中使用了不兼容的数据类型,编译器将在编译时报错,而不是在运行时出现错误。

代码简化和减少重复:函数模板可以简化代码并减少重复。通过使用函数模板,您可以避免编写多个相似的函数来处理不同类型的数据,从而减少了代码量和维护成本。

三、函数模板具体案例——数组排序

3.1 目的:测试char数组和int数组的排序

如:定义char数组:“badcfe”,要求按照字母表的顺序进行从前往后的排序(即ASCII码数值大小),同时,定义int数组:2 5 8 7 4 1 6 3,要求从小到大排序。
使用函数模板进行泛化处理,提高重用性。

3.2 思路:

采用冒泡排序,每次比较相邻元素的大小,让较大的那个排后面,不断循环。
定义一个函数模板,传参的数组数据类型用模板代替。

template <typename T>

void sort(T arr[], int len) {

    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }

}

3.3 全部代码

#include <iostream>
#include<string>

using namespace std;

template <typename T>

void sort(T arr[], int len) {

    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }

}


int main(void) {


    char chararr[] = { 'b','a','d','c','f','e'};
    int charlen = sizeof(chararr) / sizeof(chararr[0]);
    sort(chararr, charlen);
    std::cout << "按字母表顺序排序后的char数组:";
    for (int i = 0; i < charlen; i++) {
        std::cout << chararr[i] << " ";
    }
    std::cout << std::endl;

    int intarr[] = { 2, 5, 8, 7, 4, 1, 6, 3 };
    int intlen = sizeof(intarr) / sizeof(intarr[0]);
    sort(intarr, intlen);
    std::cout << "从小到大排序后的int数组:";
    for (int i = 0; i < intlen; i++) {
        std::cout << intarr[i] << " ";
    }
    std::cout << std::endl;


	system("pause");
	return 0;
}


四、普通函数与函数模板的区别

定义一个普通函数max,用于比较两个整数的大小并返回较大的数:

int max(int a, int b) {
    return (a > b) ? a : b;
}

接下来,我们定义一个函数模板templateMax,也用于比较两个数的大小并返回较大的数:

template<typename T>
T templateMax(T a, T b) {
  return (a > b) ? a : b;
}

现在,我们来比较普通函数和函数模板的区别。

(1)定义方式:

普通函数的定义是针对特定类型的,如上述的max函数是针对整数类型的定义。

函数模板的定义使用template来声明一个通用的模板函数,可以适用于多种类型。

(2)参数类型:

普通函数的参数类型是具体指定的,如上述的max函数的参数类型是int。

函数模板的参数类型是使用模板参数T来表示的,可以是通用的类型。

(3)重载:

普通函数可以通过函数重载来处理不同类型的参数,如可以定义一个max函数来处理浮点数类型的参数。

函数模板可以通过参数推导来自动匹配不同类型的参数,无需手动重载。例如,我们可以使用templateMax函数模板来比较浮点数、字符等不同类型的参数。

(4)代码生成:

普通函数在编译时会生成具体的函数代码,如上述的max函数在编译时会生成一个针对整数类型的函数。

函数模板在编译时不会生成具体的函数代码,只有在实例化时才会根据具体的类型生成对应的函数代码。例如,当我们使用templateMax函数模板比较整数时,编译器会根据实际情况生成一个针对整数类型的函数。

(5)使用方式:

普通函数可以直接调用,如max(3, 5)。

函数模板需要通过实例化来生成具体的函数,然后才能调用。例如,templateMax(3, 5)会生成一个针对整数类型的函数,并返回较大的数。

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

调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配优先调用函数模板

接下来一个个解释:

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

如果函数模板和普通函数都可以实现某个功能,编译器会优先选择普通函数进行调用,而不是实例化函数模板。

这是因为普通函数的匹配更加具体,不需要进行模板参数的推导和实例化过程,所以在编译时会更加高效。而函数模板需要在实例化时根据具体的类型生成对应的函数代码,这个过程可能会增加编译时间和代码体积。

例如,假设我们有一个普通函数foo和一个函数模板templateFoo,它们都可以接受一个整数作为参数:

void foo(int x) {
    // 普通函数实现
     std::cout << "普通函数"<< endl;
}

template<typename T>
void foo(T x) {
    // 函数模板实现
    std::cout << "函数模板"<< endl;
}


int main(void) {

     foo(40);
	system("pause");
	return 0;
}

此时编译器会调用普通函数foo。
当我们调用templateFoo函数模板时,编译器会进行实例化,生成针对具体类型的函数代码:
当我们调用foo函数时,编译器会直接选择普通函数进行调用:

但是,如果没有对应的普通函数实现,或者我们明确指定要调用函数模板,编译器会选择函数模板进行实例化和调用。

那如何告诉编译器去调用函数模板呢?

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

即:

foo<>(40);

添加这个符号,就是强制告诉编译器去调用函数模板。

5.3 函数模板也可以发生重载

函数模板也可以发生重载。函数模板的重载与普通函数的重载类似,可以根据参数类型和数量的不同来进行区分。

当存在多个函数模板时,编译器会根据函数调用的参数类型和数量来选择最匹配的函数模板进行实例化和调用。

例如,考虑以下两个函数模板:

template<typename T>
void foo(T x) {
    // 函数模板1
}

template<typename T>
void foo(T x, T y) {
    // 函数模板2
}

当我们调用foo函数时,编译器会根据参数类型和数量来选择最匹配的函数模板进行实例化和调用。

foo(42);        // 调用函数模板1,参数类型为int
foo(3.14, 2.71); // 调用函数模板2,参数类型为double

在这个例子中,根据参数类型和数量的不同,编译器可以正确地选择调用不同的函数模板。

因此,函数模板也可以发生重载,编译器会根据参数类型和数量来选择最匹配的函数模板进行实例化和调用。

5.4 如果函数模板可以产生更好的匹配优先调用函数模板

不解释了,跟5.1是一个道理

六、函数模板的局限性:不是万能的

6.1 问题提出:函数模板无法解决数组、结构体这类特殊传参的比较

对于这段代码:

template<typename T>
void foo(T a,T ,b) {
    a=b;
}

或者:

template<typename T>
void foo(T a,T ,b) {
    if(a>b){
    //
    }
}

传递参数如果是字符型或者整形还好,但是如果a和b是数组的话就不行了,无法运行。或者说a和b是我们用结构体自定义的一种数据类型,那也不行。

那如何解决这类问题呢?

6.2 解决办法

函数模板可以处理数组和结构体这类特殊传参的比较,但需要使用适当的比较操作符或自定义比较函数来实现。

对于数组的比较,你可以使用逐个比较数组元素的方式来判断它们是否相等。例如,考虑以下函数模板:

template<typename T, int size>
bool isEqual(T arr1[size], T arr2[size]) {
    for (int i = 0; i < size; i++) {
        if (arr1[i] != arr2[i]) {
            return false;
        }
    }
    return true;
}

在这个例子中,arr1和arr2是模板参数类型为T的数组,size是数组的大小。

当你调用isEqual函数模板时,它会逐个比较数组元素,并返回比较结果。

int myArray1[] = {1, 2, 3, 4, 5};
int myArray2[] = {1, 2, 3, 4, 5};
bool result = isEqual<int, 5>(myArray1, myArray2);

在这个例子中,myArray1和myArray2作为参数传递给isEqual函数模板时,它们会被自动转换为指向数组首元素的指针,并且编译器会推导出数组的类型和大小。

对于结构体的比较,你可以重载比较操作符或自定义比较函数来实现结构体的比较。例如,考虑以下结构体和函数模板:

struct Point {
    int x;
    int y;
};

template<typename T>
bool isEqual(T obj1, T obj2) {
    return obj1 == obj2;
}

在这个例子中,Point是一个结构体,isEqual函数模板使用了比较操作符==来比较结构体的相等性。

当你调用isEqual函数模板时,它会使用重载的比较操作符或自定义的比较函数来判断结构体是否相等。

Point p1 = {1, 2};
Point p2 = {1, 2};
bool result = isEqual<Point>(p1, p2);

在这个例子中,p1和p2作为参数传递给isEqual函数模板时,它们会被按值传递给函数,并使用重载的比较操作符==来比较结构体的相等性。

总结起来,函数模板可以处理数组和结构体这类特殊传参的比较,但需要使用适当的比较操作符或自定义比较函数来实现。对于数组的比较,你可以逐个比较数组元素;对于结构体的比较,你可以重载比较操作符或自定义比较函数。

七、学习模板并不是为了写模板,而是在STL(标准模板库)能够运用系统提供的模板

当然,模板分为函数模板和类模板,所以下一节讲学习类模板。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/387274.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上位机图像处理和嵌入式模块部署(上位机主要功能)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前关于机器视觉方面&#xff0c;相关的软件很多。比如说商业化的halcon、vision pro、vision master&#xff0c;当然也可以用opencv、pytorch自…

计算机服务器中了360后缀勒索病毒怎么办?360后缀勒索病毒处理流程

网络技术的不断应用与发展&#xff0c;为企业的生产运营提供了有利保障&#xff0c;越来越多的企业走向数字化办公模式&#xff0c;并且企业的发展离不开数据支撑&#xff0c;重视数据安全成为了众多企业关心的主要话题。春节前后&#xff0c;云天数据恢复中心接到很多企业的求…

用163邮箱或者outlook接收国科大邮箱的邮件

使用如图下路径&#xff0c;创建一个新的密码&#xff0c;用于在163大师邮箱或者outlook登录即可 如果不行&#xff0c;则需要手动配置邮箱服务器 参考网址&#xff1a;中国科学院邮件系统帮助中心

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…

【sql】sqlite3数据库

一、介绍 SQLite是一个轻量级的、开源的嵌入式数据库&#xff0c;由D. Richard Hipp使用C语言编写。由于其资源占用少、性能良好和零管理成本的特点&#xff0c;SQLite在嵌入式系统中得到了广泛应用&#xff0c;如Android和iPhone等操作系统中都有内置的SQLite数据库供开发人员…

尚硅谷最新Node.js 学习笔记(二)

目录 五、HTTP协议 5.1、概念 5.2、请求报文的组成 5.3、HTTP 的请求行 5.4、HTTP 的请求头 5.5、HTTP 的请求体 5.6、响应报文的组成 5.7、创建HTTP服务 操作步骤 测试 注意事项 5.8、浏览器查看 HTTP 报文 查看请求行和请求头 查看请求体 查看URL查询字符串 …

如何在Django中使用分布式定时任务并结合消息队列

如何在Django中使用分布式定时任务并结合消息队列 如何在Django中使用分布式定时任务并结合消息队列项目背景与意义实现步骤1. 安装Celery和Django-celery-beat2. 配置Celery3. 配置Django-celery-beat4. 定义定时任务5. 启动Celery worker 和 beat6. Celery 指令7. 对接消息队…

ClickHouse--08--SQL DDL 操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 SQL DDL 操作1 创建库2 查看数据库3 删除库4 创建表5 查看表6 查看表的定义7 查看表的字段8 删除表9 修改表9.1 添加列9.2 删除列9.3 清空列9.4 给列修改注释9.5 修…

基于LightGBM的回归任务案例

在本文中&#xff0c;我们将学习先进的机器学习模型之一&#xff1a;Lightgbm。在对XGB模型进行了越来越多的改进以获得更好的性能之后&#xff0c;XGBoost是一种极限梯度提升机器&#xff0c;但通过lightgbm&#xff0c;我们可以在没有太多计算的情况下实现类似或更好的结果&a…

对(一维)数组与指针的深入理解(1)

目录 1.数组名的理解2.使用指针访问&#xff08;一维&#xff09;数组3.&#xff08;一维&#xff09;数组传参的本质 1.数组名的理解 以前我们在使用指针访问数组内容时&#xff0c;有这样的代码&#xff1a; #include <stdio.h>int main() {int arr[10] { 1,2,3,4,5…

CTF-web 之 burp suite 使用

burp suite 使用 一般其是作为一个辅助工具&#xff0c;直接使用来解题的部分是少数&#xff0c;我们可以使用它来观察请 求和响应&#xff0c;并且可以反复的提交&#xff0c;关键的是他还带有很多其他的功能&#xff0c;在我们做题的过程中&#xff0c; 使用的关键点包括&…

C++LNK1207中的 PDB 格式不兼容;请删除并重新生成

在打开别人发的C文件时&#xff0c;可能出现该报错 解决办法 打开资源管理器&#xff0c;找到原来的路径 进入Debug&#xff0c; 找到对应的PDB文件删除即可。

OpenAI ChatGPT 记忆功能怎么实现?

你的聊天助手现在能“记住”你的对话了&#xff01; 2月14日凌晨&#xff0c;OpenAI宣布正在测试ChatGPT的新功能——记住用户提问内容&#xff0c;并自由控制内存。这意味着&#xff0c;ChatGPT能帮你记住那些重要的聊天内容&#xff0c;让你的对话更流畅、更自然。 想象一下…

政安晨:【示例演绎】【用TensorFlow编写线性分类器】—— 同时了解一点TensorFlow与Keras的基本概念

环境准备 如果小伙伴们第一次接触TensorFlow与Keras&#xff0c;可以先看一下我的这篇文章做些环境准备&#xff08;可以先忽略这篇文章里面代码实现部分&#xff0c;仅查看这里的环境准备部分即可&#xff09;。 文章如下&#xff1a; 政安晨&#xff1a;【详细解析】【用T…

Ps:焦点堆栈

焦点堆栈 Focus Stacking是一种摄影和图像处理技术&#xff0c;通过合并多张在不同焦距拍摄的照片来创建一张具有更大景深的图像&#xff0c;特别适用于微距摄影、风景摄影和任何需要在整个场景中保持尖锐对焦的情况。 ◆ ◆ ◆ 拍摄注意事项 1、使用三脚架 为了确保图像之间…

Spring 事务原理总结四

作为一名认知有限的中国人&#xff0c;我对年的喜爱&#xff0c;胜过其他一切&#xff0c;因为它给了我拒绝一切的合理理由。每到这个时候&#xff0c;我都会用各种理由来为自己的不作为开脱&#xff0c;今年亦是如此。看着频频发出警报的假期余额&#xff0c;我内心的焦躁变得…

VSCode无法连接远程服务器的两种解决方法

文章目录 VSCode Terminal 报错解决方式1解决方式2you are connected to an OS version that is unsupported by Visual Studio Code解决方法 VSCode Terminal 报错 直接在terminal或cmd中使用ssh命令可以连接服务器&#xff0c;但是在vscode中存在报错&#xff0c;最后一行为…

第12讲创建图文投票实现

创建图文投票实现 图文投票和文字投票基本一样&#xff0c;就是在投票选项里面&#xff0c;多了一个选项图片&#xff1b; <view class"option_item" v-for"(item,index) in options" :key"item.id"><view class"option_input&qu…

第13章 网络 Page724 asio定时器

程序代码&#xff1a; 11行&#xff0c;声明一个ios对象 13行&#xff0c;使用ios对象作为参数声明一个定时器&#xff0c;此时&#xff0c;定时器和ios完成了关联&#xff0c;后面定时器如果有任务的话&#xff0c;就可以将任务交给ios 16行&#xff0c;为定时器设置一个定…

Vue3+Vite+TS+Pinia+ElementPlus+Router+Axios创建项目

目录 初始项目组成1. 创建项目1.1 下载项目依赖1.2 项目自动启动1.3 src 别名设置vite.config.ts配置文件tsconfig.json配置若新创项目ts提示1.4 运行测试2. 清除默认样式2.1 样式清除代码下载2.2 src下创建公共样式文件夹`style`2.3 main.js中引入样式2.4 安装`sass`解析插件2…