【ONE·C++ || 异常】

总言

  主要介绍异常。

文章目录

  • 总言
  • 1、C++异常
    • 1.1、C语言传统的处理错误的方式
    • 1.2、异常概念
    • 1.3、异常的基本用法
      • 1.3.1、异常的抛出和捕获
        • 1.3.1.1、异常的抛出和匹配原则
        • 1.3.1.2、 在函数调用链中异常栈展开匹配原则
      • 1.3.2、异常的重新抛出
        • 1.3.2.1、演示一
        • 1.3.2.2、演示二
      • 1.3.3、异常安全
      • 1.3.4、异常规范
  • 2、 自定义异常体系
    • 2.1、基本说明
    • 2.2、相关演示
  • 3、标准库异常体系
  • 4、异常的优缺点
    • 4.1、优点
    • 4.2、缺点

  
  
  
  

  
  
  
  

1、C++异常

1.1、C语言传统的处理错误的方式

  1)、传统的错误处理机制:
  1. 终止程序,如assert。缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。
  2. 返回错误码。缺陷:需要自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误。

  PS:实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。
  
  
  

1.2、异常概念

  1)、基本介绍
  说明:
  ①异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
  ②如果有一个块抛出一个异常,捕获异常的方法会使用 trycatch 关键字try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。

try
{// 保护的标识代码
}
catch (ExceptionName e1)
{// catch 块
}
catch (ExceptionName e2)
{// catch 块
}
catch (ExceptionName eN)
{// catch 块
}

  throw: 当问题出现时,程序会 “抛出” 一个异常,通过使用 throw 关键字来完成的。
  catch: 在想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获。
  try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。
  
  PS:相比于C语言,C++的异常是通过对象(ExceptionName)接收的,根据之前所学,一个对象内可存储的数据信息多种多样,如此丰富了错误信息。
  
  
  
  2)、基础演示
  相关代码如下:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}void Func()
{//int a = 2, b = 3;int a = 2, b = 0;cout << "a: " << a << ",  b: " << b << endl;cout << Division(a, b) << endl;
}int main()
{try {Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (...) {cout << "unkown exception" << endl;}return 0;
}

在这里插入图片描述
  
  
  
  
  

1.3、异常的基本用法

1.3.1、异常的抛出和捕获

1.3.1.1、异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  演示如下:这里有两个catch,但上述throw "Division by zero condition!"相对匹配的类型是char*的对象。

在这里插入图片描述

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)//throw 123;throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}void Func()
{//int a = 2, b = 3;int a = 2, b = 0;cout << "a: " << a << ",  b: " << b << endl;cout << Division(a, b) << endl;
}int main()
{try {Func();}catch (const int errid){cout << "catch (const int errid)" << endl;//用于验证cout << errid << endl;}catch (const char* errmsg){cout << "const char* errmsg" << endl;//用于验证cout << errmsg << endl;}catch (...) {cout << "unkown exception" << endl;}return 0;
}

  
  
  
  
  2. 被选中的处理代码是调用链中与该对象类型匹配离抛出异常位置最近的调用链中那一个。
在这里插入图片描述

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0)throw 123;//throw "Division by zero condition!";elsereturn ((double)a / (double)b);
}void Func()
{try {//int a = 2, b = 3;int a = 2, b = 0;cout << "a: " << a << ",  b: " << b << endl;cout << Division(a, b) << endl;}catch (const char* errmsg){cout << "Func() ------ const char* errmsg" << endl;//用于验证cout << errmsg << endl;}
}int main()
{try {Func();}catch (const int errid){cout << "main() ------ catch (const int errid)" << endl;//用于验证cout << errid << endl;}catch (...) {cout << "unkown exception" << endl;}return 0;
}

  
  
  
  

  PS:异常必须被捕获,不能存在没有匹配的异常类型的情况(会报错/终止程序,但这样子的程序行为是过于“大惊小怪的”。例如,①使用异常处理,本意是出错后弹出个对话框,用以错误警告,但该程序其它功能仍旧能正常执行,②若因没有匹配类型而直接把程序终止掉,这是我们不期望的行为方式。)

  为了防止出现此类情况,引入catch(...)

  3. catch(...)可以捕获任意类型的异常,只是不知道该异常原因具体是什么。(只有在其它类型的捕获异常都匹配不上时,才用...捕获,表示未知异常。)
在这里插入图片描述

  
  
  
  

  4. 虽然在throw处我们可以自定义填写内容,但一般情况都是抛一个异常对象。写法如下:

class Exception//用于返回异常的对象
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg),_errid(id){}int GetErrid()const{return _errid;}string GetErrmsg()const{return _errmsg;}protected:string _errmsg;//错误信息int _errid;//标识id
};double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){Exception e("除零错误!", 1);throw e;}elsereturn ((double)a / (double)b);
}void Func()
{//int a = 2, b = 3;int a = 2, b = 0;cout << "a: " << a << ",  b: " << b << endl;cout << Division(a, b) << endl;
}int main()
{try {Func();}catch (const Exception& e){cout << e.GetErrid() << "---" << e.GetErrmsg() << endl;}catch (...) {cout << "未知异常!!!" << endl;}return 0;
}

  PS:抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
在这里插入图片描述

  
  
  
  
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获
  
  PS:此处相关演示代码见下述小节2:自定义异常体系。
  
  
  
  
  

1.3.1.2、 在函数调用链中异常栈展开匹配原则

  根据上述异常的使用,当项目中存在多个函数调用时,异常的匹配规则如下:
  1. 首先检查throw本身是否在try块内部,如果在,则查找匹配的catch语句。若有匹配的,则调到catch的地方进行处理。
  2. 没有匹配的catch,则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。 上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。(实际中都会在最后加一个catch(...)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。)
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行
在这里插入图片描述

  
  
  
  
  

1.3.2、异常的重新抛出

  在一些场景中,有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
  
  
  

1.3.2.1、演示一

  以下述小节2中,自定义异常体系的代码为例。假设在 HttpServer中,我们要求当异常为“网络错误”时,要先连续请求三次,若都失败才将异常抛出。


// 服务器开发中通常使用的异常继承体系
class Exception//基类:异常对象
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}int Geterrid()const{return _id;}
protected:string _errmsg;//错误信息int _id;//错误码
};class HttpServerException : public Exception//异常对象:表示由协议产生的异常,继承了基类 Exception
{
public:HttpServerException(const string& errmsg, int id, const string& type):Exception(errmsg, id)//注意这里初始化基类方式:详细看继承和多态章节, _type(type){}virtual string what() const{string str = "HttpServerException:";str += _type;str += ":";str += _errmsg;return str;}
private:const string _type;
};void SendMsg(const string & str)
{srand(time(0));if (rand() % 2 == 0){throw HttpServerException("当前网络错误", 1, "get");}else if (rand() % 5 == 0){throw HttpServerException("当前权限不足", 2, "post");}cout << "本次请求成功:" << str << endl;
}void HttpServer()
{int count = 3;//用于表示请求次数string str = "客户端发送了一个请求.";while (1){Sleep(1000);try {SendMsg(str);}catch (const Exception& e){if (e.Geterrid() == 1 && count){count--;cout << "count:" << count << endl;//方便测试观察continue;//继续while循环,再次try}else{throw e;//将异常抛出到上一层}}}}int main()
{while (1){cout << "------------------------" << endl;//用以区分每一回合:方便测试观察try {HttpServer();//服务端层层调用}catch (const Exception& e) // Exception是基类对象。尽管上述通过继承设置了不同的异常体系,但这里直接捕获父类对象就可以。{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

  演示结果如下:
在这里插入图片描述

  
  
  

1.3.2.2、演示二

  除了上述情况,还有以下这类情景:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "除零错误!";}return (double)a / (double)b;
}
void Func()
{int* array = new int[10];//new:在堆区开辟空间try {int a = 2, b = 0;cout << Division(a, b) << endl;}//需要在当前try块内部进行异常捕获catch (...)//因为throw直接抛出到main的栈中,这里堆区开辟的空间没有得到释放,会造成内存泄漏{cout << "delete []" << array << endl;//但这里捕获只是为了释放堆区空间delete[] array;throw;//还需要将异常重新抛出,交给外部做后续处理。}// ...cout << "delete []" << array << endl;delete[] array;
}
int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}

  演示结果如下:
在这里插入图片描述
  
  
  
  

1.3.3、异常安全

  1、构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化。
  2、析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)

  3、C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题,关于RAII在智能指针中讲解。
在这里插入图片描述
  
  
  
  

1.3.4、异常规范

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型。

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);

  2. 函数的后面接throw(),表示函数不抛异常。

// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。
  
  注意事项:异常所提供的规范,只是期望能按照此规则执行,但不代表强制性行为。实际大多时候,C++中都会使用自定义异常体系,以下将进行讲解说明。
  
  
  
  
  

2、 自定义异常体系

2.1、基本说明

  说明:实际使用中,很多时候会自定义自己的异常体系,进行规范的异常管理。通常,抛出的都是继承的派生类对象,使用一个基类进行捕获。
在这里插入图片描述
  
  
  

2.2、相关演示

  相关代码如下:

// 服务器开发中通常使用的异常继承体系
class Exception//基类:异常对象
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}int Geterrid()const{return _id;}
protected:string _errmsg;//错误信息int _id;//错误码
};class SqlException : public Exception //异常对象一:表示由SQL产生的异常,继承了基类 Exception
{
public:SqlException(const string& errmsg, int id, const string& sql):Exception(errmsg, id), _sql(sql){}virtual string what() const{string str = "SqlException:";str += _errmsg;str += "->";str += _sql;return str;}
private:const string _sql;
};class CacheException : public Exception//异常对象二:表示由缓存产生的异常,继承了基类 Exception
{
public:CacheException(const string& errmsg, int id):Exception(errmsg, id){}virtual string what() const{string str = "CacheException:";str += _errmsg;return str;}
};class HttpServerException : public Exception//异常对象三:表示由协议产生的异常,继承了基类 Exception
{
public:HttpServerException(const string& errmsg, int id, const string& type):Exception(errmsg, id), _type(type){}virtual string what() const{string str = "HttpServerException:";str += _type;str += ":";str += _errmsg;return str;}
private:const string _type;
};void SQLMgr()
{srand(time(0));if (rand() % 7 == 0){throw SqlException("权限不足", 5, "select * from name = 'SQL'");}//throw "xxxxxx";cout << "本次请求成功" << endl;
}void CacheMgr()
{srand(time(0));if (rand() % 5 == 0){throw CacheException("权限不足", 3);}else if (rand() % 6 == 0){throw CacheException("数据不存在", 4);}SQLMgr();
}void HttpServer()
{// ...srand(time(0));if (rand() % 3 == 0){throw HttpServerException("请求资源不存在", 1, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 2, "post");}CacheMgr();
}int main()
{while (1){Sleep(1000);try {HttpServer();//服务端层层调用}catch (const Exception& e) // Exception是基类对象。尽管上述通过继承设置了不同的异常体系,但这里直接捕获父类对象就可以。{// 多态cout << e.what() << endl;}catch (...){cout << "Unkown Exception" << endl;}}return 0;
}

在这里插入图片描述
  
  
  
  
  

3、标准库异常体系

  相关链接:std::exception
  C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的。(若有需要可查阅文档学习了解)

在这里插入图片描述

  
  

4、异常的优缺点

4.1、优点

  1、相比错误码的方式,异常对象可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  2、传统方式中,返回错误码有一个很大的问题:在函数调用链中,深层的函数返回错误时,需要层层返回,才能在最外层拿到错误。(而若是异常体系,只需要抛出异常,会直接跳到相应catch捕获的地方,直接处理错误)
  3、 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用这些库时也需要使用到异常
  4、部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator(const size_t pos)这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。
  
  
  
  

4.2、缺点

  1、异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。
  2、 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3、C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
  4、C++标准库的异常体系定义得不完善,导致大家各自定义各自的异常体系,造成混乱。
  5、异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:①抛出异常类型都继承自一个基类。②函数是否抛异常、抛什么异常,都使用 func() throw();的方式规范化。
  
  
  
  
  
  
  

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

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

相关文章

【20】c++设计模式——>组合模式

组合模式定义 C组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;他允许将对象组合成树形结构来表示“部分-整体”的层次结构&#xff1b;在组合模式中有两种基本类型的对象&#xff1a;叶子对象和组合对象&#xff0c;叶子对象时没有子对象…

C++11 Qt QFutureWatcher lambda

目录 Lambda 介绍 【QT】Qt之QFutureWatcher 简述 传参&#xff1a; 还可以使用 QProgressDialog 作为阻堵 函数&#xff0c;变成同步&#xff1b; 完成后&#xff0c;关闭&#xff1b; MyQProgressDialog 效果&#xff1a; Lambda 介绍 Lambda 函数也叫匿名函数&…

VueRouter与expres/koa中间件的关联

ueRouter: runQueue 路由守卫都是有三个参数to,from,next。其中next就是下方的fn执行时候传入的第二个参数(回调函数)&#xff0c;只有该回调执行后才会挨个遍历queue内的守卫。 中间件的作用 隔离基础设施与业务逻辑之间的细节。详细的内容位于《深入浅出Node.js》P210 另外一…

mysql面试题30:什么是数据库连接池、应用程序和数据库建立连接的过程、为什么需要数据库连接池、你知道哪些数据库连接池

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:什么是数据库连接池? 数据库连接池是一种用于管理和复用数据库连接的技术。它是在应用程序和数据库之间建立一组数据库连接,并以池的形式存储起…

Oracle笔记-对ROWNUM的一次理解(简单分页)

此博文记录时间&#xff1a;2023-05-05&#xff0c;发到互联网上是2023-10-09 这个在分页里面用得比较多&#xff0c;在MySQL中&#xff0c;通常使用limit去操作&#xff0c;而去感觉比较简单&#xff0c;Oracle中无此关键字。 通过查阅资料后&#xff0c;要实现分页需要用到…

对于使用win32 API获取性能计数器的理解

微软提供了获取性能计数器的接口&#xff0c;如下 LSTATUS RegQueryValueExA([in] HKEY hKey,[in, optional] LPCSTR lpValueName,LPDWORD lpReserved,[out, optional] LPDWORD lpType,[out, optional] LPBYTE lpData,[in, out, optional] L…

dubbo协议与triple协议的对比

分别使用dubbo协议和triple协议&#xff0c;按照官方文档搭建Demo。 两个流程对比下来发现&#xff0c;dubbo协议搭建起来比较简单直接&#xff0c;定义好接口&#xff0c;实现类&#xff0c;然后启动provider和consumer就完事了。而triple协议则需要先定义proto文件 然后增加…

Visual Leak Detector内存泄漏检测机制源码剖析

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

每日leetcode_2441_对应负数同时存在的最大整数

Leetcode每日一题_2441_对应负数同时存在的最大整数 记录自己的成长&#xff0c;加油。 题目 解题 class Solution {public int findMaxK(int[] nums) {int k -1;Set<Integer> set new HashSet<Integer>();for (int x : nums) {set.add(x);}for (int x : nums) …

Spark 9:Spark 新特性

Spark 3.0 新特性 Adaptive Query Execution 自适应查询(SparkSQL) 由于缺乏或者不准确的数据统计信息(元数据)和对成本的错误估算(执行计划调度)导致生成的初始执行计划不理想&#xff0c;在Spark3.x版本提供Adaptive Query Execution自适应查询技术&#xff0c;通过在”运行…

通过位运算,实现单字段标识多个状态位

可能经常有如下这种需求: 需要一张表,来记录学员课程的通过与否. 课程数量不确定,往往很多,且会有变动,随时可能新增一门课. 这种情况下,在设计表结构时,一门课对应一个字段,就有些不合适, 因为不知道课程的具体数量,也无法应对后期课程的增加. 考虑只用一个状态标志位,利用位运…

C/C++实现简单高并发http服务器

基础知识 html&#xff0c;全称为html markup language&#xff0c;超文本标记语言。 http&#xff0c;全称hyper text transfer protocol&#xff0c;超文本传输协议。用于从万维网&#xff08;WWW&#xff1a;World Wide Web&#xff09;服务器传输超文本到本地浏览器的传送…

6-3 递增的整数序列链表的插入 分数 5

List Insert(List L, ElementType X) {//创建结点List node (List)malloc(sizeof(List));node->Data X;node->Next NULL;List head L->Next; //定位real头指针//空链表 直接插入if (head NULL) {L->Next node;node->Next head;return L;}//插入数据比第…

嵌入式养成计划-38----C++--匿名对象--友元--常成员函数和常对象--运算符重载

八十七、匿名对象 概念&#xff1a;没有名字对象格式 &#xff1a;类名&#xff08;&#xff09;;作用 用匿名对象给有名对象初始化的用匿名对象给对象数组初始化的匿名对象作为函数实参使用 示例 : #include <iostream> using namespace std; class Dog { private:s…

在Kubernetes中实现gRPC流量负载均衡

在尝试将gRPC服务部署到Kubernetes集群中时&#xff0c;一些用户&#xff08;包括我&#xff09;面临的挑战之一是实现适当的负载均衡。在深入了解如何平衡gRPC的方式之前&#xff0c;我们首先需要回答一个问题&#xff0c;即为什么需要平衡流量&#xff0c;如果Kubernetes已经…

亘古难题——前端开发or后端开发

一、引言 前端开发 前端开发是创建WEB页面或APP等前端界面呈现给用户的过程&#xff0c;通过HTML&#xff0c;CSS及JavaScript以及衍生出来的各种技术、框架、解决方案&#xff0c;来实现互联网产品的用户界面交互。 前端开发从网页制作演变而来&#xff0c;名称上有很明显的时…

C语言-数组

C 语言支持数组数据结构&#xff0c;数组是一个由若干相同类型变量组成的有序集合。 这里的有序是指数组元素在内存中的存放方式是有序的&#xff0c;即所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素&#xff0c;最高的地址对应最后一个元素。 在 C 语言中&am…

GNU和Linux的关系、 Linux的发行版本、CentOs和RedHat的区别

GNU和Linux的关系 其实&#xff0c;我们通常称之为的"Linux"系统&#xff0c;相对更准确的名称应该称为“GNU/Linux”系统&#xff01; 一个功能完全的操作系统需要许多不同的组成部分&#xff0c;其中就包括内核及其他组件&#xff1b;而在GNU/Linux系统中的内核就…

物联网AI MicroPython传感器学习 之 MQ136硫化氢传感器

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 MQ136 是一种硫化氢检测传感器&#xff0c;感应范围为 1 - 200ppm。传感元件是 SnO2&#xff0c;它在清洁空气中的电导率较低。当存在 H₂S 气体时&#xff0c;传感器的电导率随着气体浓度的升…

LeetCode 24.两两交换链表中的结点

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 首先可以特判一下&#xff0c;如果结点数目小于等于1&#xff0c;则直接返回即可&#xff0c;因为数目小于等于1就不需要交换了。 然后我们可以创建一个虚拟的头结点&#xff0c;然…