[C++11] 可变参数模板


文章目录

  • 基本语法及原理
    • 可变参数模板的基本语法
      • 参数包的两种类型
      • 可变参数模板的定义
    • `sizeof...` 运算符
    • 可变参数模板的实例化原理
    • 可变参数模板的意义
  • 包扩展
    • 包扩展的基本概念
    • 包扩展的实现原理
    • 编译器如何展开参数包
      • 包扩展的高级应用
  • `emplace` 系列接口
    • `emplace_back` 和 `emplace` 的作用和接口定义
    • `emplace` 系列接口的优势
    • `emplace_back` 的使用示例
    • `emplace_back` 内部实现分析
      • `ListNode` 节点类的实现
      • `emplace_back` 和 `insert` 的实现
      • 完美转发的作用
    • 编译器生成的代码

基本语法及原理

C++11引入了可变参数模板(Variadic Templates),使得我们可以定义参数数量可变的模板。可变参数模板广泛应用于泛型编程中,让开发者能够编写更加灵活和通用的代码。可变参数模板支持零或多个参数,极大地提升了模板的扩展性。


可变参数模板的基本语法

在C++11之前,为了实现不同数量的参数支持,必须针对不同数量的参数编写多个重载版本的函数或类模板。C++11提供了可变参数模板语法,允许开发者编写参数数量不定的模板函数和模板类。

参数包的两种类型

可变参数模板中的参数被称为参数包(Parameter Pack)。在C++11中,有两种参数包:

  1. 模板参数包:表示零或多个模板参数,使用 class...typename... 关键字声明。
  2. 函数参数包:表示零或多个函数参数,使用类型名后跟 ... 表示。
template <class ...Args> void Func(Args... args) {}
template <class ...Args> void Func(Args&... args) {}
template <class ...Args> void Func(Args&&... args) {}

可变参数模板的定义

下面给出一个简单的可变参数模板的定义示例:

template <class ...Args> 
void Func(Args... args) {}template <class ...Args> 
void Func(Args&... args) {}template <class ...Args> 
void Func(Args&&... args) {}

在上面的代码中:

  • Args... 是一个模板参数包,表示零个或多个类型参数。
  • args... 是一个函数参数包,对应零个或多个形参对象。

函数参数包可以用左值引用(Args&...)或右值引用(Args&&...)的形式表示,允许参数通过引用传递,从而符合C++的引用折叠规则。通过这些形式,我们可以灵活地处理传入的不同数量和类型的参数。

sizeof... 运算符

sizeof... 是一个操作符,用于计算参数包中参数的数量。它可以直接应用于模板参数包或函数参数包,返回参数包中包含的元素数量。以下是一个示例代码:

#include <iostream>
#include <string>
using namespace std;template <class ...Args>
void Print(Args&&... args) {cout << sizeof...(args) << endl;
}int main() {double x = 2.2;Print();                          // 包中有0个参数Print(1);                         // 包中有1个参数Print(1, string("xxxxx"));        // 包中有2个参数Print(1.1, string("xxxxx"), x);   // 包中有3个参数return 0;
}

该代码示例中,通过调用 sizeof...(args) 运算符,我们可以看到传入 Print 函数的参数数量。

可变参数模板的实例化原理

从编译的角度来看,可变参数模板的本质是在编译过程中,根据参数的数量和类型,实例化出多个函数版本。例如,上述示例中的 Print 函数调用,编译器会自动生成以下四个函数:

double x = 2.2;
Print();                          // 包中有0个参数
Print(1);                         // 包中有1个参数
Print(1, string("xxxxx"));        // 包中有2个参数
Print(1.1, string("xxxxx"), x);   // 包中有3个参数void Print();                                           // 0个参数
void Print(int&& arg1);                                 // 1个参数
void Print(int&& arg1, string&& arg2);                  // 2个参数
void Print(double&& arg1, string&& arg2, double& arg3); // 3个参数

这样,编译器会在调用处生成特定版本的函数。这种自动生成函数的方式,极大地简化了编写支持多个参数数量的函数的工作量。

可变参数模板的意义

在没有可变参数模板的情况下,我们需要通过写多个重载的函数模板来支持不同数量的参数:

void Print(); // 没有参数template <class T1>
void Print(T1&& arg1);template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);

随着参数数量的增加,这种做法不仅繁琐,代码的可维护性也很差。通过可变参数模板,编译器可以自动生成相应数量和类型的函数版本,进一步解放了开发者的精力,使泛型编程更加灵活。

包扩展

在C++11中,可变参数模板不仅可以处理可变数量的参数,还支持对参数包进行“扩展”操作。包扩展允许我们分解参数包中的各个元素,并为每个元素应用某种模式,从而对其进行逐个处理。包扩展为模板元编程带来了极大的灵活性,使得我们可以编写简洁、高效的代码来处理不定数量的参数。

本文将深入探讨包扩展的概念、使用方法和实现原理。

包扩展的基本概念

对于一个参数包,我们可以:

  1. 计算参数包的元素数量(使用 sizeof... 操作符)。
  2. 进行包扩展,将参数包的元素逐个展开,并应用指定的模式。

在包扩展中,我们通过在模式的右边放置一个省略号(...)来触发扩展操作。扩展操作会将参数包逐个展开并应用模式,生成一个参数列表。例如,Args... args 表示参数包 Args... 被逐个展开为一个个单独的参数,供函数逐个处理。

包扩展的实现原理

包扩展的实现依赖于编译时递归调用和模式匹配。以下代码示例展示了如何通过包扩展实现对参数包中每个元素的打印:

#include <iostream>
#include <string>
using namespace std;// 递归终止条件:没有参数时停止递归
void ShowList() {cout << endl;
}// 递归展开参数包
template <class T, class ...Args>
void ShowList(T x, Args... args) {cout << x << " ";     // 打印当前参数ShowList(args...);     // 递归调用,展开剩余参数
}// 主调用接口,将参数包传给ShowList处理
template <class ...Args>
void Print(Args... args) {ShowList(args...);
}int main() {Print();                              // 输出:空行Print(1);                             // 输出:1Print(1, string("xxxxx"));            // 输出:1 xxxxxPrint(1, string("xxxxx"), 2.2);       // 输出:1 xxxxx 2.2return 0;
}

在上述代码中:

  1. 递归终止条件void ShowList() 函数定义为空函数,当参数包为空时调用它,从而终止递归。
  2. 递归展开参数包ShowList(T x, Args... args) 接受第一个参数 x,打印它,然后递归调用 ShowList(args...) 继续处理剩余的参数。

通过递归展开,Print 函数会依次打印每个参数,实现了包扩展。

编译器如何展开参数包

编译器在遇到包扩展时,会将参数包逐个展开为独立的参数并生成相应的函数调用。例如,以下代码调用 Print(1, string("xxxxx"), 2.2); 会展开为:

void ShowList(int x, string y, double z) {cout << x << " ";ShowList(y, z);
}

依次展开后,每次递归调用的 ShowList 函数都会处理一个参数,直到最后一个参数被处理完。

包扩展的高级应用

C++11 支持更复杂的包扩展,可以直接将参数包依次展开,并作为实参传递给另一个函数。例如,假设我们有一个 GetArg 函数,用于处理单个参数,并将其返回。我们可以使用包扩展,将参数包的每个元素都传递给 GetArg 处理,并将结果传给另一个函数 Arguments

以下代码展示了这种高级的包扩展应用:

#include <iostream>
#include <string>
using namespace std;template <class T>
const T& GetArg(const T& x) {cout << x << " ";return x;
}template <class ...Args>
void Arguments(Args... args) {// 空函数,不做实际操作,仅用于接受展开后的参数
}template <class ...Args>
void Print(Args... args) {// 使用包扩展,将每个参数传递给GetArg处理,结果传给ArgumentsArguments(GetArg(args)...);// Arguments(GetArg(x), GetArg(y), GetArg(z));
}int main() {Print(1, string("xxxxx"), 2.2);    // 输出:1 xxxxx 2.2return 0;
}

在这段代码中:

  1. GetArg 是一个函数模板,用于打印并返回每个参数。
  2. Print 中,GetArg(args)... 会将参数包 args... 依次传递给 GetArg 函数,并将 GetArg 的返回值传递给 Arguments,相当于利用Arguments这个空函数然后使用实际要将参数包各个参数传递给的函数GetArg(),然后实际上编译时的GetArg(args)...会变为:_**<font style="color:rgb(160,161,167);">Arguments(GetArg(x), GetArg(y), GetArg(z));</font>**_
  3. 编译器会在编译时生成以下代码来完成包扩展:
void Print(int x, string y, double z) {Arguments(GetArg(x), GetArg(y), GetArg(z));
}

emplace 系列接口

C++11 为 STL 容器引入了 emplace 系列接口,例如 emplace_backemplace,这些接口大幅提升了插入效率,尤其是在避免不必要的临时对象创建和拷贝构造方面。相比传统的 push_backinsertemplace 系列允许在容器的内存空间上直接构造对象,减少了资源消耗。


emplace_backemplace 的作用和接口定义

emplace_backemplace 的作用是直接在容器空间中构造对象,避免了拷贝或移动构造。它们接受一组参数,通过可变参数模板实现:

template <class... Args>
void emplace_back(Args&&... args);template <class... Args>
iterator emplace(const_iterator position, Args&&... args);
  • emplace_back 接口将对象插入到容器末尾。
  • emplace 接口允许在容器的指定位置插入对象。

这两个接口都使用了可变参数模板 Args&&... args,可以接受任意数量的构造参数,使得在某些情况下比 push_backinsert 更高效。

emplace 系列接口的优势

emplace 系列的优势在于它可以避免创建临时对象。举个例子,假设我们有一个类型 T 和一个容器 container<T>,通过 emplace 接口,我们可以直接将构造 T 所需的参数传递给容器,直接在容器内存中构造 T 对象。这样减少了对象拷贝的需求,尤其在构造复杂对象时效率更高。

emplace_back 的使用示例

以下示例代码展示了 emplace_back 的不同用法:

#include <list>
#include <string>
#include <iostream>using namespace std;int main() {list<string> lt;// 传递左值,类似于 push_back,会调用拷贝构造string s1("111111111111");lt.emplace_back(s1);// 传递右值,类似于 push_back,会调用移动构造lt.emplace_back(move(s1));// 直接传递构造 string 的参数,emplace_back 在容器空间直接构造对象lt.emplace_back("111111111111");list<pair<string, int>> lt1;// 构造 pair 并拷贝/移动到 list 节点pair<string, int> kv("苹果", 1);lt1.emplace_back(kv);  // 拷贝lt1.emplace_back(move(kv)); // 移动// 直接传递构造 pair 的参数,在容器空间直接构造 pair 对象// 将参数包直接向下传,一直传到pair的构造,然后在对象的位置直接构造lt1.emplace_back("苹果", 1);return 0;
}

在上面的代码中,我们演示了 emplace_back 的三种使用方式:

  1. 传入左值参数(s1),进行拷贝构造。
  2. 传入右值参数(move(s1)),进行移动构造。
  3. 直接传递构造 stringpair 的参数,在容器内存中直接构造对象。

emplace_back 内部实现分析

为了理解 emplace 系列接口的实现,我们在给定代码中模拟了 list 容器的 emplace_backinsert 方法的实现。这些方法使用了可变参数模板和完美转发,确保参数类型的精确传递。

ListNode 节点类的实现

ListNode中,通过模板构造函数实现直接构造数据类型 T 的对象,避免了额外的拷贝:

template <class T>
struct ListNode {ListNode<T>* _next;ListNode<T>* _prev;T _data;// 移动构造ListNode(T&& data): _next(nullptr), _prev(nullptr), _data(move(data)) {}// 可变参数模板构造函数template <class... Args>ListNode(Args&&... args): _next(nullptr), _prev(nullptr), _data(std::forward<Args>(args)...) {}
};

在这里,我们定义了 ListNode 的两种构造方式:

  1. 移动构造函数,用于右值参数。
  2. 可变参数模板构造函数,通过 std::forward<Args>(args)... 完美转发参数,实现对象的直接构造。

emplace_backinsert 的实现

emplace_back 是通过调用 insert 来实现的,而 insert 则会根据传递的参数类型直接在节点位置构造对象 T

template <class T>
class list {typedef ListNode<T> Node;public:template <class... Args>void emplace_back(Args&&... args) {insert(end(), std::forward<Args>(args)...);}template <class... Args>iterator insert(iterator pos, Args&&... args) {Node* cur = pos._node;Node* newnode = new Node(std::forward<Args>(args)...); // 直接构造新节点Node* prev = cur->_prev;// 插入节点到链表prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}private:Node* _head;
};
  • emplace_back 将参数包传递给 insert 方法。
  • insert 接受位置迭代器 pos 和一组可变参数,并通过 std::forward<Args>(args)... 将参数完美转发给 Node 构造函数。
  • insert 方法直接在链表节点位置构造对象,避免了不必要的拷贝操作。

完美转发的作用

传递参数包过程中,如果是<font style="color:rgb(31,35,41);"> Args&&... args </font>的参数包,要⽤完美转发参数包,⽅式如下<font style="color:rgb(31,35,41);">std::forward<Args>(args)... </font>,否则编译时包扩展后右值引⽤变量表达式就变成了左值。

emplace_back 的实现中,我们使用了 std::forward<Args>(args)...。完美转发确保参数类型保持不变(左值或右值),而不受函数调用的影响。如果不使用 std::forward,右值引用参数在传递过程中会被转换为左值引用,从而无法实现高效的移动语义。

编译器生成的代码

在实际编译过程中,编译器会根据传入的参数类型为 emplace_backinsert 生成适当的重载版本。例如,对于以下代码:

lt.emplace_back("111111111111");

编译器会自动生成以下版本的 emplace_back 函数:

void emplace_back(const char* s) 
{insert(end(), std::forward<const char*>(s));
}

每当传入不同参数组合,编译器会生成相应的重载函数,以实现高效的对象构造。

模拟实现的整体List.h:

// List.h
namespace bit
{template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(T&& data):_next(nullptr), _prev(nullptr), _data(move(data)){}template <class... Args>ListNode(Args&&... args): _next(nullptr), _prev(nullptr), _data(std::forward<Args>(args)...){}};template<class T, class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}// ++it;Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Ref operator*(){return _node->_data;}bool operator!=(const Self& it){return _node != it._node;}};template<class T>class list{typedef ListNode<T> Node;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;}list(){empty_init();}void push_back(const T& x){insert(end(), x);}void push_back(T&& x){insert(end(), move(x));}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}iterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* newnode = new Node(move(x));Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}template <class... Args>void emplace_back(Args&&... args){insert(end(), std::forward<Args>(args)...);}// 原理:本质编译器根据可变参数模板⽣成对应参数的函数/*void emplace_back(string& s){insert(end(), std::forward<string>(s));}void emplace_back(string&& s){insert(end(), std::forward<string>(s));}void emplace_back(const char* s){insert(end(), std::forward<const char*>(s));}*/template <class... Args>iterator insert(iterator pos, Args&&... args){Node* cur = pos._node;Node* newnode = new Node(std::forward<Args>(args)...);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}private:Node* _head;};
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

欺诈文本分类检测(十八):基于llama.cpp+CPU推理

1. 前言 前文我们用Lora训练出自己的个性化模型后&#xff0c;首先面临的问题是&#xff1a;如何让模型在普通机器上跑起来&#xff1f;毕竟模型微调时都是在几十G的专用GPU上训练的&#xff0c;如果换到只有CPU的普通电脑上&#xff0c;可能会面临几秒蹦一个词的尴尬问题。 …

硬件基础06 滤波器——无源、有源(含Filter Solutions、Filter Pro、MATLAB Fdatool)

目录 一、Filter Solutions 1、软件资源及安装教程如下 2、使用相关内容 二、Filter Pro使用 1、软件资源及安装教程如下 2、使用相关内容 三、MATLAB Fdatool 1、在matlab命令中输入fdatool 2、输入相关参数&#xff0c;例如低通、FIR、20阶、hamming窗 3、调用 &am…

【HGT】文献精讲:Heterogeneous Graph Transformer

【HGT】文献精讲&#xff1a;Heterogeneous Graph Transformer 标题&#xff1a; Heterogeneous Graph Transformer &#xff08;异构图Transformer&#xff09; 作者团队&#xff1a; 加利福尼亚大学Yizhou Sun 摘要&#xff1a; 近年来&#xff0c;图神经网络&#xff08;GN…

大厂基本功 | MySQL 三大日志 ( binlog、redo log 和 undo log ) 的作用?

前言 MySQL日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中&#xff0c;比较重要的还要属二进制日志binlog&#xff08;归档日志&#xff09;和事务日志redo log&#xff08;重做日志&#xff09;和undo log&#xff08;回滚日志&#xff09;…

【系统架构设计师(第2版)】五、软件工程基础知识

5.1 软件工程 20世纪60年代&#xff0c;为了解决软件危机&#xff0c;提出了软件工程的概念。 软件危机的具体表现&#xff1a; 软件开发进度难以预测&#xff1b;软件开发成本难以控制&#xff1b;软件功能难以满足用户期望&#xff1b;软件质量无法保证&#xff1b;软件难以…

手机内卷下一站,AI Agent

作者 | 辰纹 来源 | 洞见新研社 2024年除夕夜&#xff0c;OPPO在央视春晚即将开始前举办了一场“史上最短发布会”&#xff0c;OPPO首席产品官刘作虎宣布&#xff0c;“OPPO正式进入AI手机时代”。 春节假期刚过&#xff0c;魅族又公开表示&#xff0c;将停止“传统智能手机…

科研绘图系列:R语言组合堆积图(stacked plot)

文章目录 介绍加载R包数据数据预处理画图1画图2组合图形系统信息介绍 堆积图(Stacked Chart),也称为堆叠图,是一种常用的数据可视化图表,主要用于展示不同类别的数据量在总体中的分布情况。堆积图可以是柱状图、条形图或面积图的形式,其中各个类别的数据量被叠加在一起,…

Node.js 完全教程:从入门到精通

Node.js 完全教程&#xff1a;从入门到精通 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;允许开发者在服务器端使用 JavaScript。它的非阻塞 I/O 和事件驱动架构使得 Node.js 非常适合于构建高性能的网络应用。本文将详细介绍 Node.js 的安装、基本语…

微服务day03

导入黑马商城项目 创建Mysql服务 由于已有相关项目则要关闭DockerComponent中的已开启的项目 [rootserver02 ~]# docker compose down WARN[0000] /root/docker-compose.yml: version is obsolete [] Running 4/4✔ Container nginx Removed …

每日一题之二叉树

已知结点元素值为正整数且值不相同的一棵二叉树。 该二叉树通过给出其先序遍历序列和中序遍历序列构造而成。 输入一个整数x&#xff0c;针对此二叉树编写程序求出x的右子树中所有结点值的和&#xff08;若x不在树上&#xff0c;输出-1&#xff09;。 输入说明&#xff1a;第一…

win10系统使用Visual Studio 2019或cmake编译SDL2为32位库时出现error C2118: 负下标winnt.h的解决方法

提示&#xff1a; 下图蓝体字中的VS2008是错误的&#xff0c;其实SDL.sln是用VS2010版本的软件开发的&#xff08;对于SDL-release-2.0.5.zip源码而言至少是这样&#xff0c;而2024-11-6为止SDL是2.30.9版本了&#xff0c;2.30.9版本则无需自己编译&#xff0c;只需下载带后缀…

推荐一款管道数据检索工具:Pipedata-Pro

Pipedata-Pro是一款专为设计石油、天然气、水和蒸汽管道及管道系统的工程师开发的应用程序。该应用程序提供了设计管道系统所需的工程数据&#xff0c;拥有一个全面的管道类型、配件和材料数据库。 软件特点&#xff1a; 1. 技术参数查询&#xff1a;Pipedata-Pro 提供关于管道…

算法竞赛(Python)-数组

文章目录 一 、排序算法二 、二分查找1 二分查找讲解2 二分查找题目&#xff08;1&#xff09;二分查找&#xff08;2&#xff09;在排序数组中查找元素的第一个和最后一个位置&#xff08;3&#xff09;两数之和 II - 输入有序数组 三、数组双指针1对撞指针对撞指针题目1&…

基于STM32的LCD1602显示Proteus仿真设计(仿真+程序+设计报告+讲解视频)

这里写目录标题 1.主要功能0. 资料清单&下载链接资料下载链接&#xff1a;2.仿真设计3. 程序设计4. 设计报告5. 框图 基于STM32的LCD1602显示Proteus仿真设计(仿真程序设计报告讲解视频&#xff09; 仿真图proteus 8.9 程序编译器&#xff1a;keil 5 编程语言&#xff1a…

SpringBoot项目编译报错 类文件具有错误的版本 61.0, 应为 52.0

springboot项目在编译时报错&#xff1a; /Users/Apple/Developer/art/caicai/cai-api/dubbo-samples/1-basic/dubbo-samples-spring-boot/dubbo-samples-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/ProviderApplication.java:22:32 java…

PVE纵览-备份与快照指南

PVE纵览-备份与快照指南 文章目录 PVE纵览-备份与快照指南摘要1 备份与快照概述定义与区别备份与快照在PVE中的应用场景 2 PVE 备份功能详解备份类型与策略配置备份任务自动化备份管理 3 PVE 快照功能详解快照的工作原理快照的创建与恢复机制快照对系统性能的影响快照的使用场景…

Mac如何实现最简单的随时监测实时运行状态的方法

Mac book有着不同于Windows的设计逻辑与交互设计&#xff0c;使得Mac book有着非常棒的使用体验&#xff0c;但是在Mac电脑的使用时间过长时&#xff0c;电脑也会出现响应速度变慢或应用程序崩溃的情况&#xff0c;当发生的时候却不知道什么原因导致的&#xff0c;想要查询电脑…

JavaWeb合集23-文件上传

二十三 、 文件上传 实现效果&#xff1a;用户点击上传按钮、选择上传的头像&#xff0c;确定自动上传&#xff0c;将上传的文件保存到指定的目录中&#xff0c;并重新命名&#xff0c;生成访问链接&#xff0c;返回给前端进行回显。 1、前端实现 vue3AntDesignVue实现 <tem…

WPF之iconfont(字体图标)使用

1&#xff0c;前文&#xff1a; WPF的Xaml是与前端的Html有着高度相似性的标记语言&#xff0c;所以Xaml也可同Html一般轻松使用阿里提供的海量字体图标&#xff0c;从而有效的减少开发工作度。 2&#xff0c;下载字体图标&#xff1a; 登录阿里图标库网iconfont-阿里巴巴矢量…

leetcode92:反转链表||

给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a;[1,4,3,2…