| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 12020 人关注过本帖, 3 人收藏
标题:[原创]初学汇编,写的几个小程序(超详细注释)(11.21更新)
取消只看楼主 加入收藏
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏(3)
 问题点数:0 回复次数:10 
[原创]初学汇编,写的几个小程序(超详细注释)(11.21更新)

*/ --------------------------------------------------------------------------------------
*/ 出自: 编程中国 http://www.bc-cn.net
*/ 作者: 永夜的极光
*/ 时间: 2007-11-14 编程论坛首发
*/ 声明: 尊重作者劳动,转载请保留本段文字
*/ --------------------------------------------------------------------------------------


上星期下定决心开始学习8086汇编,断断续续学了一个星期,终于编出来几个小程序,发出来给大家看看

各位高手中手低手,见笑了.最好能给我提提一些改进的建议,程序是在写的太冗长了

目前写一个这种小程序,估计要4,5个小时......

下面是目录

1楼 从键盘输入年份,判断是否为闰年
2楼 接受输入一个字符串,找出重复长度最大的一段
3楼 计算两个长整数相加
4楼 输入升序数据,并使用二分法搜索
5楼 用快速排序法对一维数组进行排序
9楼 利用自定义的库函数,输出任意不大于FFFFFFFF的10进制数
11楼 利用宏汇编和条件汇编,实现子程序的寄存器入栈和出栈

程序代码:

;从键盘输入年份,判断是否为闰年
;作者:永夜的极光
;时间:2007-11-11

;程序运行结果
;Please Input Year: 1996
;This is a leap year!
;Do you want to try again?(Y/N) y
;Please Input Year: 1900
;This is not a leap year!
;Do you want to try again?(Y/N) y
;Please Input Year: 2000
;This is a leap year!
;Do you want to try again?(Y/N) y
;Please Input Year: 2007
;This is not a leap year!
;Do you want to try again?(Y/N) n
;Press any key to exit.


assume cs:code,ds:data
data segment
;初始化各种提示语句,前面的0dh,0ah表示回车
inf db 0dh,0ah,'Please Input Year: $'
inf_ok db 0dh,0ah,'This is a leap year! $'
inf_no db 0dh,0ah,'This is not a leap year! $'
inf_err db 0dh,0ah,'Input Error! $'
inf_ag db 0dh,0ah,'Do you want to try again?(Y/N) $'
inf_end db 0dh,0ah,'Press any key to exit. $'
;将输入的年份字符串转为数字,存在此处
year dw 0
;缓冲区,用于接受输入,第一个字节为缓冲区长度,第二个字节为实际输入长度,后面是缓冲区
buf db 8
db ?
db 8 dup (?)
data ends

stack segment
db 80 dup (0)
stack ends

code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,80

;dx保存提示语句的首地址,调用21H中断09功能,显示在屏幕上
lea dx,inf
mov ax,0900H
int 21H

;dx指向缓冲区首字节(表示缓冲区大小的字节),调用21H中断0AH功能,接受输入字符串
lea dx,buf
mov ax,0a00H
int 21H

;cx清零,程序一开始,cx就不是0,这个不知道什么原因
xor cx,cx
mov cl,[buf+1];cl取得实际输入字符数
cmp cl,4 ;如果实际输入字符数>4,则提示输入错误
ja input_error

lea si,[buf+2];si指向实际的缓冲区起始位置
push cx
is_num: ;判断输入的是否都是数字,如果不是,则提示输入错误
cmp byte ptr [si],'0'
jb input_error
cmp byte ptr [si],'9'
ja input_error
inc si ;si后移
loop is_num
pop cx

dec si ;上面循环结束后,si指向输入字符串的最后一个字符的后面,所以减1,使之指向最后一个字符
lea di,year ;di指向保存转换后数字的空间
mov word ptr [di],0 ;该空间清0
call str2int ;调用函数,将输入字符串转换为数字
call isleap ;判断这个数字是否为闰年

jmp short try_again ;询问是否继续输入
input_error:
lea dx,inf_err ;提示输入错误
mov ax,0900H
int 21H

try_again:
lea dx,inf_ag ;询问是否继续
mov ax,0900H
int 21H

mov ax,0100H ;调用中断21H的01功能,接受一个字符输入并回显
int 21H

cmp al,'y' ;判断输入字符是否为y或Y,如果是,则跳回函数开头,重新执行
jz start
cmp al,'Y'
jz start

main_end:
lea dx,inf_end
mov ax,0900H
int 21H

mov ax,0700H
int 21H

mov ax,4c00H ;程序结束
int 21H

;函数功能:将一个数字字符串转换成对应的数字
;输入参数:cx 字符串长度
; si 指向字符串的最后一个字符
; di 指向转换结果保存的位置,大小为word型
str2int proc near

push ax ;寄存器入栈
push bx
push cx
push dx

mov ax,1 ;ax初始为1
l1:
push ax ;每次循环,ax*10,但是后面计算乘法的时候,还需要用到ax,所以先把ax的值入栈
mov bl,[si] ;把si指向的字符存入bl,并减掉字符'0'
sub bl,'0'
mov bh,0 ;bh置0,计算乘法,因为可能要乘1000,只能用16位乘16位的乘法
mul bx
add ds:[di],ax ;乘法结果加到保存计算结果的地方(也就是di指向的地址)
pop ax ;ax出栈,并乘10
mov bx,10
mul bx
dec si ;si递减
loop l1 ;这个循环开头没有写cx,因为cx是作为参数传进来的

pop dx
pop cx
pop bx
pop ax
ret

str2int endp

;函数功能:判断某个数字是否为闰年
;输入参数:di 指向被判断数字的存储地址,word型
;输出参数:无,直接屏幕显示是否闰年
isleap proc near

push ax ;寄存器入栈
push bx
push cx
push dx

mov cx,ds:[di] ;cx保留输入字符串
mov ax,cx
mov dx,0 ;因为除法结果可能超过8位,所以要做32位除16位的除法,所以dx要清零
mov bx,4 ;除4
div bx
cmp dx,0 ;dx是余数,判断是否为0,不是0则肯定不是闰年
jnz not_leap
mov ax,cx ;ax恢复为待计算数字,这次计算的结果只需要8位就够,所以用16位除8位就行
mov bx,100 ;除100
div bl
cmp ah,0 ;ah是余数,如果为0,那还要继续判断,如果不为0,那就肯定是闰年了
jnz is_leap
mov ax,cx ;x恢复为待计算数字,因为要除以400,超过8位,所以用32位除16位除法
mov dx,0
mov bx,400
div bx
cmp dx,0 ;余数如果为0,说明是闰年
jz is_leap
jmp short not_leap ;不是闰年
is_leap:
lea dx,inf_ok ;是闰年的话,提示是闰年
mov ax,0900H
int 21H
jmp short end_sub
not_leap:
lea dx,inf_no ;不是闰年的话,提示不是闰年
mov ax,0900H
int 21H

end_sub:
pop dx
pop cx
pop bx
pop ax
ret

isleap endp
code ends
end start

[此贴子已经被作者于2007-11-21 13:48:58编辑过]

搜索更多相关主题的帖子: 注释 初学 汇编 
2007-11-14 16:15
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 

程序代码:

;接受输入一个字符串,找出重复长度最大的一段
;作者:永夜的极光
;时间:2007-11-13

;程序运行结果
;*********** Welcome ***********
;Input string(length<30): aaaaaaaabbaaaaa
;The max length is 08
;Repeat char is a

;Press any key to exit...
assume cs:code,ds:data
data segment
inf0 db '*********** Welcome ***********',0dh,0ah,'Input string(length<30): $';各提示语句
inf1 db 0dh,0ah,'The max length is ';这一段是提示语句加数据合在一起,到时候调用一次中断就能一次性输出
maxlen dw ?
inf2 db 0dh,0ah,'Repeat char is '
repchar db ?
inf3 db 0dh,0ah,'$'
inf_end db 0dH,0aH,'Press any key to exit...$';按任意键退出
buf db 30;缓冲区(这一位表示缓冲区实际长度)
db ?;实际输入字符数
db 30 dup (?);实际缓冲区位置
data ends

stack segment
db 80 dup (0)
stack ends

code segment
start:
mov ax,data;设置段寄存器
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,80
call main;调用主函数
lea dx,inf_end;输出提示语句(按任意键结束)
mov ax,0900H
int 21H
mov ax,0700H;等待一个按键,不回显
int 21H
mov ax,4c00H;结束
int 21H

;主函数
main proc near

lea dx,inf0 ;提示用户输入字符串
mov ax,0900H
int 21H
lea dx,buf ;接受输入并存入缓冲区
mov ax,0a00H
int 21H

xor cx,cx ;cx清零
mov cl,[buf+1] ;实际输入长度保存在cl
lea si,[buf+2] ;si指向缓冲区实际位置

l1:
call countsame ;调用函数,找到当前si所指字符后面连续的与之相等的字符个数
cmp ch,[maxlen] ;比较目前保存的最大长度和此次调用获得的长度
jna s
mov [maxlen],ch ;如果此次调用函数获得长度比目前保存的maxlen还大,那就把maxlen替换掉,同时也记录重复的字符
mov ax,[si]
mov [repchar],al
s:
mov si,di ;di指向调用函数后,找到的第一个不同的字符,把这个位置赋值给si,再继续循环
sub cl,ch ;ch保存的是本次调用所获得的相同字符的长度,每次调用就从总长度减掉这个值,等减到最后为0,也就是跳出循环的时候
cmp cl,0
jnz l1

mov ax,[maxlen] ;把最大长度放到ax,为的是把数字转变为字符串
mov bl,10 ;除10,ah保存余数,al保存商,因为长度最大才30,所以只要在ah和al都加上30H,就可以变为字符串了.
div bl
add ax,3030H
mov [maxlen],ax ;把转换后的结果放回去

lea dx,inf1 ;把整个结果字符串输出
mov ax,0900H
int 21H


ret

main endp

;函数功能:找到si之后与[si]连续的相等的字符个数
;输入参数:si 指向寻找的字符
;输出参数:di 指向si后面第一个和si不同的字符
; ch 和si相同的字符个数(包括si本身,也就是至少为1)
countsame proc near

push ax
lea di,[si+1] ;di指向si后面一个
mov ch,1 ;ch最小是1
ll1:
mov al,ds:[di];把di指向的字符放到al里面,与si指向的字符比较
cmp al,[si]
jnz l2 ;如果不相等,就可以结束循环了
inc di ;di后移
inc ch ;计数加1
jmp short ll1
l2:
pop ax
ret

countsame endp
code ends
end start

[此贴子已经被作者于2007-11-14 16:23:57编辑过]


从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-14 16:17
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 

程序代码:

;计算两个长整数相加
;作者:永夜的极光
;时间:2007-11-14

;程序运行结果
;Please input num1:12345678901234567890
;Please input num2:987654321987654321
;num1+num2=13333333223222222211
;Press any key to exit...

assume ds:data,cs:code,ss:stack
data segment
inf0 db 0dh,0ah,'Please input num1:$'
num1 db 30,?,30 dup ('0')
inf1 db 0dh,0ah,'Please input num2:$'
num2 db 30,?,30 dup ('0')
inf2 db 0dh,0ah,'num1+num2=$'
sum db 31 dup (0) ;保存和的地方,上面限制两个数不超过29位,和最多30位,最后一位放'$'
inf3 db 0dh,0ah,'Press any key to exit...$'
data ends

stack segment
dw 80 dup (0)
stack ends

code segment
start:
mov ax,data ;设置数据和栈的段寄存器
mov ds,ax
mov ax,stack
mov ss,ax
call main ;调用主函数

lea dx,inf3 ;提示输入任意键退出
mov ax,0900H
int 21H
mov ax,0700H ;接受一个按键,不回显
int 21H
mov ax,4c00H
int 21H

main proc near

lea dx,inf0 ;提示输入两个整数
mov ax,0900H
int 21H
lea dx,num1
mov ax,0a00H
int 21H
lea dx,inf1
mov ax,0900H
int 21H
lea dx,num2
mov ax,0a00H
int 21H

mov cl,[num1+1] ;调用函数,把字符串变为数字,这里没有做错误检查,假设用户输入是正确的数字
mov ch,0 ;cl保存字符串长度,si指向字符串起始位置
lea si,[num1+2]
call numchange
mov cl,[num2+1]
lea si,[num2+2]
call numchange

mov cl,[num1+1] ;把两个数字的位数分别赋给cl和dl,si和di指向这两串数字的最后一个数字
mov dl,[num2+1]
lea si,[num1+1]
add si,cx
lea di,[num2+1]
add di,dx
lea bx,[sum] ;bx指向保存结果的位置

cmp dl,cl ;比较两个数字串的长度,cl存放较短数字串的长度,dl存放较长的
jnb c1 ; si指向较短数字串的末尾,di指向较长的
xchg dl,cl
xchg di,si
c1:
push dx ;保存较长数字串的长度,后面判断和的位数要用
clc ;清除进位标志
add bx,dx ;bx向后移动到和的最后一位(先假设和比最长数字串长度大1)
l1:
mov al,[si] ;放第一个数
adc al,ds:[di];进位加法 加第二个数
aaa ;按非压缩BCD码整理(如果cl中的数字后四位大于9,则cl后四位加6,不向前四位进位,而是向ah进位)
mov [bx],al ;把和存入保存结果的地方
dec bx ;指针前移
dec dl
dec si
dec di
loop l1
mov al,0 ;这三句是处理最后一次加法有进位的情况,比如:500+500
adc al,0
mov [bx],al
cmp dl,0 ;dl如果等于0,说明两个数字串都加完了,如果不等于0,则表示di指向的数字串还没处理完
jz sum_end
mov cl,dl ;还剩下dl个数字没处理,所以要循环dl次,把dl赋值给cl
l2:
mov al,[di] ;要处理的数字
adc al,[bx] ;bx指向的是保存和的地方,如果没有修改过的话,那片空间都是0不直接加0的原因是,考虑上面的循环最后一次加法有进位的情况
aaa ;整理
mov [bx],al ;保存和
dec di ;指针前移
dec bx
loop l2
mov al,0 ;还是处理最后一次加法有进位的情况
adc al,0
mov [bx],al
sum_end:
pop cx ;栈里面保存的是较长数字串的长度
inc cx ;这个长度加1,就是和的长度
lea si,sum ;调用函数,把数字变为字符
call numchange
mov byte ptr [si],'$';在末尾加上'$'

lea dx,inf2 ;输出提示语句
mov ax,0900H
int 21H

lea dx,sum ;如果和的第一位是0,说明没有进位,这个0不显示
cmp [sum],'0'
jnz ok
inc dx
ok:
mov ax,0900H ;输出结果
int 21H
ret

main endp

;函数功能:数字字符串和数字串互相转换
;输入参数:si 指向第一个待处理的数字(或数字字符)
; cx 待处理的长度
;输出参数:无
numchange proc near

numchange_l1:
xor byte ptr [si],30H ;异或30H,数字和数字字符就可以互相转换了
inc si
loop numchange_l1 ;循环次数cx是作为参数传入的
ret

numchange endp

code ends
end start

[此贴子已经被作者于2007-11-14 16:26:11编辑过]


从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-14 16:19
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 

程序代码:

;输入升序数据,并使用二分法搜索
;作者:永夜的极光
;时间:2007-11-15

;程序运行结果:
;Please input ascending data(0<data<=255)
;Divide with any char except 0~9:
;1 2 3 40 50 60 100 150 200 210 220 250 255
;Witch num you want to find:1
;This num is in position 01
;Find another num(Y/N)?y
;Witch num you want to find:50
;This num is in position 05
;Find another num(Y/N)?y
;Witch num you want to find:255
;This num is in position 13
;Find another num(Y/N)?y
;Witch num you want to find:101
;Sorry,cannot find this num!
;Find another num(Y/N)?n
;Press any key to exit...

assume ds:data,cs:code,ss:stack
data segment
inf0 db 0dh,0ah,'Please input ascending data(0<data<=255)',13,10,'Divide with any char except 0~9:',13,10,'$'
buf db 250,?,250 dup ('0') ;保存输入数据字符串的缓冲区
table db 100 dup (0) ;分析输入的字符串,分解为数字,保存在这个表中
inf1 db 0dh,0ah,'Witch num you want to find:$'
num db ? ;保存转换后的待寻找的数据
inf2 db 0dh,0ah,'Sorry,cannot find this num!$'
buf2 db 4,?,0,0,0,0 ;接受输入待寻找数据的缓冲区
inf3 db 0dh,0ah,'This num is in position $'
inf4 db 13,10,'Find another num(Y/N)?$'
inf_end db 13,10,'Press any key to exit...$'
data ends

stack segment
dw 80 dup (0)
stack ends

code segment
start:
mov ax,data
mov ds,ax
mov es,ax
mov ax,stack
mov ss,ax
call main ;调用主函数

lea dx,inf_end ;提示按任意键结束
mov ax,0900H
int 21H
mov ax,0700H
int 21H
mov ax,4c00H
int 21H

main proc near

lea dx,inf0 ;提示输入字符串,并接受输入,存在buf
mov ah,09H
int 21H
lea dx,buf
mov ah,0aH
int 21H

call SaveToArray ;调用函数,处理输入的字符串,整理为数字数组
mov bp,cx ;保存数组的长度

find_value:
call init ;清空buf2中的数据和要搜索的数字,避免第二次搜索时出错
call getfindnum ;获取要搜索的数字,处理后放入num

mov cx,bp ;bp保存的是数组长度
shr cx,1 ;cx除2,取整
lea si,[table] ;si指向第一个数字,di指向最后一个数字,bx指向中间的数字
lea bx,[table]
lea di,ds:[table+bp-1]
add bx,cx


call HalfFind ;用二分法搜索,函数调用后,si=di
mov al,[num] ;把要搜索的值放入al
cmp al,[si] ;比较要搜索的值和si指向的值
jnz no_find ;如果不相等,说明不存在

lea dx,[inf3] ;如果相等,那这个值就是要搜索的值了,那么先显示提示语句
mov ah,09H
int 21H

mov ax,si ;这3行是求出si指向的值跟table起始位置的差值,保存在al,其实也就是搜索到的数值的下标
lea bx,[table]
sub ax,bx
inc ax ;习惯上从1开始编号,所以这个要加1
mov bl,10 ;下标除10,刚好余数在ah,商在al,一起增加30H,商是十位,余数是个位
div bl
add ax,3030H

mov dl,al ;先输出十位
mov bl,ah
mov ah,2
int 21H
mov dl,bl ;再输出个位
int 21H
jmp short again ;跳转到询问是否重复

no_find:
lea dx,[inf2] ;如果没找到的话,提示没有找到
mov ax,0900H
int 21H

again:
lea dx,[inf4] ;询问是否重复查找
mov ax,0900H
int 21H
mov ax,0100H ;接受一个字符输入并回显
int 21H
cmp al,'y' ;比较输入的字符,如果是y或者Y,就返回到上面,继续搜索
jz find_value
cmp al,'Y'
jz find_value
ret ;如果输入其他字符,那主函数结束,返回
main endp

;函数功能:初始化待搜索数字,和buf2
;输入参数:无
;输出参数:无
init proc near

mov byte ptr [num],0 ;待搜索数字初始化为0
mov byte ptr [buf2+1],0 ;实际输入字符数清0
mov word ptr [buf2+2],0 ;缓冲区清0
mov word ptr [buf2+4],0
ret

init endp

;函数功能:提示用户输入要搜索的数字,接受输入,并整理为数字,存放到num里面
;输入参数:无
;输出参数:[num]保存了要搜索的数字
getfindnum proc near

lea dx,inf1 ;提示输入
mov ax,0900H
int 21H
lea dx,buf2 ;接受输入到缓冲区buf2
mov ax,0a00H
int 21H

mov cl,[buf2+1] ;实际输入字符数
lea si,[buf2+2] ;缓冲区实际开始位置
main_l1:
mov al,[num] ;把数字字符串整理为数字
mov bl,10
mul bl
add al,[si]
sub al,30H
mov [num],al
inc si
loop main_l1

ret

getfindnum endp

;函数功能:用二分法搜索指定数字
;输入参数:si 指向被搜索数组的第一个数字
; di 指向被搜索数组的最后一个数字
; bx 指向被搜索数组的中间数字
;输出参数:si和di指向同一个数字,通过判断这个数字是否等于要搜索的数字,就可以判断是否可以找到
HalfFind proc near
cmp si,di ;如果si和di都指向了同一个数字,那么表示搜索结束,可以直接返回
jz return
mov al,[num] ;al存放要搜索的数字
cmp [bx],al ;与bx指向的数字比较,bx指向的是si和di的中间
ja above ;如果[bx]比较大
jb below ;如果[bx]比较小
mov si,bx ;如果两者相等,则si和di都指向bx指向的位置,这个数就是要找的数,可以直接返回
mov di,bx
jmp short return
above: ;如果[bx]比较大,也就是说,要搜索的数据在[si]和[bx-1]之间
mov cx,bx ;下面三句计算si和bx的差值
mov ax,si
sub cx,ax
shr cx,1 ;差值除2取整
mov di,bx ;di指向bx指向的位置
dec di ;di再前移一位,因为数据比[bx]小,所以肯定在[si]和[bx-1]之间
sub bx,cx ;bx指向新的si和di中间
call HalfFind ;递归调用,此时的si和di的范围已经缩小了一半了
jmp short return ;跳转到返回语句
below: ;对于[bx]比待搜索数字小的情况,和上面的处理方式差不多,也是递归调用,只不过移动的是si
mov cx,di
mov ax,bx
sub cx,ax
shr cx,1
mov si,bx
inc si
add bx,cx
call HalfFind
jmp short return
return:
ret

HalfFind endp

;函数功能:把输入的字符串整理为数组
;输入参数:无
;输出参数:cx 整理出来的数组长度,数组存放在table里面
SaveToArray proc near

mov cl,[buf+1] ;输入字符串的长度
mov ch,0
lea si,[buf+2] ;指向输入字符串的起始位置
lea di,[table] ;指向存放整理后数字的位置
mov bh,0 ;bh保存的是数组长度
SaveToArray_l1:
cmp byte ptr [si],'0' ;如果这个字符不在0~9范围内,说明上一个数字输入结束
jb EndOfANum
cmp byte ptr [si],'9'
ja EndOfANum
mov al,[di] ;如果这个字符是数字字符,那么把原来的值乘10,加上这个数字,减去30H
mov bl,10
mul bl
mov bl,[si]
sub bl,'0'
add al,bl
mov [di],al ;保存回去
jmp short continue ;继续下一次循环
EndOfANum: ;上一个数字输入完毕
cmp byte ptr [di],0 ;如果现在[di]的值为0,那不进行处理,因为已经规定,数字不为0
jz continue
inc di ;如果[di]不为0,则di后移一位
inc bh ;数组长度加1
continue:
inc si ;si后移一位
loop SaveToArray_l1 ;循环
inc bh ;最后找到的一个数字,还没有计算进数组长度,所以这里加了1
mov cl,bh ;数组长度保存到cx
mov ch,0
ret

SaveToArray endp

code ends
end start


从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-15 15:17
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 

这个程序1个半小时就写好了,注释写了半个小时

这种算法,只要理解清楚了,写起来还是比较快的

关键就是要注意递归时候的现场保护和恢复,其他的倒是没啥.

程序代码:

;快速排序
;作者:永夜的极光
;时间:2007-11-15

;程序运行结果:
;Please input data(0<data<=255)
;Divide with any char except 0~9:
;15 100 50 105 205 120 30 200
;Befor quick sort:15,100,50,105,205,120,30,200
;After quick sort:15,30,50,100,105,120,200,205
;Press any key to exit...

assume ds:data,cs:code,ss:stack
data segment
inf0 db 0dh,0ah,'Please input data(0<data<=255)',13,10,'Divide with any char except 0~9:',13,10,'$'
buf db 250,?,250 dup ('0') ;保存输入数据字符串的缓冲区
table db 100 dup (0) ;分析输入的字符串,分解为数字,保存在这个表中
buf2 db 250 dup (0) ;将数组转换成可以显示的字符串,每个数字用逗号分隔,保存在这里,方便显示
len db ? ;保存数组长度
inf1 db 0dh,0ah,'Befor quick sort:$'
inf2 db 0dh,0ah,'After quick sort:$'
inf4 db 13,10,'Find another num(Y/N)?$'
inf_end db 13,10,'Press any key to exit...$'
data ends

stack segment
dw 80 dup (0)
stack ends

code segment
start:
mov ax,data
mov ds,ax
mov es,ax
mov ax,stack
mov ss,ax
call main ;调用主函数

lea dx,inf_end ;提示按任意键结束
mov ax,0900H
int 21H
mov ax,0700H
int 21H
mov ax,4c00H
int 21H

main proc near

lea dx,inf0 ;提示输入字符串,并接受输入,存在buf
mov ah,09H
int 21H
lea dx,buf
mov ah,0aH
int 21H

call SaveToArray ;调用函数,处理输入的字符串,整理为数字数组
lea dx,[inf1] ;提示语句
mov ah,09H
int 21H
lea si,[table] ;传递两个参数,调用函数在屏幕上显示数组
lea di,[buf2]
call DisplayArray
lea di,[table] ;下面5句使di指向数组的最后一个数字
mov al,[len]
mov ah,0
add di,ax
dec di
call QuickSort ;调用快速排序函数,直接整理数组里面的元素
lea dx,[inf2] ;提示语句
mov ah,09H
int 21H
lea si,[table] ;调用函数,显示数组
lea di,[buf2]
call DisplayArray
ret
main endp

;函数功能:对于si和di范围内的数字进行整理,使左边的所有数都不大于右边的任意一个,返回中间位置的指针
;输入参数:si 指向待整理范围的第一个数字,而且也以这个数字作为分界,比这个数字大的放右边,比他小的放左边
; di 指向待整理范围的最后一个数字
;输出参数:dx 指向整理后的中间数字,在他左边的数字都不大于他右边的任意一个数字
Partition proc near

push si
push di
push ax
mov al,[si] ;保存第一个数字,作为比较大小的标准
dec si ;si后移,因为下面循环的第一步是si前移,所以要先后移一位,不然会以后第一个数
inc di ;di前移,原因如上
P_l1:
P_l2:
dec di ;di前移,如果[di]比标准数字大,则继续循环,这个循环结束后,di指向从右边开始第一个比标准数字小的数字(如果存在的话)
cmp [di],al
ja P_l2
P_l3:
inc si ;si后移,如果[si]比标准数字小,则继续循环,这个循环结束后,di指向从右边开始第一个比标准数字大的数字(如果存在的话)
cmp [si],al
jb P_l3
cmp si,di ;如果此时si>=di,那么跳出循环,整理结束
jnb P_s1
mov ah,[di] ;这三句是交换[si]和[di]
xchg [si],ah
mov [di],ah
jmp P_l1 ;如果si还是小于di,那么继续开始循环,知道整理结束
P_s1:
mov dx,di ;现在di指向中间元素,赋值个dx,作为返回值
pop ax
pop di
pop si
ret

Partition endp

;函数功能:对si和di范围内的数组进行排序
;输入参数:si 指向排序范围的第一个数字
; di 指向排序范围的最后一个数字
;输出参数:无
QuickSort proc near

push si ;寄存器入栈,保护现场
push di
push dx
cmp si,di ;如果si>=di,则直接返回
jge return
call Partition ;调用函数,整理数组,返回值dx,在dx左边的数字都不大于右边的数字
push di ;保存di
mov di,dx ;对于si和bx范围内的数组,递归调用本函数
call QuickSort
pop di ;取出di
mov si,dx ;对dx+1和di范围内的数组,递归调用本函数
inc si
call QuickSort
return:
pop dx ;寄存器出栈,恢复现场
pop di
pop si
ret

QuickSort endp

;函数功能:把si指向的数组,转变成一个显示的字符串,并输出
;输入参数:si 指向需要处理的数组
; di 保存转换后字符串的位置
;输出参数:无(直接在屏幕上显示字符串)
DisplayArray proc near

push ax ;寄存器入栈
push bx
push cx
push dx
push di
push si

mov cl,[len] ;设置循环次数等于数组元素个数
mov ch,0
push di ;di指向的是存放转换后字符串的地方,这个地方最后赋值给dx,可以显示
DA_l1: ;因为每个数字最多为三位,所以下面直接进行处理,不写通用的转换程序
mov al,[si] ;当前处理的数字放入ax
mov ah,0
mov bl,100 ;16位除8位的除法
div bl
cmp al,0 ;如果商为0,跳转到处理余数的部分
jz DA_s1
add al,'0' ;商不为0,则加上30H,放入di指向的位置,di后移
mov [di],al
inc di
mov al,ah ;把余数除10,然后ax加上3030H,在用16位长度放入[di]中
mov ah,0
mov bl,10
div bl
add ax,3030H
mov word ptr [di],ax
add di,2
mov byte ptr [di],',' ;后面加逗号
inc di ;di后移
inc si ;已经一个数字处理结束,si后移
jmp DA_s3 ;跳到loop的地方
DA_s1:
mov al,ah ;数字不是三位数,把余数放入al,再除10
mov ah,0
mov bl,10
div bl
cmp al,0 ;比较商是否为0,如果为0,说明是一位数,跳转到下面
jz DA_s2
add al,'0' ;如果商不为0,则是两位数,商加30H,放入[di],di后移
mov [di],al
inc di
DA_s2:
add ah,'0' ;把余数加上30H,放入[di],di后移
mov [di],ah
inc di
mov byte ptr [di],',' ;放入逗号
inc di
inc si ;一个数字处理完成,si后移
DA_s3:
loop DA_l1 ;继续处理下一个数字
dec di ;现在字符串最后一位是逗号,把这个逗号换成'$'就好了
mov byte ptr [di],'$'
pop dx ;把指向字符串第一个字符的位置出栈,赋值给bx
mov ah,09H ;调用中断输出
int 21H
pop si ;寄存器出栈
pop di
pop dx
pop cx
pop bx
pop ax
ret

DisplayArray endp

;函数功能:把输入的字符串整理为数组
;输入参数:无
;输出参数:cx 整理出来的数组长度,数组存放在table里面
SaveToArray proc near

mov cl,[buf+1] ;输入字符串的长度
mov ch,0
lea si,[buf+2] ;指向输入字符串的起始位置
lea di,[table] ;指向存放整理后数字的位置
mov bh,0 ;bh存放的是数组长度
SaveToArray_l1:
cmp byte ptr [si],'0' ;如果这个字符不在0~9范围内,说明上一个数字输入结束
jb EndOfANum
cmp byte ptr [si],'9'
ja EndOfANum
mov al,[di] ;如果这个字符是数字字符,那么把原来的值乘10,加上这个数字,减去30H
mov bl,10
mul bl
mov bl,[si]
sub bl,'0'
add al,bl
mov [di],al ;保存回去
jmp short continue ;继续下一次循环
EndOfANum: ;上一个数字输入完毕
cmp byte ptr [di],0 ;如果现在[di]的值为0,那不进行处理,因为已经规定,数字不为0
jz continue
inc di ;如果[di]不为0,则di后移一位
inc bh ;数组长度加1
continue:
inc si ;si后移一位
loop SaveToArray_l1 ;循环
inc bh ;最后找到的一个数字,还没有计算进数组长度,所以这里加了1
mov [len],bh ;数组长度保存到[len]
mov cl,bh ;数组长度保存到cx
mov ch,0
ret

SaveToArray endp

code ends
end start


下面c写的快速排序


/*****************************************
函数功能:快速排序
作者:永夜的极光
调用:QuickSort(a,p,r)
其中 a为待排序数组的数组名,p为排序范围的第一个数字的下标,r为最后一个数字的下标
*****************************************/
int Partition(int a[],int p,int r)
{
int x=a[p],i=p-1,j=r+1;
while(1)
{
do
{
j--;
}while(a[j]>x);
do
{
i++;
}while(a[i]<x);
if(i<j)
{
int tmp=a[i];
a[i]=a[j];
a[j]=tmp;
}
else
return j;
}
}

void QuickSort(int a[],int p,int r)
{
if(p<r)
{
int q=Partition(a,p,r);
QuickSort(a,p,q);
QuickSort(a,q+1,r);
}
}

[此贴子已经被作者于2007-11-16 10:14:07编辑过]


从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-16 10:04
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 
楼上两位过奖了,目前还处于初学阶段,要多做练习来巩固

从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-16 13:19
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 

这几天在看另外一本教材,没有写新的程序,刚好看到利用自定义的函数库写程序,就把以前写的一个简单的函数改了一下,调试了半天,终于成功了,与大家共享一下

首先是三个文件:
1.asm


;显示不大于FFFFFFFF的任意10进制数
;作者:永夜的极光
;时间:2007-11-19

;程序运行结果:
;12666
;123468
;120
;150000
;9999999
;1031846
;215554151

;注意:mylib1.lib这个文件是通过命令lib mylib1 +2 +3生成的,利用到了2.asm,3.asm生成的两个obj文件
includelib mylib1.lib
extrn dtoc:far
.model small
.data
num dd 12666,123468,120,150000,9999999,1031846,215554151 ;待显示的数字,32位
len equ ($-num)/type num ;保存上面数字的个数($表示当前地址,$-num表示当前地址和num的地址的差值,type num表示num占的字节数)
dis db 16 dup (0) ;存放待显示字符的空间
crlf db 13,10,'$' ;换行符
.stack 128 ;定义栈
.code
mov ax,@data ;用这种精简方式定义各种段,如果要取得地址的话,就要用这种方式
mov ds,ax ;因为masm5.0不支持\".startup\",所以只能自己设置ds,ss和sp不用管,编译器会自动设置
lea bx,[num] ;指向要显示的数字
mov cx,len ;数字个数
l1:
mov ax,[bx] ;ax保存低16位
mov dx,[bx+2] ;dx保存高16位
lea si,[dis] ;di指向存放字符的空间
call dtoc ;调用函数,将dx:ax的数字转换后保存在si指向的空间,函数定义见2.asm
lea dx,[dis] ;显示转换后的字符串
mov ax,0900H
int 21H
lea dx,[crlf] ;显示换行符
int 21H
add bx,4 ;bx指向下一个要显示的数字
loop l1
mov ax,4c00H
int 21H
end


2.asm

;函数功能:将输入的一个数,转换为10进制字符串
;输入参数:ds:si 一段空闲空间的起始点,字符串从这里开始写
; ax 待转换数字的低16位
; dx 待转换数字的高16位
;输出参数:从指定的位置开始的一个字符串,以'$'结尾
public dtoc
extrn divdw:far
segdotc segment 'code'
assume cs:segdotc ;如果函数中使用到了标号,这一句不能没有,不然编译器会提示没找到cs
dtoc proc far
push bx ;寄存器入栈
push cx
push ax
push dx
push si
mov bx,0 ;bx记录转换后数字的位数
dtoc_l1:
mov cx,10 ;每次循环,除10
call divdw ;调用32位除16位不溢出除法,函数定义见3.asm
inc bx ;位数加1
push cx ;先把余数入栈,每次循环都把余数入栈,等最后循环结束,逐个出栈就可以得到转换后的结果

cmp ax,0 ;如果ax不为0,继续循环
jnz dtoc_l1
cmp dx,0 ;如果dx不为0,继续循环
jnz dtoc_l1

mov cx,bx ;ax,dx都为0,说明转换结束,现在bx保存的就是转换后的位数
dtoc_l2:
pop bx ;原来保存在栈里面的余数出栈,刚好利用栈的先进后出
add bx,30H ;变成字符
mov [si],bl ;存入指定的位置
inc si ;指针后移
loop dtoc_l2 ;循环
mov byte ptr [si],'$' ;最后加上'$'
pop si ;寄存器出栈
pop dx
pop ax
pop cx
pop bx
ret
dtoc endp
segdotc ends
end

3.asm

;不会溢出的除法(32位除16位,商如果只能是16位,则可能溢出,比如FFFFF div 1)
;因此,把商也要增大到32位
;输入参数:ax 保存被除数低16位,dx 保存被除数高16位,cx 保存除数
;输出参数:ax 保存商低16位,dx 保存商高16位,cx保存余数
;计算方法:商的高16位=被除数高16位 div 除数 的 商
; 商的低16位=((被除数高16位 div 除数 的 余数)*FFFF+被除数低16位)div 除数 的 商
; 余数=上式的余数
public divdw
segdivdw segment 'code'
divdw proc far
push bx ;保存bx
mov bx,ax ;先把低16位保存
mov ax,dx ;高16位移到低16位
mov dx,0 ;高16位置零
div cx ;除
push ax ;保存商
mov ax,bx ;取出原来保存的低16位,高16位是上次除法的余数,已经在dx里面了
div cx ;再除
mov cx,dx ;把余数保存到cx
pop dx ;把第一次除的商,放入dx,作为结果的高16位
pop bx ;恢复bx
ret ;返回
divdw endp
segdivdw ends
end


程序写好后,在command中,用masm 2.asm;和masm 3.asm;生成了两个同名obj文件
再用lib mylib1 +2 +3生成了库文件mylib1.lib
最后编译1.asm
masm 1.asm;
link 1.obj;
终于编好了,运行1.exe就看到结果啦


从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-19 15:38
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 

这次尝试的是利用条件汇编,实现子程序开头的寄存器压栈和结束时的寄存器出栈,简化写子程序的写法.
这个功能其实在masm6以后的编译器,可以利用一些伪代码来实现,不过我用的是masm5,所以就自己写了

宏已经写成了一个文件mac1.inc,代码如下


;****************************************************
;文件名:mac1.inc
;包括宏:pushonereg,poponereg,pushreg,popreg,procstart,procend
;作者:永夜的极光
;制作时间:2007-11-21
;****************************************************

;功能:根据前两个参数的测试结果,决定是否将第三个参数指定的寄存器压栈
;pushnum 用二进制保存需要压栈的寄存器
;testnum 测试某一位是否为0的数字
;reg 要压栈的寄存器
pushonereg macro regnum,testnum,reg
if (regnum and testnum) ;条件汇编
push reg
endif
endm
;功能:根据前两个参数的测试结果,决定是否将第三个参数指定的寄存器出栈
;popnum 用二进制保存需要出栈的寄存器
;testnum 测试某一位是否为0的数字
;reg 要出栈的寄存器
poponereg macro regnum,testnum,reg
if (regnum and testnum)
pop reg
endif
endm
;功能:根据传入的参数,将指定的寄存器压栈
;regnum 指定寄存器,每个二进位对应一个寄存器是否入栈,如果为1则入栈,0则不入,每个位和寄存器的对应关系从下面的程序可以看出来
pushreg macro regnum
pushonereg regnum,10000000b,ax
pushonereg regnum,01000000b,bx
pushonereg regnum,00100000b,cx
pushonereg regnum,00010000b,dx
pushonereg regnum,00001000b,ds
pushonereg regnum,00000100b,es
pushonereg regnum,00000010b,si
pushonereg regnum,00000001b,di
endm
;功能:根据传入的参数,将指定的寄存器出栈
;regnum 指定寄存器,每个二进位对应一个寄存器是否出栈,如果为1则出栈,0则不出,每个位和寄存器的对应关系从下面的程序可以看出来
;注意,对应关系于上面的宏完全相同,不过根据栈的先进后出特性,要把顺序颠倒过来
popreg macro regnum
poponereg regnum,00000001b,di
poponereg regnum,00000010b,si
poponereg regnum,00000100b,es
poponereg regnum,00001000b,ds
poponereg regnum,00010000b,dx
poponereg regnum,00100000b,cx
poponereg regnum,01000000b,bx
poponereg regnum,10000000b,ax
endm
;功能:在每个子程序的开头,实现寄存器的压栈和预留空间给局部变量
;regnum 用二进制表示的需要入栈的寄存器
;varlen 需要预留多少空间给局部变量,以byte为单位
procstart macro regnum,varlen
push bp
mov bp,sp
ifnb <varlen> ;条件汇编,如果varlen有对应的实参,才执行下面的语句
sub sp,varlen
endif
pushreg regnum
endm
;功能:在每个子程序的末尾,实现寄存器的压栈和返回(可以用ret n的格式)
;regnum 用二进制表示的需要出栈的寄存器
;retnum 程序返回后,sp还要加上的数字,也就是ret n中的n
procend macro regnum,retnum
popreg regnum
mov sp,bp
pop bp
ifnb <retnum>;条件汇编,如果retnum有对应的实参,才执行下面的语句,否则执行else后面的语句
ret retnum
else
ret
endif
endm


下面是使用这个宏文件的示例

;利用自定义的宏文件,实现子函数中寄存器的入栈和出栈.
;作者:永夜的极光
;时间:2007-11-21
;运行结果:无
;用debug -u 查看编译后的程序如下:
;0CF1:0000 B8F60C MOV AX,0CF6
;0CF1:0003 8ED8 MOV DS,AX
;0CF1:0005 B83412 MOV AX,1234
;0CF1:0008 BB7856 MOV BX,5678
;0CF1:000B E81000 CALL 001E
;0CF1:000E B83412 MOV AX,1234
;0CF1:0011 50 PUSH AX
;0CF1:0012 B87856 MOV AX,5678
;0CF1:0015 50 PUSH AX
;0CF1:0016 E81300 CALL 002C
;0CF1:0019 B8004C MOV AX,4C00
;0CF1:001C CD21 INT 21
;0CF1:001E 55 PUSH BP
;0CF1:001F 8BEC MOV BP,SP

;0CF1:0021 53 PUSH BX
;0CF1:0022 03C3 ADD AX,BX
;0CF1:0024 BBBC9A MOV BX,9ABC
;0CF1:0027 5B POP BX
;0CF1:0028 8BE5 MOV SP,BP
;0CF1:002A 5D POP BP
;0CF1:002B C3 RET
;0CF1:002C 55 PUSH BP
;0CF1:002D 8BEC MOV BP,SP
;0CF1:002F 83EC02 SUB SP,+02
;0CF1:0032 51 PUSH CX
;0CF1:0033 52 PUSH DX
;0CF1:0034 8B4E04 MOV CX,[BP+04]
;0CF1:0037 8B5606 MOV DX,[BP+06]
;0CF1:003A C746FE0000 MOV WORD PTR [BP-02],0000
;0CF1:003F 014EFE ADD [BP-02],CX

;0CF1:0042 0156FE ADD [BP-02],DX
;0CF1:0045 8B46FE MOV AX,[BP-02]
;0CF1:0048 5A POP DX
;0CF1:0049 59 POP CX
;0CF1:004A 8BE5 MOV SP,BP
;0CF1:004C 5D POP BP
;0CF1:004D C20400 RET 0004
.model small
include mac1.inc
.data
.stack 128
.code
mov ax,@data
mov ds,ax
mov ax,1234H
mov bx,5678H
call add1 ;调用函数1,求ax和bx的和,结果保存在ax
mov ax,1234H ;函数2也是求两个数的和,不过是用栈的方式来传递,结果通过ax返回
push ax ;先把两个参数压栈
mov ax,5678H
push ax
call add2 ;调用函数2
mov ax,4c00H
int 21H
add1 proc near
add1_regnum equ 01000000b ;保护bx的值,其实这个小函数是不需要改变bx的,这个只是作为一个例子
procstart add1_regnum ;程序起始,第二个参数可以省略,就是不需要为局部变量预留空间
add ax,bx
mov bx,9abcH ;随便改变bx的值
procend add1_regnum ;程序结束,第二个参数可以省略,那么程序只是简单的返回,不在返回后再修改sp
;程序最后不再需要ret,不过写上也没关系,因为不可能执行的到
add1 endp
add2 proc near
add2_regnum equ 00110000b ;保护cx和dx的值,也是为了作为例子
procstart add2_regnum,2 ;给这个函数预留了两个byte的保存局部变量的空间,用[bp-1]和[bp-2]访问
;如果要访问输入的参数,用[bp+4]和[bp+6].而[bp]保存的是原来bp的值,[bp+2]保存的是调用函数前压栈的IP,也就是这个函数运行完毕后,用ret返回的位置
mov cx,[bp+4] ;cx保存第二个数
mov dx,[bp+6] ;dx保存第一个数,要注意,先压栈的数所在地址比较大
mov word ptr [bp-2],0 ;把局部变量的位置清0
add [bp-2],cx ;加上cx
add [bp-2],dx ;加上dx
mov ax,[bp-2] ;把局部变量的值传递给ax
;实际上,求两个数的和不必要这么麻烦,只是为了演示局部变量和保护寄存器才写的这么长
procend add2_regnum,4 ;调用这个函数前,压栈了两个16位的数,所以返回时sp要多增加4个byte

add2 endp
end


从BFS(Breadth First Study)到DFS(Depth First Study)
2007-11-21 13:47
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 
论坛改版怎么把我的帖子弄成这样啊,而且还不让我编辑

斑竹能不能帮忙编辑一下

全部代码整理在楼下了

[[italic] 本帖最后由 永夜的极光 于 2007-12-18 08:33 编辑 [/italic]]

从BFS(Breadth First Study)到DFS(Depth First Study)
2007-12-18 08:15
永夜的极光
Rank: 6Rank: 6
等 级:贵宾
威 望:27
帖 子:2721
专家分:1
注 册:2007-10-9
收藏
得分:0 
全部代码如下:
1楼 从键盘输入年份,判断是否为闰年
程序代码:
 
;从键盘输入年份,判断是否为闰年
;作者:永夜的极光
;时间:2007-11-11

;程序运行结果
;Please Input Year: 1996
;This is a leap year!
;Do you want to try again?(Y/N) y
;Please Input Year: 1900
;This is not a leap year!
;Do you want to try again?(Y/N) y
;Please Input Year: 2000
;This is a leap year!
;Do you want to try again?(Y/N) y
;Please Input Year: 2007
;This is not a leap year!
;Do you want to try again?(Y/N) n
;Press any key to exit.

assume cs:code,ds:data
data segment
;初始化各种提示语句,前面的0dh,0ah表示回车
inf db 0dh,0ah,'Please Input Year: $'
inf_ok db 0dh,0ah,'This is a leap year! $'
inf_no db 0dh,0ah,'This is not a leap year! $'
inf_err db 0dh,0ah,'Input Error! $'
inf_ag db 0dh,0ah,'Do you want to try again?(Y/N) $'
inf_end db 0dh,0ah,'Press any key to exit. $'
;将输入的年份字符串转为数字,存在此处
year dw 0
;缓冲区,用于接受输入,第一个字节为缓冲区长度,第二个字节为实际输入长度,后面是缓冲区
buf db 8
db ?
db 8 dup (?)
data ends

stack segment
db 80 dup (0)
stack ends

code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,80

;dx保存提示语句的首地址,调用21H中断09功能,显示在屏幕上
lea dx,inf
mov ax,0900H
int 21H

;dx指向缓冲区首字节(表示缓冲区大小的字节),调用21H中断0AH功能,接受输入字符串
lea dx,buf
mov ax,0a00H
int 21H

;cx清零,程序一开始,cx就不是0,这个不知道什么原因
xor cx,cx
mov cl,[buf+1];cl取得实际输入字符数
cmp cl,4 ;如果实际输入字符数>4,则提示输入错误
ja input_error

lea si,[buf+2];si指向实际的缓冲区起始位置
push cx
is_num: ;判断输入的是否都是数字,如果不是,则提示输入错误
cmp byte ptr [si],'0'
jb input_error
cmp byte ptr [si],'9'
ja input_error
inc si ;si后移
loop is_num
pop cx

dec si ;上面循环结束后,si指向输入字符串的最后一个字符的后面,所以减1,使之指向最后一个字符
lea di,year ;di指向保存转换后数字的空间
mov word ptr [di],0 ;该空间清0
call str2int ;调用函数,将输入字符串转换为数字
call isleap ;判断这个数字是否为闰年

jmp short try_again ;询问是否继续输入
input_error:
lea dx,inf_err ;提示输入错误
mov ax,0900H
int 21H

try_again:
lea dx,inf_ag ;询问是否继续
mov ax,0900H
int 21H

mov ax,0100H ;调用中断21H的01功能,接受一个字符输入并回显
int 21H

cmp al,'y' ;判断输入字符是否为y或Y,如果是,则跳回函数开头,重新执行
jz start
cmp al,'Y'
jz start

main_end:
lea dx,inf_end
mov ax,0900H
int 21H

mov ax,0700H
int 21H

mov ax,4c00H ;程序结束
int 21H

;函数功能:将一个数字字符串转换成对应的数字
;输入参数:cx 字符串长度
; si 指向字符串的最后一个字符
; di 指向转换结果保存的位置,大小为word型
str2int proc near

push ax ;寄存器入栈
push bx
push cx
push dx

mov ax,1 ;ax初始为1
l1:
push ax ;每次循环,ax*10,但是后面计算乘法的时候,还需要用到ax,所以先把ax的值入栈
mov bl,[si] ;把si指向的字符存入bl,并减掉字符'0'
sub bl,'0'
mov bh,0 ;bh置0,计算乘法,因为可能要乘1000,只能用16位乘16位的乘法
mul bx
add ds:[di],ax ;乘法结果加到保存计算结果的地方(也就是di指向的地址)
pop ax ;ax出栈,并乘10
mov bx,10
mul bx
dec si ;si递减
loop l1 ;这个循环开头没有写cx,因为cx是作为参数传进来的

pop dx
pop cx
pop bx
pop ax
ret

str2int endp

;函数功能:判断某个数字是否为闰年
;输入参数:di 指向被判断数字的存储地址,word型
;输出参数:无,直接屏幕显示是否闰年
isleap proc near

push ax ;寄存器入栈
push bx
push cx
push dx

mov cx,ds:[di] ;cx保留输入字符串
mov ax,cx
mov dx,0 ;因为除法结果可能超过8位,所以要做32位除16位的除法,所以dx要清零
mov bx,4 ;除4
div bx
cmp dx,0 ;dx是余数,判断是否为0,不是0则肯定不是闰年
jnz not_leap
mov ax,cx ;ax恢复为待计算数字,这次计算的结果只需要8位就够,所以用16位除8位就行
mov bx,100 ;除100
div bl
cmp ah,0 ;ah是余数,如果为0,那还要继续判断,如果不为0,那就肯定是闰年了
jnz is_leap
mov ax,cx ;x恢复为待计算数字,因为要除以400,超过8位,所以用32位除16位除法
mov dx,0 
mov bx,400
div bx
cmp dx,0 ;余数如果为0,说明是闰年
jz is_leap
jmp short not_leap ;不是闰年
is_leap:
lea dx,inf_ok ;是闰年的话,提示是闰年
mov ax,0900H
int 21H
jmp short end_sub
not_leap:
lea dx,inf_no ;不是闰年的话,提示不是闰年
mov ax,0900H
int 21H

end_sub:
pop dx
pop cx
pop bx
pop ax
ret

isleap endp
code ends
end start
2楼 接受输入一个字符串,找出重复长度最大的一段
程序代码:
 
;接受输入一个字符串,找出重复长度最大的一段
;作者:永夜的极光
;时间:2007-11-13

;程序运行结果
;*********** Welcome ***********
;Input string(length<30): aaaaaaaabbaaaaa
;The max length is 08
;Repeat char is a

;Press any key to exit...
assume cs:code,ds:data
data segment
inf0 db '*********** Welcome ***********',0dh,0ah,'Input string(length<30): $';各提示语句
inf1 db 0dh,0ah,'The max length is ';这一段是提示语句加数据合在一起,到时候调用一次中断就能一次性输出
maxlen dw ?
inf2 db 0dh,0ah,'Repeat char is '
repchar db ?
inf3 db 0dh,0ah,'$'
inf_end db 0dH,0aH,'Press any key to exit...$';按任意键退出
buf db 30;缓冲区(这一位表示缓冲区实际长度)
db ?;实际输入字符数
db 30 dup (?);实际缓冲区位置
data ends

stack segment
db 80 dup (0)
stack ends

code segment
start:
mov ax,data;设置段寄存器
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,80
call main;调用主函数
lea dx,inf_end;输出提示语句(按任意键结束)
mov ax,0900H
int 21H
mov ax,0700H;等待一个按键,不回显
int 21H
mov ax,4c00H;结束
int 21H

;主函数
main proc near

lea dx,inf0 ;提示用户输入字符串
mov ax,0900H
int 21H
lea dx,buf ;接受输入并存入缓冲区
mov ax,0a00H
int 21H

xor cx,cx ;cx清零
mov cl,[buf+1] ;实际输入长度保存在cl
lea si,[buf+2] ;si指向缓冲区实际位置

l1:
call countsame ;调用函数,找到当前si所指字符后面连续的与之相等的字符个数
cmp ch,[maxlen] ;比较目前保存的最大长度和此次调用获得的长度
jna s
mov [maxlen],ch ;如果此次调用函数获得长度比目前保存的maxlen还大,那就把maxlen替换掉,同时也记录重复的字符
mov ax,[si]
mov [repchar],al
s:
mov si,di ;di指向调用函数后,找到的第一个不同的字符,把这个位置赋值给si,再继续循环
sub cl,ch ;ch保存的是本次调用所获得的相同字符的长度,每次调用就从总长度减掉这个值,等减到最后为0,也就是跳出循环的时候
cmp cl,0
jnz l1

mov ax,[maxlen] ;把最大长度放到ax,为的是把数字转变为字符串
mov bl,10 ;除10,ah保存余数,al保存商,因为长度最大才30,所以只要在ah和al都加上30H,就可以变为字符串了.
div bl
add ax,3030H
mov [maxlen],ax ;把转换后的结果放回去

lea dx,inf1 ;把整个结果字符串输出
mov ax,0900H
int 21H


ret

main endp

;函数功能:找到si之后与[si]连续的相等的字符个数
;输入参数:si 指向寻找的字符
;输出参数:di 指向si后面第一个和si不同的字符
; ch 和si相同的字符个数(包括si本身,也就是至少为1)
countsame proc near

push ax
lea di,[si+1] ;di指向si后面一个
mov ch,1 ;ch最小是1
ll1:
mov al,ds:[di];把di指向的字符放到al里面,与si指向的字符比较
cmp al,[si]
jnz l2 ;如果不相等,就可以结束循环了
inc di ;di后移
inc ch ;计数加1
jmp short ll1
l2:
pop ax
ret

countsame endp
code ends
end start
3楼 计算两个长整数相加
程序代码:
 
;计算两个长整数相加
;作者:永夜的极光
;时间:2007-11-14

;程序运行结果
;Please input num1:12345678901234567890
;Please input num2:987654321987654321
;num1+num2=13333333223222222211
;Press any key to exit...

assume ds:data,cs:code,ss:stack
data segment
inf0 db 0dh,0ah,'Please input num1:$'
num1 db 30,?,30 dup ('0')
inf1 db 0dh,0ah,'Please input num2:$'
num2 db 30,?,30 dup ('0')
inf2 db 0dh,0ah,'num1+num2=$'
sum db 31 dup (0) ;保存和的地方,上面限制两个数不超过29位,和最多30位,最后一位放'$'
inf3 db 0dh,0ah,'Press any key to exit...$'
data ends

stack segment
dw 80 dup (0)
stack ends

code segment
start:
mov ax,data ;设置数据和栈的段寄存器
mov ds,ax
mov ax,stack
mov ss,ax
call main ;调用主函数

lea dx,inf3 ;提示输入任意键退出
mov ax,0900H
int 21H
mov ax,0700H ;接受一个按键,不回显
int 21H
mov ax,4c00H
int 21H

main proc near

lea dx,inf0 ;提示输入两个整数
mov ax,0900H
int 21H
lea dx,num1
mov ax,0a00H
int 21H
lea dx,inf1
mov ax,0900H
int 21H
lea dx,num2
mov ax,0a00H
int 21H

mov cl,[num1+1] ;调用函数,把字符串变为数字,这里没有做错误检查,假设用户输入是正确的数字
mov ch,0 ;cl保存字符串长度,si指向字符串起始位置
lea si,[num1+2]
call numchange
mov cl,[num2+1]
lea si,[num2+2]
call numchange

mov cl,[num1+1] ;把两个数字的位数分别赋给cl和dl,si和di指向这两串数字的最后一个数字
mov dl,[num2+1]
lea si,[num1+1]
add si,cx
lea di,[num2+1]
add di,dx
lea bx,[sum] ;bx指向保存结果的位置

cmp dl,cl ;比较两个数字串的长度,cl存放较短数字串的长度,dl存放较长的
jnb c1 ; si指向较短数字串的末尾,di指向较长的
xchg dl,cl
xchg di,si
c1:
push dx ;保存较长数字串的长度,后面判断和的位数要用
clc ;清除进位标志
add bx,dx ;bx向后移动到和的最后一位(先假设和比最长数字串长度大1)
l1:
mov al,[si] ;放第一个数
adc al,ds:[di];进位加法 加第二个数
aaa ;按非压缩BCD码整理(如果cl中的数字后四位大于9,则cl后四位加6,不向前四位进位,而是向ah进位)
mov [bx],al ;把和存入保存结果的地方
dec bx ;指针前移
dec dl
dec si
dec di
loop l1
mov al,0 ;这三句是处理最后一次加法有进位的情况,比如:500+500
adc al,0
mov [bx],al
cmp dl,0 ;dl如果等于0,说明两个数字串都加完了,如果不等于0,则表示di指向的数字串还没处理完
jz sum_end
mov cl,dl ;还剩下dl个数字没处理,所以要循环dl次,把dl赋值给cl
l2:
mov al,[di] ;要处理的数字
adc al,[bx] ;bx指向的是保存和的地方,如果没有修改过的话,那片空间都是0不直接加0的原因是,考虑上面的循环最后一次加法有进位的情况
aaa ;整理
mov [bx],al ;保存和
dec di ;指针前移
dec bx
loop l2
mov al,0 ;还是处理最后一次加法有进位的情况
adc al,0
mov [bx],al
sum_end:
pop cx ;栈里面保存的是较长数字串的长度
inc cx ;这个长度加1,就是和的长度
lea si,sum ;调用函数,把数字变为字符
call numchange
mov byte ptr [si],'$';在末尾加上'$'

lea dx,inf2 ;输出提示语句
mov ax,0900H
int 21H

lea dx,sum ;如果和的第一位是0,说明没有进位,这个0不显示
cmp [sum],'0'
jnz ok
inc dx
ok:
mov ax,0900H ;输出结果
int 21H
ret

main endp

;函数功能:数字字符串和数字串互相转换
;输入参数:si 指向第一个待处理的数字(或数字字符)
; cx 待处理的长度
;输出参数:无
numchange proc near

numchange_l1:
xor byte ptr [si],30H ;异或30H,数字和数字字符就可以互相转换了
inc si
loop numchange_l1 ;循环次数cx是作为参数传入的
ret

numchange endp

code ends
end start
4楼 输入升序数据,并使用二分法搜索
程序代码:
 
;输入升序数据,并使用二分法搜索
;作者:永夜的极光
;时间:2007-11-15

;程序运行结果:
;Please input ascending data(0<data<=255)
;Divide with any char except 0~9:
;1 2 3 40 50 60 100 150 200 210 220 250 255
;Witch num you want to find:1
;This num is in position 01
;Find another num(Y/N)?y
;Witch num you want to find:50
;This num is in position 05
;Find another num(Y/N)?y
;Witch num you want to find:255
;This num is in position 13
;Find another num(Y/N)?y
;Witch num you want to find:101
;Sorry,cannot find this num!
;Find another num(Y/N)?n
;Press any key to exit...

assume ds:data,cs:code,ss:stack
data segment
inf0 db 0dh,0ah,'Please input ascending data(0<data<=255)',13,10,'Divide with any char except 0~9:',13,10,'$'
buf db 250,?,250 dup ('0') ;保存输入数据字符串的缓冲区
table db 100 dup (0) ;分析输入的字符串,分解为数字,保存在这个表中
inf1 db 0dh,0ah,'Witch num you want to find:$'
num db ? ;保存转换后的待寻找的数据
inf2 db 0dh,0ah,'Sorry,cannot find this num!$'
buf2 db 4,?,0,0,0,0 ;接受输入待寻找数据的缓冲区
inf3 db 0dh,0ah,'This num is in position $'
inf4 db 13,10,'Find another num(Y/N)?$'
inf_end db 13,10,'Press any key to exit...$'
data ends

stack segment
dw 80 dup (0)
stack ends

code segment
start:
mov ax,data
mov ds,ax
mov es,ax
mov ax,stack
mov ss,ax
call main ;调用主函数

lea dx,inf_end ;提示按任意键结束
mov ax,0900H
int 21H
mov ax,0700H
int 21H
mov ax,4c00H
int 21H

main proc near

lea dx,inf0 ;提示输入字符串,并接受输入,存在buf
mov ah,09H
int 21H
lea dx,buf
mov ah,0aH
int 21H

call SaveToArray ;调用函数,处理输入的字符串,整理为数字数组
mov bp,cx ;保存数组的长度

find_value:
call init ;清空buf2中的数据和要搜索的数字,避免第二次搜索时出错
call getfindnum ;获取要搜索的数字,处理后放入num

mov cx,bp ;bp保存的是数组长度
shr cx,1 ;cx除2,取整
lea si,[table] ;si指向第一个数字,di指向最后一个数字,bx指向中间的数字
lea bx,[table]
lea di,ds:[table+bp-1]
add bx,cx


call HalfFind ;用二分法搜索,函数调用后,si=di
mov al,[num] ;把要搜索的值放入al
cmp al,[si] ;比较要搜索的值和si指向的值
jnz no_find ;如果不相等,说明不存在

lea dx,[inf3] ;如果相等,那这个值就是要搜索的值了,那么先显示提示语句
mov ah,09H
int 21H

mov ax,si ;这3行是求出si指向的值跟table起始位置的差值,保存在al,其实也就是搜索到的数值的下标
lea bx,[table]
sub ax,bx
inc ax ;习惯上从1开始编号,所以这个要加1
mov bl,10 ;下标除10,刚好余数在ah,商在al,一起增加30H,商是十位,余数是个位
div bl
add ax,3030H

mov dl,al ;先输出十位
mov bl,ah
mov ah,2
int 21H
mov dl,bl ;再输出个位
int 21H
jmp short again ;跳转到询问是否重复

no_find:
lea dx,[inf2] ;如果没找到的话,提示没有找到
mov ax,0900H
int 21H

again:
lea dx,[inf4] ;询问是否重复查找
mov ax,0900H
int 21H
mov ax,0100H ;接受一个字符输入并回显
int 21H
cmp al,'y' ;比较输入的字符,如果是y或者Y,就返回到上面,继续搜索
jz find_value
cmp al,'Y'
jz find_value
ret ;如果输入其他字符,那主函数结束,返回
main endp

;函数功能:初始化待搜索数字,和buf2
;输入参数:无
;输出参数:无
init proc near

mov byte ptr [num],0 ;待搜索数字初始化为0
mov byte ptr [buf2+1],0 ;实际输入字符数清0
mov word ptr [buf2+2],0 ;缓冲区清0
mov word ptr [buf2+4],0
ret

init endp

;函数功能:提示用户输入要搜索的数字,接受输入,并整理为数字,存放到num里面
;输入参数:无
;输出参数:[num]保存了要搜索的数字
getfindnum proc near

lea dx,inf1 ;提示输入
mov ax,0900H
int 21H
lea dx,buf2 ;接受输入到缓冲区buf2
mov ax,0a00H
int 21H

mov cl,[buf2+1] ;实际输入字符数
lea si,[buf2+2] ;缓冲区实际开始位置
main_l1:
mov al,[num] ;把数字字符串整理为数字
mov bl,10
mul bl
add al,[si]
sub al,30H
mov [num],al
inc si
loop main_l1

ret

getfindnum endp

;函数功能:用二分法搜索指定数字
;输入参数:si 指向被搜索数组的第一个数字
; di 指向被搜索数组的最后一个数字
; bx 指向被搜索数组的中间数字
;输出参数:si和di指向同一个数字,通过判断这个数字是否等于要搜索的数字,就可以判断是否可以找到
HalfFind proc near
cmp si,di ;如果si和di都指向了同一个数字,那么表示搜索结束,可以直接返回
jz return
mov al,[num] ;al存放要搜索的数字
cmp [bx],al ;与bx指向的数字比较,bx指向的是si和di的中间
ja above ;如果[bx]比较大
jb below ;如果[bx]比较小
mov si,bx ;如果两者相等,则si和di都指向bx指向的位置,这个数就是要找的数,可以直接返回
mov di,bx
jmp short return
above: ;如果[bx]比较大,也就是说,要搜索的数据在[si]和[bx-1]之间
mov cx,bx ;下面三句计算si和bx的差值
mov ax,si
sub cx,ax
shr cx,1 ;差值除2取整
mov di,bx ;di指向bx指向的位置
dec di ;di再前移一位,因为数据比[bx]小,所以肯定在[si]和[bx-1]之间
sub bx,cx ;bx指向新的si和di中间
call HalfFind ;递归调用,此时的si和di的范围已经缩小了一半了
jmp short return ;跳转到返回语句
below: ;对于[bx]比待搜索数字小的情况,和上面的处理方式差不多,也是递归调用,只不过移动的是si
mov cx,di
mov ax,bx
sub cx,ax
shr cx,1
mov si,bx
inc si
add bx,cx
call HalfFind
jmp short return
return:
ret

HalfFind endp

;函数功能:把输入的字符串整理为数组
;输入参数:无
;输出参数:cx 整理出来的数组长度,数组存放在table里面
SaveToArray proc near

mov cl,[buf+1] ;输入字符串的长度
mov ch,0
lea si,[buf+2] ;指向输入字符串的起始位置
lea di,[table] ;指向存放整理后数字的位置
mov bh,0 ;bh保存的是数组长度
SaveToArray_l1:
cmp byte ptr [si],'0' ;如果这个字符不在0~9范围内,说明上一个数字输入结束
jb EndOfANum
cmp byte ptr [si],'9'
ja EndOfANum
mov al,[di] ;如果这个字符是数字字符,那么把原来的值乘10,加上这个数字,减去30H
mov bl,10
mul bl
mov bl,[si]
sub bl,'0'
add al,bl
mov [di],al ;保存回去
jmp short continue ;继续下一次循环
EndOfANum: ;上一个数字输入完毕
cmp byte ptr [di],0 ;如果现在[di]的值为0,那不进行处理,因为已经规定,数字不为0
jz continue
inc di ;如果[di]不为0,则di后移一位
inc bh ;数组长度加1
continue:
inc si ;si后移一位
loop SaveToArray_l1 ;循环
inc bh ;最后找到的一个数字,还没有计算进数组长度,所以这里加了1
mov cl,bh ;数组长度保存到cx
mov ch,0
ret

SaveToArray endp

code ends
end start
5楼 用快速排序法对一维数组进行排序
程序代码:
 
;快速排序
;作者:永夜的极光
;时间:2007-11-15

;程序运行结果:
;Please input data(0<data<=255)
;Divide with any char except 0~9:
;15 100 50 105 205 120 30 200
;Befor quick sort:15,100,50,105,205,120,30,200
;After quick sort:15,30,50,100,105,120,200,205
;Press any key to exit...

assume ds:data,cs:code,ss:stack
data segment
inf0 db 0dh,0ah,'Please input data(0<data<=255)',13,10,'Divide with any char except 0~9:',13,10,'$'
buf db 250,?,250 dup ('0') ;保存输入数据字符串的缓冲区
table db 100 dup (0) ;分析输入的字符串,分解为数字,保存在这个表中
buf2 db 250 dup (0) ;将数组转换成可以显示的字符串,每个数字用逗号分隔,保存在这里,方便显示
len db ? ;保存数组长度
inf1 db 0dh,0ah,'Befor quick sort:$'
inf2 db 0dh,0ah,'After quick sort:$'
inf4 db 13,10,'Find another num(Y/N)?$'
inf_end db 13,10,'Press any key to exit...$'
data ends

stack segment
dw 80 dup (0)
stack ends

code segment
start:
mov ax,data
mov ds,ax
mov es,ax
mov ax,stack
mov ss,ax
call main ;调用主函数

lea dx,inf_end ;提示按任意键结束
mov ax,0900H
int 21H
mov ax,0700H
int 21H
mov ax,4c00H
int 21H

main proc near

lea dx,inf0 ;提示输入字符串,并接受输入,存在buf
mov ah,09H
int 21H
lea dx,buf
mov ah,0aH
int 21H

call SaveToArray ;调用函数,处理输入的字符串,整理为数字数组
lea dx,[inf1] ;提示语句
mov ah,09H
int 21H
lea si,[table] ;传递两个参数,调用函数在屏幕上显示数组
lea di,[buf2]
call DisplayArray
lea di,[table] ;下面5句使di指向数组的最后一个数字
mov al,[len]
mov ah,0
add di,ax
dec di
call QuickSort ;调用快速排序函数,直接整理数组里面的元素
lea dx,[inf2] ;提示语句
mov ah,09H
int 21H
lea si,[table] ;调用函数,显示数组
lea di,[buf2]
call DisplayArray
ret
main endp

;函数功能:对于si和di范围内的数字进行整理,使左边的所有数都不大于右边的任意一个,返回中间位置的指针
;输入参数:si 指向待整理范围的第一个数字,而且也以这个数字作为分界,比这个数字大的放右边,比他小的放左边
; di 指向待整理范围的最后一个数字
;输出参数:dx 指向整理后的中间数字,在他左边的数字都不大于他右边的任意一个数字
Partition proc near

push si
push di
push ax
mov al,[si] ;保存第一个数字,作为比较大小的标准
dec si ;si后移,因为下面循环的第一步是si前移,所以要先后移一位,不然会以后第一个数
inc di ;di前移,原因如上
P_l1:
P_l2:
dec di ;di前移,如果[di]比标准数字大,则继续循环,这个循环结束后,di指向从右边开始第一个比标准数字小的数字(如果存在的话)
cmp [di],al
ja P_l2
P_l3:
inc si ;si后移,如果[si]比标准数字小,则继续循环,这个循环结束后,di指向从右边开始第一个比标准数字大的数字(如果存在的话)
cmp [si],al
jb P_l3
cmp si,di ;如果此时si>=di,那么跳出循环,整理结束
jnb P_s1
mov ah,[di] ;这三句是交换[si]和[di]
xchg [si],ah
mov [di],ah
jmp P_l1 ;如果si还是小于di,那么继续开始循环,知道整理结束
P_s1:
mov dx,di ;现在di指向中间元素,赋值个dx,作为返回值
pop ax
pop di
pop si
ret

Partition endp

;函数功能:对si和di范围内的数组进行排序
;输入参数:si 指向排序范围的第一个数字
; di 指向排序范围的最后一个数字
;输出参数:无
QuickSort proc near

push si ;寄存器入栈,保护现场
push di
push dx
cmp si,di ;如果si>=di,则直接返回
jge return
call Partition ;调用函数,整理数组,返回值dx,在dx左边的数字都不大于右边的数字
push di ;保存di
mov di,dx ;对于si和bx范围内的数组,递归调用本函数
call QuickSort
pop di ;取出di
mov si,dx ;对dx+1和di范围内的数组,递归调用本函数
inc si
call QuickSort
return:
pop dx ;寄存器出栈,恢复现场
pop di
pop si
ret

QuickSort endp

;函数功能:把si指向的数组,转变成一个显示的字符串,并输出
;输入参数:si 指向需要处理的数组
; di 保存转换后字符串的位置
;输出参数:无(直接在屏幕上显示字符串)
DisplayArray proc near

push ax ;寄存器入栈
push bx
push cx
push dx
push di
push si

mov cl,[len] ;设置循环次数等于数组元素个数
mov ch,0
push di ;di指向的是存放转换后字符串的地方,这个地方最后赋值给dx,可以显示
DA_l1: ;因为每个数字最多为三位,所以下面直接进行处理,不写通用的转换程序
mov al,[si] ;当前处理的数字放入ax
mov ah,0
mov bl,100 ;16位除8位的除法
div bl
cmp al,0 ;如果商为0,跳转到处理余数的部分
jz DA_s1
add al,'0' ;商不为0,则加上30H,放入di指向的位置,di后移
mov [di],al 
inc di
mov al,ah ;把余数除10,然后ax加上3030H,在用16位长度放入[di]中
mov ah,0
mov bl,10
div bl
add ax,3030H
mov word ptr [di],ax
add di,2
mov byte ptr [di],',' ;后面加逗号
inc di ;di后移
inc si ;已经一个数字处理结束,si后移
jmp DA_s3 ;跳到loop的地方
DA_s1:
mov al,ah ;数字不是三位数,把余数放入al,再除10
mov ah,0
mov bl,10
div bl
cmp al,0 ;比较商是否为0,如果为0,说明是一位数,跳转到下面
jz DA_s2
add al,'0' ;如果商不为0,则是两位数,商加30H,放入[di],di后移
mov [di],al
inc di
DA_s2:
add ah,'0' ;把余数加上30H,放入[di],di后移
mov [di],ah
inc di
mov byte ptr [di],',' ;放入逗号
inc di
inc si ;一个数字处理完成,si后移
DA_s3:
loop DA_l1 ;继续处理下一个数字
dec di ;现在字符串最后一位是逗号,把这个逗号换成'$'就好了
mov byte ptr [di],'$'
pop dx ;把指向字符串第一个字符的位置出栈,赋值给bx
mov ah,09H ;调用中断输出
int 21H
pop si ;寄存器出栈
pop di
pop dx
pop cx
pop bx
pop ax
ret

DisplayArray endp

;函数功能:把输入的字符串整理为数组
;输入参数:无
;输出参数:cx 整理出来的数组长度,数组存放在table里面
SaveToArray proc near

mov cl,[buf+1] ;输入字符串的长度
mov ch,0
lea si,[buf+2] ;指向输入字符串的起始位置
lea di,[table] ;指向存放整理后数字的位置
mov bh,0 ;bh存放的是数组长度
SaveToArray_l1:
cmp byte ptr [si],'0' ;如果这个字符不在0~9范围内,说明上一个数字输入结束
jb EndOfANum
cmp byte ptr [si],'9'
ja EndOfANum
mov al,[di] ;如果这个字符是数字字符,那么把原来的值乘10,加上这个数字,减去30H
mov bl,10
mul bl
mov bl,[si]
sub bl,'0'
add al,bl
mov [di],al ;保存回去
jmp short continue ;继续下一次循环
EndOfANum: ;上一个数字输入完毕
cmp byte ptr [di],0 ;如果现在[di]的值为0,那不进行处理,因为已经规定,数字不为0
jz continue
inc di ;如果[di]不为0,则di后移一位
inc bh ;数组长度加1
continue:
inc si ;si后移一位
loop SaveToArray_l1 ;循环
inc bh ;最后找到的一个数字,还没有计算进数组长度,所以这里加了1
mov [len],bh ;数组长度保存到[len]
mov cl,bh ;数组长度保存到cx
mov ch,0
ret

SaveToArray endp

code ends
end start
程序代码:
 
/*****************************************
函数功能:快速排序
作者:永夜的极光
调用:QuickSort(a,p,r)
其中 a为待排序数组的数组名,p为排序范围的第一个数字的下标,r为最后一个数字的下标
*****************************************/
int Partition(int a[],int p,int r)
{
int x=a[p],i=p-1,j=r+1;
while(1)
{
do
{
j--;
}while(a[j]>x);
do
{
i++;
}while(a[i]<x);
if(i<j)
{
int tmp=a[i];
a[i]=a[j];
a[j]=tmp;
}
else
return j;
}
}

void QuickSort(int a[],int p,int r)
{
if(p<r)
{
int q=Partition(a,p,r);
QuickSort(a,p,q);
QuickSort(a,q+1,r);
}
}
9楼 利用自定义的库函数,输出任意不大于FFFFFFFF的10进制数
程序代码:
 
;显示不大于FFFFFFFF的任意10进制数
;作者:永夜的极光
;时间:2007-11-19

;程序运行结果:
;12666
;123468
;120
;150000
;9999999
;1031846
;215554151

;注意:mylib1.lib这个文件是通过命令lib mylib1 +2 +3生成的,利用到了2.asm,3.asm生成的两个obj文件
includelib mylib1.lib
extrn dtoc:far
.model small
.data
num dd 12666,123468,120,150000,9999999,1031846,215554151 ;待显示的数字,32位
len equ ($-num)/type num ;保存上面数字的个数($表示当前地址,$-num表示当前地址和num的地址的差值,type num表示num占的字节数)
dis db 16 dup (0) ;存放待显示字符的空间
crlf db 13,10,'$' ;换行符
.stack 128 ;定义栈
.code
mov ax,@data ;用这种精简方式定义各种段,如果要取得地址的话,就要用这种方式
mov ds,ax ;因为masm5.0不支持".startup",所以只能自己设置ds,ss和sp不用管,编译器会自动设置
lea bx,[num] ;指向要显示的数字
mov cx,len ;数字个数
l1:
mov ax,[bx] ;ax保存低16位
mov dx,[bx+2] ;dx保存高16位
lea si,[dis] ;di指向存放字符的空间
call dtoc ;调用函数,将dx:ax的数字转换后保存在si指向的空间,函数定义见2.asm
lea dx,[dis] ;显示转换后的字符串
mov ax,0900H
int 21H
lea dx,[crlf] ;显示换行符
int 21H
add bx,4 ;bx指向下一个要显示的数字
loop l1
mov ax,4c00H
int 21H
end
程序代码:
 
;函数功能:将输入的一个数,转换为10进制字符串
;输入参数:ds:si 一段空闲空间的起始点,字符串从这里开始写
; ax 待转换数字的低16位
; dx 待转换数字的高16位
;输出参数:从指定的位置开始的一个字符串,以'$'结尾 
public dtoc
extrn divdw:far
segdotc segment 'code'
assume cs:segdotc ;如果函数中使用到了标号,这一句不能没有,不然编译器会提示没找到cs
dtoc proc far
push bx ;寄存器入栈
push cx
push ax
push dx
push si
mov bx,0 ;bx记录转换后数字的位数
dtoc_l1:
mov cx,10 ;每次循环,除10
call divdw ;调用32位除16位不溢出除法,函数定义见3.asm
inc bx ;位数加1
push cx ;先把余数入栈,每次循环都把余数入栈,等最后循环结束,逐个出栈就可以得到转换后的结果

cmp ax,0 ;如果ax不为0,继续循环
jnz dtoc_l1
cmp dx,0 ;如果dx不为0,继续循环
jnz dtoc_l1

mov cx,bx ;ax,dx都为0,说明转换结束,现在bx保存的就是转换后的位数
dtoc_l2:
pop bx ;原来保存在栈里面的余数出栈,刚好利用栈的先进后出
add bx,30H ;变成字符
mov [si],bl ;存入指定的位置
inc si ;指针后移
loop dtoc_l2 ;循环
mov byte ptr [si],'$' ;最后加上'$'
pop si ;寄存器出栈
pop dx
pop ax
pop cx
pop bx
ret
dtoc endp
segdotc ends
end
程序代码:
 
;不会溢出的除法(32位除16位,商如果只能是16位,则可能溢出,比如FFFFF div 1)
;因此,把商也要增大到32位
;输入参数:ax 保存被除数低16位,dx 保存被除数高16位,cx 保存除数
;输出参数:ax 保存商低16位,dx 保存商高16位,cx保存余数
;计算方法:商的高16位=被除数高16位 div 除数 的 商
; 商的低16位=((被除数高16位 div 除数 的 余数)*FFFF+被除数低16位)div 除数 的 商
; 余数=上式的余数
public divdw
segdivdw segment 'code'
divdw proc far
push bx ;保存bx
mov bx,ax ;先把低16位保存
mov ax,dx ;高16位移到低16位
mov dx,0 ;高16位置零
div cx ;除
push ax ;保存商
mov ax,bx ;取出原来保存的低16位,高16位是上次除法的余数,已经在dx里面了
div cx ;再除
mov cx,dx ;把余数保存到cx
pop dx ;把第一次除的商,放入dx,作为结果的高16位
pop bx ;恢复bx
ret ;返回
divdw endp
segdivdw ends
end
11楼 利用宏汇编和条件汇编,实现子程序的寄存器入栈和出栈
程序代码:
 
;****************************************************
;文件名:mac1.inc
;包括宏:pushonereg,poponereg,pushreg,popreg,procstart,procend
;作者:永夜的极光
;制作时间:2007-11-21
;****************************************************

;功能:根据前两个参数的测试结果,决定是否将第三个参数指定的寄存器压栈
;pushnum 用二进制保存需要压栈的寄存器
;testnum 测试某一位是否为0的数字
;reg 要压栈的寄存器
pushonereg macro regnum,testnum,reg
if (regnum and testnum) ;条件汇编
push reg
endif
endm
;功能:根据前两个参数的测试结果,决定是否将第三个参数指定的寄存器出栈
;popnum 用二进制保存需要出栈的寄存器
;testnum 测试某一位是否为0的数字
;reg 要出栈的寄存器
poponereg macro regnum,testnum,reg
if (regnum and testnum)
pop reg
endif
endm
;功能:根据传入的参数,将指定的寄存器压栈
;regnum 指定寄存器,每个二进位对应一个寄存器是否入栈,如果为1则入栈,0则不入,每个位和寄存器的对应关系从下面的程序可以看出来
pushreg macro regnum
pushonereg regnum,10000000b,ax
pushonereg regnum,01000000b,bx
pushonereg regnum,00100000b,cx
pushonereg regnum,00010000b,dx
pushonereg regnum,00001000b,ds
pushonereg regnum,00000100b,es
pushonereg regnum,00000010b,si
pushonereg regnum,00000001b,di
endm
;功能:根据传入的参数,将指定的寄存器出栈
;regnum 指定寄存器,每个二进位对应一个寄存器是否出栈,如果为1则出栈,0则不出,每个位和寄存器的对应关系从下面的程序可以看出来
;注意,对应关系于上面的宏完全相同,不过根据栈的先进后出特性,要把顺序颠倒过来
popreg macro regnum
poponereg regnum,00000001b,di
poponereg regnum,00000010b,si
poponereg regnum,00000100b,es
poponereg regnum,00001000b,ds
poponereg regnum,00010000b,dx
poponereg regnum,00100000b,cx
poponereg regnum,01000000b,bx
poponereg regnum,10000000b,ax
endm
;功能:在每个子程序的开头,实现寄存器的压栈和预留空间给局部变量
;regnum 用二进制表示的需要入栈的寄存器
;varlen 需要预留多少空间给局部变量,以byte为单位
procstart macro regnum,varlen
push bp
mov bp,sp
ifnb <varlen> ;条件汇编,如果varlen有对应的实参,才执行下面的语句
sub sp,varlen
endif
pushreg regnum
endm
;功能:在每个子程序的末尾,实现寄存器的压栈和返回(可以用ret n的格式)
;regnum 用二进制表示的需要出栈的寄存器
;retnum 程序返回后,sp还要加上的数字,也就是ret n中的n
procend macro regnum,retnum
popreg regnum
mov sp,bp
pop bp
ifnb <retnum>;条件汇编,如果retnum有对应的实参,才执行下面的语句,否则执行else后面的语句
ret retnum
else
ret
endif
endm
程序代码:
 
;利用自定义的宏文件,实现子函数中寄存器的入栈和出栈.
;作者:永夜的极光
;时间:2007-11-21
;运行结果:无
;用debug -u 查看编译后的程序如下:
;0CF1:0000 B8F60C MOV AX,0CF6
;0CF1:0003 8ED8 MOV DS,AX
;0CF1:0005 B83412 MOV AX,1234
;0CF1:0008 BB7856 MOV BX,5678
;0CF1:000B E81000 CALL 001E
;0CF1:000E B83412 MOV AX,1234
;0CF1:0011 50 PUSH AX
;0CF1:0012 B87856 MOV AX,5678
;0CF1:0015 50 PUSH AX
;0CF1:0016 E81300 CALL 002C
;0CF1:0019 B8004C MOV AX,4C00
;0CF1:001C CD21 INT 21
;0CF1:001E 55 PUSH BP
;0CF1:001F 8BEC MOV BP,SP

;0CF1:0021 53 PUSH BX
;0CF1:0022 03C3 ADD AX,BX
;0CF1:0024 BBBC9A MOV BX,9ABC
;0CF1:0027 5B POP BX
;0CF1:0028 8BE5 MOV SP,BP
;0CF1:002A 5D POP BP
;0CF1:002B C3 RET
;0CF1:002C 55 PUSH BP
;0CF1:002D 8BEC MOV BP,SP
;0CF1:002F 83EC02 SUB SP,+02
;0CF1:0032 51 PUSH CX
;0CF1:0033 52 PUSH DX
;0CF1:0034 8B4E04 MOV CX,[BP+04]
;0CF1:0037 8B5606 MOV DX,[BP+06]
;0CF1:003A C746FE0000 MOV WORD PTR [BP-02],0000
;0CF1:003F 014EFE ADD [BP-02],CX

;0CF1:0042 0156FE ADD [BP-02],DX
;0CF1:0045 8B46FE MOV AX,[BP-02]
;0CF1:0048 5A POP DX
;0CF1:0049 59 POP CX
;0CF1:004A 8BE5 MOV SP,BP
;0CF1:004C 5D POP BP
;0CF1:004D C20400 RET 0004
.model small
include mac1.inc
.data
.stack 128
.code
mov ax,@data
mov ds,ax
mov ax,1234H
mov bx,5678H
call add1 ;调用函数1,求ax和bx的和,结果保存在ax
mov ax,1234H ;函数2也是求两个数的和,不过是用栈的方式来传递,结果通过ax返回
push ax ;先把两个参数压栈
mov ax,5678H
push ax
call add2 ;调用函数2
mov ax,4c00H
int 21H
add1 proc near
add1_regnum equ 01000000b ;保护bx的值,其实这个小函数是不需要改变bx的,这个只是作为一个例子
procstart add1_regnum ;程序起始,第二个参数可以省略,就是不需要为局部变量预留空间
add ax,bx
mov bx,9abcH ;随便改变bx的值
procend add1_regnum ;程序结束,第二个参数可以省略,那么程序只是简单的返回,不在返回后再修改sp
;程序最后不再需要ret,不过写上也没关系,因为不可能执行的到
add1 endp
add2 proc near
add2_regnum equ 00110000b ;保护cx和dx的值,也是为了作为例子
procstart add2_regnum,2 ;给这个函数预留了两个byte的保存局部变量的空间,用[bp-1]和[bp-2]访问
;如果要访问输入的参数,用[bp+4]和[bp+6].而[bp]保存的是原来bp的值,[bp+2]保存的是调用函数前压栈的IP,也就是这个函数运行完毕后,用ret返回的位置
mov cx,[bp+4] ;cx保存第二个数
mov dx,[bp+6] ;dx保存第一个数,要注意,先压栈的数所在地址比较大
mov word ptr [bp-2],0 ;把局部变量的位置清0
add [bp-2],cx ;加上cx
add [bp-2],dx ;加上dx
mov ax,[bp-2] ;把局部变量的值传递给ax
;实际上,求两个数的和不必要这么麻烦,只是为了演示局部变量和保护寄存器才写的这么长
procend add2_regnum,4 ;调用这个函数前,压栈了两个16位的数,所以返回时sp要多增加4个byte

add2 endp
end

从BFS(Breadth First Study)到DFS(Depth First Study)
2007-12-18 08:27
快速回复:[原创]初学汇编,写的几个小程序(超详细注释)(11.21更新)
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.026297 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved