今天翻找以前的代码, 突然发现, 以前曾经用汇编写的二叉树, 以前对汇编过于执着. 是好事, 也是坏事. 个人觉得, 用MASM写代码其实和C语言的差别是非常小的, 但是毕竟是不同的语言, 就和C和C++的差别一样, C++在C的基础之上多了一点东西, 多的这点东西带来的是思想上的转变, 汇编也是一样, 用汇编写的代码, 我觉得带来的其实也是一种思想. 一种用汇编想问题的思想.
有个朋友C++学的很不错, 但是他始终对代码编译出的二进制模型比较模糊, 当然如果使用汇编, 长期养成的习惯就不会有这个问题了. 当然这个汇编不只是32位了.. 16位现在虽然已经过时了. 但是不可否认从16位开始学起, 会更加了解什么是代码, 什么是数据. 目前的masm的版本是11了. 跟随着vs2012发布, 很多人写16位的代码还是用masm5. 究其原因就是16位的代码都用masm5, 32位都用masm6xx以上, 其实这种观点是不对的, 包括最新的masm11在内, 都是可以写16位的代码的, 我要特别提到这点, 空间里面有很多的保护模式下面的文章, 我都是用masm9写的.. 哪个说不能写16位代码??
使用汇编是其中一个, 另外一个为了组件自己的开发环境, 没有使用masm32包, 我自己搞了个大杂烩 我当时使用了vs2010 ml nmak rc link等工具, 外加cl. 然后把Windows SDK的头文件, CRT头文件, MFC头文件, 库文件打包到一起, 开发包不到50M.. 写C/C++ MFC. 都够了.
虽然现在基本不怎么用汇编写代码了. 但是感谢曾经有那么一段执着的日子. 下面这段二叉树操作的代码摘抄自曾经写的一个<<学员管理系统>> 可以看到里面调用了很多类C的函数, 比如cprintf. 这个函数在MASM32里面是真实存在的, 只是名字是_imp___cprintf. 还有MASM32的作者对msvcrt.lib进行了改动, 他改动的这个msvcrt.lib可以用在汇编上, 另外剔除了C++ 使用的一些库函数, 只保留了C的一些函数, 比如printf, scanf啊一类的.. 而且这个库有个优点, 不用初始化, C/C++里面带那个msvcrt.lib必须给初始化一下非常恶心.. so 汇编也可以调用C的函数和系统提供的api..
二叉树的话, 也是增, 删, 改, 查. 下面基本都有了. 比较难的是删除. 删除考虑的事情有点多.. 另外二叉树也是不平衡的, 最坏的情况就变链表了.. 所以二叉树还应该做平衡, 有时间再看看AVL树..
PATH_ASSEMBLY equ 1
Include Common.inc
NameStudent struct
lpStudent dword ? ;当前学员地址
dwNumber dword ? ;当前学员编号
lpLeft dword ? ;左节点指针
lpRight dword ? ;右节点指针
lpBefor dword ? ;如果有重复的话, 前一个名称地址
lpNext dword ? ;如果有重复的话, 下一个名称地址
SzName byte 32 dup(?) ;学员名字
NameStudent ends
NumberStudent struct
lpStudent dword ? ;当前学员地址
dwNumber dword ? ;学员编号
lpLeft dword ? ;左节点指针
lpRight dword ? ;右节点指针
NumberStudent ends
.data?
SzIniFile byte MAX_PATH dup(?) ;ini文件名称
dwCount dword ? ;遍历计数器
.code
;============================================================================
;遍历学员信息
;_lpStProInfo:程序信息头
;_lpStNumberHead:编号树头节点
;返回true表示遍历成功, 返回false表示没有节点给遍历
_IterateStudentNode proc public _lpStProInfo:dword, _lpStNumberHead:dword
assume ebx:ptr NumberStudent
assume esi:ptr Student
assume edi:ptr Score
assume edx:ptr Course
mov ebx, _lpStNumberHead
.if ebx != null
;============================================================================
;先从左边开始遍历, 如果左边有那么就会一直追溯进去, 在堆栈中
;很明显就会形成大的在上面, 小的在下面, 然后在左边遍历完了以后
;开始返回, 返回一个打印一个, 达到了遍历的目的
;============================================================================
Invoke _IterateStudentNode,_lpStProInfo, [ebx].lpLeft
mov ebx, _lpStNumberHead
mov esi, [ebx].lpStudent
Invoke cprintf, cfm$( "============================================================================\n" )
Invoke cprintf, cfm$( "学员编号: %d\n学员名字: %s\n课程总数: %d\n" ), \
[esi].dwNumber, addr [esi].szName, [esi].dwScoreLen
lea edi, [esi].StScore
mov ecx, [esi].dwScoreLen
@@:
;============================================================================
;对于右边就先打印了才去遍历, 因为右边是从小到大, 总之这样就
;完成了遍历
;============================================================================
push ecx
Invoke _FindCourseNode, _lpStProInfo, [edi].dwCourse ;去查找该课程的字符串表示
mov edx, eax
.if !edx
Invoke cprintf, cfm$( "课程编号: %d(无效课程, 请尽快修改)\n课程分数: %d\n" ),\
[edi].dwCourse, [edi].dwScore
.else
Invoke cprintf, cfm$( "课程编号: %d\n课程名称: %s\n任课老师: %s\n课程分数: %d\n" ),\
[edi].dwCourse, addr [edx].szName, addr [edx].szTeach, [edi].dwScore
.endif
add edi, 8
pop ecx
loop @b
inc dwCount
;============================================================================
;如果用户输入了想退出, 因为这里是链表, 也许会有很多层, 很难跳
;我这里就在外面挂了一个异常, 如果想跳出去, 直接触发一样..
;外面捕获自己就出去了
;============================================================================
.if dwCount == 20
push 0
pop dwCount
Invoke Jpause
.if !eax
xor eax, eax
mov dword ptr [eax], 0
.endif
.endif
Invoke _IterateStudentNode,_lpStProInfo, [ebx].lpRight
.endif
_ret:
ret
_IterateStudentNode endp
;============================================================================
;以编号查询一个学生
;_lpStNumberHead:编号树头
;_dwNumber:要查找的编号
;找到返回该节点指针, 没有找到返回NULL
_FindNumberStudnet proc public _lpStNumberHead:dword, _dwNumber:dword
assume ebx:ptr NumberStudent
mov ebx, _lpStNumberHead
.if ebx != null
mov eax, [ebx].dwNumber
.if eax < _dwNumber ;大于向右
mov ebx, dword ptr [ebx].lpRight ;这里已经是二级指针了, 不用转了
Invoke _FindNumberStudnet, ebx, _dwNumber
.elseif eax > _dwNumber ;小于向左
mov ebx, dword ptr [ebx].lpLeft ;这里已经是二级指针了, 不用转了
Invoke _FindNumberStudnet,ebx, _dwNumber
.elseif eax == _dwNumber ;等于返回
mov eax, [ebx].lpStudent
.endif
.else
xor eax, eax
.endif
_ret:
ret
_FindNumberStudnet endp
;============================================================================
;以名称查询一个学生
;_lpStNumberHead:编号树头
;_lpSzName:要查找的名字
;找到返回树中节点的指针, 在外面自己要重新做解析, 没有找到返回NULL
_FindNameStudnet proc public _lpStNameHead:dword, _lpSzName:dword
;============================================================================
assume ebx:ptr NameStudent
mov ebx, _lpStNameHead
.if ebx != null
lea ecx, [ebx].SzName
Invoke lstrcmp, ecx, _lpSzName
cmp eax, 0 ;.if eax > 0
jg _else2
cmp eax, 0 ;.elseif eax < 0
jl _else1
jmp _else3 ;.elseif eax == 0
;============================================================================
_else1:
mov ebx, dword ptr [ebx].lpRight ;大的到右边
Invoke _FindNameStudnet,ebx, _lpSzName
jmp _ret
_else2:
mov ebx, dword ptr [ebx].lpLeft ;小的到左边
Invoke _FindNameStudnet, ebx, _lpSzName
jmp _ret
_else3:
;============================================================================
;注意, 这里返回的指针, 和其他函数有点不一样.由于这里需要返回多个同名
;的结构指针, 所以直接把内部的链表结构指针返回了
;============================================================================
mov eax, ebx
.else
xor eax, eax
.endif
_ret:
ret
_FindNameStudnet endp
;============================================================================
.data?
lpFather dword ?
.code
;============================================================================
;查找某个需要删除的值的位置, 并保存其父节点
;============================================================================
_FindNumberNode proc private uses ebx _lpCurHead:dword, _lpHead:dword, _dwValue:dword
;==============================================================
assume ebx:ptr NumberStudent
mov ebx, _lpHead
;mov ebx, dword ptr [ebx]
.if ebx != NULL
mov eax, [ebx].dwNumber ;学员编号
.if eax > _dwValue ;从左边查找
Invoke _FindNumberNode, ebx, [ebx].lpLeft, _dwValue
.elseif eax < _dwValue ;从右边查找
Invoke _FindNumberNode, ebx, [ebx].lpRight, _dwValue
.else
push _lpCurHead ;将父节点保存起来
pop lpFather
mov eax, ebx ;将当前节点返回
.endif
.else
xor eax, eax
.Endif
ret
_FindNumberNode Endp
;============================================================================
;在一个树中, 从右边查找比头节点只大一号的值, 这个值一定是从该树的右子树, 一直往
;左, 直到左边的那个节点的左子树为NULL为止..
;lpRightFather:这个值的作用是, 如果右边找到的最小值如果其右子树还有值, 那么就要
;把他们挂上来...
_FindNumberRightMin proc private _lpHead:dword
assume ebx:ptr NumberStudent
mov ebx, _lpHead
.if [ebx].lpLeft != NULL ;如果右子树不为NULL
Invoke _FindNumberRightMin, [ebx].lpLeft;那就直到查找到为NULL为止
.else
mov eax, ebx ;遍历到了最右边的返回就可以了
.endif
ret
_FindNumberRightMin endp
;============================================================================
;删除一个学员节点(同时删除在编号树中的节点)
;_lpStProInfo:程序信息头
;_dwNumber:要删除的编号
_DelNumberStudentNodo proc public _lpStProInfo:dword, _dwNumber:dword
;============================================================================
local _lpDelNode :dword ;需要删除的节点
local _lpWrieteMem :dword ;要写入内存中的值
assume ebx:ptr NumberStudent
assume esi:ptr NumberStudent
push -1
pop _lpWrieteMem
push NULL
pop lpFather
;============================================================================
mov eax, _lpStProInfo
mov eax, [eax+ProInfo.lpNumberStudent]
Invoke _FindNumberNode, NULL, eax, _dwNumber ;查找某个需要删除的值
mov _lpDelNode, eax
mov ebx, _lpDelNode
;如果左子树和右子树都为NULL删除就比较容易了
.if [ebx].lpLeft == NULL && [ebx].lpRight == NULL
;if要删除的父节点为NULL(说明整个程序只有一个节点)
.if lpFather == NULL
mov _lpWrieteMem, NULL ;;写入NULL到_lpWrieteMem
;else要删除的父节点不为NULL判断该节点是左还是右, 置为NULL就可以了
.else
mov ebx, lpFather
mov eax, _lpDelNode
.if [ebx].lpLeft == eax ;如果是左边
mov [ebx].lpLeft, NULL ;左边置NULL
.else
mov [ebx].lpRight, NULL ;右边置为NULL
.endif
.endif
;如果左子树和右子树都不为NULL
.elseif [ebx].lpLeft != NULL && [ebx].lpRight != NULL
;在右子树中查找最小的一个值的指针
Invoke _FindNumberRightMin, [ebx].lpRight
.if !eax
jmp _ret
.endif
;将左子树挂到右子树中最小的那个节点的左子树中
mov ebx, _lpDelNode
push [ebx].lpLeft
mov ebx, eax
pop [ebx].lpLeft
;if要删除的父节点为NULL(说明要删除的是头节点
.if lpFather == NULL
mov ebx, _lpDelNode
push [ebx].lpRight ;将要删除的右子树作为新的头节点
pop _lpWrieteMem
;否则需要判断是需要删除的节点是左子树还是右子树
.else
mov ebx, lpFather
mov eax, _lpDelNode
mov esi, _lpDelNode
push [esi].lpRight
.if [ebx].lpLeft == eax
pop [ebx].lpLeft ;如果要删除的节点原来在左边就挂左边
.else
pop [ebx].lpRight ;如果要删除的节点原来在右边就挂右边
.endif
.endif
;如果只有左边为NULL
.elseif [ebx].lpLeft == NULL
;if如果要删除的父节点为NULL
.if lpFather == NULL
push [ebx].lpRight ;右节点成为新的头节点
pop _lpWrieteMem ;写入_lpWrieteMem中
;如果要删除的父节点不为NULL
.else
push [ebx].lpRight ;要删除节点的右子树
mov ebx, _lpDelNode
mov esi, lpFather ;将右边挂到父节点下面
.if [esi].lpLeft == ebx
pop [esi].lpLeft ;左边相等挂左边
.else
pop [esi].lpRight;右边相等挂右边
.endif
.endif
;如果只有右子树为NULL
.elseif [ebx].lpRight == NULL
;if如果要删除的父节点为NULL
.if lpFather == NULL
push [ebx].lpRight ;左节点成为新的头节点
pop _lpWrieteMem ;写入_lpWrieteMem中
;如果要删除的节点不为NULL
.else
push [ebx].lpLeft
mov esi, lpFather ;被删除值的左子树
.if [esi].lpLeft == ebx
pop [esi].lpLeft ;将左边挂到父节点下面
.else
pop [esi].lpRight ;如果右边相等就挂右边
.endif
.endif
.endif
;.if _lpWrieteMem != -1
.if _lpWrieteMem != -1 ;如果不为NULL就写入ini文件, 和内存中
mov ebx, eax
Invoke JWritePrivateProfileInt,\ ;写入注册表中
offset SzSection, offset SzKeylpNumStu, _lpWrieteMem , offset SzIniFile
.if !eax
jmp _ret
.endif
mov esi, _lpStProInfo ;更新文件中的结构
push _lpWrieteMem
pop [esi+ProInfo.lpNumberStudent]
.endif
;释放内存
mov ebx, _lpDelNode
Invoke Jfree, [ebx].lpStudent
Invoke Jfree, ebx
_ret:
ret
_DelNumberStudentNodo endp
;============================================================================
;插入一个节点到编号树中(是个递归函数)
;_lpStNumberHead:头节点指针, 二级指针
;_lpNewStudent:新学员指针
;_dwNumber:编号
;成功返回生成的头指针, 否则返回false
_InsertNumberTree proc private _lpStNumberHead:dword, _lpNewStudent:dword, _dwNumber:dword
;============================================================================
assume ebx:ptr NumberStudent
assume esi:ptr NumberStudent
;============================================================================
;如果树目前不是空的, 那么就递归调用, 直到空为止
;============================================================================
mov ebx, _lpStNumberHead
mov eax, dword ptr [ebx] ;二级指针
.if eax
mov eax, _dwNumber
;这句非常重要, 因为这里是3级指针, 比上面的又多加了一层..
;昨天调试了一晚上就是这个地方
mov ebx, dword ptr [ebx]
.if [ebx].dwNumber <= eax ;大的到右边
lea ecx, [ebx].lpRight
Invoke _InsertNumberTree, ecx, _lpNewStudent, _dwNumber
.else ;小的到左边
lea ecx, [ebx].lpLeft
Invoke _InsertNumberTree,ecx , _lpNewStudent, _dwNumber
.endif
.else
;============================================================================
; 生成一个新的树节点
;============================================================================
Invoke Jmalloc, sizeof NumberStudent
.if !eax
jmp _ret
.endif
xchg esi, eax
push _lpNewStudent ;新的头节点
pop [esi].lpStudent
push null
pop [esi].lpLeft ;左子树为NULL
push null
pop [esi].lpRight ;右子树为NULL
push _dwNumber
pop [esi].dwNumber ;编号
mov dword ptr [ebx], esi ;返回该地址指针
mov eax, esi
.endif
;============================================================================
_ret:
ret
_InsertNumberTree endp
;============================================================================
;插入一个节点到名称树中(是个递归函数)
;_lpStNumberHead:头节点指针, 二级指针
;_lpNewStudent:新学员指针
;_lpSzName:名字
;成功返回生成的头指针, 否则返回false
_InsertNameTree proc private _lpStNumberHead:dword, _lpNewStudent:dword, _lpSzName:dword
assume ebx:ptr NameStudent
assume esi:ptr NameStudent
;============================================================================
;如果树目前不是空的, 那么就递归调用, 直到空为止
;============================================================================
mov ebx, _lpStNumberHead
mov eax, dword ptr [ebx] ;二级指针
.if eax
mov eax, _lpSzName
;这句非常重要, 因为这里是3级指针, 比上面的又多加了一层..
;昨天调试了一晚上就是这个地方
mov ebx, dword ptr [ebx]
lea ecx, [ebx].SzName
Invoke lstrcmp, ecx, eax
cmp eax, 0 ;.if eax > 0
jg _else1
cmp eax, 0 ;.elseif eax < 0
jl _else2
jmp _else3 ;.elseif eax == 0
;============================================================================
_else1:
lea ecx, [ebx].lpLeft ;小的到左边
Invoke _InsertNameTree,ecx , _lpNewStudent, _lpSzName
jmp _ret
_else2:
lea ecx, [ebx].lpRight ;大的到右边
Invoke _InsertNameTree, ecx, _lpNewStudent, _lpSzName
jmp _ret
_else3:
;============================================================================
; 插入同名的学员, 远比我们想象的复杂
;============================================================================
Invoke Jmalloc, sizeof NameStudent
.if !eax
jmp _ret
.endif
xchg esi, eax
push _lpNewStudent ;新的头节点
pop [esi].lpStudent
push null
pop [esi].lpLeft ;左子树为NULL
push null
pop [esi].lpRight ;右子树为NULL
push NULL
pop [esi].lpNext ;下一个同名节点为NULL
mov eax, _lpNewStudent
mov eax, [eax+Student.dwNumber] ;填写学员编号
push eax
pop [esi].dwNumber
Invoke RtlMoveMemory, addr [esi].SzName, _lpSzName, sizeof Student.szName
;============================================================================
;这里也是由原来的单向链表我修改成了双向的, 不过还是在链表末尾添加
;============================================================================
.if [ebx].lpNext == NULL
mov [ebx].lpNext, esi ;将同名的学员链在学员后面
push ebx
pop [esi].lpBefor ;之前一个就置为ebx, 这样就双向了
.else
@@: mov ebx, [ebx].lpNext
or [ebx].lpNext, 0 ;在同名学员链表最后添加
jnz @b
push esi ;天下下一个
pop [ebx].lpNext
push ebx ;之前的一个就是保存的前一个节点了
pop [esi].lpBefor
.endif
mov eax, esi ;返回该地址指针
;============================================================================
.else
;============================================================================
; 生成一个新的树节点
;============================================================================
Invoke Jmalloc, sizeof NameStudent
.if !eax
jmp _ret
.endif
xchg esi, eax
push _lpNewStudent ;新的头节点
pop [esi].lpStudent
push null
pop [esi].lpLeft ;左子树为NULL
push null
pop [esi].lpRight ;右子树为NULL
push NULL
pop [esi].lpNext ;下一个同名节点为NULL
push NULL
pop [esi].lpBefor ;前一个同名节点为NULL
mov eax, _lpNewStudent
mov eax, [eax+Student.dwNumber] ;填写学员编号
push eax
pop [esi].dwNumber
Invoke RtlMoveMemory, addr [esi].SzName, _lpSzName, sizeof Student.szName
mov dword ptr [ebx], esi ;返回该地址指针
mov eax, esi
.endif
_ret:
ret
_InsertNameTree endp
;============================================================================
;将一条学员信息插入到树中
;_lpStProInfo:程序基本信息结构
;_lpStStudent:指向学员信息结构
;成功返回true, 否则返回false
_AddStudentNode proc public _lpStProInfo:dword, _lpStStudent:dword
;============================================================================
local _lpNewStudent :dword
local _dwLen :dword
local _dwBuf :dword
assume ebx:ptr Student
assume esi:ptr ProInfo
mov ebx, _lpStStudent
mov esi, _lpStProInfo
;============================================================================
; 在文件中构造一个新的学员对象
;============================================================================
mov eax, [ebx].dwScoreLen
lea eax, [eax*8]
add eax, sizeof Student - 8
xchg _dwLen, eax ;由于学员信息结构是变长的, 这里求长度
Invoke Jmalloc, _dwLen ;分配一个学员大小的内存
.if !eax
jmp Error
.endif
xchg _lpNewStudent, eax
Invoke RtlMoveMemory, _lpNewStudent, _lpStStudent, _dwLen
Invoke lstrcpy, offset SzIniFile, offset SzCurrentDir
invoke lstrcat, offset SzIniFile, offset SzIniPath
;============================================================================
; 将新建立的学员节点插入树中(编号树)
;============================================================================
mov esi, _lpStProInfo
mov ebx, _lpNewStudent
.if [esi].lpNumberStudent == null ;如果是首次插入
;============================================================================
;插入到编号树中
;============================================================================
mov _dwBuf, null
Invoke _InsertNumberTree, addr _dwBuf , _lpNewStudent, [ebx].dwNumber
.if !eax
jmp Error
.endif
mov ebx, eax
Invoke JWritePrivateProfileInt,\ ;写入注册表中
offset SzSection, offset SzKeylpNumStu, ebx , offset SzIniFile
.if !eax
jmp Error
.endif
mov esi, _lpStProInfo ;更新文件中的结构
push ebx
pop [esi].lpNumberStudent
mov _dwBuf, NULL
;============================================================================
;插入到名称树中
;============================================================================
mov ebx, _lpNewStudent ;插入名称树中
Invoke _InsertNameTree, addr _dwBuf, _lpNewStudent, addr [ebx].szName
.if !eax
jmp Error
.endif
mov ebx, eax
Invoke JWritePrivateProfileInt,\ ;写入注册表中
offset SzSection, offset SzKeylpNameStu, ebx , offset SzIniFile
.if !eax
jmp Error
.endif
mov esi, _lpStProInfo ;更新文件中的结构
push ebx
pop [esi].lpNameStudent
;============================================================================
.else
lea ecx, [esi].lpNumberStudent ;插入到编号树中
Invoke _InsertNumberTree, ecx ,_lpNewStudent, [ebx].dwNumber
.if !eax
jmp Error
.endif
mov esi, _lpStProInfo
mov ebx, _lpNewStudent
lea ecx, [esi].lpNameStudent ;插入到名称树中
Invoke _InsertNameTree, ecx ,_lpNewStudent, addr [ebx].szName
.endif
;============================================================================
mov eax, true
Error:
ret
_AddStudentNode endp
End