学习C++就看这篇--->泛型编程之模板

一、泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

什么意思呢?我们举个例子,假如我们想实现两个数的交换,但是我们又想交换不同类型的数,在C语言当中,我们只能通过定义不同的函数来实现,进入C++呢,以我们现在的知识,只能利用函数重载来实现同一函数名调用不同函数。

如下述一段代码:

代码语言:javascript

AI代码解释

void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } void Swap(char& left, char& right) { char temp = left; left = right; right = temp; }

观察上述代码就可以发现,虽然C++的函数重载实现了上述功能,但是代码复用率太低,有没有一种东西可以解决上述问题呢? 模板就来了

📕二、函数模板

✨2.1 函数模板的定义与使用方式

概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本。

模板参数: 使用 template <typename T>template <class T> 声明。T 是一个占位符类型(称为类型参数),代表在编译时将被实际类型替换掉。可以使用多个类型参数(如 template <typename T, typename U>

通用函数定义: 在模板声明之后,定义一个函数。在这个函数的参数、返回值或函数体内,使用占位符类型 T 来代替具体的类型。

见下述使用:

代码语言:javascript

AI代码解释

template<class T> template<typename T> //与上述两种方式均可, 模板参数->类型,可以写多个参数根据情况而定 //template<typename T1, typename T2,......,typename Tn> void Swap(T& x1, T& x2) { T x = x1; x1 = x2; x2 = x//如果此处; 我们不写是不会报错的,因为如果该函数模板 未被实例化,有些编译器是不会对模板内部进行检查的,,但是会对模板的声明做检查 //即{}或者上面参数部分等描述错误会仍然报错 }

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

✨2.2 函数模板的原理

如下述使用:

代码语言:javascript

AI代码解释

template<class T> template<typename T> void Swap(T& x1, T& x2) { T x = x1; x1 = x2; x2 = x; } int main() { int a = 10; int b = 20; Swap(a, b); double c = 1.11; double d = 2.21; Swap(c, d); return 0; }

原理如下:

编译时生成: 函数模板本身不是一个可以直接调用的函数。它只是一个蓝图。

按需生成: 当编译器在代码中遇到使用该模板函数的语句时(例如 Swap(a, b);Swap(c, d);),它会:

  1. 根据调用时传递的实际参数类型(或显式指定的类型 <...>),推导出模板参数 T 的具体类型(这里是 intdouble)。
  2. 根据模板定义,生成(称为实例化)一个处理该具体类型的实际函数(例如 Swap(int a, int b)Swap(double c, double d))。

这个生成的实际函数称为模板函数函数模板的一个特化/实例

听了上面的介绍,问个问题上面我们调用的是否是同一个函数? 如果调试的时候我们可以看到,调用的是上述同一个模板,但是是同一个函数吗? 不是。 这里我们不能调用函数模板,调用的是函数模板实例化生成的对应类型的函数,可以调试的时候观察汇编代码。

所以总结一下函数模板让你只写一次函数逻辑,然后让编译器根据你调用时使用的具体数据类型,自动生成处理该类型数据的实际函数版本。 它是实现“一次编写,处处使用(不同类型)”的关键工具,也就是说事实上该有的函数一个都没有少,只不过这部分函数编译器背后帮我们去解决了,我们只写一个模板就可以了
✨2.3 函数模板的实例化

模板参数实例化分为:隐式实例化显式实例化

1️⃣隐式实例化:让编译器根据实参推演模板参数的实际类型

代码语言:javascript

AI代码解释

template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2);//这种就是模板隐式实例化,也就是说是编译器在背后自己推导出类型的 Add(d1, d2); //但如果是这种形式 //Add(a1, d1);//编译器就无法处理 // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化 return 0; }

2️⃣显式实例化:在函数名后的<>中指定模板参数的实际类型

代码语言:javascript

AI代码解释

int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化 Add(a1, (int)d1); //或者显式实例化 Add<int>(a1, d1);// 显式实例化:在函数名后的<>中指定模板参数的实际类型 return 0; }

注意:如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

✨2.4 模板参数的匹配原则

1️⃣情况1 :一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函 数

代码语言:javascript

AI代码解释

 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } void Test() { Add(1, 2);// 与非模板函数匹配,编译器不需要实例化,也就是说,编译器能调用已经存在的就不会再费劲的去模板实例化 Add<int>(1, 2);// 调用编译器实例化的Add版本 }

2️⃣ 情况2:对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

代码语言:javascript

AI代码解释

void main() { Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化 Add(1, 2.1);// 编译器会优先选择非模板函数int Add(int, int),而非函数模板。 //也就是说这要非模板函数能匹配成功,但是这是在模板函数参数不匹配的情况下,我们退其次才选择丢失精度的非模板函数 //但如果只要template<class T1, class T2>,Add(T1 left, T2 right),模板能完全匹配的情况下就不会调用非模板函数 //即重载决议优先级:完全匹配(模板) > 需要转换的匹配(非模板),编译器优先选择参数类型完全匹配的模板函数 //编译器会自我判断哪种情况更好 //模板函数不允许自动类型转换,但普通函数可以进行自动类型转换 }

对于Add(1,2.1) 解释如下:

1. 函数重载解析规则

  • 当存在模板和非模板的重载函数时,编译器优先考虑非模板函数(如果匹配成功)。
  • 非模板函数int Add(int, int)要求两个int参数。调用时传入的1int类型)完全匹配第一个参数,而2.1double类型)可通过隐式类型转换doubleint)匹配第二个参数。因此非模板函数是可行的。

2. 模板函数的限制

  • 函数模板T Add(T left, T right)要求两个参数类型必须相同(均为T)。
  • 调用Add(1, 2.1)传入intdouble,编译器无法推导出唯一的TT不能同时满足intdouble),导致模板参数推导失败。因此模板函数不被考虑。

📕三、类模板

背景:如我们C语言当中定义一个栈

代码语言:javascript

AI代码解释

typedef int StackDataType; typedef struct Stack { StackDataType* _Array; int _top; int _capacity; }Stack; 

这就有一个问题,我们如果有这也的应用场景,一个栈存int,一个栈存double,怎么办是不是必须要定义两个,那代码量是不是陡然提升

在C语言当中是没法解决这也的问题的

所以模板类就可以解决了

✨3.1 类模板的定义格式

代码语言:javascript

AI代码解释

template <class T> class Stack_CPP { public: Stack_CPP() {} ~Stack_CPP() {} void Push(T x) {} private: T* _a; int _size; int _capacity; }; int main() { Stack_CPP<int> st_cpp_int;//和类实例化的唯一区别就是在类型后面加<模板类型参数> st_cpp_int.Push(1);//注意:这里和类实例化没有任何区别,仍然是有两个参数,有个隐含的this指针 st_cpp_int.Push(2); Stack_CPP<double> st_cpp_double;//模板类就可以轻松解决上述问题 st_cpp_double.Push(1.1); st_cpp_double.Push(2.1); }

细节注意:1️⃣类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

Read more

从农田到算法:DWR注意力模块如何重塑小麦病害检测的精度边界

从农田到算法:DWR注意力模块如何重塑小麦病害检测的精度边界 当清晨的阳光洒在麦田上,叶片上那些肉眼难以察觉的锈斑和霉点,正悄然影响着全球粮食安全的根基。传统农业检测方法依赖人工巡查,不仅效率低下,且漏检率高达30%以上。而今天,一种融合可扩张残差(DWR)注意力模块的YOLOv8算法,正在田间地头的智能终端上实现每秒处理42帧图像的能力,将小麦病害识别精度推升至91.2%的新高度。 1. 小麦病害检测的技术演进与挑战 小麦叶片上的病斑检测堪称计算机视觉领域的"显微镜级任务"。典型的锈病斑直径仅2-3毫米,在500万像素的无人机航拍图中只占据不到0.01%的像素面积。传统检测方法面临三重困境: * 尺度敏感性问题:同一病害在不同生长阶段呈现5-8个数量级的尺寸变化 * 环境干扰难题:光照变化可使叶片RGB值波动达±40%,阴影与露水造成伪特征 * 形态复杂性:病斑边缘模糊度可达15-20像素,重叠目标占比超过35% # 典型小麦病害图像特征统计(基于WheatDatasets分析) import numpy as np disease_stats = { 'healt

By Ne0inhk
Flutter 三方库 libsignal 的鸿蒙化适配指南 - 实现 Signal 协议加密通信、双大鼠(Double Ratchet)算法与前向安全性保障

Flutter 三方库 libsignal 的鸿蒙化适配指南 - 实现 Signal 协议加密通信、双大鼠(Double Ratchet)算法与前向安全性保障

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 libsignal 的鸿蒙化适配指南 - 实现 Signal 协议加密通信、双大鼠(Double Ratchet)算法与前向安全性保障 前言 在 Flutter for OpenHarmony 的高度安全通信领域,Signal 协议是目前全球公认的即时通讯加密标准。libsignal 是 Signal 协议的核心 Dart 实现。它能够为鸿蒙应用提供从身份认证到会话加密的全套解决方案,确保每一个字节的通信都具备前向安全性(Forward Secrecy)。本文将深入解析如何在鸿蒙端利用该库构建极致安全的加密通信能力。 一、原理解析 / 概念介绍 1.1 基础原理 Signal 协议的核心在于“双大鼠(Double Ratchet)”算法。它结合了 Diffie-Hellman

By Ne0inhk
【优选算法】双指针算法:专题二

【优选算法】双指针算法:专题二

目录 【611.有效三角形个数】 1、题目描述 2、实现核心及思路 解题步骤: 思路可视化: 代码实现: 【179.查找总价格为目标值的两个商品】 1、题目描述: 2、实现核心及思路: 代码实现: 【15.三数之和】 1、题目描述: 2、实现核心及思路: 解题步骤: 思路可视化: 代码实现: 【18.四数之和】 1、题目描述: 编辑2、实现核心即思路: 解题步骤: 代码实现: 【611.有效三角形个数】 1、题目描述 2、实现核心及思路 构成三角形的条件:设三角形三边长分别为a(最长边),b(最短边),c。 则有 a + b >

By Ne0inhk
动态规划 线性 DP 经典四题一遍吃透

动态规划 线性 DP 经典四题一遍吃透

文章目录 * 台阶问题 * 最大子段和 * 传球游戏 * 乌龟棋 线性dp 是动态规划问题中最基础、最常⻅的⼀类问题。它的特点是状态转移只依赖于前⼀个或前⼏个状态,状态之间的关系是线性的,通常可以⽤⼀维或者⼆维数组来存储状态。 我们在⼊⻔阶段解决的《下楼梯》以及《数字三⻆形》其实都是线性dp,⼀个是⼀维的,另⼀个是⼆ 维的。 台阶问题 题目描述 题目解析 本题就是上一节下楼梯的问题的加强版,总体思路不变,下面我们还是按照动规5板斧来分析一下这道题。 1、状态表示 dp[i]表示走到第i个台阶的所有方案数 2、状态转移方程 第i个台阶的方案数等于从i-1阶到i-k阶的所有方案数之和,因为本题数据比较大,用long long都无法保证数据不越界,所以题目规定方案数还需要模100003,第i个台阶的方案数等于从i-1阶到i-k阶的所有方案数之和再模上100003,所以但是注意是可能越界访问的,比如i为3,

By Ne0inhk