主要是放一些做过的题,以后可能就不想做了
自我感觉我的注释还是很详细的,希望愿意看我代码的人能因为我的详细的注释而理解方便,获得思路
题目集合
- 题目:从键盘输入两个1位十进制数,计算这两个数之和,并将结果在屏幕上显示出来。
- 题目:从键盘输入0~9中任意一个自然数,将其立方值在屏幕上显示出来(使用查表法实现)。
- 题目:求两个有符号字节型内存数X和Y的绝对值之和,保存在Z中
- 题目:输入一个数N(0-65535),计算Σ(i从1到N)i(0-65535)并输出
- 题目:输入数字之后冒泡排序
- 题目:从键盘输入十个无符号的十进制数(小于256),将其转换为二进制数并存放在NUM字节型变量中,找出其中的最大数,并在屏幕上显示出来。
- 题目:从键盘输入N个十进制数,求它们的和(累加和要求不大于65 535),并将累加结果在屏幕上显示出来。要求给出必要的提示信息(用宏调用完成);累加功能由子程序调用实现;二进制数形式的累加和转换为十进制数并显示由子程序调用实现。
- 题目:从键盘输入一个带符号字节数据(如-56)存入内存(10进制在内存中显示),并将结果在显示器上以十进制形式显示输出。
- 编写子程序段实现:字节变量S1中存放着一个字符串,以0DH为结束标志,试求串长度并送字变量CD中
- 编写宏汇编程序段实现把S1区中所有字符逆向传送到O1区
- 期中复习
题目:从键盘输入两个1位十进制数,计算这两个数之和,并将结果在屏幕上显示出来。
分析:DOS功能调用介绍
1.从键盘输入一个1位十进制的方法。
通过功能号为1的DOS功能调用实现从键盘输入一个字符,格式如下:
MOV AH, 01H
INT 21H
SUB AL, 30H
2.提示信息字符串的显示。
通过功能号为9的DOS功能调用实现字符串显示,注意字符串的最后一个字符必需为‘$’。指令格式如下:
MOV DX, OFFSET INFOR1
MOV AH,09H
INT 21H
3.非压缩型BCD码加法调整指令的使用。
设从键盘输入的数值已存放在寄存器AL,BL中,可用下列程序完成数据相加和调整操作:
XOR AH, AH
ADD AL, BL
AAA
4.计算结果的显示。
执行完AAA指令后,只需分别将AH(十位上的数值)和AL(个位上的数值)加上30H,并依次调用功能号为2的DOS功能调用来显示字符,具体指令格式如下:
PUSH AX
MOV DL, AH
ADD DL, 30H
MOV AH, 02H
INT 21H
POP AX
MOV DL, AL
ADD DL, 30H
MOV AH, 02H
INT 21H
全部代码:
DATA SEGMENT
INFOR1 DB "Please Input the First Data(<10): $"
INFOR2 DB 0AH, 0DH, "Please Input the Second Data(<10): $"
INFOR3 DB 0AH, 0DH, "The Result is: $"
;0AH和0DH分别为换行和非显示字符回车的ASCII码值,
;美圆符号“”为字符串结束标志,不能省略。
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:MOV AX, DATA
MOV DS, AX
;功能调用,显示提示信息INFOR1并从键盘接收一个字符的指令序列
MOV DX, OFFSET INFOR1
MOV AH,09H
INT 21H
;功能调用,现从键盘输入一个字符
MOV AH, 01H
INT 21H
;从键盘输入的是十六进制的ascii码,减30H变成十六进制数
SUB AL, 30H
;第一个数原本存在AL中,为了复用输入字符的代码(使用AL接受),将AL中的数据转移到BL中,给AL腾出空间
MOV BL, AL
;功能调用,显示提示信息INFOR2并从键盘接收一个字符的指令序列
MOV DX, OFFSET INFOR2
MOV AH,09H
INT 21H
;功能调用,从键盘输入一个字符
MOV AH, 01H
INT 21H
;从键盘输入的是十六进制的ascii码,减30H变成十六进制数
SUB AL, 30H
;求和并调整指令序列
XOR AH, AH;清空AH,为了AL和BL的加法做准备
ADD AL, BL;AL加BL
AAA;进位?
PUSH AX;保护AX
;功能调用,显示提示信息INFOR3的指令序列
MOV DX, OFFSET INFOR3
MOV AH,09H
INT 21H
POP AX;弹回AX
PUSH AX;保护AX
;显示十位及个位上数字的指令序列
;因为要显示的字符的ascii码保存在DL,所以先把数据移动到DL,这里是移动高位到DL
MOV DL, AH
ADD DL, 30H
;功能调用,单字符输出一个字符,要显示的字符的ascii码保存在DL
MOV AH, 02H
INT 21H
;弹回AX,恢复AX的值
;这里程序分别取AH和AL的值,看上去好像不用维护AX,看上去好像AX的值始终不变。实际上,在DOS功能调用的时候,程序的返回值会保存在AX中,会改变AX的值。因此还是有必要维护AX的。
POP AX
;因为要显示的字符的ascii码保存在DL,所以先把数据移动到DL,这里是移动低位到DL
MOV DL, AL
ADD DL, 30H
;功能调用,单字符输出一个字符,要显示的字符的ascii码保存在DL
MOV AH, 02H
INT 21H
;功能调用,结束当前程序,返回DOS
MOV AH, 4CH
INT 21H
CODE ENDS
END START
(图1 注释掉POP AX之后,8086执行功能调用的第一步)
(图2 注释掉POP AX之后,8086执行功能调用的第二步。可以在寄存器面板里看到AL的值被悄悄修改了)
题目:从键盘输入0~9中任意一个自然数,将其立方值在屏幕上显示出来(使用查表法实现)。
代码实现:
DATA SEGMENT
INPUT DB 'PLEASE INPUT X(0-9):$'
OUTPUT DB 0DH,0AH,'THE CUBE OF X IS:$'
TAB DW 0,1,8,27,64,125,216,343,512,729
;2^8=256,但是TAB表中最大值是729,超过了8位数据的表示范围,所以必须要将数据扩展到16位
DATA ENDS
CODE SEGMENT ‘CODE’
ASSUME CS:CODE,DS:DATA
START: MOV AX,DATA
MOV DS,AX
MOV DX,OFFSET INPUT
MOV AH,9
INT 21H
MOV AH,1
INT 21H
;把高位清零,原来输入的是ASCII码保存在AL中,所以相当于减30,但是比减法更快
AND AL,0FH
;一个字占两个地址,地址加上他本身相当于地址乘2,同时AL最大才9,不会溢出
ADD AL,AL
MOV BL,AL
;清零BH,为了在TAB表中找到第BL+1个数做准备
MOV BH,0
;AX用为指针,要用到BX作为基址,找到了TAB表的第BX+1个元素,寄存器间接寻址
;程序的目标是输入一个0到9的数,查表获得它的立方值,那么你可能认为输入的数X就对应TAB中第X+1个元素,所以你可能会认为AL不需要自乘2。实际上,TAB表是以DW型存储,那么表中第X+1个元素的地址应该是2*(X+1)和2*(X+1)+1
MOV AX,TAB[BX]
PUSH AX
LEA DX,OUTPUT
MOV AH,9
INT 21H
POP AX
;AX除以BL
MOV BL,100D
DIV BL
;调用DOS前,使用堆栈维护AX
;除数为一个字节,被除数为AX,除法结束后,AL是商,AH是余数
;输出百位数字
PUSH AX
MOV DL,AL
ADD DL,30H
MOV AH,02H
INT 21H
;将余数摆到AL,清零AH,使得AX的值等于上一次除法的余数
POP AX
MOV AL,AH
AND AH,0
MOV BL,10D
DIV BL
;调用DOS前,使用堆栈维护AX
;除数为一个字节,被除数为AX,除法结束后,AL是商,AH是余数
;输出十位数字
PUSH AX
MOV DL,AL
ADD DL,30H
MOV AH,02H
INT 21H
;输出个位数字
POP AX
MOV DL,AH
ADD DL,30H
MOV AH,02H
INT 21H
;程序结束
MOV AH,4CH
INT 21H
CODE ENDS
END START
题目:求两个有符号字节型内存数X和Y的绝对值之和,保存在Z中
代码:
DATA SEGMENT
X DB -99
Y DB 65
Z DB 0
DATA ENDS
CODE SEGMENT
ASSUME CD:CODE, DS:DATA
START:
;------------------取代码段首地址-------------------
MOV AX, DATA
MOV DS, AX
;-----------------------结束--------------------------
;-------------------求绝对值再相加-------------------
;先清零
MOV AX, 0
MOV BX, 0
;因为加数都是字节,所以都放寄存器的低位
MOV AL, X
MOV BL, Y
;有符号数在计算机中以补码表示,需要按位求反之后+1
CMP AL,0
;有符号数之间判断大小,如果AL<=0,说明需要求相反数,则跳转到MARK1
JLE MARK1
;不需要求相反数则直接跳转到ENDIF1结束本回合判断
JMP ENDIF1
MARK1:
;返回自己的相反数
NEG AL
ENDIF1:
;同理
CMP BL,0
JLE MARK2
JMP ENDIF2
MARK2:
NEG BL
ENDIF2:
;将得到的绝对值相加(未考虑溢出
ADD AL,BL
;将结果保存在Z中(题目要求
MOV Z,AL
CODE ENDS
END START
题目:输入一个数N(0-65535),计算Σ(i从1到N)i(0-65535)并输出
分析:
该程序的作用是输入一个数N(0-65535),计算Σ(i从1到N)i(0-65535)并输出
不需要输入的数大于627才出现问题,根据等差数列求和公式,输入的数大于361时就会溢出
解决方法是在“AX中存放的二进制数转换为十进制数并显示的指令序列”这一步中将AX扩展为双字
代码:
DATA SEGMENT
INF1 DB "Please input a number(0-65535):$"
;缓冲区第一字节存放接受字符串的最大字符个数,缓冲区第二字节存放实际输入的字符(不包括回车符),第三字节开始存放接收的字符串
;在这里,开了最多7个字节的位置,第一字节占一个,第二字节占一个,后面还剩5个字节的位置,第二字节和后面5个字节的位置合起来就是六个变量所以是6 DUP(?)
IBUF DB 7, 0, 6 DUP('?')
OBUF DB 6 DUP('?')
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS: DATA
START:
;取代码段
MOV AX, DATA
MOV DS, AX
;接收从键盘输入的十进制数,并将其转换为二进制数(存AX寄存器)的指令序列
;输出字符串INF1
MOV DX, OFFSET INF1
MOV AH, 09H
INT 21H
;字符串输入,寄存器DS存放接收缓冲区段地址,DX存放接收缓冲区偏移地址
MOV DX, OFFSET IBUF
MOV AH, 0AH
INT 21H
;将实际接受的字符数移到CL中,用于计数
MOV CL, IBUF+1
;将CX的高位CH清零,方便计数
MOV CH, 0
;将接收到的第一个字符的偏移地址移到SI中
MOV SI, OFFSET IBUF+2
;清零AX
MOV AX, 0
AGAIN:
;乘数为10
MOV DX, 10
;字乘时,DX,AX<-(AX)*(src),乘积的高16位存在DX中,低16位存在AX中,此处src为DX
MUL DX
;用word ptr指明了指令访问的内存单元是一个字单元
;用byte ptr指明了指令访问的内存单元是一个字节单元
;IBUF中第SI个元素原来是一个ASCII码,只保留一个16位数,相当于减30
AND BYTE PTR [SI], 0FH
;将IBUF中第SI个元素加到AX中
ADD AL, [SI]
;上一步中如果CF=1,说明AL最高位出现了进位,所以下一步需要对AH执行可能的进位操作
;ADC dest,src中,如果src=0,那么就相当于对dest执行可能的进位
ADC AH, 0
;SI自加1,即接下来要对IBUF数组中的下一个数进行操作
INC SI
;如果(CX)=0,那么结束循环,否则跳转到标号处
;简单计算一下,假设进入循环之前(CX)=3,第一次进入循环,CX没变,AX等于输入的第一个十进制数,一轮结束,CX自减1
;(CX)=2,AX等于输入的两个数组成的十位十进制数,一轮结束,CX自减1
;(CX)=1,AX等于输入的三个数组成的百位十进制数,一轮结束,CX自减1,此时(CX)=0,循环结束,往下执行
;因此(CX)=N时,AX内保存N位十进制数,可以满足程序目标
LOOP AGAIN
;若(AX)=N,计算Σ(i从1到N)i
;将AX里的数移到CX中,结合后面的LOOP指令可知这个移动是想要使用原先AX里面的数作为计数
MOV CX, AX
;AX清零
MOV AX, 0
;BX赋初值1
MOV BX, 1
LOOP2:
;计算累加和
ADD AX, BX
;BX作为累加元素,自加1
INC BX
;如果(CX)=0,那么结束循环,否则跳转到标号处
;简单计算一下,假设进入循环之前(CX)=3,第一次进入循环,CX没变,(AX)=1,一轮结束,CX自减1
;(CX)=2,(AX)=1+2,一轮结束,CX自减1
;(CX)=1,(AX)=1+2+3,一轮结束,CX自减1,此时(CX)=0,循环结束,往下执行
;因此(CX)=N时,(AX)=Σ(i从1到N)i,可以满足程序目标
LOOP LOOP2
;将AX中存放的二进制数转换为十进制数并显示的指令序列
;DS已存代码段的段地址
;在OBUF的末尾放入$符号作为结束符号
MOV BX, OFFSET OBUF+5
MOV BYTE PTR [BX], '$'
;CX放入10作为除数
MOV CX, 10
LOOP1:
;字除时,被除数是(DX,AX),所以清零DX
MOV DX, 0
;(DX,AX)/(CX),商存在AX中,余数是个位数,存在DX中
DIV CX
;余数加30变成ASCII码形式
ADD DL, 30H
;BX自减1,从OBUF的末尾往前走一位
DEC BX
;将ASCII码形式的DL从后往前放入OBUF
MOV [BX], DL
;AX与自己进行或运算,AX不变,但是可以影响符号位SF,ZF,PF,且CF,OF清零
;这里的目的是影响ZF,如果(AX)=0,那么ZF=1,JNZ不执行,否则ZF=0,JNZ进行,跳转到标号处,即判断AX是否为0
OR AX, AX
JNZ LOOP1
;BX中本来存着OBUF的偏移地址,现在由于要使用字符串输出的DOS功能调用,转移给DX
;DS已经存好了段地址
MOV DX, BX
;回车换行
PUSH DX
MOV AH,02H
MOV DL,0DH
INT 21H
MOV AH,02H
MOV DL,0AH
INT 21H
POP DX
;字符串输出
MOV AH, 09H
INT 21H
;程序结束
MOV AH, 4CH
INT 21H
CODE ENDS
END START
普通的冒泡法
DATA_SEG SEGMENT
ARRAY DB 38, 25, 63, 17, 22
;这里不可以写成 N DB 5,因为后面用到的N是一个立即数,这样写的话N就变成了一个地址
;$代表它自己所在位置的地址,ARRAY代表数组的首地址,则$-ARRAY代表数组元素个数
N=$-ARRAY
DATA_SEG ENDS
CODE_SEG SEGMENT
ASSUME CD:CODE_SEG, DS:DATA_SEG
START:
;取代码段
MOV AX, DATA_SEG
MOV DS, AX
;CL获得N-1,N为数据的总数
MOV CL, N
DEC CX
OUTER:
;进栈,保存第一个计数器,相当于for(i=N-1;i--;i>=0)中的i被保存
PUSH CX
;进栈之后的CX作为第二个计数器,相当于for(j=N-1;j--;j>=0)中的j
;BX作为ARRAY的序号,从0开始
MOV BX,0
INNER:
;ARRAY[BX]与ARRAY[BX+1]比较
MOV AL,ARRAY[BX]
CMP AL,ARRAY[BX+1]
;ARRAY[BX]<=ARRAY[BX+1]时,无需交换,跳转到NEXT
JNG NEXT
;ARRAY[BX]>ARRAY[BX+1]时,需交换
XCHG AL, ARRAY[BX+1]
MOV ARRAY[BX],AL
NEXT:
;BX自加1,以便进行下一组ARRAY[BX]与ARRAY[BX+1]比较
INC BX
;相当于for(j=N-1;j--;j>=0)中的j--,只是这里自减的是CX,如果CX=0则结束循环,否则转移到INNER标号处
LOOP INNER
;程序运行到这里,说明内层的第二个计数器CX=0,说明已经进行了一轮内层的比较
;出栈,返回外层的第一个计数器
POP CX
;相当于for(i=N-1;i--;i>=0)中的i--,只是这里自减的是CX,如果CX=0则结束循环,否则转移到OUTER标号处
LOOP OUTER
;程序运行到这里,说明外层的第一个计数器CX=0,说明外层比较已结束
EXIT:
MOV AH,4CH
INT 21H
CODE_SEG ENDS
END
从键盘输入一个无符号十进制数(小于65535),将其转换为二进制数,统计该二进制数中包含的1的个数,并将统计结果在屏幕上显示出来
DATA SEGMENT
INF1 DB "Please input a number(0-65535):$"
;缓冲区第一字节存放接受字符串的最大字符个数,缓冲区第二字节存放实际输入的字符(不包括回车符),第三字节开始存放接收的字符串
;在这里,开了最多7个字节的位置,第一字节占一个,第二字节占一个,后面还剩5个字节的位置,第二字节和后面5个字节的位置合起来就是六个变量所以是6 DUP(?)
IBUF DB 7, 0, 6 DUP('?')
OBUF DB 6 DUP('?')
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS: DATA
START:
;取代码段
MOV AX, DATA
MOV DS, AX
;接收从键盘输入的十进制数,并将其转换为二进制数(存AX寄存器)的指令序列
;输出字符串INF1
MOV DX, OFFSET INF1
MOV AH, 09H
INT 21H
;字符串输入,寄存器DS存放接收缓冲区段地址,DX存放接收缓冲区偏移地址
MOV DX, OFFSET IBUF
MOV AH, 0AH
INT 21H
;将实际接受的字符数移到CL中,用于计数
MOV CL, IBUF+1
;将CX的高位CH清零,方便计数
MOV CH, 0
;将接收到的第一个字符的偏移地址移到SI中
MOV SI, OFFSET IBUF+2
;清零AX
MOV AX, 0
AGAIN:
;乘数为10
MOV DX, 10
;字乘时,DX,AX<-(AX)*(src),乘积的高16位存在DX中,低16位存在AX中,此处src为DX
MUL DX
;用word ptr指明了指令访问的内存单元是一个字单元
;用byte ptr指明了指令访问的内存单元是一个字节单元
;IBUF中第SI个元素原来是一个ASCII码,只保留一个16位数,相当于减30
AND BYTE PTR [SI], 0FH
;将IBUF中第SI个元素加到AX中
ADD AL, [SI]
;上一步中如果CF=1,说明AL最高位出现了进位,所以下一步需要对AH执行可能的进位操作
;ADC dest,src中,如果src=0,那么就相当于对dest执行可能的进位
ADC AH, 0
;SI自加1,即接下来要对IBUF数组中的下一个数进行操作
INC SI
;如果(CX)=0,那么结束循环,否则跳转到标号处
;简单计算一下,假设进入循环之前(CX)=3,第一次进入循环,CX没变,AX等于输入的第一个十进制数,一轮结束,CX自减1
;(CX)=2,AX等于输入的两个数组成的十位十进制数,一轮结束,CX自减1
;(CX)=1,AX等于输入的三个数组成的百位十进制数,一轮结束,CX自减1,此时(CX)=0,循环结束,往下执行
;因此(CX)=N时,AX内保存N位十进制数,可以满足程序目标
LOOP AGAIN
;--------------下面开始统计二进制AX中1的个数-----------------
;把BX和CX清零
MOV BX, 0
MOV CX, 0
MARK2:
;为了每一次右移CL位之后得到正确的数字,要保护AX
PUSH AX
;AX逻辑右移CL位
SHR AX,CL
;取移位后的AX的最低一位上的数字
AND AX,0000000000000001B
;CX计数器自加一
INC CX
;AX的最低一位上的数字与1比较,相等则给BX计数器加1,不相等则接着判断CX计数器
CMP AX, 1
;恢复AX,为了下一次的比较做准备
POP AX
JNZ MARK
INC BX
;如果CX计数器的数字等于16,说明已经左移比较了15次,程序达成目标
MARK:
CMP CX,16
JNZ MARK2
;回车换行
PUSH BX
MOV AH,02H
MOV DL,0DH
INT 21H
MOV AH,02H
MOV DL,0AH
INT 21H
POP BX
MOV AX, BX
MOV BX, 10
DIV BL
PUSH AX
MOV DL,AL
ADD DL,30H
MOV AH,02H
INT 21H
POP AX
PUSH AX
MOV DL,AH
ADD DL,30H
MOV AH,02H
INT 21H
POP AX
;程序结束
MOV AH, 4CH
INT 21H
CODE ENDS
END START
调试代码的时候,明明代码相同,输入相同,但是单步运行和不中断运行的结果就是不一样
我还以为是编译器的问题,没想到学霸提点我是,单步运行在输入时中断之后,不承认之前的输入,是我傻了ᕕ( ゚∀。)ᕗ
一开始不懂的时候还上贴吧去问了……
测试程序:
DATA SEGMENT
INF1 DB "Please input a number(0-65535):$"
;缓冲区第一字节存放接受字符串的最大字符个数,缓冲区第二字节存放实际输入的字符(不包括回车符),第三字节开始存放接收的字符串
;在这里,开了最多7个字节的位置,第一字节占一个,第二字节占一个,后面还剩5个字节的位置,第二字节和后面5个字节的位置合起来就是六个变量所以是6 DUP(?)
IBUF DB 7, 0, 6 DUP('?')
OBUF DB 6 DUP('?')
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS: DATA
START:
;取代码段
MOV AX, DATA
MOV DS, AX
;接收从键盘输入的十进制数,并将其转换为二进制数(存AX寄存器)的指令序列
;输出字符串INF1
MOV DX, OFFSET INF1
MOV AH, 09H
INT 21H
;字符串输入,寄存器DS存放接收缓冲区段地址,DX存放接收缓冲区偏移地址
MOV DX, OFFSET IBUF
MOV AH, 0AH
INT 21H
;将实际接受的字符数移到CL中,用于计数
MOV CL, IBUF+1
;将CX的高位CH清零,方便计数
MOV CH, 0
;程序结束
MOV AH, 4CH
INT 21H
CODE ENDS
END START
就是说,我输入数字没按回车的时候停下程序,看一看,瞅一瞅,再继续程序的时候,我要重新输入一遍……
这么简单的事情却是我当时一辈子都想不到的,如果学霸没跟我说的话……
图1 单步运行中断输入之后我没有重新输入就直接按回车了
图2 不中断运行,正常
图3 单步运行中断输入之后我重新输入
题目:输入数字之后冒泡排序
遇到的问题1:
感觉程序也没有错误,但是就是输出有问题,所以我先输入了一组不需要冒泡排序的数组想要输出,结果也发现了输出错误,因此判断是输出函数的问题
单步调试之后发现,输出函数居然会改变我要输出的BUF数组内数据的值
观察正在运行的代码,发现是输出函数使用的OUTPUT数据区和我的BUF数据区重叠了,我对OUTPUT赋值时便会修改BUF
回看我在数据区对数组的定义,我当时完全没有想到这个错误
解决方法:
缓冲区定义为
INPUT DB 4 0, 3 DUP ('?')
OUTPUT DB 4 0, 3 DUP ('?')
就好了
原因:
请看实例:
buf db 100
db ?
db 100 dup (0)
解析:在内存中申请一个缓冲区为100个字节,首地址给BUF。
缓冲区的第一个字节内放的是100,表示申请的存放数据的缓冲区的字节数为100个
第二个字节“?”表示的是实际存放的字节个数(就是说,你放入2个字节的数据,“?”变成2,放10个字节的数据,变成10)
输入的数据从第三个字节开始存放,第100个字节存放回车符(0DH),0DH作为输入数据的结束。
DUP(0)表示的是存放数据的100个字节初始值全为0,即为:100 0 0 0 0······0 ODH。
也就是说,这种三字节结构的缓冲区格式是对于所有缓冲区数组都生效的
我的书本上只有在DOS功能调用的0AH字符串输入功能才讲到了这个缓冲区格式,我就以为只是这个DOS功能要求缓冲区数组这样的格式,不用于字符串输入的数组可以不用这个格式
实际上不是我想的这样的
遇到的问题2:
输出的数据还是被修改了,下图为运行结果
初步调试,输入的数据保存下来是没有问题的,下图的单步运行到输入结束
调试到冒泡法结束,排序并不正确,于是开始重看冒泡法
最后发现原来是我在冒泡法判断大小的时候使用了JNG而不是使用JNA
正确的应该是使用JNA
最终的正确代码:
DATA SEGMENT
INFOR1 DB 0AH,0DH,"How many nubmers do you want to enter(<256):$"
INFOR2 DB 0AH,0DH,"Enter a number(<256):$"
N DB 0
INPUT DB 4 0, 3 DUP ('?')
OUTPUT DB 4 0, 3 DUP ('?')
BUF DB 200 DUP ('?')
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
;------------------取代码段首地址-------------------
MOV AX, DATA
MOV DS, AX
;-----------------------结束--------------------------
;---------获得处理数据的个数N存在CL中-------------
;输出字符串INFOR1
MOV DX, OFFSET INFOR1
MOV AH, 09H
INT 21H
;调用函数,要获得一个不大于255的三位十进制整数存在AX里,传入NUM1的串首的偏移地址
CALL GETNUMBER
;调用一次函数之后,AX中是得到的“处理数据的个数N”,所以AL存到N中
MOV N,AL
;-------------------结束-------------------------------
;---------获得若干个数据存在BUF中-------------------
;BX用来计算放入数据的序号,先清零
MOV BX,0
;CX作为LOOP的计数器,获得N的值
MOV CX,0
MOV CL,N
AGAIN2:
;输出字符串INFOR2
MOV DX, OFFSET INFOR2
MOV AH, 09H
INT 21H
;调用函数,要获得一个不大于255的三位十进制整数存在AX里,传入NUM1的串首的偏移地址
CALL GETNUMBER
;将这一次输入的数存入BUF数组的第BX个元素
MOV BUF[BX],AL;
;BX作为BUF元素的序号,自加1
INC BX;
;如果(CX)=0,那么结束循环,否则跳转到标号处
;简单计算一下,假设进入循环之前(CX)=3,第一次进入循环,CX没变,BUF存入一个数,一轮结束,CX自减1
;(CX)=2,BUF存入一个数,一轮结束,CX自减1
;(CX)=1,BUF存入一个数,一轮结束,CX自减1,此时(CX)=0,循环结束,往下执行
;因此(CX)=N时,BUF存入N个数,可以满足程序目标
LOOP AGAIN2
;-------------------结束-------------------------------
;---------------冒泡法开始,CX作为计数器,BUF为待排序数组---------------
;CL获得N-1,N为数据的总数
MOV CL, N
DEC CX
OUTER:
;进栈,保存第一个计数器,相当于for(i=N-1;i--;i>=0)中的i被保存
PUSH CX
;进栈之后的CX作为第二个计数器,相当于for(j=N-1;j--;j>=0)中的j
;BX作为BUF的序号,从0开始
MOV BX,0
INNER:
;BUF[BX]与BUF[BX+1]比较
MOV AL, BUF[BX]
CMP AL, BUF[BX+1]
;BUF[BX]<=BUF[BX+1]时,无需交换,跳转到NEXT(无符号数,用JNA不用JNG!)
JNA NEXT
;BUF[BX]>BUF[BX+1]时,需交换
XCHG AL, BUF[BX+1]
MOV BUF[BX],AL
NEXT:
;BX自加1,以便进行下一组BUF[BX]与BUF[BX+1]比较
INC BX
;相当于for(j=N-1;j--;j>=0)中的j--,只是这里自减的是CX,如果CX=0则结束循环,否则转移到INNER标号处
LOOP INNER
;程序运行到这里,说明内层的第二个计数器CX=0,说明已经进行了一轮内层的比较
;出栈,返回外层的第一个计数器
POP CX
;相当于for(i=N-1;i--;i>=0)中的i--,只是这里自减的是CX,如果CX=0则结束循环,否则转移到OUTER标号处
LOOP OUTER
;程序运行到这里,说明外层的第一个计数器CX=0,说明外层比较已结束
;---------------------------冒泡法结束-----------------------------------
;---------------------下面开始输出数据串--------------------------------
;BX赋初值0作为序号
MOV BX,0
AGAIN3:
;因为BUF每一个数最大不超过255,所以只使用AL
MOV AL, BUF[BX]
;高位清零
MOV AH,0
;输出AX的十进制格式
CALL SHOWNUMBER
;BX作为序号自加1
INC BX
;如果BL的数目等于N,说明已经输出了从BUF[0]到BUF[N-1],输出目标达成
CMP BL,N
JNZ AGAIN3
MOV AH,4CH
INT 21H
;-----------------------------------------
;子程序名:GETNUMBER
;功能:得到一个3位十进制数字
;入口参数:内存区数据区字符串的串首的偏移地址INPUT
;出口参数:AX
;-----------------------------------------
GETNUMBER PROC
;保护现场
PUSH BX
PUSH CX
PUSH DX
PUSHF
;------------------字符串输入到INPUT-----------------
;寄存器DS存放接收缓冲区段地址,DX存放接收缓冲区偏移地址
MOV DX, OFFSET INPUT
MOV AH, 0AH
INT 21H
;-------------------------结束--------------------------
;-------------------将INPUT中的数转化成二进制数存在AX中------------------
;INPUT+1为输入的个数
;CL不能随便定义,一定要用INPUT+1.
;比如如果你认为输入的是三位十进制数,就想让(CL)=3
;但是当你输入一位十进制数x的时候若(CL)=3,程序结束时AX存的不是x而是x*100!
MOV CL, INPUT+1
;将CX的高位CH清零,方便计数
MOV CH, 0
;将接收到的第一个字符的偏移地址移到SI中
MOV SI, OFFSET INPUT+2
;清零AX
MOV AX, 0
AGAIN1:
;乘数为10
MOV DX, 10
;字乘时,DX,AX<-(AX)*(src),乘积的高16位存在DX中,低16位存在AX中,此处src为DX
MUL DX
;用word ptr指明了指令访问的内存单元是一个字单元
;用byte ptr指明了指令访问的内存单元是一个字节单元
;NUM中第SI个元素原来是一个ASCII码,只保留一个16位数,相当于减30
AND BYTE PTR [SI], 0FH
;将INPUT中第SI个元素加到AL中
;AL不超过256所以可以不用看AH
ADD AL, [SI]
;SI自加1,即接下来要对INPUT数组中的下一个数进行操作
INC SI
;如果(CX)=0,那么结束循环,否则跳转到标号处
;简单计算一下,假设进入循环之前(CX)=3,第一次进入循环,CX没变,AX等于输入的第一个十进制数,一轮结束,CX自减1
;(CX)=2,AX等于输入的两个数组成的十位十进制数,一轮结束,CX自减1
;(CX)=1,AX等于输入的三个数组成的百位十进制数,一轮结束,CX自减1,此时(CX)=0,循环结束,往下执行
;因此(CX)=N时,AX内保存N位十进制数,可以满足程序目标
LOOP AGAIN1
;----------------------------------结束----------------------------------------
;恢复现场
POPF
POP DX
POP CX
POP BX
;回到主程序
RET
GETNUMBER ENDP
;-----------------------------------------
;子程序名:SHOWNUMBER
;功能:输出一个3位十进制数字
;入口参数:AX,内存区数据区字符串的串首的偏移地址OUTPUT
;出口参数:无
;-----------------------------------------
SHOWNUMBER PROC
;保护现场
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSHF
;------------将AX中存放的二进制数转换为十进制数存在OUTPUT中---------------
;在OUTPUT的末尾放入$符号作为结束符号
MOV BX,OFFSET OUTPUT+3
MOV BYTE PTR [BX], '$'
;CX放入10作为除数
MOV CX, 10
LOOP1:
;字除时,被除数是(DX,AX),所以清零DX
MOV DX, 0
;(DX,AX)/(CX),商存在AX中,余数是个位数,存在DX中
DIV CX
;余数加30变成ASCII码形式
ADD DL, 30H
;BX自减1,从OBUF的末尾往前走一位
DEC BX
;将ASCII码形式的DL从后往前放入OUTPUT
MOV [BX], DL
;AX与自己进行或运算,AX不变,但是可以影响符号位SF,ZF,PF,且CF,OF清零
;这里的目的是影响ZF,如果(AX)=0,那么ZF=1,JNZ不执行,否则ZF=0,JNZ进行,跳转到标号处,即判断AX是否为0
OR AX, AX
JNZ LOOP1
;-------------------------------结束---------------------------------------------
;回车换行
PUSH DX
MOV AH,02H
MOV DL,0DH
INT 21H
MOV AH,02H
MOV DL,0AH
INT 21H
POP DX
;--------------输出OUTPUT字符串---------------
MOV DX,OFFSET OUTPUT
MOV AH, 09H
INT 21H
;----------------------结束----------------------
;恢复现场
POPF
POP DX
POP CX
POP BX
POP AX
;回到主程序
RET
SHOWNUMBER ENDP
CODE ENDS
END START
题目:从键盘输入十个无符号的十进制数(小于256),将其转换为二进制数并存放在NUM字节型变量中,找出其中的最大数,并在屏幕上显示出来。
要求:
①在屏幕上显示字符串提示信息的功能由宏指令DSTRING实现;
②将键盘输入的十进制数转换成二进制数由子程序DTOB实现;
③在N个无符号字节型二进制数中找出最大数的功能由子程序FMAX实现;
④将一个无符号字节型二进制数转换为十进制数并在屏幕上显示的功能由子程序BTOAD实现
我的实现:
COUNT EQU 10
DATA SEGMENT
NUM DW 10 DUP('?')
INPUT DB 7, 0, 6 DUP('?')
OUTPUT DB 6 DUP(0)
INFOR1 DB "Please Input the 10 Numbers:", 0AH, 0DH, '$'
INFOR2 DB "The max found in the 10 numbers is $"
INFOR3 DB 0AH, 0DH, '$'
DATA ENDS
STACK SEGMENT stack
DATA2 DW 40 DUP(?)
TOP='$'-DATA2
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK
START:
;--------------------------取代码段首地址--------------------------------
MOV AX, DATA
MOV DS, AX
MOV AX, STACK
MOV SS, AX
MOV AX, TOP
MOV SP, AX
;------------------------------取地址结束---------------------------------
;------------------定义显示字符串信息的宏指令DSTRING------------------
DSTRING MACRO STRING
PUSH DX
PUSH AX
MOV DX, OFFSET STRING
MOV AH, 09H
INT 21H
POP AX
POP DX
ENDM
;------------------------------宏定义结束-----------------------------------
;------------------------输入数组-------------------------
;输出提示信息INFOR1
DSTRING INFOR1
MOV BX, OFFSET NUM
;CX作为接下来的LOOP循环的计数器
MOV CX, COUNT
AGAIN3:
;从屏幕输入一个不大于65536的数字,保存在AX中
CALL GETNUMBER
;输出提示信息INFOR3换行
DSTRING INFOR3
;将输入得到的AX保存到NUM数组的某一元素中
MOV [BX], AX
;BX自加2,下一次AX将保存在NUM数组的下一个元素中
ADD BX,2
;CX计数器的初值是COUNT,即循环进行COUNT次之后结束,NUM数组中存了COUNT个数
LOOP AGAIN3
;--------------------------结束--------------------------
;-----------------------找最大数-------------------------
;利用堆栈传入NUM偏移地址和COUNT
MOV AX, OFFSET NUM
PUSH AX
MOV CX, COUNT
PUSH CX
;取NUM的最大数保存在AX中
CALL FINDMAX
;--------------------------结束---------------------------
;------------------------输出结果-------------------------
;输出提示信息INFOR3换行
DSTRING INFOR2
;将存在AX中的十六进制数显示成十进制数
CALL SHOWNUMBER
;---------------------------结束--------------------------
;程序结束
MOV AH, 4CH
INT 21H
;-----------------------------------------
;子程序名:GETNUMBER
;功能:得到一个不大于2^16=65536的十进制数字
;入口参数:内存区数据区字符串的串首的偏移地址INPUT
;出口参数:AX
;--------------