Rust之错误处理(三):panic!还是不要panic!

【Rust错误处理】Rust错误处理机制详解与应用实战 在 Rust 中,错误处理是一个核心特性,它通过 Result 和 Option 类型以及 panic! 机制来管理错误和异常情况。Rust 的错误处理强调显式性和安全性,避免了传统异常机制的隐式控制流问题。 Rust 主要有两种错误处理方式:可恢复错误和不可恢复错误。 阅读详情

开发环境

  • Windows 10
  • Rust 1.68.0

 

  •   VS Code 1.76.2

项目工程

这里继续沿用上次工程rust-demo

panic!还是不要panic! 

那么,你如何决定何时应该调用panic!以及何时应该返回Result?当代码失败时,没有办法恢复。你可以在任何错误情况下调用panic!,无论是否有可能恢复,但你是代表调用代码来决定一个情况是不可恢复的。当你选择返回一个Result值时,你给了调用代码一些选择。调用代码可以选择尝试以适合其情况的方式进行恢复,或者它可以决定这种情况下的Err值是不可恢复的,所以它可以调用panic!并将你的可恢复错误变成不可恢复的错误。因此,当你定义一个可能失败的函数时,返回 Result是一个很好的默认选择。

在诸如例子、原型代码和测试等情况下,写出崩溃而不是返回Result的代码更为合适。让我们来探讨一下原因,然后讨论在哪些情况下编译器无法判断失败是不可能的,但作为人类的你可以。本章最后将介绍一些关于如何决定是否在库代码中进行恐慌的一般准则。

示例、原型代码和测试

当你写一个例子来说明一些概念时,也包括健壮的错误处理代码会使这个例子不那么清晰。在例子中,我们可以理解为,对像unwrap这样的方法的调用可能会引起恐慌,这是你希望你的应用程序处理错误的方式的一个占位符,它可以根据你的其他代码的做法而有所不同。 

同样,在你准备决定如何处理错误之前,unwrap和expect方法在原型设计时是非常方便的。它们在你的代码中留下了清晰的标记,以便你准备使你的程序更加健壮。

如果在测试中一个方法调用失败,你会希望整个测试都失败,即使这个方法不是被测试的功能。因为panic!是一个测试被标记为失败的方式,调用unwrap或expect正是应该发生的。

你比编译器拥有更多信息的案例 

当你有一些其他的逻辑来确保结果会有一个Ok值时,调用unwrap或expect也是合适的,但这个逻辑不是编译器所能理解的东西。你仍然有一个需要处理的Result值:无论你调用的是什么操作,在一般情况下仍然有失败的可能,尽管在你的特定情况下逻辑上是不可能的。如果你能通过手动检查代码来确保永远不会出现Err变量,那么调用unwrap是完全可以接受的,甚至最好在expect文本中记录你认为永远不会出现Err变量的原因。这里有一个例子。 

use std::net::IpAddr;

let home: IpAddr = "127.0.0.1"
     .parse()
     .expect("Hardcoded IP address should be valid");

我们正在通过解析一个硬编码的字符串来创建一个IpAddr实例。我们可以看到 127.0.0.1 是一个有效的 IP 地址,所以在这里使用 expect是可以接受的。然而,有一个硬编码的有效字符串并没有改变parse方法的返回类型:我们仍然得到一个Result值,而且编译器仍然会让我们把这个Result当作Err变量来处理,因为编译器还没有聪明到可以看出这个字符串总是一个有效的IP地址。如果这个IP地址字符串来自用户,而不是硬编码到程序中,因此确实有失败的可能,我们肯定会想用一种更稳健的方式来处理Result。提到这个IP地址是硬编码的假设将促使我们改变expect,以更好地处理错误的代码,如果将来我们需要从其他来源获得IP地址的话。 

错误处理指南

当你的代码有可能结束在一个坏的状态时,建议让你的代码恐慌。在这里,坏状态是指一些假设、保证、契约或不变性被破坏,比如无效的值、矛盾的值或缺失的值被传递给你的代码,再加上以下一个或多个。

  • 不良的状态是出乎意料的事情,而不是可能偶尔发生的事情,比如用户以错误的格式输入数据。
  • 你的代码在这一点上需要依靠不处于这种坏状态,而不是在每一步都检查问题。
  • 在你使用的类型中没有一个好的方法来编码这些信息。我们将在第后面章节的 "将状态和行为编码为类型 "一节中通过一个例子来说明我们的意思。

如果有人调用你的代码并传入一些不合理的值,如果可以的话,最好是返回一个错误,这样库的用户就可以决定在这种情况下他们要做什么。然而,在继续可能是不安全的或有害的情况下,最好的选择可能是调用panic!并提醒使用你的库的人注意他们代码中的错误,这样他们就可以在开发过程中修复它。同样,如果你正在调用不受你控制的外部代码,并且它返回了一个你无法修复的无效状态,那么panic!往往是合适的。

然而,当失败是预料之中的时候,返回一个Result比调用一个panic!更合适。这方面的例子包括解析器得到了畸形的数据,或者HTTP请求返回的状态表明你已经达到了速率限制。在这些情况下,返回一个Result表明失败是一种预期的可能性,调用代码必须决定如何处理。

当你的代码执行一个操作时,如果使用无效的值调用,可能会使用户处于危险之中,你的代码应该首先验证这些值是否有效,如果值无效,就应该崩溃。这主要是出于安全考虑:试图对无效的数据进行操作会使你的代码暴露在漏洞中。这也是标准库在你试图进行越界内存访问时会调用panic!的主要原因:试图访问不属于当前数据结构的内存是一个常见的安全问题。函数通常有约定:只有当输入满足特定要求时,它们的行为才会得到保证。当约定被违反时,程序崩溃是有道理的,因为违反约定总是表明有一个调用方的错误,这不是一种你希望调用代码必须明确处理的错误。事实上,调用代码没有合理的方法来恢复;调用程序员需要修复代码。一个函数的约定,特别是当违反约定会导致崩溃时,应该在该函数的API文档中加以解释。

然而,在你所有的函数中都有大量的错误检查将是冗长和令人讨厌的。幸运的是,你可以使用Rust的类型系统(以及由编译器完成的类型检查)来为你做许多检查。如果你的函数有一个特定的类型作为参数,你可以继续你的代码逻辑,因为你知道编译器已经确保了你有一个有效的值。例如,如果你有一个类型而不是一个Option,你的程序就会期望有一个东西而不是什么都没有。那么你的代码就不需要处理Some和None变体的两种情况:它只需要处理一个肯定有值的情况。试图向你的函数传递无的代码甚至不会被编译,所以你的函数在运行时不需要检查这种情况。另一个例子是使用无符号的整数类型,如u32,这样可以确保参数永远不会是负数。

创建用于验证的自定义类型

让我们把使用Rust的类型系统来确保我们有一个有效的值的想法更进一步,看看如何创建一个自定义类型来进行验证。回顾一下之前章节中的猜谜游戏,我们的代码要求用户在1到100之间猜一个数字。在与我们的秘密数字进行核对之前,我们从未验证过用户的猜测是否在这些数字之间;我们只验证了猜测是肯定的。在这种情况下,后果不是很严重:我们输出的 "太高 "或 "太低 "仍然是正确的。但如果能引导用户进行有效的猜测,并在用户猜测出一个超出范围的数字时与用户输入字母时有不同的行为,这将是一个有用的改进。

一种方法是将猜测解析为i32,而不仅仅是u32,以允许潜在的负数,然后添加一个数字是否在范围内的检查,像这样。

loop {
    // --片段--

    let guess: i32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => continue,
    };

    if guess < 1 || guess > 100 {
        println!("The secret number will be between 1 and 100.");
        continue;
    }

    match guess.cmp(&secret_number) {
}

if表达式检查我们的值是否超出了范围,告诉用户这个问题,并调用continue来开始下一次循环的迭代,并要求再次进行猜测。在if表达式之后,我们可以继续进行guess和秘密数字之间的比较,因为我们知道guess在1到100之间。

然而,这并不是一个理想的解决方案:如果程序只对1到100之间的数值进行操作是绝对关键的,而且它有许多函数都有这样的要求,在每个函数中都有这样的检查是很乏味的(而且可能影响性能)。

相反,我们可以建立一个新的类型,并将验证放在一个函数中,以创建该类型的实例,而不是到处重复验证。这样一来,函数就可以安全地在签名中使用新类型,并自信地使用它们收到的值。下例例13显示了一种定义Guess类型的方法,只有当new函数收到1到100之间的值时,才会创建一个Guess的实例。

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }

    pub fn value(&self) -> i32 {
        self.value
    }
}

例13

首先,我们定义了一个名为Guess的结构,该结构有一个名为value的字段,存放着一个i32。这就是数字将被存储的地方。

然后,我们在Guess上实现一个名为new的关联函数,创建Guess值的实例。new函数被定义为有一个i32类型的参数名为value,并返回一个Guess。新函数正文中的代码测试value以确保它在1到100之间。如果value没有通过这个测试,我们就会发出一个panic!调用,这将提醒正在编写调用代码的程序员,他们有一个需要修复的错误,因为用这个范围以外的值创建一个Guess将违反Guess::new所依赖的约定。Guess::new可能发生崩溃的条件应该在其面向公众的API文档中讨论;我们将在后面章节中介绍表明崩溃可能性的文档惯例!在你创建的API文档中。如果value确实通过了测试,我们就创建一个新的Guess,其value字段设置为value参数并返回Guess。

接下来,我们实现一个名为value的方法,它借用了self,没有任何其他参数,并返回一个i32。这种方法有时被称为getter,因为它的目的是从其字段中获取一些数据并返回。这种公共方法是必要的,因为Guess结构的value域是私有的。重要的是,value域是私有的,所以使用Guess结构的代码不允许直接设置value:模块外的代码必须使用Guess::new函数来创建Guess的实例,从而确保Guess没有办法拥有一个未经Guess::new函数检查的值。 

一个有参数或只返回1到100之间的数字的函数可以在其签名中声明它接收或返回一个Guess而不是一个i32,并且不需要在其主体中做任何额外的检查。

总结

Rust的错误处理功能是为了帮助你写出更健壮的代码。panic!宏表示你的程序处于它无法处理的状态,并让你告诉进程停止,而不是试图用无效或不正确的值继续进行。Result枚举使用Rust的类型系统来表示操作可能会失败,而你的代码可以从中恢复。你可以使用Result来告诉调用你的代码的代码,它也需要处理潜在的成功或失败。在适当的情况下使用panic!和Result将使你的代码在面对不可避免的问题时更加可靠。

现在你已经看到了标准库用Option和Result枚举使用泛型的有用方法,我们将讨论泛型是如何工作的以及你如何在你的代码中使用它们。

本章重点

  • panic!还是不panic!的原则
  • unwrap或expect的使用方法和注意细节
  • 错误处理的指导步骤
  • 如何创建用于验证的自定义类型
Rust 错误处理:panic! 和 Result Rust错误处理机制解析:使用panic!处理不可恢复错误,通过Result<T, E>处理可恢复错误。panic!会终止程序执行,可通过设置RUST_BACKTRACE环境变量查看调用栈回溯。Result枚举将错误显式编码为返回值,配合模式匹配进行细粒度错误处理。unwrap和expect方法提供了快捷处理方式,分别在错误时直接panic或自定义错误信息。这种设计强调显式错误处理,避免隐式异常控制流,提高代码可靠性。 阅读详情

相关推荐

Slurm集群调度策略详解(1)-主调度

Slurm调度系统中针对作业的调度主要有三种,主调度,回填调度,GANG调度,这篇文章主要针对主调度进行解析。

h_m_l的博客 6030

Rust之错误处理(一):无法恢复的错误panic!

Rust之常用错误处理, 无法恢复的错误panic!

wing的专栏 1292

金蝶云星空API实战:从零到一,用Postman打通业务数据流

本文详细介绍了如何使用Postman调试金蝶云星空API,从创建第三方应用到实战调试技巧,帮助开发者快速打通业务数据流。文章重点讲解了API配置的关键细节、常见问题排查方法以及自动化测试方案,提升开发效率与准确性。

weixin_33712987的博客 518

Rust异常处理

错误 错误在软件中无可否认事实,rust很多特性处理错误清晰,能够在编译代码之前采取行动,使得程序更加健壮, rust中将错误归类两种类型可恢复错误(报告错误并且重试,逻辑错误) , 不可恢复错误(bug) rust 中没有异常,有可以恢复错误Resu<T> , 不可恢复错误panic! panic! 宏 栈展开(回溯栈并且清理遇到内存)/ 终止由系统清理内存 在Cargo.toml增加panic = 'abort' 切换到终止模式 fn main() { panic!("crash a

上后谈感情的博客 1907

解密TMS320F28377D双核工程中的内存分配艺术:从CMD文件到实战优化

本文深入解析TMS320F28377D双核DSP的内存管理艺术,从CMD文件配置到实战优化技巧。详细介绍了SARAM、GSRAM等内存类型的特性与配置方法,提供双核协同、性能优化及高级调试的实用方案,帮助开发者提升系统实时性和稳定性。

weixin_29190651的博客 662

过压保护电路(OVP)

作者:AirCity 2020.2.4 Aircity007@sina.com 本文所有权归作者Aircity所有 1 示例一 当VBUS=5V时,各点电压电流如图所示,Q1接近截止,输出信号是5V。 当VBUS=5.4V开始,输出信号开始下降,直到VBUS=6.1V,输出信号降低为0.6V。 下面是实测波形,这个电路,输出信号的电压不会超过5.4V,这个输出信号可用于电源的控制信号。 2 ...

AirCity123的博客 3万+

Rust 11:错误处理(Option<T>、Result<T,E>、panic、catch_unwind)

C使用返回值;Java使用异常;Rust使用类型系统(Option/Result/Error trait)+panic!;Rust中?问号操作符简化Result<T,E>类型的处理,让代码更优雅。

编程架构三分天下:分层、分治、分时序。 1745

400G光模块测试全攻略:从PCS层到CMIS合规的避坑指南

本文为网络设备测试工程师和光通信研发人员提供了一份详尽的400G光模块测试实战指南。文章深入剖析了从PCS层信号完整性、FEC容限到CMIS管理接口合规性测试的全流程,并分享了ANLT协议分析、SerDes眼图调试以及系统级流量压力测试中的核心技巧与常见避坑点,旨在帮助读者构建一套高效、精准的测试体系,确保高速光模块的可靠性与性能。

weixin_29300931的博客 210

Rust语言——panic

Result枚举 enum Result<T, E>{ Ok(T),//T:操作成功情况下,Ok变体里返回的数据类型 Err(E), } use std::fs::File; fn main(){ let f = File::open("hello.txt"); let f = match f{ Ok(file)=>file, Err(error)=>{ panic!("Error ope

xzc_tobehero的博客 605

DAMMFND: Domain-Aware Multimodal Multi-view Fake News Detection

虚假新闻的泛滥不仅严重阻碍了互联网社交媒体的健康发展,还在政治、经济和社会层面造成了实际危害,凸显了自动化虚假新闻检测的重要性(Zhou 等,2015;Cui 等,2019;Zhu 等,2022a;Li 等,2024c,b,a)。现有的虚假新闻检测方法通常聚焦于特定领域的新闻,例如健康或政治领域。然而,在现实世界中,社交媒体上的新闻往往跨越多个领域,这就要求现有的虚假新闻检测方法必须考虑多域泛化(Nan 等,2021;Qi 等,2019;Zhu 等,2022b)。

qq_52053775的博客 337

【仿真技巧】Smart Eye Probe在ADS信号仿真中的应用

Smart_Eye_Probe是一个眼探头,允许灵活数量的输入通道,输入通道支持单端或差分信号的混合,并且,可以使用一个共同的时钟信号触发通道的眼图。目前,仅支持瞬态(Transient)和通道(Channel Sim)逐位模式(Bit by bit)模拟,不支持信道统计(Statistical)模式和DDR总线(DDRSim)仿真。

一只豌豆象的博客 1269

Rust:深入浅出说一说 Error 类型

在 Rust 标准库中,`std::io::Error` 是一个用于表示 I/O 操作错误的类型。这个类型是由 Rust 标准库提供的,而不是由用户直接定义的。不过,我们可以根据 Rust 的错误处理机制和类型系统的特点,给出一个示意性的表示,以帮助你理解 `std::io::Error` 是如何被设计的。

程序猿视角 2097

AutoJs调用DeepSeek大模型接口实战教程 AutoJs接入DeepSeek、 DeepSeek API调用 、 手机脚本接入大模型

通过这篇文章,我们用 AutoJs 成功接入了 DeepSeek Chat API。使用发送请求在 Header 里正确携带 API Key从里拿到最终返回结果这样一来,你就可以在 AutoJs 里实现一个智能助手脚本,甚至做更复杂的 AI 自动化工具。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 467

Rust 使用 panic! 还是不用 panic!

让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第 2 章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的,我们只验证它是否为正。在这种情况下,其影响并不是很严重:“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。

m0_52796585的博客 1093

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

【Rust】——什么时候应该用panic!

🎯总体原则 🎯编写示例、原型代码、测试 🎯有时候你比编译器掌握更多信息 🎯错误处理的指导性建议 ✨场景建议 ✨为验证创建自定义类型

shsjssnn的博客 1281

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Rust 错误处理(上)

错误是软件中不可否认的事实,所以 Rust 有一些处理出错情况的特性。在许多情况下,Rust 要求你承认错误的可能性,并在你的代码编译前采取一些行动。这一要求使你的程序更加健壮,因为它可以确保你在将代码部署到生产环境之前就能发现错误并进行适当的处理。)和)错误。对于一个可恢复的错误,比如文件未找到的错误,我们很可能只想向用户报告问题并重试操作。不可恢复的错误总是 bug 出现的征兆,比如试图访问一个超过数组末端的位置,因此我们要立即停止程序。

专注于大数据方向,区块链,前后端,数据可视化,人工智能等领域 3280

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Rust开发之使用panic!处理不可恢复错误

可恢复错误(recoverable errors)和不可恢复错误(unrecoverable errors)。可恢复错误通常使用类型表示,比如文件打开失败、网络请求超时等,程序可以在出错后尝试重试或提供替代路径。不可恢复错误则是那些程序无法继续安全运行的情况,例如访问越界数组元素、解引用空指针、逻辑断言失败等。对于这类严重错误,Rust提供了panic!宏来立即终止程序执行。调用panic!打印出错信息;展开(unwind)调用栈,清理资源;结束进程。虽然panic!

风中追风 1291

【 Rust探索】用panic!处理不可恢复错误

可恢复错误(recoverable errors)和不可恢复错误(unrecoverable errors)。可恢复错误通常使用类型表示,比如文件打开失败、网络请求超时等,程序可以在出错后尝试重试或提供替代路径。不可恢复错误则是那些程序无法继续安全运行的情况,例如访问越界数组元素、解引用空指针、逻辑断言失败等。对于这类严重错误,Rust提供了panic!宏来立即终止程序执行。调用panic!打印出错信息;展开(unwind)调用栈,清理资源;结束进程。虽然panic!

IRonSir 1419

REA 逆向分析提示词写法:3 个场景模板 + 4 个避坑点

很多人第一次用 REA(reverse-engineer-anything,一个把 AI Agent 接到二进制、JavaScript/Electron 应用、.NET 程序集分析能力上的开源工具)时,都会踩同一个坑:给 Agent 一句"帮我分析一下这个 App",Agent 来回追问三轮,最后吐出一堆"这个功能可能是……"——全是猜测,没有一条能追溯到具体代码。问题不在工具,在提示词。 RE

gitblog_00734的博客 1067

Rust 错误处理中的 backtrace:panic 和 Error 都能拿到调用栈

panic!的 backtrace 只能在程序崩溃时看到,但对于可恢复的错误(用Result/// 自定义错误类型,在创建时自动捕获调用栈/// 网络请求错误Network {/// 错误发生时的调用栈},/// 数据解析错误Parse {},/// 业务逻辑错误Business {},/// 创建网络错误,自动捕获 backtracebacktrace: Backtrace::capture(), // 在这里捕获调用栈/// 创建解析错误/// 创建业务错误。

no1coder的博客 3984

30天拿下Rust之错误处理

在软件开发领域,对错误的妥善处理是保证程序稳定性和健壮性的重要环节。Rust作为一种系统级编程语言,以其对内存安全和所有权的独特设计而著称,其错误处理机制同样体现了Rust的严谨与实用。在Rust中,错误处理通常分为两大类:不可恢复的错误和可恢复的错误。这两种错误的处理方式在Rust的设计哲学中扮演着不同的角色,并且适用于不同的场景。在Rust中,可以通过实现std::error::Error trait来创建自定义错误类型。这允许我们定义自己的错误类型,并能够更具体地描述程序中可能发生的错误情况。

hope_wisdom的技术博客 4539

115、Rust错误处理与异常捕获:掌握panic!宏和Result类型

本文介绍了Rust语言中的错误处理与异常捕获机制。首先讲解了`panic!`宏的使用,它用于处理无法恢复的错误。接着介绍了`Result`类型,它表示一个操作的成功或失败。然后通过网络编程和文件操作的实际案例,演示了如何使用这两种机制来处理错误。最后,我们自定义了一个错误类型,并将其应用于异步编程中。通过学习这些内容,开发者可以更好地理解和掌握Rust的错误处理方式,编写出更稳定和可靠的程序。

silenceallat的博客 1446

【Rust自学】9.4. 什么时候该使用panic!

当你认为自己可以替代调用你代码的调用者来决定某些情况是不可恢复的时候,就可以写`panic!`。

A Rookie in San Domenico School 1181

[Rust 入门] Rust 错误处理 - Panic 与 Result

自定义JsonError错误。// 错误应该能打印write!// 错误应该实现 std::error::Error 特型,但使用 Error 各个方法的默认定义就够了可以使用thiserrorcrate 来简化上面的操作。

编程回响 1568

Rust 错误处理详解

Rust错误处理机制详解:基于Result和panic!的显式设计。Result<T,E>枚举用于可恢复错误,提供match、unwrap、?操作符等多种处理方式;panic!处理不可恢复错误。文章还介绍了Option<T>处理值缺失,并展示了自定义错误类型和错误传播的实战示例。Rust通过这种显式、类型安全的方式强制开发者处理潜在错误,构建健壮可靠的程序。

本博客分享软硬件一体化开发实战。硬件涵盖 PCB 设计、STM32 等 MCU 底层开发;软件包含 C++、Rust,Qt/QML、PyQt、Slint、TouchGFX、LVGL 各类 GUI 框架。方便软硬件开发者查阅学习。欢迎关注! 874

【Rust】——panic!和不可恢复的错误

🎯Rust错误处理概述 🎯不可恢复与panic! 🎯使用panic的backtrace

shsjssnn的博客 1640
上一篇: Rust之错误处理(二):带结果信息的可恢复错误
下一篇: Rust之泛型、特性和生命期(一):基本概念
枫★曦
博客等级 码龄19年 251粉丝 153原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值