基本控制結構
字符與字符串的輸入/輸出方法
DOS系統調用INT 21H提供了字符及字符串的I/O功能,例如:
01H:從鍵盤讀入一個字符
02H:顯示一個字符
09H:顯示一個字符串
0AH:從鍵盤讀入一個字符串
字符與字符串的輸入/輸出方法
DOS系統調用INT 21H提供了字符及字符串的I/O功能,例如:
01H:從鍵盤讀入一個字符
02H:顯示一個字符
09H:顯示一個字符串
0AH:從鍵盤讀入一個字符串
1. 從鍵盤讀入一個字符
功能號:01H
出口參數:AL = 輸入字符的ASCII碼
功能:等待從鍵盤讀入一個字符,將其ASCII碼送入AL,同時將該字符顯示在屏幕上。
調用方法:
mov ah, 1
int 21h
說明:輸入一個字符後,不需要回車。若只鍵入回車,則AL = 0DH。
2. 顯示一個字符
功能號:02H
入口參數:DL = 要顯示字符的ASCII碼
功能:在當前光標位置顯示DL中的字符,光標右移。
調用方法示例:
mov dl, 'A' ; 顯示字符'A'
mov ah, 2
int 21h
說明:顯示字符0DH和0AH將產生回車和換行的作用。
3. 顯示一個字符串
功能號:09H
入口參數:DS:DX = 欲顯示字符串在內存的首地址,且字符串必須以'$'(24H)作爲結束符。
功能:在當前光標位置,顯示由DS:DX所指的、以'$'結尾的字符串,且光標右移。其中,'$'不算在顯示的字符串之內。
調用方法示例:
String db 'Hello!', '$' ; 顯示字符串'Hello!'
mov ax, seg String
mov ds, ax
mov dx, offset String
mov ah, 9
int 21h
4. 從鍵盤讀入一個字符串
功能號:0AH
入口參數:DS:DX = 輸入緩衝區首地址
輸入緩衝區格式:第0字節事先設置爲緩衝區最多能容納的字符個數(包括回車);第1字節將存放實際字符個數(不包括回車),由系統自動設置;從第2字節開始存放實際輸入的字符串,最後爲回車符。
功能:從鍵盤讀入一個字符串,存入DS:DX所指的緩衝區。
調用方法示例:
buf db 10, ?, 10 dup (?) ; 定義輸入緩衝區
mov ax, seg buf
mov ds, ax
lea dx, buf
mov ah, 0ah
int 21h
5. 字符與字符串的輸入/輸出舉例
DOS系統調用只提供了字符與字符串的輸入/輸出方法。如果要輸入/輸出其它類型的數據,如常用的整數,則必須由應用程序來實現與字符之間的轉換。例如,欲輸出整數126,只能以字符(串)的方式輸出'1' 、 '2' 、 '6' 。
對於程序員來說,在需要用戶輸入時給出輸入提示,對輸入數據進行有效性檢查,在輸出結果時說明其表示的含義,是一種好的設計習慣。
功能號:01H
出口參數:AL = 輸入字符的ASCII碼
功能:等待從鍵盤讀入一個字符,將其ASCII碼送入AL,同時將該字符顯示在屏幕上。
調用方法:
mov ah, 1
int 21h
說明:輸入一個字符後,不需要回車。若只鍵入回車,則AL = 0DH。
2. 顯示一個字符
功能號:02H
入口參數:DL = 要顯示字符的ASCII碼
功能:在當前光標位置顯示DL中的字符,光標右移。
調用方法示例:
mov dl, 'A' ; 顯示字符'A'
mov ah, 2
int 21h
說明:顯示字符0DH和0AH將產生回車和換行的作用。
3. 顯示一個字符串
功能號:09H
入口參數:DS:DX = 欲顯示字符串在內存的首地址,且字符串必須以'$'(24H)作爲結束符。
功能:在當前光標位置,顯示由DS:DX所指的、以'$'結尾的字符串,且光標右移。其中,'$'不算在顯示的字符串之內。
調用方法示例:
String db 'Hello!', '$' ; 顯示字符串'Hello!'
mov ax, seg String
mov ds, ax
mov dx, offset String
mov ah, 9
int 21h
4. 從鍵盤讀入一個字符串
功能號:0AH
入口參數:DS:DX = 輸入緩衝區首地址
輸入緩衝區格式:第0字節事先設置爲緩衝區最多能容納的字符個數(包括回車);第1字節將存放實際字符個數(不包括回車),由系統自動設置;從第2字節開始存放實際輸入的字符串,最後爲回車符。
功能:從鍵盤讀入一個字符串,存入DS:DX所指的緩衝區。
調用方法示例:
buf db 10, ?, 10 dup (?) ; 定義輸入緩衝區
mov ax, seg buf
mov ds, ax
lea dx, buf
mov ah, 0ah
int 21h
5. 字符與字符串的輸入/輸出舉例
DOS系統調用只提供了字符與字符串的輸入/輸出方法。如果要輸入/輸出其它類型的數據,如常用的整數,則必須由應用程序來實現與字符之間的轉換。例如,欲輸出整數126,只能以字符(串)的方式輸出'1' 、 '2' 、 '6' 。
對於程序員來說,在需要用戶輸入時給出輸入提示,對輸入數據進行有效性檢查,在輸出結果時說明其表示的含義,是一種好的設計習慣。
JMP指令的靈活運用
用JMP指令實現轉移的多種方法:
使用標號, 近轉移
使用標號, 遠轉移
以通用寄存器表示目標偏移地址
以內存變量表示目標偏移地址
以內存變量表示目標的32位分段地址
用JMP指令實現轉移的多種方法:
使用標號, 近轉移
使用標號, 遠轉移
以通用寄存器表示目標偏移地址
以內存變量表示目標偏移地址
以內存變量表示目標的32位分段地址
雙分支結構
雙分支結構相當於高級語言的IF-THEN-ELSE形式。IF-THEN結構只是IF-THEN-ELSE形式的特例,即ELSE部分爲空。
通常,使用條件轉移指令Jcc與無條件轉移指令JMP來實現分支。一般必須先安排比較或算術、邏輯運算等影響標誌位的指令,然後用Jcc指令判斷條件,以實現分支轉移。
對應於IF-THEN-ELSE結構的80x86彙編語言代碼形如:
<爲測試條件cc做準備>
Jcc ElseCode
<THEN 程序段>
jmp EndOfIF
ElseCode:
<ELSE 程序段>
EndOfIF:
注意,程序隱含是順序執行的,在THEN分支體執行後,不會自動跳過ELSE分支體,而是繼續執行其後的代碼。
對應於IF-THEN結構的80x86彙編語言代碼形如:
<爲測試條件cc做準備>
Jcc EndOfIF
<THEN 程序段>
EndOfIF:
雙分支結構相當於高級語言的IF-THEN-ELSE形式。IF-THEN結構只是IF-THEN-ELSE形式的特例,即ELSE部分爲空。
通常,使用條件轉移指令Jcc與無條件轉移指令JMP來實現分支。一般必須先安排比較或算術、邏輯運算等影響標誌位的指令,然後用Jcc指令判斷條件,以實現分支轉移。
對應於IF-THEN-ELSE結構的80x86彙編語言代碼形如:
<爲測試條件cc做準備>
Jcc ElseCode
<THEN 程序段>
jmp EndOfIF
ElseCode:
<ELSE 程序段>
EndOfIF:
注意,程序隱含是順序執行的,在THEN分支體執行後,不會自動跳過ELSE分支體,而是繼續執行其後的代碼。
對應於IF-THEN結構的80x86彙編語言代碼形如:
<爲測試條件cc做準備>
Jcc EndOfIF
<THEN 程序段>
EndOfIF:
多分支結構
多分支結構相當於高級語言的CASE語句。
多分支程序的設計方法主要有:
邏輯分解方法
將多分支結構以邏輯等效的方法,分解爲一串雙分支結構。
地址表方法
在數據段定義一個地址表,依次存放各分支對應處理程序的入口地址。
通過將相應處理程序入口地址取入某寄存器,用間接轉移指令實現轉移。
轉移表方法
在代碼段建立一個轉移表,依次存放實現各分支的轉移指令。
通過跳到轉移表的相應位置執行其中的轉移指令,從而實現轉到相應分支的處理程序。
循環結構
循環結構的基本形式
循環結構的兩種基本形式:
WHILE結構:先判斷、後執行
其特點是進入循環後,先判斷循環控制條件。若滿足循環結束條件,則退出循環;否則,繼續執行循環體。循環體可能一次也不執行。
UNTIL結構:先執行、後判斷
其特點是進入循環後,先執行循環體,然後判斷循環控制條件。若滿足循環結束條件,則退出循環;否則,繼續執行循環體。循環體至少執行一次。該結構相當於PASCAL語言的REPEAT-UNTIL結構或C語言的DO-WHILE結構。
具體選擇哪一種結構,取決於問題的特性以及使用者的偏好。一般來說,WHILE結構可能用得多一些。
此外,象高級語言的FOR循環結構,主要用在循環次數已知的情況下。FOR結構總可以轉換爲等價的WHILE或UNTIL結構。
循環程序的控制方法
(1)計數控制
適用於循環次數已知的情況,包括正計數與倒計數兩種方法。
(2)條件控制
在實際問題中,常常有循環次數未知的情況。此時,就必須通過特定條件來控制循環。
(3)開關控制
有時,循環內部又有分支,且每次循環執行的分支具有一定規律,這種結構非常類似於多分支結構。此時,可以採用開關來控制循環。具體方法是:
在進入循環前,預置第一次循環的開關走向;在每次循環結束前,設置下一個開關方向,以便執行相應的操作。
在實際應用中,開關的形式多種多樣。例如,設置一個狀態變量,取值0、1和2,分別表示執行的不同操作,就是一個開關。
(4)邏輯尺控制
如果循環內分支的規律性不強,開關控制方法就難以勝任了。一種較有效的方法就是邏輯尺控制。所謂邏輯尺,就是一個位串,用1位或多位來控制每次循環所執行的操作。
(1)計數控制
適用於循環次數已知的情況,包括正計數與倒計數兩種方法。
(2)條件控制
在實際問題中,常常有循環次數未知的情況。此時,就必須通過特定條件來控制循環。
(3)開關控制
有時,循環內部又有分支,且每次循環執行的分支具有一定規律,這種結構非常類似於多分支結構。此時,可以採用開關來控制循環。具體方法是:
在進入循環前,預置第一次循環的開關走向;在每次循環結束前,設置下一個開關方向,以便執行相應的操作。
在實際應用中,開關的形式多種多樣。例如,設置一個狀態變量,取值0、1和2,分別表示執行的不同操作,就是一個開關。
(4)邏輯尺控制
如果循環內分支的規律性不強,開關控制方法就難以勝任了。一種較有效的方法就是邏輯尺控制。所謂邏輯尺,就是一個位串,用1位或多位來控制每次循環所執行的操作。
串操作
串操作指令及其用途
1. 串操作指令
(1) MOVS(Move String):串傳送
語法格式:
MOVSB
MOVSW
MOVSD
功能描述:
ES:[DI] = DS:[SI];
if ( DF = 0 ) then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,MOVS(B/W/D)將DS:SI所指源串的1個元素(字節/字/雙字)複製到ES:DI所指的內存單元。然後,SI和DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:無。
串操作指令及其用途
1. 串操作指令
(1) MOVS(Move String):串傳送
語法格式:
MOVSB
MOVSW
MOVSD
功能描述:
ES:[DI] = DS:[SI];
if ( DF = 0 ) then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,MOVS(B/W/D)將DS:SI所指源串的1個元素(字節/字/雙字)複製到ES:DI所指的內存單元。然後,SI和DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:無。
(2)LODS(Load String):串裝入
語法格式:
LODSB
LODSW
LODSD
功能描述:
AL/AX/EAX = DS:[SI];
if ( DF = 0 )then
SI = SI + size;
else
SI = SI – size;
endif
其中, size = 1(B)、2(W)或4(D)。
也就是說,LODS(B/W/D)將DS:SI所指源串的1個元素(字節/字/雙字)複製到AL/AX/EAX。然後,SI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:無。
(3)STOS(Store String):串存儲
語法格式:
STOSB
STOSW
STOSD
功能描述:
ES:[DI] = AL/AX/EAX;
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,STOS(B/W/D)將AL/AX/EAX的值複製到ES:DI所指的內存單元。然後,DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:無。
語法格式:
LODSB
LODSW
LODSD
功能描述:
AL/AX/EAX = DS:[SI];
if ( DF = 0 )then
SI = SI + size;
else
SI = SI – size;
endif
其中, size = 1(B)、2(W)或4(D)。
也就是說,LODS(B/W/D)將DS:SI所指源串的1個元素(字節/字/雙字)複製到AL/AX/EAX。然後,SI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:無。
(3)STOS(Store String):串存儲
語法格式:
STOSB
STOSW
STOSD
功能描述:
ES:[DI] = AL/AX/EAX;
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,STOS(B/W/D)將AL/AX/EAX的值複製到ES:DI所指的內存單元。然後,DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:無。
(4)CMPS(Compare Strings):串比較
語法格式:
CMPSB
CMPSW
CMPSD
功能描述:
DS:[SI] – ES:[DI];
if ( DF = 0 )then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,CMPS(B/W/D)將DS:SI所指源串的1個元素(字節/字/雙字)與ES:DI所指目的串的1個元素(字節/字/雙字)進行比較,根據比較結果設置標誌位。然後,SI和DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:同CMP。
語法格式:
CMPSB
CMPSW
CMPSD
功能描述:
DS:[SI] – ES:[DI];
if ( DF = 0 )then
SI = SI + size; DI = DI + size;
else
SI = SI – size; DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,CMPS(B/W/D)將DS:SI所指源串的1個元素(字節/字/雙字)與ES:DI所指目的串的1個元素(字節/字/雙字)進行比較,根據比較結果設置標誌位。然後,SI和DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
對標誌位的影響:同CMP。
(5)SCAS(Scan String):串掃描
語法格式:
SCASB
SCASW
SCASD
功能描述:
AL/AX/EAX – ES:[DI];
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,SCAS(B/W/D)將AL/AX/EAX與ES:DI所指目的串的1個字節/字/雙字進行比較,根據比較結果設置標誌位。然後,DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
(6)重複前綴
REP ; 當CX <> 0時,重複執行後面的串指令
; 每執行1次,CX = CX – 1
; 只能用在MOVS、LODS或STOS(B/W/D)之前
REPZ/REPE ; 當CX <> 0且ZF = 1時,重複執行後面的串指令
; 每執行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
REPNZ/REPNE ; 當CX <> 0且ZF = 0時,重複執行後面的串指令
; 每執行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
說明:
若CX初值爲0,則不執行任何操作,而且標誌位不變。
語法格式:
SCASB
SCASW
SCASD
功能描述:
AL/AX/EAX – ES:[DI];
if ( DF = 0 )then
DI = DI + size;
else
DI = DI – size;
endif
其中,size = 1(B)、2(W)或4(D)。
也就是說,SCAS(B/W/D)將AL/AX/EAX與ES:DI所指目的串的1個字節/字/雙字進行比較,根據比較結果設置標誌位。然後,DI增加或減少1/2/4。若DF = 0,則增加,否則減少。
(6)重複前綴
REP ; 當CX <> 0時,重複執行後面的串指令
; 每執行1次,CX = CX – 1
; 只能用在MOVS、LODS或STOS(B/W/D)之前
REPZ/REPE ; 當CX <> 0且ZF = 1時,重複執行後面的串指令
; 每執行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
REPNZ/REPNE ; 當CX <> 0且ZF = 0時,重複執行後面的串指令
; 每執行1次,CX = CX – 1
; 只能用在CMPS(B/W/D)或SCAS(B/W/D)之前
說明:
若CX初值爲0,則不執行任何操作,而且標誌位不變。
2. 串指令的共性
源串:地址由DS:SI表示。
目的串:地址由ES:DI表示。
自動修改地址:每次數據操作後,SI和DI自動遞增或遞減,取決於操作的數據類型(字節、字或雙字)以及DF的值。若DF = 0, 則SI和DI自動增加1、2或4。否則,減1、2或4。可以使用CLD或STD指令設置DF。
計數器:使用重複前綴時,由CX表示數據個數,每重複1次操作,CX減1。
源串:地址由DS:SI表示。
目的串:地址由ES:DI表示。
自動修改地址:每次數據操作後,SI和DI自動遞增或遞減,取決於操作的數據類型(字節、字或雙字)以及DF的值。若DF = 0, 則SI和DI自動增加1、2或4。否則,減1、2或4。可以使用CLD或STD指令設置DF。
計數器:使用重複前綴時,由CX表示數據個數,每重複1次操作,CX減1。
3. 串指令的用途
串指令主要用於處理連續的內存單元,與重複前綴配合使用更有效。例如:
MOVS用於將一個內存塊的數據複製到另一塊;
CMPS用於比較兩個內存區的數據;
SCAS可以在一個內存區中掃描與給定值首次匹配或不匹配的元素;
STOS能將一個內存區的所有單元初始化爲給定值。
LODS一般不用重複前綴。
串指令主要用於處理連續的內存單元,與重複前綴配合使用更有效。例如:
MOVS用於將一個內存塊的數據複製到另一塊;
CMPS用於比較兩個內存區的數據;
SCAS可以在一個內存區中掃描與給定值首次匹配或不匹配的元素;
STOS能將一個內存區的所有單元初始化爲給定值。
LODS一般不用重複前綴。
字符串處理
1. ASCII字符串的表示方法
字符串是一種特殊的數據串,比其它類型的串更常使用,因此,有時將字符串簡稱爲串。
ASCII字符串的表示方法主要有下列兩種:
(1)長度前綴法。
由首字節指出字符串長度,常被稱作Pascal串,爲Pascal語言的大多數版本所支持。例如:
String1 db 6, 'String‘
(2)0終止法。
以0作爲結束標誌,常被稱作C串,爲C/C++語言所採用。例如:
String2 db ' String ', 0
1. ASCII字符串的表示方法
字符串是一種特殊的數據串,比其它類型的串更常使用,因此,有時將字符串簡稱爲串。
ASCII字符串的表示方法主要有下列兩種:
(1)長度前綴法。
由首字節指出字符串長度,常被稱作Pascal串,爲Pascal語言的大多數版本所支持。例如:
String1 db 6, 'String‘
(2)0終止法。
以0作爲結束標誌,常被稱作C串,爲C/C++語言所採用。例如:
String2 db ' String ', 0
2. 字符串處理舉例