汇编实现二叉树

今天翻找以前的代码, 突然发现, 以前曾经用汇编写的二叉树, 以前对汇编过于执着. 是好事, 也是坏事. 个人觉得, 用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

 

 

发表评论


Verify Code   If you cannot see the CheckCode image,please refresh the page again!