通讯录

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
MyStack segment stack
db 256 dup(?)
MyStack ends

MyData segment
MENU1 db "1.add", 0dh, 0ah
db "2.delete", 0dh, 0ah
db "3.modify", 0dh, 0ah
db "4.query", 0dh, 0ah
db "5.exit", 0dh, 0ah, '$'
INPUT_ERROR db "input error", 0dh, 0ah, '$'
INPUT_NAME db "input name:", 0dh, 0ah, '$'
INPUT_PHONE db "input phone:",0dh, 0ah, '$'
NAME_BUF db 16, 0, 16 dup(0)
PHONE_BUF db 16, 0, 16 dup(0)
ALL_DATA db 340 dup(0) ;flag[2] name[16] phone [16]
NEXT_LINE db 0DH, 0AH, '$'
DATA_COUNT dw 0 ;数量
MyData ends

MyCode segment
MAIN:
mov ax, MyData
mov ds, ax
mov es, ax

SHOW_MENU:
mov dx, offset MENU1
mov ah, 09h
int 21h ;显示菜单

GET_INPUT:
mov ah, 01h ;获取用户输入
int 21h

cmp al, '1'
jz ADD1
cmp al, '2'
jz DELETE1
cmp al, '3'
jz MODIFY1
cmp al, '4'
jz QUERY1
cmp al, '5'
jz EXIT1
jmp DEFAULT1

ADD1:
mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

;增加联系人
mov dx, offset INPUT_NAME
mov ah, 09h
int 21h

mov dx, offset NAME_BUF
mov ah, 0Ah
int 21h

; 获取输入的字符串长度并添加终止符
mov bx, dx
mov al, byte ptr [bx+1]
mov ah, 0
mov si, ax
mov byte ptr [bx+si+2], '$'

mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

mov dx, offset INPUT_PHONE
mov ah, 09h
int 21h

mov dx, offset PHONE_BUF
mov ah, 0Ah
int 21h

; 获取输入的字符串长度并添加终止符
mov bx, dx
mov al, byte ptr [bx+1]
mov ah, 0
mov si, ax
mov byte ptr [bx+si+2], '$'

mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

; 保存数据
mov bx, offset ALL_DATA
mov ax, ds:[DATA_COUNT]
mov cx, 34
mul cx
add bx, ax ; ary+count*34

; 给标志
mov word ptr [bx], 1
mov cx, 8
mov si, 2
mov bp, offset NAME_BUF

LOOP1:
mov ax, word ptr ds:[bp+si]
mov word ptr [bx+si], ax
add si, 2
dec cx
jnz LOOP1

mov cx, 8
mov si, 18
mov di, 2
mov bp, offset PHONE_BUF
LOOP2:
mov ax, word ptr ds:[bp+di]
mov word ptr [bx+si], ax
add si, 2
add di, 2
dec cx
jnz LOOP2

inc ds:[DATA_COUNT]
jmp SHOW_MENU

DELETE1:
jmp SHOW_MENU

MODIFY1:
jmp SHOW_MENU

QUERY1:
mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

mov bx, offset ALL_DATA
mov cx, ds:[DATA_COUNT]
LOOP3:
cmp word ptr [bx], 0
je LABEL1

lea dx, [bx+2]
mov ah, 09h
int 21h
mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

lea dx, [bx+18]
mov ah, 09h
int 21h
mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

LABEL1:
add bx, 34 ; ary+count*34
dec cx
jnz LOOP3

jmp SHOW_MENU

EXIT1:
mov ax, 4c00h
int 21h

DEFAULT1:
mov dx, offset INPUT_ERROR
mov ah, 09h
int 21h
jmp SHOW_MENU

MyCode ends

end MAIN

数据段定义

1
2
3
MyStack segment stack
db 256 dup(?)
MyStack ends

定义了一个堆栈段,包含256个字节的未初始化数据。

1
2
3
4
5
6
MyData segment
MENU1 db "1.add", 0dh, 0ah
db "2.delete", 0dh, 0ah
db "3.modify", 0dh, 0ah
db "4.query", 0dh, 0ah
db "5.exit", 0dh, 0ah, '$'

定义菜单选项字符串,包含换行符。

1
2
3
INPUT_ERROR db "input error", 0dh, 0ah, '$'
INPUT_NAME db "input name:", 0dh, 0ah, '$'
INPUT_PHONE db "input phone:",0dh, 0ah, '$'

定义错误信息、输入名称提示、输入电话号码提示字符串,字符串以$结尾以便于DOS的09h服务显示。

1
2
NAME_BUF db 16, 0, 16 dup(0)
PHONE_BUF db 16, 0, 16 dup(0)

定义了用于存储名称和电话号码的缓冲区,格式为:第一个字节表示长度,第二个字节是已输入字符数量,后面是实际数据。

输入缓冲区结构

对于0Ah功能码,输入缓冲区的结构如下:

  • 第一个字节:缓冲区的最大长度
  • 第二个字节:实际输入的字符数(由DOS填充)
  • 后续字节:存储实际输入的字符
1
2
3
4
ALL_DATA db 340 dup(0) ;flag[2] name[16] phone [16]
NEXT_LINE db 0DH, 0AH, '$'
DATA_COUNT dw 0 ;数量
MyData ends

定义了一个存储所有联系人信息的缓冲区,每个联系人占34个字节(标志2字节,姓名16字节,电话16字节)。DATA_COUNT记录了联系人数量。

代码段定义

1
2
3
4
5
MyCode segment
MAIN:
mov ax,MyData
mov ds,ax
mov es,ax

设置数据段寄存器DS和附加段寄存器ES指向MyData段。

1
2
3
4
SHOW_MENU:
mov dx,offset MENU1
mov ah,09h
int 21h ;显示菜单

显示菜单选项。

1
2
3
GET_INPUT:
mov ah,01h ;获取用户输入
int 21h

获取用户输入的菜单选项。

处理菜单选项

1
2
3
4
5
6
7
8
9
10
11
cmp al,'1'
jz ADD1
cmp al,'2'
jz DELETE1
cmp al,'3'
jz MODIFY1
cmp al,'4'
jz QUERY1
cmp al,'5'
jz EXIT1
jmp DEFAULT1

根据用户输入的选项跳转到对应的处理代码段。如果输入无效,跳转到DEFAULT1处理。

添加联系人

1
2
3
mov dx, offset NEXT_LINE
mov ah, 09h
int 21h

换行

1
2
3
4
ADD1:   ;增加联系人
mov dx,offset INPUT_NAME
mov ah,09h
int 21h

提示输入联系人姓名。

09h - 显示字符串

功能描述:显示从DX寄存器指向的内存地址开始的字符串,直到遇到字符串结束符号$为止。

输入

  • DX:指向要显示的字符串的地址
  • AH:09h

输出:将字符串显示在屏幕上

1
2
3
mov dx,offset NAME_BUF
mov ah,0Ah
int 21h

读取用户输入的姓名,存储在NAME_BUF中。

0Ah - 输入字符串

功能描述:从标准输入设备(通常是键盘)读取字符串并存储到由DX指向的缓冲区中。用户输入的字符串存储在缓冲区中,以回车键(Enter)结束。

输入

  • DX:指向输入缓冲区的地址
  • AH:0Ah

输出:输入的字符串存储在缓冲区中,第一个字节表示缓冲区的大小,第二个字节表示实际输入的字符数,后续字节存储实际输入的字符。

1
2
3
4
5
mov bx, dx                  ; 将 DX 寄存器的值(缓冲区地址)移动到 BX 寄存器中
mov al, byte ptr [bx+1] ; 从 [BX + 1] 地址处读取实际输入的字符数,存储到 AL 寄存器中
mov ah, 0 ; 将 AH 寄存器置为 0
mov si, ax ; 将 AX 寄存器的值移动到 SI 寄存器中
mov byte ptr [bx+si+2], '$' ; 在输入字符串末尾添加 '$' 字符

将姓名字符串的结尾设为$,以便显示。

  1. mov bx, dx
    • DX 寄存器的值移动到 BX 寄存器。此时,BX 寄存器中存储的是输入缓冲区的地址。
  2. mov al, byte ptr [bx+1]
    • BX 寄存器指向输入缓冲区。
    • 输入缓冲区的结构是:
      • 第一个字节:缓冲区的最大长度
      • 第二个字节:实际输入的字符数
      • 后续字节:存储实际输入的字符
    • [bx+1] 是实际输入的字符数的地址。将该值即实际输入的字符数移动到 al 寄存器中。
  3. mov byte ptr [bx+si+2], '$'
    • SI 寄存器中存储的是实际输入的字符数。
    • [bx+si+2] 是输入字符串末尾的下一个位置。
    • $ 字符存储到 [bx+si+2] 位置,标志输入字符串的结束。

示例

假设缓冲区 NAME_BUF 定义如下:

1
NAME_BUF db 20, 0, 20 dup(0)

输入 "John" 后,缓冲区的内容如下:

1
20, 4, 'J', 'o', 'h', 'n'

执行这段代码后:

  1. BX 指向缓冲区起始地址。
  2. [bx+1] 读取实际输入的字符数 4,存储到 SI
  3. [bx+4+2][bx+6] 位置写入 $,缓冲区变为:
1
20, 4, 'J', 'o', 'h', 'n', '$'

这使得输入的字符串以 $ 结束,可以使用 DOS 中断 21h, 功能码 09h 显示字符串。

1
2
3
mov dx,offset INPUT_PHONE
mov ah,09h
int 21h

提示输入电话号码。

1
2
3
mov dx,offset PHONE_BUF
mov ah,0Ah
int 21h

读取用户输入的电话号码,存储在PHONE_BUF中。

1
2
3
4
5
mov bx,dx
mov al,byte ptr [bx+1]
mov ah,0
mov si,ax
mov byte ptr [bx+si+2],'$'

将电话号码字符串的结尾设为$,以便显示。

1
2
3
4
5
6
;保存数据
mov bx,offset ALL_DATA
mov ax,ds:[DATA_COUNT]
mov cx,34
mul cx
add bx,ax ;ary+count*34

计算新联系人在ALL_DATA中的存储位置。

1
2
3
4
5
6
7
8
9
10
11
    ;给标志
mov word ptr [bx],1
mov cx,8
mov si,2
mov bp,offset NAME_BUF
LOOP1:
mov ax,word ptr ds:[bp+si]
mov word ptr [bx+si],ax
add si,2
dec cx
jnz LOOP1

复制姓名数据到ALL_DATA中的对应位置。

**mov ax, word ptr ds:[bp+si]**:

  • NAME_BUFBP 寄存器指向)加上 SI 偏移量的位置读取一个字(16 位)到 AX 寄存器。

**mov word ptr [bx+si], ax**:

  • AX 中的数据写入到 BX 指向的位置加上 SI 偏移量的位置。

**add si, 2**:

  • 更新 SI 寄存器,将其偏移量增加 2,移动到下一个字(16 位)的位置。

**dec cx**:

  • CX 寄存器的值减 1,减少循环次数。

**jnz LOOP1**:

  • 如果 CX 不为零,则跳回 LOOP1 标签,继续循环

示例

假设 NAME_BUF 的内容如下:

1
NAME_BUF db 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120

执行这段代码时,NAME_BUF 从偏移量 2 开始的数据(30 40, 50 60, 70 80)将被复制到 BX 指向的内存位置,每次复制一个字(16 位)。这段代码假设 NAME_BUF 至少有 16 字节的数据。

1
2
3
4
5
6
7
8
9
10
11
12
    mov cx,8
mov si,18
mov di,2
mov bp,offset PHONE_BUF
LOOP2:
mov ax, word ptr ds:[bp+di]
mov word ptr [bx+si], ax
add si, 2
add di, 2
dec cx
jnz LOOP2
inc ds:[DATA_COUNT]

复制电话号码数据到ALL_DATA中的对应位置,并增加联系人数量。

1
jmp SHOW_MENU

返回显示菜单。

查询联系人

1
2
3
4
5
6
QUERY1:
mov bx,offset ALL_DATA
mov cx,ds:[DATA_COUNT]
LOOP3:
cmp word ptr [bx],0
je LABEL1

开始查询联系人,从ALL_DATA中遍历联系人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lea dx,[bx+2]
mov ah,09h
int 21h

mov dx,offset NEXT_LINE
mov ah,09h
int 21h

lea dx,[bx+18]
mov ah,09h
int 21h

mov dx,offset NEXT_LINE
mov ah,09h
int 21h

显示联系人姓名和电话号码。

1
2
3
4
LABEL1:
add bx,34 ;ary+count*34
dec cx
jnz LOOP3

处理下一个联系人。

1
jmp SHOW_MENU

返回显示菜单。

退出程序

1
2
3
EXIT1:
mov ax,4c00h
int 21h

退出程序。

默认处理无效输入

1
2
3
4
5
DEFAULT1:
mov dx,offset INPUT_ERROR
mov ah,09h
int 21h
jmp SHOW_MENU

显示输入错误信息,并返回显示菜单。

代码段结束

1
2
3
MyCode ends

end MAIN

结束代码段,并定义程序入口点为MAIN

调试和运行

开始调试

输入姓名和电话

image-20240806201648267

找到存储数据的地址并查看,可以看到数据已经被存入

image-20240806201930809

开始运行

输入数据

image-20240806202356143

查询数据,成功存入数据

image-20240806202706728

成功退出

image-20240806203029426