>_<
▊ Q1 :从键盘输入一个字符并在屏幕上显示,给出其ASCII二进制表达式及其包含的1的个
数,如果是“Esc”,那么退出程序。重复上述过程。
参考效果:
▽ 我的解法
DATA SEGMENT
BUF DB 'Please input a character : $'
BUF_0 DB '0$' ; 多余的操作,直接MOV DL, '0' 即可, 自动存入其ASCII码
BUF_1 DB '1$' ; 多余的操作,直接MOV DL, '1' 即可
BUF_2 DB ' ASCII: $'
BUF_3 DB 'B, The number of '1' is $'
CTRL DB 0DH, 0AH, '$'
DATA ENDS
STACK SEGMENT 'STACK'
DW 100 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
START:
MOV AX, DATA
MOV DS, AX ; 初始化数据段
LOP:
MOV DX, OFFSET BUF
MOV AH, 9
INT 21H ; 输出提示
MOV AH, 1
INT 21H ; 输入(到AL)
CMP AL, 1BH
JZ EXIT ; 分支结构(是否为ESC)
PUSH AX
MOV DX, OFFSET BUF_2
MOV AH, 9
INT 21H
POP AX
MOV BL, AL ; 对BL进行左移操作
MOV AL, 0 ; 用AL计数
MOV CX, 8 ; 一共左移8次(CX控制循环LOOP的次数)
L1:
SHL BL, 1 ; 循环左移,最左边的那一位被递送到CF
JC L2 ; 是1,跳到L2
JMP L3 ; 无条件跳到L3,这种条件跳转紧跟着无条件跳转的用法特别常见,这使得满足条件时多执行了一段代码
L2:
PUSH AX
MOV DX, OFFSET BUF_1 ; 输出'1'
MOV AH, 9
INT 21H
POP AX
INC AL ; 计数加一
JMP L4
L3:
PUSH AX
MOV DX, OFFSET BUF_0 ; 输出'0'
MOV AH, 9
INT 21H
POP AX
L4:
LOOP L1
PUSH AX
MOV DL, OFFSET BUF_3
MOV AH, 9
INT 21H
POP AX
ADD AL, 30H ; 计数器加上30H转换为对应数字的ASCII
MOV DL, AL
MOV AH, 2
INT 21H ; 输出结果,即1的个数
MOV DX, OFFSET CTRL
MOV AH, 9
INT 21H ; 换行
JMP LOP
EXIT:
MOV AH, 4CH
INT 21H
CODE ENDS
END START
▽ 老师的解法
DATA SEGMENT 'DATA'
Str1 DB 'Please input a character: $'
Str4 DB ' ASCII: $'
Str5 DB 'B, #'1' = $'
Str6 DB '. ', 0DH, 0AH, '$'
DATA ENDS
STACK SEGMENT 'STACK'
DW 100H DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
START: MOV AX, DATA ; 初始化数据段
MOV DS, AX
Next: MOV DX, OFFSET Str1 ; 提示:输入一个字符
MOV AH, 9
INT 21H
MOV AH, 1 ; 键盘输入一个字符(到AL)并回显
INT 21H
MOV BH, AL ; 接下来对BH进行操作
MOV DX, OFFSET Str4
MOV AH, 9
INT 21H
MOV BL, 0 ; 计数器:计数1的个数
MOV CX, 8 ; 准备循环左移8次
MOV DH, 0
Shift: MOV DL, '0' ; 默认显示ASCII 0
ROL BH, 1 ; 循环左移1次
JNC Bit0 ; 是二进制0,跳转
Bit1: INC BL ; 是二进制1
MOV DL, '1' ; 准备显示ASCII 1
Bit0: MOV AH, 2 ; 显示ASCII 0/1
INT 21H
LOOP Shift
MOV DX, OFFSET Str5 ; 显示提示
MOV AH, 9
INT 21H
ADD BL, 30H ; 计数器加上30H转换为对应数字的ASCII
MOV DH, 0
MOV DL, BL
MOV AH, 2 ; 显示1的个数
INT 21H
MOV DX, OFFSET Str6 ; 显示:回车换行
MOV AH, 9
INT 21H
CMP BH, 1BH ; 是否是Esc?
JZ EXIT ; 是Esc,跳转
JMP Next
EXIT: MOV AH, 4CH
INT 21H
CODE ENDS
END START
▲ 对比总结 :
❶ 首先关键思路完全相同:对寄存器中的数据进行多次左或右移,对移出后存在CF的数字(0/1)进行判断即可
❷ 巧妙分配寄存器;如果某个寄存器必须要完成两个作用(比如AH既要存DIV除法的余数,又要用来完成DOS指令),那么注意使用PUSH POP保护现场
❸ 显示各种提示比较麻烦,而且会占用着AH和DX无法做其他事情
▊ Q2 :从键盘输入一个字符并在屏幕上显示,如果是“Esc”,那么退出程序;如果是十六进制数(0-9,a-f,A-F),那么显示其平方值;如果是其它字符,提示不是十六进制数。重复上述过程。
参考效果:
▽ 我的代码
DATA SEGMENT
TABLE DB 0, 2, 4, 6, 8, 11, 14, 17, 20, 23
DB 26, 30, 34, 38, 42, 46
LOLI DB '0$1$4$9$16$25$36$49$64$81$100$121$144$169$196$225$'
BUF_1 DB ' Exclude from 0~9 or a~f or A~F $'
BUF_2 DB 'H ----> $'
ENTER DB 0DH, 0AH,'$'
RESULT DB 0
DATA ENDS
STACK SEGMENT STACK
DB 100 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
START:
MOV AX, DATA
MOV DS, AX
MOV CL, 10
MOV CH, 0
LOP:
LEA BX, TABLE ;BX获取TABLE首址(用于XLAT查表)
MOV AH, 01H
INT 21H
PUSH AX
LEA DX, BUF_2
MOV AH, 09H
INT 21H ;这里会改变AL,因此要PUSHPOP保护
POP AX
CALL JUDGE ;修正
XLAT ;AL获得偏移量
LEA BX, LOLI ;BX获取LOLI首址
MOV DX, BX ;给DX用于打印
XOR AH, AH ;AH清零(目的是让DX与AX得以累加)(16位)
ADD DX, AX ;对用于打印的DX指针做修正
CMP CH, 0
JA TO
MOV AH, 09H
INT 21H
;换行再循环
TO:
MOV CH, 0
MOV DX, OFFSET ENTER
MOV AH, 09H
INT 21H
LOOP LOP
EXIT:
MOV AH, 4CH
INT 21H
;判断输入并进行修正
JUDGE PROC NEAR
CMP AL, '0'
JB NO
CMP AL, '9'
JA NEXT
SUB AL, 30H ;是0~9,进行修正
RET
NEXT:
CMP AL, 'A'
JB NO
CMP AL, 'F'
JA FINAL
SUB AL, 55 ;是A~F,进行修正
RET
FINAL:
CMP AL, 'a'
JB NO
CMP AL, 'f'
JA NO
SUB AL, 87 ;是a~f,进行修正
RET
NO:
MOV DX, OFFSET BUF_1
MOV AH, 09H
INT 21H
MOV CH, 1 ;当做FLAG(利用寄存器在主程序和子程序间传递信息)
RET
JUDGE ENDP
CODE ENDS
END START
▽ 老师的代码
DATA SEGMENT 'DATA'
Str1 DB 'Please input a HEX: $'
Str2 DB ' Excluded from {0~9, a~f, A~F}!', 0DH, 0AH, '$'
Str3 DB 0DH, 0AH, '$'
Table DB 'H = 0^2 = 0.$'
DB 'H = 1^2 = 1.$'
DB 'H = 2^2 = 4.$'
DB 'H = 3^2 = 9.$'
DB 'H = 4^2 = 16.$'
DB 'H = 5^2 = 25.$'
DB 'H = 6^2 = 36.$'
DB 'H = 7^2 = 49.$'
DB 'H = 8^2 = 64.$'
DB 'H = 9^2 = 81.$'
DB 'H = 10^2 = 100.$'
DB 'H = 11^2 = 121.$'
DB 'H = 12^2 = 144.$'
DB 'H = 13^2 = 169.$'
DB 'H = 14^2 = 196.$'
DB 'H = 15^2 = 225.$' ; 每个字符串恰好是16个字节!!!
DATA ENDS
STACK SEGMENT 'STACK'
DW 100H DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
START: MOV AX, DATA ; 初始化数据段
MOV DS, AX
Next: MOV DX, OFFSET Str1 ; 提示:输入一个十六进制数
MOV AH, 9
INT 21H
MOV AH, 1 ; 键盘输入一个字符并回显
INT 21H
CMP AL, 1BH ; 是否是Esc?
JZ EXIT ; 是Esc,跳转
CMP AL, '0' ; 是否属于ASCII 0~9?
JC NotHEX ; 不是ASCII HEX,跳转
CMP AL, '9'+1 ; 是否属于ASCII 0~9?
JC Is0_9 ; 属于ASCII 0~9,跳转
CMP AL, 'A' ; 是否属于ASCII A~F?
JC NotHEX ; 不是ASCII HEX,跳转
CMP AL, 'F'+1 ; 是否属于ASCII A~F?
JC Upper ; 属于ASCII A~F,跳转
CMP AL, 'a' ; 是否属于ASCII a~f?
JC NotHEX ; 不是ASCII HEX,跳转
CMP AL, 'f'+1 ; 是否属于ASCII a~f?
JC Lower ; 属于ASCII a~f,跳转
NotHEX: MOV DX, OFFSET Str2 ; 提示:不是一个十六进制数
MOV AH, 9
INT 21H
JMP Next
Lower: SUB AL, 20H ; 变为ASCII A~F
Upper: SUB AL, 7 ; 尾随ASCII 0~9
Is0_9: SUB AL, '0' ; 变为HEX 0~F
Lookup: MOV DX, OFFSET Table ; 平方表的基地址
MOV AH, 0
SHL AL, 4 ; 平方表的偏移地址
ADD DX, AX ; 显示平方表
MOV AH, 9
INT 21H
MOV DX, OFFSET Str3 ; 回车换行
MOV AH, 9
INT 21H
JMP Next
EXIT: MOV AH, 4CH
INT 21H
CODE ENDS
END START
▲ 对比总结 :
❶ 思路基本相同,都是查表的思想;
❷ 区别在于我的代码是XLAT自动查表,其实XLAT的实质就是BX获取表的首址,AL中就是偏移量(字节),查到的内容返回AL;局限性在于所查的内容大小只能是一个字节,想要查字符串只能讲这个字节的内容设置为偏移量,再进行一次计算得到真正字符串的首址;
❸ 而第二种方法很巧妙 :
不使用XLAT自动查表,利用已知的表的首址,以及将输入转化为偏移地址,进行一次计算后得到字符串的位置。
巧妙之处在于,字符串的长度恰好设置为16个字节,那么第一、二、三…个字符串的偏移量是0,16, 32…,而这个数字可以由0, 1, 2左移4位得到(有点HashMap的意味)。
因而,最好将表的内容的长度设置为1, 2, 4, 8, 16…字节,偏移量直接移位就能得到。
❹ 麻烦和难点对输入进行修正:将键入的那个ASCII码修正为我们真正需要的ASCII码数值(其实这个ASCII码对应的字符对我们来说根本没有意义)。