构造、析构、拷贝语意学

深入C++对象模型:构造、拷贝、析构与内存布局全解析 C++对象模型是理解C++底层机制的核心概念,它定义了对象在内存中的布局、生命周期管理及多态实现原理。通过剖析编译器隐式生成的构造函数、拷贝操作与析构函数,开发者可以掌握资源管理、性能优化及避免内存错误的底层逻辑。这一模型的技术价值在于为编写高效、安全的C++代码提供理论基础,尤其在涉及RAII、智能指针及移动语义的现代C++工程实践中至关重要。应用场景包括高性能计算、游戏引擎、系统编程等对内存控制要求严格的领域。本文聚焦于C++对象模型中的构造语意学、拷贝语意学及虚函数表机制,通过分析浅拷贝与深拷贝的陷阱 阅读详情


一、构造

1.1、"无继承"情况下的对象构造

考虑下面代码:
在这里插入图片描述
L1、L5、L6表现出三种不同的对象产生方式:global内存配置、local内存配置和heap内存配置。一个对象的声明,是该对象的一个执行期属性。local对象的声明从L5的定义开始,到L10结束。global对象的声明和整个程序的生命相同。heap对象的生命从它被new运算符配置出来开始,到它被delete运算符销毁为止。

下面是Point的声明,可以写成C程序。C++ Standard说这是一种所谓的Plain old data(点击查看plain old data)。
在这里插入图片描述
如果我们以C++ 来编译这段代码,会发生什么事情?观念上,编译器会为Point声明一个trivial default constructor、一个trivial destructor、一个trivial copy constructor,以及一个trivial copy assignment operator。但实际上,编译器会分析这个声明,并为它贴上plain old data标签。

当编译器遇到这样的定义:
在这里插入图片描述
观念上Point的trivial constructor和destructor都会被产生并被调用,constructor在程序起始处被调用而destructor在程序的exit()处被调用(exit()是由系统产生的,放在main()结束之前)。然而事实上那些trivial members要不就是没被定义,就是没被调用,程序的行为一如它在C中的表现一样。

foobar()函数中的L5,有一个local对象,同样也是既没有被构造也没有被析构。

heap对象在L6的初始化操作:
在这里插入图片描述
会被转换为对new运算符的调用:
在这里插入图片描述
需要注意的是,并没有default constructor施行于new运算符所传回的对象身上。

L9执行一个delete操作:
在这里插入图片描述
会被转换为对delete运算符的调用:
在这里插入图片描述
观念上,这样的操作会触发Point的trivial destructor。但一如我们所见,destructor要不是没有被产生就是没有被调用。

1.1.1、抽象数据类型

下面是Point的第二次声明,在public接口之下多了private数据,提供完整的封装性,但没有任何虚函数:
在这里插入图片描述
这个经过封装的Point类,其大小并没有改变,还是三个连续的float。我们并没有为Point定义一个copy constructor或copy operator,因为默认的位语意(default bitwise semantics)已经足够。我们也不需要提供一个destructor,因为程序默认的内存管理方法也已经足够。

观念上,我们的Point类有一个相关的default copy constructor、copy operator和destructor。然而它们都是trivial,而且编译器实际上根本没有产生它们。

1.1.2、为继承做准备

下面是Point的第三次声明,将为继承性质以及某些操作的动态决议(dynamic resolution)做准备。
在这里插入图片描述
我们并没有定义一个copy constructor、copy operator、destructor。我们的所有成员都以数值来存储,因此在程序层面的默认语意下,行为良好。

虚函数的导入促使每一个Point对象拥有一个虚函数表指针(vptr),这个指针给我们提供virtual接口的弹性,其成本是:每一个对象需要额外的一个word空间。

除了每一个类对象多负担一个vptr之外,虚函数的导入也引发编译器对于我们的Point类产生膨胀作用:

  • 我们所定义的constructor被附加了一些代码,以便将vptr初始化。这些代码必须被附加在任何基类构造函数调用之后,但必须在任何由程序员提供的代码之前。例如,下面是可能的附加结果:
    在这里插入图片描述
  • 合成一个copy constructor和一个copy assignment operator,而且其操作不再是trivial。如果一个Point对象被初始化或以一个派生类对象赋值,那么以位为基础(bitwise)的操作可能对vptr带来非法设定。
    在这里插入图片描述

1.2、继承体系下的对象构造

当我们定义一个对象:T object;,实际上会发生什么事情?如果T有一个constructor,它会被调用。这很明显,比较不明显的是,constructor的调用真正伴随了什么?

constructor可能内含大量的隐藏代码,因为编译器会扩充每一个constructor,扩充程度视类T的继承体系而定。一般而言编译器所做的扩充操作大约如下:

  • 1、记录在成员初始化列表中的数据成员初始化操作会被放进constructor的函数体,并以成员的声明顺序为顺序
  • 2、如果有一个成员并没有出现在成员初始化列表之中,但它有一个default constructor,那么该default constructor必须被调用
  • 3、在那之前,如果类对象有虚函数表指针,它必须被设定初值,指向适当的虚函数表
  • 4、在那之前,所有上一层的基类构造函数必须被调用,以基类的声明顺序为顺序(与成员初始化列表中的顺序没关联):
    • 如果基类被列于成员初始化列表中,那么任何显式指定的参数都应该传递过去
    • 如果基类没有被列于成员初始化列表中,而它有default constructor,那么就调用
    • 如果基类是多重继承下的第二个或后继的基类,那么this指针必须有所调整
  • 5、在那之前,所有虚函数基类的构造函数必须被调用,从左到右,从最深到最浅:
    • 如果类被列于成员初始化列表中,那么如果有任何显式指定的参数,都应该传递过去。若没有列于成员初始化列表中,而类有一个default constructor,亦应该调用
    • 此外,类中的每一个虚基类子对象的偏移位置必须在执行期可被存取
    • 如果类对象是最底层(most-derived)的类,其构造函数可能被调用;某些用以支持这一行为的机制必须被放进来

接下来,我们从"C++ 语言对类所保证的语意"这个角度,探讨constructor扩充的必要性。再次以Point为例,并为它增加一个copy constructor、一个copy operator、一个virtual destructor,如下所示:
在这里插入图片描述
在我们开始介绍并一步步走过以Point为根源的继承体系之前,先很快地看看Line类的声明和扩充结果,它由_begin和_end两个点构成:
在这里插入图片描述
每一个explicit constructor都会被扩充以调用其两个成员类对象的constructor。如果我们定义constructor如下:
在这里插入图片描述
它会被编译器扩充并转换为:
在这里插入图片描述
由于Point声明了一个copy constructor、一个copy operator,以及一个destructor,所以类Line的implicit copy constructor、copy operator和destructor都将有具体效用(nontrivial)。

当写下这样的代码:Line a;时,implicit Line destructor会被合成出来。其中,它的成员类对象的destructor会被调用(以其构造的相反顺序):
在这里插入图片描述
类似的道理,当我们写下:Line b = a;时,implicit Line copy constructor会被合成出来,成为一个inline public member。

当我们写下:a = b;时,implicit copy assignment operator会被合成出来,成为一个inline public member。

1.2.1、虚继承

考虑下面这个继承体系:
在这里插入图片描述
传统的constructor扩充并没有用,因为虚基类的共享性之故。试想以下三种类的派生情况:
在这里插入图片描述
Vertex的constructor必须也调用Point的constructor。然而,当Point3d和Vertex同为Vertex3d的subobject时,它们对Point constructor的调用操作一定不可以发生;取而代之的是,作为一个最底层的类,Vertex3d有责任将Point初始化。而更往下的继承,则由PVertex来负责完成被共享的Point subobject的构造。

在这里存在某种状态,在此状态中,虚基类的构造函数被调用有着明确的定义:只有当一个完整的类对象被定义出来时,它才会被调用;如果对象只是某个完整的对象的subobject,它就不会被调用。

1.2.2、vptr初始化语意学

在继承体系中,虚函数的调用是由虚函数候选列表决定的。而虚函数候选列表又由vptr决定,所以为了控制一个类中有所作用的函数,编译系统只要简单地控制住vptr的初始化和设定操作即可。当然,设定vptr是编译器的责任,任何程序员都不必操心此事。

vptr初始化操作应该如何处理呢?答案是在基类构造函数调用操作之后,但是在程序员提供的代码或是成员初始化列表中所列的成员初始化操作之前。

令每一个基类构造函数设定其对象的vptr,使它指向相关的virtual table之后,构造中的对象就可以严格而正确地变成构造过程中所幻化出来的每一个类的对象。也就是说,一个PVertex对象会先形成一个Point对象、一个Point3d对象、一个Vertex对象、一个Vertex3d对象,然后才成为一个PVertex对象。在每一个基类构造函数中,对象可以与构造函数的类的完整对象做比较。构造函数的执行算法通常如下:

  • 1、在派生类构造函数中,所有的虚基类及上一层的基类的构造函数会被调用
  • 2、上述完成之后,对象的vptr被初始化,指向相关的virtual table
  • 3、如果有成员初始化列表,将在构造函数体内扩展开来。这必须在vptr被设定之后,以免有一个虚成员函数被调用。
  • 4、最后,执行程序员所提供的代码

但是有两种情况下,vptr必须被设定:

  • 1、当一个完整的对象被构造时。如果我们声明一个Point对象,则Point constructor必须先设定其vptr
  • 2、当一个subobject constructor调用了一个虚函数时

二、拷贝

以Point类为例:
在这里插入图片描述
没有什么理由需要禁止拷贝一个Point对象。问题是:默认行为是否足够?如果我们要支持的只是一个简单的拷贝操作,那么默认行为不但足够而且有效率,我们没有理由再自己提供一个copy assignment operator。

只有在默认行为所导致的语意不安全或不正确时,我们才需要设计一个copy assignment operator。如果我们不对Point供应一个copy assignment operator,而只是依赖默认的memberwise copy,编译器会产生出一个实例吗?这个答案和copy constructor(有关copy constructor的详细情况,请参看构造函数语意学)的情况一样:实际上不会!由于此类已经有了bitwise copy语意,所以implicit copy assignment operator被视为毫无用处,也根本不会被合成出来。

一个类对于默认的copy assignment operator,在以下情况,不会表现出bitwise copy语意:

  • 1、当类内含一个成员对象,而该成员对象所属的类有一个copy assignment operator时
  • 2、当一个类的基类有一个copy assignment operator时
  • 3、当一个类声明了任何虚函数时
  • 4、当类继承自一个虚基类时

C++ Standard上说copy assignment operator并不表示bitwise copy semantics是nontrivial。实际上,只有nontrivial instances才会被合成出来。

于是,对于我们的Point类,这样的赋值操作:
在这里插入图片描述
由bitwise copy完成,把Point b拷贝到Point a,其间并没有copy assignment operator被调用。注意,我们还是可能提供一个copy constructor,为的是把NRV打开。copy constructor的出现不应该让我们以为也一定要提供一个copy assignment operator。

现在,我们增加一个copy assignment operator,用以说明在继承之下的行为:
在这里插入图片描述
现在派生一个Point3d类:
在这里插入图片描述
如果我们没有为Point3d定义一个copy assignment operator,编译器就必须合成一个。合成的东西可能看起来像这样:
在这里插入图片描述

三、析构

如果类没有定义destructor,那么只有在类内含的成员对象(或者类的基类)拥有destructor的情况下,编译器才会自动合成出一个来。例如,我们的Point,默认情况下并没有被编译器合成出一个destructor——虽然它拥有一个虚函数:
在这里插入图片描述
类似的道理,如果我们把两个Point对象组合成一个Line类:
在这里插入图片描述
Line类也不会拥有一个被合成出来的destructor,因为Point并没有destructor。

为了决定类是否需要一个程序层面的destructor(或是constructor),请想想一个类对象的生命在哪里结束(或开始)?需要什么操作才能保证对象的完整?这是constructor和destructor什么时候起作用的关键。例如:
在这里插入图片描述
我们看到,pt和p在作为foo()函数的参数之前,都必须先初始化为某些坐标值。这时候需要一个constructor,否则使用者必须显式地提供坐标值。一般而言,类的使用者没有办法检验一个local变量或heap变量以知道它们是否被初始化。把constructor想象为程序员的一个额外负担是错误的,因为它们的工作有其必要性。如果没有它们,抽象化的使用就会有错误的倾向。

当我们显式地delete p,会如何?有任何程序上必须处理的吗?是否需要在delete之前这么做:
在这里插入图片描述
不,当然不需要。没有任何理由说在delete一个对象之前先得将其内容清楚干净。你也不需要归还任何资源。在结束pt和p的生命之前,没有任何类使用者层面的程序操作是绝对必要的。因此,也就不需要一个destructor。

一个由程序员定义的destructor被扩展的方式类似constructor被扩展的方式,但顺序相反:

  • 1、destructor的函数体首先被执行
  • 2、如果类拥有成员类对象,而该对象所属的类拥有destructor,那么它们会以其声明顺序的相反顺序被调用
  • 3、如果对象内含一个vptr,现在被重新设定,指向适当的基类virtual table
  • 4、如果有任何直接的(上一层)非虚基类拥有destructor,它们会以其声明顺序的相反顺序被调用
  • 5、如果有任何虚基类拥有destructor,而目前讨论的这个类是最底(most-derived)的类,那么它们会以其原来的构造顺序的相反顺序被调用

四、总结

在这里插入图片描述

Adaboost入门教程——最通俗易懂的原理介绍(图文实例) 阅读详情

相关推荐

AI 赋能测试全生命周期:多 Agent 协同的自动化提效方案

本文提出了一种基于Coze平台的多Agent协同测试解决方案,旨在解决软件测试中PRD解析不清晰、测试点覆盖不全、用例编写效率低、代码生成耗时等痛点。通过PRD优化、测试点生成、测试用例生成和单元测试框架生成四个Agent的协同工作,结合Cursor工具实现从需求到代码的全链路自动化。以电商平台登录模块为例,展示了从模糊PRD到标准化测试用例和可执行Java单元测试代码的完整转化流程。该方案通过AI赋能显著提升测试效率,减少人工干预,实现测试覆盖率≥95%,为快速迭代的软件研发提供了高效的测试自动化支持。

QINJIEEE的博客 1101

《深度探索C++对象模型》学习笔记 — 构造、析构、拷贝语义学(The Semantics of Construction,Destruction,and copy)

《深度探索C++对象模型》学习笔记 — 构造、析构、拷贝语义学(The Semantics of Construction,Destruction,and copy)一、抽象类和纯虚函数二、无继承下的对象构造1、POD三、继承体系下的对象构造1、构造顺序 一、抽象类和纯虚函数 如果我们尝试在抽象类中声明数据成员,那么我们至少应该提供protected权限的初始化数据成员的构造函数;相应的,如果这些成员需要手动释放或解锁,我们需要提供析构函数执行相应操作。一般而言,我们会选择将析构函数声明为纯虚函数,然后提供

coding-hwz 585

家庭网络中,路由器和交换机的连接顺序

它的目的是给路由器增加端口,用户访问互联网的速度比情况一要快,常用于办公网络、家庭成员多的场景。光纤入户首先连接的肯定是光猫,而在光猫的后面,是先连接交换机?还是应该接路由器呢?家庭网络中路由器和交换机的先后连接顺序。它目的是让更多的用户访问互联网。常用于一些超大型机房、网吧等。至于路由器和交换机的区别。两者之间很多功能都有兼容。但用户上网速度相对较慢。在技术日益迭代的今天。

m0_74075744的博客 1万+

C++对象模型:构造、析构、拷贝语意学

目录 1、有关纯虚函数 2、无继承情况下的函数构造 2.1struct 构造 2.2抽象数据类型 2.3有虚函数的情况 3.继承体系下的对象构造 3.1概述 3.2虚拟继承的情况 3.3vptr的初始化 4.对象复制语意学 5.析构函数语意学 1、有关纯虚函数 抽象基类的数据成员初始化 如果一个类被声明为抽象基类(其中有 purevirtual function),则...

SimonxxSun的博客 476

告别GIS软件!用R语言ggplot2+ggmapcn绘制出版级世界地图(附5种投影对比图)

本文提供了一份完整的实践指南,教你如何利用R语言及其强大的ggplot2生态系统,结合ggmapcn包,绘制出出版级的世界地图。文章详细对比了五种经典地图投影(如摩尔维德、罗宾森投影)的代码实现与视觉差异,并讲解了如何添加比例尺、指北针等专业元素,最终导出高清PDF,实现从数据分析到专业可视化的全流程代码化操作,彻底告别传统GIS软件。

p5l2m9n4o6q的博客 407

STM32CubeIDE离线安装芯片PACK包全攻略(附离线包下载与导入步骤)

本文详细介绍了在STM32CubeIDE中离线安装芯片PACK包的完整流程,包括官方和第三方资源获取方法、本地安装步骤及常见问题解决方案。特别针对网络隔离环境下的嵌入式开发需求,提供了实用的版本管理和自动化部署技巧,帮助开发者高效完成芯片固件支持包的离线配置。

weixin_30825581的博客 299

C++的拷贝构造函数

From Wikipedia, the free encyclopedia Jump to: navigation, search <!-- start content --> A copy constructor is a special constructor in the C++ programming language used to...

互联网 420

[Dify实战] 合同审阅助手:识别风险条款、生成修改建议-带绑定资源

摘要: Dify合同审阅助手通过AI技术提升合同审阅效率,快速识别风险条款并输出结构化修改建议。方案包括合同输入、关键条款识别、风险评级和报告生成流程,重点关注付款、责任限制等核心条款。采用分层风险分级(高/中/低)和"红线清单"机制,确保审阅标准化。实施建议先建立条款库和模板,通过样本测试优化模型,定期更新规则库。该方案可缩短审阅时间50%以上,提高风险识别准确率,最终实现法务经验的可复用沉淀。

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 595

C++拷贝构造函数

定义 拷贝构造函数(copy constructor),又称复制构造函数,是在基于同一个类的其他对象进行构造的时候(赋值初始化、参数传递、返回值)被编译器调用的一种特殊构造函数。假设有一个类类型为T,拷贝构造函数的第一个形参必须是该类类型的引用,(即T&)也可以用cv限定符来修饰(const T&、volatile T&或const volatile T&),也可...

程序人生 1433

观测器选择与工程实践建议

摘要:永磁同步电机(PMSM)无传感器控制中,观测器选型需综合考虑应用场景、性能需求与成本。消费级(家电、工具)推荐滑模观测器(SMO),成本低、鲁棒性强;工业级(伺服、泵阀)适用扩展卡尔曼滤波器(EKF),精度高但计算复杂;车规级(EPS、主驱动)需混合观测器(SMO+EKF),兼顾全速域性能与可靠性。低速时需高频注入法,IPMSM更适用。SMO易实现但存在抖振,EKF调试复杂。未来趋势是混合架构与AI增强,提升适应性与性能。选型需权衡速度范围、电机类型、处理器能力及成本。

汽车软件开发 145

【C#程序员入门AI】必知的一些知识

2026年C#调用AI,核心就4个概念+3种方式4个概念:大模型(大脑)、嵌入(数字密码)、向量(载体)、Agent(超级助理)3种方式:云端API(快)、本地模型(隐私)、混合部署(最佳)不用怕复杂,跟着这个思路走,你就能从“AI小白”变成“C# AI开发能手”,把AI集成到你的.NET项目里,提升效率、创造价值!

人工智能AI技术 409

note: candidate constructor not viable: requires 2 arguments, but 0 were provided 缺乏无参构造报错

创建vector容器时里面的参数类型为person,此时调用了匿名无参构造函数,没有无参构造会报错。将注释的无参构造使用即可消除错误。生成已完成,但出现错误。

weixin_44236630的博客 3527

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

第5章 构造, 析构, 拷贝语意学

目录 第5章 构造, 析构, 拷贝语意学 5.1 "无继承"情况下的对象构造 C struct的Point声明 抽象数据类型 为继承做准备 5.2 继承体系下的对象构造 自我拷贝问题 虚继承 vptr...

weixin_30500473的博客 208

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

深入探索C++对象模型之五 --- 析构、构造、拷贝语意学

深入探索C++对象模型之五 — 析构、构造、拷贝语意学一般而言,class的data member应该被初始化,并且只在constructor中或是在class的其它member functions中指定初值,其它任何操作都将破坏封装性质,使class的维护和修改更加困难。纯虚函数我们可以定义和调用一个pure virtual function:不过它只能被静态的调用,不能经由虚拟机制调用。而对于p

Aaron的专栏 769

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

C++对象模型 第五章 构造、析构、拷贝语意学

第五章  构造、析构、拷贝语意学 1)构造析构的顺序 例:class A{ public: A(int a=0):_a(a){cout ~A(){cout private: int _a; }; class C{ public: C(int c=0):_c(c){cout ~C(){cout private: int _c; }; class B:publi

yskyj的专栏 627

《深度探索C++对象模型》读书笔记第五章:构造析构拷贝语意学

《深度探索C++对象模型》读书笔记第五章:构造析构拷贝语意学 对于abstract base class(抽象基类),class中的data member应该被初始化,并且只在constructor中或者在其他member functions中指定初值。 纯虚函数的存在 纯虚函数,虚函数在声明后加上 = 0,纯虚函数的意义在于只定义一个可继承的接口,并没有实现(虚函数意义在于定义一个可继...

yhcuac 1027

深度探索C++对象模型:5.构造、析构、拷贝语意学

第五章:构造、析构、拷贝语意学 考虑下面这个abstract base class声明: class Abstract_base{ public: virtual ~Abstract_base() = 0; virtual void interface() const =0; virtual const char* mumble() const {return_m...

系统 运维 250

Inside The C++ Object Model(五)构造、拷贝、析构语意学

待续...

linux系统、网络编程和分布式计算 1015

第五章、构造、析构、拷贝语意学

class Abstract_base{public: virtual ~Abstract_base()=0;//有问题,链接不通过,不能是纯虚函数 virtual void interface() const =0;//如果改到类的成员变量,最好不要设置为const //下面函数很糟糕,因为是virtual,函数定义内容不与类型有关,因而几乎不会被后继的derived...

weixin_30562507的博客 86

【C++】深度探索C++对象模型之构造、析构、拷贝语意学

一、纯虚函数的存在 可以定义和调用一个pure virtual function, 不过只能被静态调用(invoked statitcally),不能经由虚拟机制调用 Abstract_base::interface();如上述,Abstract_base是一个虚基类。这只取决于类设计者要不要这么做。 但是对于pure virtual destrcutor, 类设计者一定要定义它,因为每个d

小do在努力 831

第五章 构造、析构、拷贝语意学

纯虚函数的存在:   它只能被静态调用,不能经由虚拟机制调用。   惟一的例外是纯虚函数的析构函数:每一个继承的析构函数被编译器扩展,以静态方式调用其每一个virtual base class以及上一层的base class的析构函数。只要缺乏任何一个的定义就会导致链接失败。 准则:接口不const ,析构函数不是纯析构 关于构造函数里面的内容:  初始化列表,虚函数表的指针,继承来的所

nt13的专栏 459

第5章 构造、析构、拷贝语义学3:对象复制语意学

如果不对class point提供copy assignment operator,光是依赖默认的memberwise copy,编译器不会产生出一个实例,因为此class已经有了bitwise copy语意,所以implicit copy assignment operator被视为毫无用处,根本不会合成出来。当一个class声明了任何virtual function(一定不要拷贝右端class object的vptr地址,因为他可能是一个derived class object)时。

qq_40178082的博客 451
上一篇: 构造函数语意学
下一篇: OpenGL+VS2019+Win32/x64环境配置
Spring_24
博客等级 码龄12年 46粉丝 41原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Spring_24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值