汇编语言(九):汇编语言格式、变量定义、顺序/分支/循环程序设计、子程序设计

作为汇编语言的课程笔记,方便之后的复习与查阅

本篇为课程第十和第十一次课内容

汇编语句格式

标识符

  • 标识符(Identifier)一般最多由31个字母、数字及规定的特殊符号(如 _、$、?、@)组成,不能以数字开头。默认情况下,汇编程序不区别标识符中的字母大小写
  • 一个程序中,每个标识符的定义是唯一的,还不能是汇编语言采用的保留字。
  • 保留字(Reserved Word)是汇编程序已经利用的标识符,主要有:
    硬指令助记符——例如:MOV、ADD
    伪指令助记符——例如:DB、EQU
    操作符——例如:OFFSET、PTR
    寄存器名——例如:AX、CS

硬指令、执行性语句

硬指令:使CPU产生动作、并在程序执行时才处理的语句

执行性语句——由硬指令构成的语句,它通常对应一条机器指令,出现在程序的代码段中,格式:

标号: 硬指令助记符 操作数,操作数				;注释
again: mov 			dx,		offset string		;注释
  • 标号:反映硬指令位置(逻辑地址)的标识符,后跟一个冒号分隔
  • 硬指令助记符:可以是任何一条处理器指令
  • 处理器指令的操作数可以是立即数、寄存器和存储单元

伪指令、说明性语句

伪指令(Directive)——不产生CPU动作、在程序执行前由汇编程序处理的说明性语句
伪指令与具体的处理器类型无关,但与汇编程序的版本有关

说明性语句——由伪指令构成的语句,它通常指示汇编程序如何汇编源程序:

名字  伪指令助记符  参数,参数,…		;注释
string 	db 		‘Hello,world’		;注释
  • 名字:反映伪指令位置(逻辑地址)和属性的标识符,后跟空格或制表符分隔,没有冒号
  • 伪指令助记符:定义字节数据和字符串的DB就是伪指令
  • 伪指令的参数可以是常数、变量名、表达式等,可以有多个,参数之间用逗号分隔

变量定义

变量定义(Define)伪指令为变量申请固定长度的存储空间,并可同时将相应的存储单元初始化

变量定义伪指令要放在.exit后,end之前,因为它不是可执行的语句

变量名

变量名为用户自定义标识符,表示初值表首元素的逻辑地址;用这个符号表示地址,常称为符号地址。设置变量名是为了方便存取它指示的存储单元。变量名可以没有。这种情况,汇编程序将直接为初值表分配空间。

初值表

初值表是用逗号分隔的参数

主要由数值常数、表达式或?DUP组成

  • ?——表示初值不确定,即未赋初值
BUFFER	DB ?
  • DUP——表示重复初值
    DUP的格式为:重复次数 DUP(重复参数)
BUFFER	DB 10 dup(0)
BUFFER	DB 10 dup(?)

变量定义伪指令助记符

DB——定义字节伪指令
DW——定义字伪指令
DD——定义双字伪指令

DF——定义3字伪指令
DQ——定义4字伪指令
DT——定义10字节伪指令

重点掌握前三个

定义字节单元伪指令DB

DB伪指令用于分配一个或多个字节单元,并可以将它们初始化为指定值

初值表中每个数据一定是字节量(Byte),存放一个8位数据:

  • 0~255的无符号数
  • -128~+127带符号数
  • 字符串常数
X	db 'a',-5
	db 2 dup(100),?		;第二行没有带名字,默认由x往下继续进行内存分配
Y	db 'ABC'

在这里插入图片描述

定义字单元伪指令DW

初值表中每个数据一定是字量(Word),一个字单元可用于存放任何16位数据:

  • 一个段地址
  • 一个偏移地址
  • 两个字符
  • 0~65535之间的无符号数
  • -32768~+32767之间的带符号数
count	dw 8000h,?,'AB'			; A在高地址处
maxint	equ 64h					; equ是等价的意思,表示之后maxint就是64h
number	dw maxint
array	dw maxint dup(0)

在这里插入图片描述

定义双字单元伪指令DD

初值表中每个数据是一个32位的双字量(Double Word):

  • 有符号或无符号的32位整数
  • 用来表达16位段地址(高位字)和16位的偏移地址(低位字)的远指针
vardd	DD 0,?,12345678h
farpoint	DD 00400078h

PTR操作符

PTR操作符使名字或标号具有指定的类型

  • 类型名可以是
    BYTE/WORD/DWORD/FWORD/QWORD/TBYTE
mov byte ptr [2000H],10H

使用PTR操作符,可以临时改变名字或标号的类型

顺序程序设计

求两数之和

NUM1和NUM2单元中各存放着一个双字无符号数,编制程序计算两数之和,将结果存于SUM双字单元中,进位保存到FSUM字节单元中

  • 分析:最多一次处理16位,双字需分开处理
.model tiny
.code
.startup 
	mov ax,word ptr num1    	;处理低16位
	add ax,word ptr num2
	mov word ptr sum,ax     	;保存低16位
	mov ax,word ptr num1+2  	;处理高16位
	adc ax,word ptr num2+2
	mov word ptr sum+2,ax   	;保存高16位
	
	mov dx,0 		   			;最高进位
	rcl dl,1
	mov fsum,dl		   			;保存进位	
	.exit 0           
	                     
	num1   dd 82348567H
	num2   dd 87658321H
	sum   dd ?
	fsum   db ? 
end

也可以用串操作

移位

ddvarw为一个32位的数据12ab56cdH,编程将其整个循环左移4位(使用顺序结构)得到 2ab56cd1H

在这里插入图片描述

.model tiny
.code
.startup
	mov ax,word ptr ddvar  		;低16位
	mov dx,word ptr ddvar+2		;高16位
	mov  cl , 4
	mov  bh , dh				;保存高8位到BH 
	mov  bl , ah				;保存低8位到BL
	shl  dx  , cl 				;高16位左移 4位
	shl  ax , cl  				;低16位左移 4位
	shr  bh , cl 				;高8位右移4位,高4位到bh中
	shr  bl , cl 				;低8位右移 4位,低4位到bl中
	or   dl , bl  				;原低4位转到高4位
	or   al, bh   				;原高4位转到低4位
	.exit 0 
	
	ddvar	dd 12ab56cdh
end

代码转换 XLAT

用查表法,实现一个8位二进制数(00-0FH)转换为ASCII码显示

.model tiny
.code
.startup
	mov bx,offset ASCII			;BX指向ASCII码表
	mov al,hex					;AL取得一位16进制数,	
	and al,0fh					;只有低4位是有效的,高4位清0
	xlat						; 换码:AL←DS:[BX+AL]
	mov dl,al					;入口参数:DL←AL
	mov ah,2					;02号DOS功能调用
	int 21h						;显示一个ASCII码字符
	.exit 0

	ASCII db 30h,31h,32h,33h,34h,35h
	      db 36h,37h,38h,39h	;0~9的ASCII码
	      db 41h,42h,43h,44h,45h,46h ;A~F的ASCII码
  	hex db 0bh    ;任意设定了一个待转换的1位16进制数
end

分支程序设计

  • 判断的条件是各种指令,如CMPTEST等执行后形成的状态标志
  • 转移指令JccJMP可以实现分支控制

二分支

比较两个数大小:在DATA1和DATA2单元中各有一个16位二进制无符号数,找出其中较小的数,存于MIN单元中

.model tiny
.code
.startup
	mov ax,data1
	cmp ax,data2
	jb next
	mov ax,data2 		;CF=0
	next:  
		mov min,ax		;存结果
	.exit 0

	data1   dw  2000h
	data2   dw  3f80h
	min      dw  ?
end

三分支

判断变量var中的值,若为正数,在result中存入1;为负数,在result中存入-1;为零,在result中存入0

.model tiny
.code
.startup
	mov ax,var
	cmp ax,0
	jz zero  				;等于0
	jg great				;大于0
	mov ax,0ffffh			;小于0
	jmp ext
	
	zero: mov ax,0
		 jmp next
	great: mov ax,0001h
	next: mov result,ax
	.exit 0
	   
	var	dw 8001h
	result  	dw 0
end	

多分支

(了解即可)

  • 可用跳跃表法,使程序根据不同的条件转移到多个程序分支中去执行

需要在数据段事先安排一个按顺序排列的转移地址表。因为转移地址为16位偏移地址,所以表地址偏移量需要加2(type=2)调整。可用JMP指令的变址寻址,或寄存器间接寻址,或基址变址寻址方式来实现跳跃表法的程序

Table dw disp1,disp2,disp3,disp4,disp5,disp6,disp7,disp8

在这里插入图片描述
:根据键盘输入的1-8数字跳转到8个不同的处理程序段

.model tiny
.code
.startup
	start1:
		mov dx,offset msg		;9号功能显示字符串
		mov ah,9
		int 21h
		mov ah,1				;1号功能获取用户输入
		int 21h
		cmp al,'1'				;输入不是1~8,则跳转回去
		jb start1
		cmp al,'8'
		ja start1
		and ax,000fh			;设置地址偏移
		dec ax
		shl ax,1				;因为是双字,所以地址偏移需要用shl来乘2
		mov bx,ax
		jmp table[bx]
	start2:
		mov ah,9
		int 21h
		.exit 0
	disp1:
		mov dx,offset msg1
		jmp start2
	disp2:
		mov dx,offset msg2
		jmp start2
	……
	disp7:
		mov dx,offset msg7
		jmp start2
	disp8:
		mov dx,offset msg8
		jmp start2
		
	Msg db 'Input Number(1~8):',0dh,0ah,'$'
	Msg1 db 'Chapter 1!',0dh,0ah,'$'
	Msg2 db 'Chapter 2!',0dh,0ah,'$'
	Msg3 db 'Chapter 3!',0dh,0ah,'$'
	Msg4 db 'Chapter 4!',0dh,0ah,'$'
	Msg5 db 'Chapter 5!',0dh,0ah,'$'
	Msg6 db 'Chapter6!',0dh,0ah,'$'
	Msg7 db 'Chapter 7!',0dh,0ah,'$'
	Msg8 db 'Chapter 8!',0dh,0ah,'$'
	Table dw disp1,disp2,disp3,disp4,
	      dw disp5,disp6,disp7,disp8
end

循环程序设计

  • 循环指令和转移指令可以实现循环控制

例1: 求1+2+3+…+100的和,存在sum字单元

.model tiny
.code
.startup
	xor ax,ax    		;被加数AX清0
	mov cx,100
	again:
		add ax,cx	    ;从100,99,...,2,1倒序累加
		loop again
	mov sum,ax   	;将累加和送入指定单元
	.exit 0
    sum	dw ?
end

例2:把BX中的数以十六进制的形式显示在屏幕上。比如BX=8F6AH,屏幕显示 8F6AH
这个例子要重点理解

  • 要把bx中数据转成ascii码
  • 用循环移位,先显示高4位的16进制数,接着继续移位处理其他位

在这里插入图片描述

	mov    	ch, 4			;循环4次
rotate:       
	mov    	cl, 4			;每次移位4位
	rol   	bx, cl
	mov    	al, bl			;只取bx低4位
	and     al, 0fh
	add   	al, 30h			;’0’-’9’ ASCII 30H-39H
	cmp    	al, 3ah
	jl      printit		;有符号数的比较
	add     al, 7h			;’A’-’F’ ASCII 41H-46H
printit:      
	mov    	dl, al
	mov    	ah, 2
	int    	21h
	dec    	ch
	jnz    	rotate

例3:在数据段中从buffer单元开始存放10个16位二进制有符号数,把其中最大数找出来存于MAX单元中

.model tiny
.code
.startup
	mov bx,offset buffer
	mov cx,10
	mov ax,[bx] 
	circle:
	 	inc bx			;16位,地址一次要加2
		inc bx 
		cmp ax,[bx]
		jge next
		mov ax,[bx]
	next:	
		loop circle
		mov max,ax
	.exit 0

	buffer dw  -100,3000,-1,22,8000h
		   dw  9232,0,-3632,-3144,6322
	max    dw  ?
end

例4:字符串比较——比较内存中的两个字符串string1string2,字符串长度都为COUNT个。若完全相同,则将RESULT单元清0;不同则将RESULT单元送ffh

.model tiny
.code
.startup
	mov si,	offset string1
	mov di,	offset string2
	mov cx,	count
	cld
	repz cmpsb  						;重复比较两个字符
	jnz unmat 							;zf=0,字符串不等,转移
	mov al,0	  						;zf=1,字符串相等,设置00h
	jmp output  						;转向output
	unmat:	mov al,0ffh 				;设置ffh
	output:	mov result,al 				;输出结果标记
	.exit 0
	
	string1 	db  ‘Hello,Everybody!’
	string2 	db  ‘Hello,everybody!’
	count 		equ $-string2  			;$代表count的地址,因此count就为string2字符串的个数
end

有不同字符时,SI,DI中分别存放第一个不相同字符的下一个地址;CX中存放剩下还未比较的字符个数

例5:字符串搜索——从一个字符串中查找一个指定的字符T。若找到,则将该字符存入char单元,并记录该字符所在位置的偏移地址,存于caddr中;若未找到,则将char置为0ffh(全1)。

.model tiny
.code
.startup
	lea  di, mess				;lea 获取一个变量的有效地址 等价于mov di, offset mess
	mov  al, ‘T’
	mov  cx, count
	cld
	repne  scasb
	jnz over    				;zf=0,未找到
	;zf=1,找到
	mov char, al				;存字符值
	dec di 
	mov caddr,di 				;存字符地址
	over:	.exit
	
	mess  db  	‘COMPUTER’
	count equ 	$-mess
	char  db 	0ffh
	caddr dw 	?
end

例6:将首地址为array的字节数组从小到大排序——冒泡法

	mov cx,count		;CX←数组元素个数
	dec cx				;元素个数减1为外循环次数
outlp:	mov dx, cx		;DX←内循环次数
	mov bx, offset array
inlp:	mov al,[bx]		;取前一个元素
	cmp al,[bx+1]		;与后一个元素比较
	jna next			;前一个不大于后一个元素,则不进行交换
	xchg al,[bx+1]		;否则,进行交换
	mov [bx],al
next:	inc bx			;下一对元素
	dec dx
	jnz inlp			;内循环尾
	loop outlp			;外循环尾

子程序设计

子程序的常见格式

subname	proc	;具有缺省属性的subname过程
	push ax		;保护寄存器:顺序压入堆栈
	push bx		;ax/bx/cx仅是示例
	push cx
	…			;过程体
	pop cx		;恢复寄存器:逆序弹出堆栈
	pop bx
	pop ax
	ret			;过程返回
subname	endp	;过程结束

子程序的参数传送

  • 入口参数(输入参数):主程序提供给子程序
  • 出口参数(输出参数):子程序返回给主程序
  • 参数的形式:
    ① 数据本身(传值
    ② 数据的地址(传址
  • 传递的方法:
    寄存器变量堆栈

无参数传递的子程序

;子程序功能:实现光标回车换行
dpcrlf	proc	;过程开始
	push ax		;保护寄存器AX和DX
	push dx
	mov dl,0dh	;显示回车
	mov ah,2
	int 21h
	mov dl,0ah	;显示换行
	mov ah,2
	int 21h
	pop dx		;恢复寄存器DX和AX
	pop ax
	ret			;子程序返回
dpcrlf	endp	;过程结束

通过寄存器传递参数

  • 把参数存于约定的寄存器中,可以传值,也可以传址。
  • 子程序对带有出口参数的寄存器不能保护和恢复(主程序视具体情况进行保护)
  • 子程序对带有入口参数的寄存器可以保护,也可以不保护(视具体情况而定)

例1:将AL中存放的低4位二进制数转换为相应的ASCII码子程序。
入口参数: (AL) 存放二进制数;
出口参数: (AL)存放ASCII码。

htoasc    proc
	and   al,0fh 
	add   al,30h
	cmp  al,3ah
	jl  htoasc;0~9
	add   al,7;A~F
htoasc1: ret
htoasc    endp

例2:计算字符串长度子程序。字符串是以ASCII码值0(NULL)为结束标志的一组ASCII码字符序列
入口参数:si为字符串起始地址
出口参数:ax为字符串的长度

strlen   proc
	mov di,si
	mov ax,ds
	mov es,ax  		;es:di指向字符串
	xor al,al      	;(al)清零
	mov cx,0ffffh   ;(cx)置初值 cx赋为ffffh=-1表示一直循环
	cld
	repnz scasb   	;查找结束符
	mov ax,cx		;cx最后为-cnt-1,cnt为循环次数。取反相当于1111 1111 – cx = -1 + cnt + 1 = cnt
	not ax          ;含结束符长度
	dec ax          ;去掉结束符
	ret
strlen   endp 

通过变量传递参数

主程序与子程序公用一组变量,实现参数的传递。不同模块间共享时,需要声明。这种结构独立性差

通过堆栈传递参数

  • 主程序将子程序的入口参数压入堆栈,子程序从堆栈中取出参数
  • 子程序将出口参数压入堆栈,主程序弹出堆栈取得它们

要注意堆栈的分配情况,保证参数存取正确、子程序正确返回,并保持堆栈平衡

  • 主程序实现平衡堆栈: add sp,n
  • 子程序实现平衡堆栈: ret n

:求字数组array中所有元素的累加和(设结果无溢出),结果存于sum

;主程序
.model tiny
.code
.startup
	mov ax,offset array 	;数组首地址
	push ax
	mov ax , count			;数组长度
	push ax
	mov ax,offset sum		;sum地址
	push ax
	call sumc				;调用子程序
	.exit 0
	
	array   dw 100 dup(20)
	count   equ ($-array)/2	 
	sum     dw ?
end
;子程序
sumc	proc
	push bp
	mov bp,sp		;利用BP间接寻址存取参数,SP为堆栈的栈顶指针
	push ax
	push cx
	push si
	push di
	mov si,[bp+8] 	;SS:[BP+8]指向array偏移地址
	mov cx,[bp+6] 	;SS:[BP+6]指向元素个数
	mov di,[bp+4] 	;SS:[BP+4]指向sum偏移地址
	xor ax,ax
sumc1:	add ax,[si] 
	add si,2
	loop sumc1
	mov [di],ax   	;存放结果
	pop di
	pop si
	pop cx
	pop ax
	pop bp
	ret 6 			;表示将堆栈指针加6,之前主程序调用时入栈了3个字,现在加6使堆栈恢复原来的状态
sumc	endp
end

在这里插入图片描述

嵌套子程序

;实现AL内容的显示
ALdisp	proc	
	push ax
	push cx		
	push ax	 		;暂存ax
	mov cl,4
	shr al,cl	 	;转换、显示al的高4位
	call htoasc	 	;转换子程序调用(嵌套)
	call dischar 	;显示子程序调用(嵌套)
	pop ax	 		;转换、显示al的低4位
	call htoasc	 	;转换子程序调用(嵌套)
	call dischar 	;显示子程序调用(嵌套)
	pop cx
	pop ax
	ret
ALdisp	endp

;实现AX内容的显示
AXdisp	proc	
	push ax      	;保护ax
	push ax	 		;暂存ax
	mov al,ah
	call ALdisp 	;显示ah,子程序调用(嵌套)
	pop ax	 		;恢复AX
	call ALdisp 	;显示al,子程序调用(嵌套)		
	pop ax       	;恢复AX
	ret
AXdisp	endp


;将AL低4位表达的一位16进制数转换为ASCII码
htoasc	proc
	and al,0fh
	cmp al,9
	jbe htoasc1
	add al,37h	
	ret	
htoasc1:	add al,30h	
	ret	
htoasc 	endp

;将AL中的ASCII码表示的字符在屏幕上显示输出
dischar  proc
	push ax
	push dx
	mov dl,al	;显示
	mov ah,2
	int 21h
	pop dx
	pop ax
	ret			;子程序返回
dischar  endp

综合例题

显示有符号十进制数

  • 子程序在屏幕上显示一个有符号十进制数
  • 子程序还包含将二进制数转换为ASCII码的过程
  • 显示时,负数用“-”引导,正数直接输出、没有前导字符
  • 子程序的入口参数用共享变量传递,主程序调用该子程序显示10个数据

程序处理过程:

  • 首先判断数据是零、正数或负数,是零显示“0”退出
  • 是负数,显示“-”,求数据的绝对值;
  • 接着数据除以10,余数加30H转换为ASCII码压入堆栈
  • 重复上一步,直到余数为0结束
  • 依次从堆栈弹出各位数字,进行显示

本例采用16位寄存器表达数据,所以只能显示+327677~-32768间的数值

.code
.startup
	mov cx,count
	mov bx,offset array
again:	mov ax,[bx]
	mov wtemp,ax	;将入口参数存入共享变量
	call write		;调用子程序显示一个数据
	inc bx
	inc bx
	call dpcrlf		;便于显示下一个数据
	loop again
	.exit 0 
	
	count	= 10
	array	dw 1234,-1234,0,1,-1,32767
			dw -32768,5678,-5678,9000
	wtemp	dw ?	;共享变量
end
;显示有符号10进制数的通用子程序
;入口参数:共享变量wtemp
write	proc
	push ax
	push bx
	push dx
	mov ax,wtemp	;取出显示数据
	test ax,ax		;判断零、正数或负数
	jnz write1
	mov dl,'0'		;是零,显示“0”后退出
	mov ah,2
	int 21h
	jmp write5

write1:	jns write2	;是负数,显示“-”
	mov bx,ax		;AX数据暂存于BX
	mov dl,'-'
	mov ah,2
	int 21h
	mov ax,bx
	neg ax			;数据求补(求绝对值)

write2:	mov bx,10	;10压入堆栈,作为退出标志
	push bx
	
write3:	cmp ax,0	;数据(余数)为零
	jz write4		;转向显示
	sub dx,dx		;扩展被除数DX.AX
	div bx			;数据除以10:DX.AX÷10
	add dl,30h		;余数(09)转换为ASCII码
	push dx			;数据各位先低位后高位压入堆栈
	jmp write3
	
write4:	pop dx		;数据各位先高位后低位弹出堆栈
	cmp dl,10		;是结束标志10,则退出
	je write5
	mov ah,2		;进行显示
	int 21h
	jmp write4
	
write5:	pop dx
	pop bx
	pop ax
	ret			;子程序返回
	
write	endp
end

计算有符号数平均值

  • 子程序将16位有符号二进制数求和,然后除以数据个数得到平均值
  • 子程序的入口参数利用堆栈传递,主程序需要压入数据个数和数据缓冲区的偏移地址。子程序通过BP寄存器从堆栈段相应位置取出参数
  • 子程序的出口参数用寄存器AX传递
  • 主程序提供10个数据,并保存平均值
.code
.startup
	mov ax,count
	push ax			;压入数据个数
	mov ax,offset array
	push ax			;压入缓冲区偏移地址
	call mean		;调用子程序求平均值
	add sp,4		;平衡堆栈
	mov wmed,ax		;保存平均值(不含余数)
	.exit 0 	
	
count	= 10
array	dw 1234,-1234,0,1,-1,32767
		dw -32768,5678,-5678,9000
wmed	dw ? 		;存放平均值
;计算16位有符号数平均值子程序
;入口参数:顺序压入数据个数和缓冲区偏移地址
;出口参数:AX=平均值
mean	proc	
	push bp
	mov bp,sp
	push bx					;保护寄存器
	push cx
	push dx
	push si
	push di
	mov bx,[bp+4] 			;从堆栈取出偏移地址
	mov cx,[bp+6] 			;从堆栈取数据个数
	xor si,si				;SI保存求和的低16位值 用32位来存储求和结果,防止溢出
	mov di,si				;DI保存求和的高16位值
mean1:	mov ax,[bx]			;取出一个数据→AX
	cwd						;AX符号扩展→DX
	add si,ax				;求和低16位
	adc di,dx				;求和高16位 注意是用adc指令
	inc bx					;指向下一个数据
	inc bx
	loop mean1				;循环
	mov ax,si
	mov dx,di				;累加和在DX.AX
	mov cx,[bp+6]			;数据个数在CX
	idiv cx					;有符号数除法,求的平均值在AX中、余数在DX中
	pop di					;恢复寄存器
	pop si
	pop dx
	pop cx
	pop bx
	pop bp
	ret
mean	endp
end
  • 9
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值