30h速成c++

30sc c++

1 环境搭建

2 cin cout

位运算 gatchar() 双级应用

3 函数重载 overload

两个函数可以重复相同命名,但参数类型可以不同,这种调用是相对的,c中不可以。

能否重载的关键在于是否会产生歧义。

tips:自动进行的类型强制转换可能会造成不良影响

函数重载的本质原因是 命名倾轧 在编译实际中改变函数名保证各个函数共同存在,而在c语言中无此功能。同时重载时不同编译器规则也不同。f9断点 f5调试在调试模式中可以开启反汇编斜下方为反汇编的伪代码机器码正下方16进制01左侧内存地址每一句机器码都有地址机器码存储内存连续 代码机器码连续4个4位2进制转1个16进制2个16进制则为8个字节

即为1比特

4位2进制先转化为10进制再华为16进制

AA 一个字节 调试模式可清晰的看到其存储call 为调用之意 push为传参函数右侧括号中为其内存地址EXE存储1010类型文件 可以以ida反汇编pe文件即为EXEsub 函数debug模式生成 release 去除调试生成文件左上角有变换发布使用 ida退出时dont 双钩reliease模式会进行优化函数直接解读分析分析函数拆开以节省空间避免优化可排除 具体最大优化禁用

默认参数

默认参数 即为定义参数,调用时可以省略实参直接在定义中赋值形参, 函数主名无需传入值亦可若有传入值既覆盖 无传入既默认参数默认参数传参时赋值时左到右 故默认参数也应右到左设置默认参数只能放在声明中

默认参数可以是常量,全局符号(全局变量或函数名)

函数名:

int func(int v1,void(*p)(int))

p(v1);

ps:

void (*p) () = test;

表示返回值为void 传入参数为空

表示将test函数赋予p的地址

则以后进行调用时只需进行p()即可

指针存储函数名

使用理由 使用的参数经常为同一个值

函数重载和默认参数的设置可能会导致出现二义性,建议优先使用默认参数。

默认参数的本质:

将默认变量当作参数传入函数之中。帮你做了一件小事而已。当有默认参数存在时,sum(1,4)和sum(1)相同

汇编语句:E8 ==call 调用函数

extern C此句表示被其修饰代码以c的方式编译,可以加在函数前,或以类似函数括号方法使用,应放在声明中好处:可利用c型库或第三方框架便于移植利用

调用其他文件外部函数需进行声明 实现与其他文件cpp中调用c库 直接调用会无法解析实验置于头文件可存于头文件 自制 include利用双引号可以在头文件中直接加入externc

#ifdef __cplusplus

#endif

运用此种方式来进行对不同文件的识别 以便于c库于非c库之中的利用

#ifndef xx 名称通常是文件名以区分

#define xx

用于头文件中的开头 主函数中则不必再使用

#pragma once 防止整个文件被重复引用。版本较新,只能针对整个文件而不能精确控制。

内联函数

使用inline修饰函数的声明或者实现 建议将修饰和实现均写入以避免漏写

函数加入前缀inline 组成内联

内联函数会将函数展开为函数体代码 在主函数中直接展开。 多次展开时会导致体积增大,机器码增多。

而一般的函数调用时会开辟栈空间,结束后回收空间,重复的调用操作导致内存开销,内连函数则不存在函数调用,使执行效率变高

内连函数使用条件

1 被调用函数代码体积不大 不超过10行

2 函数调用频繁占用空间效率

ps 是否最终为内联函数取决于编译器的设定 递归死循环则不会被设置为内联函数

内联的本质

调试模式下不会进行内联

内连将简单计算直接执行而提高效率

相当于删除函数直接进行计算

堆栈操 作

编译器优化有时会使非内联存在函数调用

内联函数与宏

均可减少函数调用的开销

#define add(a,b)

a+b;宏替换 极为相似

内联函数看起来即为函数 会有提示出现 存在函数特性 宏则没有提示

宏是简单的文本替换 直接无脑进行替换 而内联函数则存在逻辑上的运算 比宏更有价值一点

c++有些表达式可以赋值

(a=b)=4;

(a>b?a:b)=4

a=1 b=2;

此时a为1 b为4

const

定义常量 被其修饰的变量不可修改

同时需要在定义那一刻将其赋值

结构体定义中 c++不需要加入const

结构体可整体赋值

date di{2011.5.6}

date d2{2019.5.3}

d2=d1; //可存在此形式

date *p=&d2;

p->year = 2019; //更改结构体中变量值

ps 结构体本身访问成员使用 .

指针访问则使用——>

*p 取出指向地址 即为d1 解引用

结论: const修饰其右侧内容

p2中,p2为常量,而*p2不是 故可以修改p2的值 而p2的值不可以修改

p0中 p0非常量,而*p0为常量

变量类型和const可以交换 意义无变化双const情况下 均不可修改const固定了指针所指向的内存空间使其不可修改

引用

c中可以用指针去获取修改变量的值,而c++中使用引用可以起到和指针类似的功能

int &refage=age; //定义引用变量

refage=20; //此时age的值也改变了

引用:变量的别名(各种物基本都有引用)

在定义时必须初始化,一旦指向某变量,则不可以再改变其指向。

(指针的方向则可以改变)

可引用引用来达到多个别名的效果

价值所在:比指针更加安全

函数修改main主题函数中的值

外部传地址 内部传指针

但使用引用时,则不需要复杂的符号设置 传入真值即可 写法简单 且无固定绑定关系 命名空间的问题

每次调用函数时都会调用,使得引用也会重新刷新,虚假的从一而终。

要在定义引用时表明指向,引用变量类型要和引用类型一致。

指针的引用

int *&ref=p;

int *表示将要引用一个指针类型的变量,并以ref进行引用

ref //p==30;

数组的引用

不存在引用的引用

引用的本质

引用实为弱化的指针。

指针: *p为age的别名 占4个字节/8个 x86/x64

引用:ref为age的别名 一个引用变量占8个字节 占用一个指针的大小

aslr 使得开始的地址随机

指针和引用的机器码完全一致 证明其本质为指针。更加精简。

常引用(const reference)

相比引用,不能通过引用再去修改值

可以做到只读操作

const写在&左边才算常引用

汇编语言

汇编指令与寄存器

汇编语言的种类

  • 8086 16

  • x86 32

  • x64 64 pc端

  • arm汇编 嵌入式移动设备

x64有两种书写格式intel windows at&t(mac)

汇编语言不区分大小写

寄存器

程序的本质

代码从硬盘载入内存 cpu从内存中读取写入指令 控制相关设备进行工作

cpu的组件

寄存器 信息存储

运算器 信息处理

控制器 控制其他器件工作

cpu将内存中的物质先放到寄存器计算 再返回到内存中

x64汇编的寄存器

rax bx cx dx 称通用寄存器

一个寄存器可以存8个字节

各种寄存器的作用不同

rax与eax

32环境下通用寄存器称为eax bx cx dx

or 16位环境 ax bx cx dx

x64兼容以前的寄存器 ,把64一半寄存器拿出来当 32位寄存器使用。0-31 (低字节)为eax寄存器 包含于rax之中,即rax的低四个字节即为eax寄存器以进行兼容

ah高字节 al低字节

mov eax 10 //会影响到rax

再将eax低2个字节拿出来当16位ax

ax又可以分为ah与al

r 64 8

e 32 4

__asm {} 此指令可以在c++嵌入汇编代码

寄存器储存指令会进行覆盖赋值操作

mov a b 类似于a=b;同时也可以用来地址赋值。

地址值存放于[ ]之中

mov word ptr [ebp-8] 7

表示将7存入1122h内存地址中 同时所占用字节为2 epb-8为变量的地址

局部变量 临时存在,每次执行函数时都会重新分配变量给它,不固定地址,因此需要ebp(其为全新的值),而全局变量的地址值则是写死的,格式与其不同。

word 2 dword 4 qword 8

吞并地址向高处吞并 hex 16进制

存储方式

  • 四个字节如何存储一个数?

16进制 00 00 00 03H

2进制 00000000000000000000011

cpu大小端模式 读取的方式(目前大部分为小端模式 从高地址读起 以最小地址值为标准地址值)

高高低低 高字节(位)放高地址

call 函数地址 ==E8

调用函数,以函数地址取之

lea dest [地址] lea==load effect address装载有效地址值

lea eax [1122h] 将1122h地址值赋给eax

ret 函数返回 置于函数结尾处有部分jump功能

xor op1 op2 异或运算 将1和2异或的结果赋值给1

add a b a加b sub 减法

inc a ==a++

dec a ==a--

jmp 地址 是jump简称,跳转内存地址 无条件跳转

jne 相等则不跳转 je 相等跳转

j开头的一般是跳转(有条件)需要其他指令配合 eg if语句的汇编

cmp compare 比较左右是否相等

cmp a b

jne ==jump not equal 比较结果不相等则跳转

函数返回值一般放到eax中,再将值返回给c (赋值等号操作)

cpu的架构决定了有些指令只能对寄存器操作而不能对汇编进行操作,需要通过寄存器作中间缓冲,不能直接内存到内存的操作。

mov只能简单赋值,不可以直接做运算。

面向对象

类 对象 成员变量 封装 继承 多态

可以用struct/class定义类,类中包含成员变与成员函数。

struct和class的区别:

  • struct的权限默认为公共变量

  • class默认权限为private;

利用类创建对象的方法:

类名 变量名:

成员变量的访问:变量名.成员;

指针名->成员名(当使用指针时)

命名的规范性:在各种变量前加上标志用于区分。

对象,指针的内存均存于函栈空间,自动分配,回收。

对象的大小尤其所包含的成员变量的决定。

指针的大小占用8个字节,存储对象的地址值。

成员变量是变化,而成员函数的内存地址是固定的,各个对象调用的是同一份函数。

内存的布局

内存在栈空间中顺序排布

当类中存储对象种类不同时,存储采用内存对齐的方式。

栈,堆,代码,全局。

栈空间和对空间的区别

  • 栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等

其中函数中定义的局部变量按照先后定义的顺序依次压入栈中,也就是说相邻变量的地址之间不会存在其它变量。栈的内存地址生长方向与堆相反,由高到底,所以后定义的变量地址低于先定义的变量。

  • 堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。c语言中用malloc函数申请free函数释放,c++中用new运算符申请,delete运算符释放。

堆的内存地址生长方向与栈相反,由低到高,但需要注意的是,后申请的内存空间并不一定在先申请的内存空间的后面,即 p2 指向的地址并不一定大于 p1 所指向的内存地址,原因是先申请的内存空间一旦被释放,后申请的内存空间则会利用先前被释放的内存,从而导致先后分配的内存空间在地址上不存在先后关系。堆中存储的数据若未释放,则其生命周期等同于程序的生命周期。

堆中内存的分配过程

操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确地释放本内存空间。由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动地将多余的那部分重新放入空闲链表。

栈和堆的区别

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;

(2)空间大小不同。每个进程拥有的栈大小要远远小于堆大小。理论上,进程可申请的堆大小为虚拟内存大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;

(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca()函数分配,但是栈的动态分配和堆是不同的,它的动态分配是由操作系统进行释放,无需我们手工实现。

(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。

无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。

汇编代码会放在代码区,cpu中的ip寄存器指针会指向下一条需要执行的机器指令的地址。当执行完成后,ip会加上执行完成的指令大小以达到下一条。

执行函数代码的本质是cpu在访问代码区的机器指令。

函数中的局部变量值需要计算,调用等操作,因此需要一段空间来存放局部变量。代码区放置的是机器码(机器指令),没有为变量开辟空间(int 4个字节.....)栈空间则用来存放变量的空间。若将值存于代码区,则会导致其无法进行更改。

函数均存放于代码区,值放入栈空间

this

指针访问

person *P=&person

使用指针来间接访问所指向对象的成员变量

原理 从指针中取出对象地址,利用对象地址与成员变量的偏移量计算出成员变量的地址,最后根据成员变量的的地址访问成员变量的存储空间。

偏移量由成员变量的排布,种类决定。(离首地址的距离)

对于对象的访问,eax存储对象的地址,之后元素的访问都是通过第一个元素的加减来实现。(偏移)

对象中储存的均是地址值,说明对象是一个指针类数组

对象直接访问成员

指针的意义:有些代码只将地址传入函数中,使用指针可以利用这些量。

在堆空间中只能通过地址值访问,故需要通过指针来进行操作。

this

在为结构体设置的函数中,可以采用指针的形式,使得传入不同的结构体时,可以访问得到不同结构体下的成员值。

this 是成员函数中的一种指针,可以使代码量简短。而不用传入地址。

用法:this->m_age 用以指代成员变量

指针访问的本质

在对象中的成员函数调用时,会将调用者的地址值传入其中。

  • person.run()

  • p->run()

这两种调用因此是不同的。指针调用时会将指针中存储的地址值调入进来,另一个则会直接传值。

0xcc出现的原因 栈空间在重新分配之后会抹除数据,需要以cc全部覆盖。

why cc? cc对应的汇编代码为int 3,是起断点作用的代码,使程序停止运行。(interrupt为中断,3为类型代号)。如果当汇编代码错乱时,这个命令可以使得代码中断,以免产生危险代码操作。(逆向中也会使得ida的分析无法继续)

汇编操作:esp d0h

开辟栈空间 d0为空间大小

rep stos 拷贝指令

esp 栈空间栈顶(小端)的顶部不是自由栈空间,而是ebx,esi,edi,ecx寄存器的空间,无法被被修改。

有时候编译器的规则会造成不便,需要将其诈骗。

内存

封装,内存布局与堆空间

封装:变量成员私有化,提供公共的getter和setter去给外界访问成员变量。

struct 定义的结构体默认为公开(public),为避免其中的数值别恶意篡改,需要将其封装,同时在结构体的公共部分设置一个函数来使其可以读取其中的值。

set:类似于输入函数,输入成员变量的值,如果符合预设条件,则将值赋给私有的值,不符合则不作任何操作(或者赋予一个预设值),具体情况具体确定。

get:类似于输出函数,返回一个私有成员的值。

内存空间的布局

内存空间的布局分为栈空间,堆空间,代码区和全局区。

每一个应用都有自己独立的空间,

代码段用于存放代码(机器码)。这个区域是只读的。

全局区存放全局变量。整个程序运行过程中一直存在

而栈空间用于存放函数中产生的局部变量,当函数结束后,栈空间将会收回。

堆空间的空间需要自己手动申请与释放。使用情况:为了自由的控制内存的生命周期,大小,会经常使用堆空间的内存。

堆空间的申请和释放

malloc(4)申请4个字节,并返回首地址。需要用指针来存储。

eg int *p=(int * )mlloc(4);

*p=10;

free(p); //回收空间

释放时不可以只释放一部分。(2)

具体汇编情况:栈空间中存放4字节(int类型的指针所需要的大小)地址指向堆空间,堆空间中存放4个字节的值(malloc函数所开辟的空间,当函数调用结束之后,栈空间中的指针会消失,而堆空间的值则不会,直到free操作执行之后。

new /delete

eg int *p=new int;//申请4个字节

delete(p) //释放空间。

new[ ]/delete[ ]

中括号中设置空间的大小。

使用时需要进行配对(连接上会)

当堆空间不够用时,无法开辟空间,可能会返回空地址。

现今的高级编程语言不需要管理堆空间,屏蔽了内存的细节,有利有弊。无法优化性能。

堆空间的初始化

*p=0;//赋值为零

memset(p,0,40)

将比较大的函数进行内存清理比较快速。

内存地址,初始化值,初始化的长度。将每一个字节都设置为0;

对象的内存

可以存在3地方;全局,栈空间,堆空间。eg

结构体存放于全局区,函数中创捷的类存在于栈空间,而函数中创建的指针则存放在堆空间中。

构造函数

也可以叫做构造器,在创建对象时自动调用,一般用于完成对象的初始化工作。为其赋值。与类名同名。

使用方式: 与对象名称相同而无初始值的函数形式调用。

特点:可以有多个构造函数,可以重载。可以设定传入参数(调用时需要符合对应的格式)一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象,不能不使用。

通过malloc调用的对象不会调用构造函数

eg person *p=(person *)malloc (sizeof(person)).

这个对象则不会调用构造函数。

new则可以(比malloc多做了一些事情)

一个错误结论:默认情况下编译器会为每一个类生成一个空的构造函数。

正确理解为在特定情况下才会(eg 为类中的成员变量赋予初始值 )

当构造函数只附加了传值空位而不传送值,挥别识别为函数的声明。除非其使用开辟的堆空间。不会创建一个类函数

成员变量的初始化问题

在栈空间和堆空间中不会初始化成员变量(堆空间中若有传值则会初始化0(加上小括号))

全局区会将其初始化为0

当自定义了构造函数之后,只有全局区会将其初始化为0 (编译器认为构造函数已经进行了初始化)

初始化函数memset函数也可以和this联动,指向其所引用的对象

eg person()

{

memset(this,0,sizeof(person));

}

析构函数

在对象销毁时调用,用于完成对象的清理工作(内存的清理工作)。

当构造函数被调用时,说明一个新的对象诞生 而析构函数则是对象销毁的的表现。析构函数的形式为在构造函数之前加入~ 同时析构函数无法被重载。

而通过malloc分配的对象不会调用析构。(类比构造)

析构和构造必须放置在对象的public中才可以被调用。

内存管理

当对象回收时,对象中的成员也随之回收。(大小有限)而如果所创建的成员是由手动开辟空间导入的其他类,由于new位于堆空间中,无法主动释放堆空间的空间。需要析构函数中设置以进行手动释放。

如果是直接导入而非开辟堆空间,则无需进行手动释放。对象内部申请的堆空间,在对象内部进行回收。

一个误区:

变量的位置在哪里取决于申请方式,没有固定的位置

内存泄露:应该释放的内存没有被释放。

声明和实现的分离

对象中创建的函数可以先只写声明,而实现置于对象之外。

eg void perosn ::setagr(int agr)

其中,person为对象名。setagr为声明的函数名。顺序不可以改变。

可以以此来实现不同文件之间的调用

.h // .cpp 函数名置于h 实现置于cpp

继承

使得子类拥有父类的所有成员变量和函数。

在同一类对象中可以将共性属性另起父类。

struct son::fath{ }

对象的内存布局

当使用继承之后,对象的大小为对象本身的大小加上总体继承的大小。

其存储值的位置为 父类在前 子类在后

成员访问权限
  • public 公开

  • protected 子类内部 当前类内部可以访问

  • private 私有 只有当前类内部可以访问

对象和指针在调用时是不同的,对象使用—> 而指针使用.

初始化列表

一种便捷的初始化成员变量的方式,用以替代繁琐的函数内语句赋值。

eg perosn (int a ,int b) : ma(a),mb(b){

}

此形式即可用于初始化成员,其汇编本质与语句赋值无区别。(先后执行)成员变量地址值比较小的先进行初始化。

代码是否符合要求看本质,只要符合其本质要求,形式的转变并无关系。

  • 和默认参数的配合使用

在有默认参数的情况下,可以不传入参数值,且可以传入任意个数参数的函数。

  • 默认参数的声明只能放在声明之中,而初始化列表需要放置在实现中。

构造函数的相互调用

有参和无参的构造函数较为相似,可以在无参的构造函数中调用有参的构造函数(特殊参数的有参函数)以精简代码,同时还可以加入其他个性化操作。

此操作必须置于初始化列表之中进行。若将其置于函数的实现中,则会被理解为定义了一个新的函数对象。

父类的构造函数

子类构造函数会默认调用父类无参构造函数。

当子类和父类中均有无参的构造函数时,会默认在调用自己的构造函数之前调用父类的构造函数。(权限与继承的综合考虑)

若子类构造函数主动地调用了父类的有参构造函数,则不会再去调用父类无参构造函数。

父类指针和子类指针

父类指针是可以指向子类对象的。

person *p=new stdudent()

当使用此方法时,由于继承原因,父类的指针可以指向的只有父类中定义的变量,即子类对象一个,较为安全。(内存大小继承)继承方式应该改为public

多态

多态的定义:同一操作对于不同的对象有不同的解释和执行效果。

实现方法 理由父类和子类的指针 继承 传参以实现

eg anmai为 cat pig的父类指针

可以定义 sakdj(animal *p)

{

sdada;

}

此时可以传入任意的子类指针 askdja(new cat());以实现多态调用。

重写 将父类的函数在子类中运用

c++默认情况下之根据指针类型来调用,不存在多态。在指针强制转换后即可骗过编译器。

cat* p=(cat* )new pig();

c++中通过虚函数来实现多态。virtual 在父类之中标注 即可在子类中运用。子类中重写的函数也会变为虚函数。

虚函数的实现通过虚表解决。

cc++中,成员函数必须为虚函数。

以类型来限制指针所可以指向的内容

虚函数比普通函数多4个字节

虚表

虚函数的实现原理,存储这最终需要调用的虚函数地址。

汇编代码分析

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wanhars

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

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

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

打赏作者

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

抵扣说明:

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

余额充值