1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
|
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
|