汇编开发(四):条件处理

1. 条件分支

允许决策的编程语言允许您使用称为条件分支的技术来改变控制流。在高级语言中,每个if状态,switch状态,分支循环都已经有分支逻辑,在汇编语言中,提供所有的你认为需要做的逻辑跳转。

2. Boolean 和Comparison 指令
3110861-1f8863bfe6bfe570.png
Selected Boolean Instructions.png
1). CPU 状态标识

Boolean 指令影响ZF,CF,SF,OF,PF标志位。

  • ZF:操作的结果为0时设置
  • CF:操作使目的操作数高位产生进位时设置。
  • SF:如果复制目的操作数的高位时,设置为负;否则清除设置为正。
  • OF:指令生成目的操作数在范围之外的结果时设置。
  • PF:指令生成目的操作的1的个数为奇数时设置。
2). AND 指令
  • 格式
AND destination, source

AND reg,reg
AND reg,mem
AND reg,imm
AND mem,reg
AND mem,imm

操作可以是8位,16位,32位或64位,但他们必须是相同的大小。

3110861-111f643d85b8d0d6.png
AND.png

示例:

mov al,10101110b
and al,11110110b ; result in AL = 10100110

标志位:AND 指令总是清除OF、CF。 目的操作数分配数值修改SF,ZF,PF。

  • 转换字符为大写

AND 指令提供一个简单的方式将小写转换为大写,通过比较ASCII码的方式进行转换,因为他们只有第5位是不同的。

0 1 1 0 0 0 0 1 = 61h ('a')
0 1 0 0 0 0 0 1 = 41h ('A')
3). OR 指令
  • 格式
OR destination,source

OR reg,reg
OR reg,mem
OR reg,imm
OR mem,reg
OR mem,imm

操作可以是8位,16位,32位或64位,但他们必须是相同的大小。

3110861-4e9320c3bac9c7d9.png
OR.png

示例:

mov al,11100011b
or al,00000100b ; result in AL = 11100111

标记:AND 指令总是清除OF、CF。 目的操作数分配数值修改SF,ZF,PF。

3110861-64460e698d66bc55.png
ZF_PF.png
4). XOR 指令
  • 格式
XOR destination,source
3110861-b94e92a24f585eec.png
XOR.png

标志位:AND 指令总是清除OF、CF。 目的操作数分配数值修改SF,ZF,PF。

5). NOR 指令
  • 格式
NOT reg
NOT mem

示例:

mov al,11110000b
not al              ; AL = 00001111b

标志位:没有标志位被设置。

6). TEST 指令
  • 作用

TEST指令在两个操作数中的每对匹配位之间执行隐含的AND操作,并根据分配给目标操作数的值设置SF,ZF和PF标志。 与AND指令的区别仅在TEST指令不修改目标操作数。

test al,00001001b; test bits 0 and 3

标志位:AND 指令总是清除OF、CF。 目的操作数分配数值修改SF,ZF,PF。

7). CMP 指令
  • 作用
    比较两个整数的大小

  • 格式

CMP destination,source

标志位:CMP指令根据目标操作数在发生实际减法时所具有的值来更改溢出,符号,零,进位,辅助进位和奇偶校验标志。

3110861-dfa94093a71b3e7b.png
ZF_CF.png

示例:

mov ax,5
cmp ax,10           ; ZF = 0 and CF = 1

mov ax,1000
mov cx,1000
cmp cx,ax           ; ZF = 1 and CF = 0

mov si,105
cmp si,0            ; ZF = 0 and CF = 0
8). 设置和清除标志位
  • 设置零标记位
test al,0       ; set Zero flag
and al,0        ; set Zero flag
or al,1         ; clear Zero flag
  • 设置符号标记位
or al,80h       ; set Sign flag
and al,7Fh      ; clear Sign flag
  • 设置进位标志位
stc         ; set Carry flag
clc         ; clear Carry flag
  • 设置溢出标记位
mov al,7Fh      ; AL = +127
inc al          ; AL = 80h (-128), OF=1
or eax,0        ; clear Overflow flag
3. 分支跳转
1). 分支结构
  • 示例1
    cmp eax,0
    jz L1       ; jump if ZF = 1
    .
    .
L1:
  • 示例2
    and dl,10110000b
    jnz L2 ;            jump if ZF = 0
    .
    .
L2:
2). Jcond 指令
  • 作用
    当状态标志条件为真时,条件跳转指令分支到目标标签。

  • 格式

Jcond destination

其中cond指的是标识一个或多个标志的状态的标志条件。例如基与CF与ZF标志位跳转如下:

3110861-4b339181d13fdf15.png
Jcond.png
  • 使用CMP 指令
cmp eax,5
je L1       ; 当EAX值等于5的时候跳转到L1

mov ax,5
cmp ax,6
jl L1       ; 当AX值小于6的时候跳转到L1

mov ax,5
cmp ax,4
jg L1       ; 当AX值大于4的时候跳转到L1
3). 条件跳转指令类型
  • 跳转指令集的四种分组

    • 基于特殊寄存器
    • (E)CX 操作数之间是否相等
    • 无符号操作数比较
    • 有符号操作数比较

下表位基于ZF,CF,OF,PF,SF标志位的跳转指令

3110861-81a5ae80917af8b8.png
Jumps Based on Specific Flag Values.png
  • 等于比较

在一些情况下两个操作数进行比较;另一些情况下,基于CX/ECX/RCX比较。

CMP leftOp,rightOp

下面是基于等号的跳转指令

3110861-e5988528a7ac8202.png
Jumps Based on Equality.png
  • 示例: JE, JNE, JCXZ, and JECXZ
Example 1:
    mov edx,0A523h
    cmp edx,0A523h
    jne L5              ; jump not taken
    je L1               ; jump is taken

Example 2:
    mov bx,1234h
    sub bx,1234h
    jne L5              ; jump not taken
    je L1               ; jump is taken

Example 3:
    mov cx,0FFFFh
    inc cx
    jcxz L2             ; jump is taken

Example 4:
    xor ecx,ecx
    jecxz L2            ; jump is taken
  • 无符号比较
3110861-84d6b6e91f2cc458.png
Jumps Based on Unsigned Comparisons.png
  • 有符号比较
3110861-44287d6bdbdceb7b.png
Jumps Based on Signed Comparisons.png

示例:

Example 1
    mov edx,-1
    cmp edx,0
    jnl L5          ; jump not taken (-1 >= 0 is false)
    jnle L5         ; jump not taken (-1 > 0 is false)
    jl L1           ; jump is taken (-1 < 0 is true)

Example 2
    mov bx,+32
    cmp bx,-35
    jng L5          ; jump not taken (+32 <= -35 is false)
    jnge L5         ; jump not taken (+32 < -35 is false)
    jge L1          ; jump is taken (+32 >= -35 is true)

Example 3
    mov ecx,0
    cmp ecx,0
    jg L5           ; jump not taken (0 > 0 is false)
    jnl L1          ; jump is taken (0 >= 0 is true)

Example 4
    mov ecx,0
    cmp ecx,0
    jl L5           ; jump not taken (0 < 0 is false)
    jng L1          ; jump is taken (0 <= 0 is true)
4). 条件跳转应用
  • 测试状态位
mov al,status
test al,00100000b ; test bit 5
jnz DeviceOffline
  • 两个数比较大小
    mov edx,eax ; assume EAX is larger
    cmp eax,ebx ; if EAX is >= EBX
    jae L1      ; jump to L1
    mov edx,ebx ; else move EBX to EDX
L1:             ; EDX contains the larger integer
  • 三个数中求最小
.data
    V1 WORD ?
    V2 WORD ?
    V3 WORD ?
.code
    mov ax,V1   ; assume V1 is smallest
    cmp ax,V2   ; if AX <= V2
    jbe L1      ; jump to L1
    mov ax,V2   ; else move V2 to AX
L1: 
    cmp ax,V3   ; if AX <= V3
    jbe L2      ; jump to L2
    mov ax,V3   ; else move V3 to AX
L2:
  • 循环直到有键按下
.data
    char BYTE ? 
.code
L1: 
    mov eax,10      ; create 10 ms delay
    call Delay
    call ReadKey    ; check for key
    jz L1           ; repeat if no key
    mov char,AL     ; save the character
5). 应用: 顺序搜索数组
; 扫描数组
; 扫描数组中的非0值

INCLUDE Irvine32.inc

.data
    intArray SWORD 0, 0, 0, 0, 1, 20, 35, -12, 66, 4, 0     ; 无符号整型数组
    noneMsg BYTE "A non-zero value was not found", 0        ; 提示信息

.code

main PROC
    mov ebx, OFFSET intArray        ; 数组指针
    mov ecx, LENGTHOF intArray      ; 循环计数

L1:
    cmp WORD PTR [ebx], 0           ; 与0比较
    jnz found                       ; 发现不等于0的值
    add ebx, 2                      ; 指向下一个数组元素
    LOOP L1                         ; 继续循环
    jmp notFound                    ; 未发现

found:                              ; 显示值
    movsx eax, WORD PTR [ebx]       ; 将有符号的值设置到EAX
    call WriteInt                   ; 输出
    jmp quit                        ; 退出程序

notFound:                           ; 显示未发现信息
    mov edx, OFFSET noneMsg
    call WriteString

quit:
    call Crlf
    exit
main ENDP

END
6). 应用:简单的字符串加密
  • 原理:( (X ⊗ Y) ⊗ Y) = X

  • 代码:

; 加密文本
INCLUDE Irvine32.inc
KEY = 239                           ; 在1——255之间的任意数字
BUFMAX = 128                        ; 最大缓冲字节

.data
    sPrompt BYTE "Enter the plain text: ", 0    ; 输入文本提示信息
    sEncrypt BYTE "Cipher text:  ", 0           ; 加密文本提示信息
    sDecrypt BYTE "Decryted:     ", 0           ; 解密文本提示信息
    buffer BYTE BUFMAX + 1 DUP(0)               ; 缓冲字节个数
    bufSize DWORD ?                             ; 缓冲字节

.code
main PROC
    call InputTheString             ; 输入待加密文本
    call TranslateBuffer            ; 加密文本
    mov edx, OFFSET sEncrypt        ; 显示加密信息
    call DisplayMessage             ; 调用显示文本程序
    call TranslateBuffer            ; 解密缓冲文本
    mov edx, OFFSET sDecrypt        ; 显示解密信息
    call DisplayMessage             ; 调用显示文本程序

    call WaitMsg                    ; 显示等待输入任意键信息
    exit
main ENDP

;-------------------------------------------------------
; 提示用户输入一个待加密文本,保存字符串及长度
; Receives: 无
; Returns: 无
;-------------------------------------------------------
InputTheString PROC
    pushad                  ; 保存32位寄存器
    mov edx, OFFSET sPrompt ; 显示提示信息
    call WriteString
    
    mov ecx, BUFMAX         ; 设置最大字符个数
    mov edx, OFFSET buffer  ; 设置缓冲指针
    call ReadString         ; 读取字符串
    mov bufSize, eax        ; 存储长度
    call Crlf

    popad
    RET
InputTheString ENDP

;-------------------------------------------------------
; 显示信息
; Receives: EDX 指针消息
; Returns: 无
;-------------------------------------------------------
DisplayMessage PROC
    pushad
    call WriteString
    mov edx, OFFSET buffer      ; 显示缓冲中的信息
    call WriteString
    call Crlf
    call Crlf
    popad
    RET
DisplayMessage ENDP

;-------------------------------------------------------
; 加解密文本
; Receives: 无
; Returns: 无
;-------------------------------------------------------
TranslateBuffer PROC
    pushad
    mov ecx, bufSize            ; 设置循环计数
    mov esi, 0                  ; 设置缓冲序号
L1:
    xor buffer[esi], KEY        ; 翻译字节
    inc esi                     ; 指向下一个字节
    LOOP L1

    popad
    RET
TranslateBuffer ENDP

END
3110861-419be64fe18382e6.png
效果.png
4.循环条件指令
1). LOOPZ 和 LOOPE 指令
  • LOOPZ (Loop if zero)
    想LOOP一样工作,附加条件:必须设置零标志,以便控制转移到目标标签。 格式如下:
LOOPZ destination
  • LOOPE (Loop if equal)
    与LOOPZ用法相同。
2). LOOPNZ 和 LOOPNE 指令
  • LOOPNZ (Loop if not zero)
    与LOOPZ相反,循环继续的条件是ECX无符号整数大于0并且ZF被清空。格式如下:
LOOPNZ destination
  • LOOPNE (Loop if not equal)
    与LOOPE相反。

  • 示例

; 循环查找非负数
INCLUDE Irvine32.inc

.data
    array SWORD -3, -6, -1, -10, 10, 30, 40, 4
    sentinel SWORD 0

.code
main PROC
    mov esi, OFFSET array
    mov ecx, LENGTHOF array

L1:
    test WORD PTR [esi], 8000h      ; 设置符号位
    pushfd                          ; 将flags入栈
    add esi, TYPE array             ; 移动到下一个元素
    popfd                           ; 将flags出栈
    LOOPNZ L1   
    jnz  quit                       ; none found
    sub  esi,TYPE array             ; ESI points to value

quit:
    movsx eax,WORD PTR[esi]         ; display the value
    call WriteInt
    call crlf

    call WaitMsg                    ; 显示等待输入任意键信息
    exit
main ENDP

END
5. 条件结构
1). IF 块结构
  • 示例1:

伪代码:

if( boolean-expression )
    statement-list-1
else
    statement-list-2

汇编代码:

    mov eax,op1
    cmp eax,op2     ; op1 == op2?
    jne L1          ; no: skip next
    mov X,1         ; yes: assign X and Y
    mov Y,2
L1:
  • 示例2:

伪代码:

if op1 > op2
    call Routine1
else
    call Routine2
end if

汇编代码:

    mov eax,op1     ; move op1 to a register
    cmp eax,op2     ; op1 > op2?
    jg A1           ; yes: call Routine1
    call Routine2   ; no: call Routine2
    jmp A2          ; exit the IF statement
A1: call Routine1
A2:
  • 示例3:

伪代码:

if op1 == op2
    if X > Y
        call Routine1
    else
        call Routine2
    end if
else
    call Routine3
end if

汇编代码:

    mov eax,op1
    cmp eax,op2     ; op1 == op2?
    jne L2          ; no: call Routine3
    ; process the inner IF-ELSE statement.
    mov eax,X
    cmp eax,Y       ; X > Y?
    jg L1           ; yes: call Routine1
    call Routine2   ; no: call Routine2
    jmp L3          ; and exit
L1: call Routine1   ; call Routine1
    jmp L3          ; and exit
L2: call Routine3
L3:
2). 符合表达式
  • 逻辑AND运算符

伪代码:

if (al > bl) AND (bl > cl)
    X = 1
end if

汇编代码:

    cmp al,bl ; first expression...
    ja L1
    jmp next
L1: 
    cmp bl,cl ; second expression...
    ja L2
    jmp next
L2: 
    mov X,1 ; both true: set X to 1
next:
3). WHILE 循环

伪代码:

while( val1 < val2 )
{
    val1++;
    val2--;
}

汇编代码:

    mov eax,val1 ; copy variable to EAX
beginwhile:
    cmp eax,val2 ; if not (val1 < val2)
    jnl endwhile ; exit the loop
    inc eax ; val1++;
    dec val2 ; val2--;
    jmp beginwhile ; repeat the loop
endwhile:
    mov val1,eax ; save new value for val1  

示例:循环中嵌套IF状态

C++代码:

int array[] = {10,60,20,33,72,89,45,65,72,18};
int sample = 50;
int ArraySize = sizeof array / sizeof sample;
int index = 0;
int sum = 0;
while( index < ArraySize )
{
    if( array[index] > sample )
    {
        sum += array[index];
    }
    index++;
}

汇编代码:

; 判断数组元素值是否大于50, 如果大于则求和。
INCLUDE Irvine32.inc

.data
    sum DWORD 0             ; 大于50的数组元素之和
    sample DWORD 50         ; 求和标准
    array DWORD 10, 60, 30, 33, 72, 89, 45, 65, 72, 18  ; 数组
    ArraySize = ($ - array) / TYPE array    ; 数组长度

.code
main PROC
    mov eax, 0              ; sum
    mov edx, sample
    mov esi, 0              ; 数组标识
    mov ecx, ArraySize

L1: 
    cmp esi, ecx            ; 判断数组标识是否小于数组长度
    jl L2
    jmp L5                  ; 无条件跳转

L2: 
    cmp array[esi * 4], edx ; 判断数组元素的数值是否大于50
    jg L3
    jmp L4

L3: 
    add eax, array[esi * 4] ; 求和

L4: 
    inc esi                 ; 数组标识+1
    jmp L1                  ; 跳转

L5:
    mov sum, eax            ; 设置和

    call WaitMsg            ; 显示等待输入任意键信息
    exit
main ENDP

END
4). 表驱动选择

示例:用户从键盘输入,根据输入的字符查找对应的字符串

; 用户从键盘输入,根据输入的字符查找对应的字符串
INCLUDE Irvine32.inc

.data
    CaseTable BYTE 'A'              ; 查找值
              DWORD Process_A       ; 程序的地址 
    EntrySize = ($ - CaseTable)
              BYTE 'B'
              DWORD Process_B       
              BYTE 'C'
              DWORD Process_C
              BYTE 'D'
              DWORD Process_D
    
    NumberOfEntries = ($ - CaseTable) / EntrySize
    prompt BYTE "Press capital A, B, C, or D: ", 0

    ; 定义每段程序的消息字符串
    msgA BYTE "Process_A", 0
    msgB BYTE "Process_B", 0
    msgC BYTE "Process_C", 0
    msgD BYTE "Process_D", 0

.code
main PROC
    mov edx, OFFSET prompt  ; 提示用户输入
    call WriteString
    call ReadChar           ; 从Al中读取一个字符
    mov ebx, OFFSET CaseTable   ; EBX 指向表
    mov ecx, NumberOfEntries    ; 循环计数
L1:
    cmp al, [ebx]               ; 是否匹配
    jne L2                      ; 未发现,继续
    call NEAR PTR [ebx + 1]     ; 查找到,调用程序
    call WriteString            ; 显示消息
    call Crlf               
    jmp L3
L2:
    add ebx, EntrySize          ; 指向下一个实体
    LOOP L1                     ; 重复,直到ECX = 0
L3: 
    call WaitMsg            ; 显示等待输入任意键信息
    exit
main ENDP

Process_A PROC
    mov edx, OFFSET msgA
    RET
Process_A ENDP

Process_B PROC
    mov edx, OFFSET msgB
    RET
Process_B ENDP

Process_C PROC
    mov edx, OFFSET msgC
    RET
Process_C ENDP

Process_D PROC
    mov edx, OFFSET msgD
    RET
Process_D ENDP

END
6. 有限状态机

有限状态机(FSM)是基于某些输入改变状态的机器或程序。

3110861-aea95bf1c89bb7ba.png
Simple finite-state machine.png
1). 验证输入字符串

程序在读取输入的字符串通常必须对输入文本的异常检测。

2). 验证有符号整型

程序验证一个数字的符合正负号。

示例代码:

; 有限状态机
INCLUDE Irvine32.inc

ENTER_KEY = 13

.data
    InvalidInputMsg BYTE "Invalid input", 13, 10, 0

.code
main PROC
    call ClrScr

StateA:
    call Getnext        ; 读取AL中的下一个字符
    cmp al, "+"         ; 判断是否为+号
    je StateB           ; 等于就去StateB
    cmp al, "-"         ; 判断是否为-号
    je StateB           ; 等于就去StateB
    call IsDigit        ; 判断是否为数字, 如果AL包含数字,则ZF等于1
    jz StateC           ; 等于就去StateC
    call DisplayErrorMsg; 显示异常信息
    jmp Quit            ; 退出程序

StateB:
    call Getnext        ; 读取AL中的下一个字符
    call IsDigit        ; 如果包含数字则ZF = 1
    jz StateC           ; ZF = 1则跳到StateC
    call DisplayErrorMsg; 显示错误信息
    jmp Quit            ; 退出

StateC:
    call Getnext        
    call IsDigit
    jz StateC
    cmp al, ENTER_KEY
    je Quit
    call DisplayErrorMsg
    jmp Quit

Quit:
    call Crlf
    call WaitMsg
    exit
main ENDP

;------------------------------------------------
; 从标准输入读取一个字符
; Receives: 无
; Returns: AL 包含一个字符
;------------------------------------------------
Getnext PROC
    call ReadChar
    call WriteChar
    RET
Getnext ENDP

;------------------------------------------------
; 显示错误信息
; Receives: 无
; Returns: 无
;------------------------------------------------
DisplayErrorMsg PROC USES edx
    call Crlf
    mov edx, OFFSET InvalidInputMsg
    call WriteString
    RET
DisplayErrorMsg ENDP

END
7. 条件控制流程指令

32位模式下,MASM包含一些高级的条件控制流程指令,帮助我们简单的使用条件状态,但在64位模式下不可用。

3110861-51594f2e8c14834b.png
Conditional Control Flow Directives.png
1). 创建IF状态
  • 格式
.IF condition1
    statements
[.ELSEIF condition2
    statements ]
[.ELSE
    statements ]
.ENDIF
  • 条件可选表达式操作符
3110861-87ca0cecc8f3532f.png
Runtime Relational and Logical Operators.png
  • 生成汇编代码
mov eax,6
.IF eax > val1
    mov result,1
.ENDIF
2). 有符号和无符号比较
  • 无符号比较
.data
    val1 DWORD 5
    result DWORD ?
.code
    mov eax,6
    .IF eax > val1
        mov result,1
    .ENDIF
  • 有符号比较
.data
    val2 SDWORD -1
    result DWORD ?
.code
mov eax,6
    .IF eax > val2
        mov result,1
    .ENDIF
  • 寄存器比较
mov eax,6
mov ebx,val2
.IF eax > ebx
    mov result,1
.ENDIF
3). 符合表达式
  • 格式
.IF expression1 ||[&&] expression2
    statements
.ENDIF
  • 示例:设置光标位置
; 设置光标位置
INCLUDE Irvine32.inc

; 使用.IF 和 .ENDIF 指令设置光标的位置

.code
main PROC
    mov dl, 79      ; X轴
    mov dh, 24      ; Y轴
    call SetCursorPosition
    
    call Crlf
    call WaitMsg
    exit
main ENDP

;------------------------------------------------
; 设置光标位置
; Receives: DL: X轴, DH: Y轴
; Returns: 无
;------------------------------------------------
SetCursorPosition PROC
.data
    BadXCoordMsg BYTE "X-Coordinate out of range!", 0Dh, 0Ah, 0
    BadYCoordMsg BYTE "Y-Coordinate out of range!", 0Dh, 0Ah, 0
.code
    .IF (DL < 0) || (DL > 79)
        mov edx, OFFSET BadXCoordMsg
        call WriteString
        jmp quit
    .ENDIF
    .IF (DH < 0) || (DH > 24)
        mov edx, OFFSET BadYCoordMsg
        call WriteString
        jmp quit
    .ENDIF
    call Gotoxy             ; 修改x,y值

quit:
    RET
SetCursorPosition ENDP

END
  • 示例:大学注册
; 大学生注册
; 使用.IF, .ENDIF, .ELSEIF指令

INCLUDE Irvine32.inc

.data
    TRUE = 1
    FALSE = 0
    gradeAverage WORD 275   ; 测试值
    credits WORD 12         ; 测试值
    OkToRegister BYTE ?

.code
main PROC
    mov OkToRegister, FALSE

    .IF gradeAverage > 350
        mov OkToRegister, TRUE
    .ELSEIF (gradeAverage > 250) && (credits <= 16)
        mov OkToRegister, TRUE
    .ELSEIF (credits <= 12)
        mov OkToRegister, TRUE
    .ENDIF

    call Crlf
    call WaitMsg
    exit
main ENDP

END
4). 使用.REPEAT.WHILE创建循环
  • 格式
; .REPEAT 指令
.REPEAT
    statements
.UNTIL condition

; .WHILE 指令
.WHILE condition
    statements
.ENDW
  • .REPEAT 示例
mov eax,0
.REPEAT
    inc eax
    call WriteDec
    call Crlf
.UNTIL eax == 10
  • .WHILE 示例
mov eax,0
.WHILE eax < 10
    inc eax
    call WriteDec
    call Crlf
.ENDW
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值