table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
28、埃拉托斯特尼筛法由同名的希腊数学家发明,它提供了一种快速找出给定范围内所有质数的方法。该算法需要创建一个字节数组,通过以下方式插入 1 来“标记”数组位置:从位置 2(它是一个质数)开始,在数组中每个 2 的倍数的位置插入 1。然后对下一个质数 3 的倍数做同样的操作。找到 3 之后的下一个质数 5,并标记所有 5 的倍数的位置。按此方式继续,直到找出所有质数的倍数。数组中未标记的剩余位置表示哪些数字是质数。对于这个程序,创建一个包含 65000 个元素的数组,并显示 2 到 65000 之间的所有质数。在未初始化的数据段声明该数组,并使用 STOSB 指令将其填充为零。
埃拉托斯特尼筛法查找质数
本题要求利用
埃拉托斯特尼筛法
找出
2 到 65000
之间的所有质数。
具体步骤:
创建一个包含 65000 个元素的数组;
在
未初始化的数据段
声明该数组;
使用
STOSB 指令
将数组填充为零;
从位置
2
开始,标记
2 的倍数
位置为 1;
接着标记
3、5 等质数的倍数
位置为 1;
最终,数组中
未标记的位置
对应的数字即为质数;
将这些质数显示出来。
29、编写一个冒泡排序程序,添加一个变量作为交换标志。当内循环中交换一对数值时将该变量设为1。如果在对数组进行一次完整遍历的过程中没有发生交换,就使用这个变量提前结束排序。
以下是添加交换标志变量以优化冒泡排序的伪代码示例:
procedure BubbleSort(arr[], n):
exchangeFlag = 0
for i from 0 to n - 1:
exchangeFlag = 0
for j from 0 to n - i - 1:
if arr[j] > arr[j + 1]:
// 交换 arr[j] 和 arr[j+1]
temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
exchangeFlag = 1
if exchangeFlag == 0:
break
在上述伪代码中,
exchangeFlag
作为交换标志变量。每次外循环开始时,将其初始化为 0。在内循环中,如果发生了交换操作,就将
exchangeFlag
设为 1。如果一次外循环结束后
exchangeFlag
仍为 0,说明这一轮没有发生交换,数组已经有序,可以提前结束排序。
30、创建一个宏,用于等待按键并返回按下的键。该宏应包含用于存储 ASCII 码和键盘扫描码的参数。编写一个测试该宏的程序。例如,以下代码等待按键;返回时,两个参数将包含 ASCII 码和扫描码:数据段定义 ascii 字节变量留空,scan 字节变量留空;代码段调用 mReadkey 宏并传入 ascii 和 scan 。
以下是实现该功能的代码:
TITLE Testing mReadkey (TestmReadkey.asm)
INCLUDE Irvine32.inc
INCLUDE Macros.inc
; 定义宏 mReadkey
MACRO mReadkey ascii, scan
call ReadKey
mov ascii, al
mov scan, ah
ENDM
.data
ascii BYTE ?
scan BYTE ?
.code
main PROC
; 调用宏等待按键
mReadkey ascii, scan
; 显示 ASCII 码和扫描码
movzx eax, ascii
call WriteDec
mWrite <' ASCII code, ', 0dh, 0ah>
movzx eax, scan
call WriteDec
mWrite <' Scan code', 0dh, 0ah>
exit
main ENDP
END main
上述代码中,首先定义了
mReadkey
宏,它调用
ReadKey
函数,然后将返回的 ASCII 码和扫描码分别存储到传入的参数中。在
main
过程里,调用该宏等待按键,并将结果存储在
ascii
和
scan
变量中,最后显示这两个值。
31、创建一个宏,该宏能以给定的文本颜色将一个以空字符结尾的字符串写入控制台。宏的参数应包括字符串名称和颜色。调用一个名为 SetTextColor 的函数来设置文本颜色。编写一个程序,用几种不同颜色的字符串测试你的宏。示例调用:数据段 myString db “Here is my string”.0 代码段 mWritestring myString, white
以下是实现该功能的宏和测试程序示例:
; 定义宏
mWriteStringColor MACRO buffer:REQ, color:REQ
push eax
mov eax, color
call SetTextColor
push edx
mov edx, OFFSET buffer
call WriteString
pop edx
pop eax
ENDM
; 测试程序
TITLE Test mWriteStringColor Macro
INCLUDE Irvine32.inc
.data
myString1 BYTE "String in white", 0
myString2 BYTE "String in blue", 0
white EQU 15
blue EQU 1
.code
main PROC
; 测试白色字符串
mWriteStringColor myString1, white
call Crlf
; 测试蓝色字符串
mWriteStringColor myString2, blue
call Crlf
exit
main ENDP
END main
这个程序定义了一个名为
mWriteStringColor
的宏,它接受字符串名称和颜色作为参数。在宏内部,先设置文本颜色,然后输出字符串。测试程序中使用了两个不同颜色的字符串进行测试。
32、创建一个名为mScroll的宏,用于在控制台窗口中显示一个彩色矩形。在宏定义中包含以下参数。如果attrib为空,假设颜色为黑色背景上的浅灰色字符:ULrow(窗口左上角行)、ULcol(窗口左上角列)、LRrow(窗口右下角行)、LRcol(窗口右下角列)、attrib(滚动行的颜色)。编写一个测试该宏的程序。
以下是满足你需求的代码示例:
```asm
; 宏定义 mScroll
mScroll MACRO ULrow, ULcol, LRrow, LRcol, attrib
LOCAL scroll_code
scroll_code:
MOV AH, 6 ; 滚动窗口向上功能号
MOV AL, 0 ; 滚动所有行
MOV CH, ULrow ; 左上角行
MOV CL, ULcol ; 左上角列
MOV DH, LRrow ; 右下角行
MOV DL, LRcol ; 右下角列
; 检查 attrib 是否为空,如果为空则设置默认颜色
IFIDNI <attrib>, <>
MOV BH, 7 ; 浅灰色字符在黑色背景上
ELSE
MOV BH, attrib
ENDIF
INT 10h ; 调用 BIOS 中断
ENDM
; 测试程序
TITLE Test mScroll Macro
INCLUDE Irvine16.inc
.code
main PROC
; 初始化数据段
MOV AX, @data
MOV DS, AX
; 调用 mScroll 宏
mScroll 5, 10, 15, 20, 12 ; 示例调用,颜色为红色
; 等待用户按键
call ReadChar
; 退出程序
MOV AH, 4Ch
INT 21h
main ENDP
END main
代码说明:
宏定义
mScroll
:该宏接受五个参数,分别是窗口的左上角行、左上角列、右下角行、右下角列和滚动行的颜色。如果
attrib
参数为空,则默认颜色为浅灰色字符在黑色背景上。
测试程序
:在
main
过程中,调用
mScroll
宏并传入示例参数,然后等待用户按键,最后退出程序。
##33、32位程序如何处理文本输入输出?32位控制台模式下如何处理颜色?Irvine32链接库是如何工作的?MS - Windows中如何处理时间和日期?如何使用MS - Windows函数读写数据文件?是否可以用汇编语言编写图形化Windows应用程序?保护模式程序如何将段和偏移量转换为物理地址?虚拟内存好的原因是什么?
## Windows下32位编程基础
本章将介绍在Microsoft Windows下进行32位编程的基础知识,以回答这些问题。
大部分信息针对32位控制台模式文本应用程序,Irvine32链接库完全基于Win32控制台函数构建。
关于图形化Windows应用程序,若用汇编语言或C编写,代码冗长且复杂。第11.2节以通用方式介绍了32位图形编程,仅作入门引导。
32位控制台模式程序与16位MS-DOS程序在运行模式和使用的函数库上存在差异:
- **32位控制台程序**:
- 运行在32位保护模式
- 使用与图形化Windows应用程序相同的库
- **MS-DOS程序**:
- 使用BIOS和MS-DOS中断
Win32 API可让用户调用32位版本MS-Windows的函数。与之密切相关的Microsoft Platform SDK是创建MS-Windows应用程序的工具集。
更多关于Win32 API函数的详细信息,可通过以下方式查询:
- Microsoft Visual C++ Express的帮助功能
- Microsoft MSDN网站
##34、使用栈参数实现自己版本的ReadString过程。向其传递一个字符串指针和一个整数,该整数表示允许输入的最大字符数。返回实际输入的字符数(存于EAX中)。该过程必须从控制台输入一个字符串,并在字符串末尾(即ODh所在位置)插入一个空字节。编写一个简短的程序来测试你的过程。
```markdown
以下是实现自定义ReadString过程及测试程序的示例代码:
```asm
TITLE Custom ReadString (CustomReadString.asm)
INCLUDE Irvine32.inc
; 自定义ReadString过程
CustomReadString PROC
push ebp
mov ebp, esp
; 获取栈参数
mov edx, [ebp + 8] ; 字符串指针
mov ecx, [ebp + 12] ; 最大字符数
; 调用ReadConsole
INVOKE GetStdHandle, STD_INPUT_HANDLE
mov ebx, eax ; 保存标准输入句柄
INVOKE ReadConsole, ebx, edx, ecx - 2, ADDR [ebp + 16], 0
; 查找ODh并替换为0
mov esi, edx
mov al, 0Dh
L1:
cmp [esi], al
jne Next
mov [esi], byte ptr 0 ; 替换为0
jmp Done
Next:
inc esi
loop L1
Done:
mov eax, [ebp + 16] ; 返回实际读取的字符数
pop ebp
ret 8 ; 清理栈
CustomReadString ENDP
; 测试程序
.data
buffer BYTE 21 DUP(0) ; 缓冲区
maxChars DWORD 20 ; 最大字符数
bytesRead DWORD ? ; 实际读取的字符数
.code
main PROC
; 调用自定义ReadString过程
push maxChars
push OFFSET buffer
call CustomReadString
mov bytesRead, eax
; 显示缓冲区内容
mov esi, OFFSET buffer
mov ecx, bytesRead
mov ebx, TYPE buffer
call DumpMem
exit
main ENDP
END main
代码说明:
CustomReadString过程
:该过程接受两个栈参数,一个是字符串指针,另一个是最大字符数。它调用Win32的ReadConsole函数从控制台读取输入,并在找到ODh时将其替换为0。最后返回实际读取的字符数。
测试程序
:在
main
过程中,我们调用
CustomReadString
过程,并将结果存储在
bytesRead
变量中。然后使用
DumpMem
函数显示缓冲区内容。
##35、编写一个汇编语言版本的清屏过程 Clrscr,该过程能清屏并将光标定位到第 0 行第 0 列。
下面是一个清屏并将光标定位到第 0 行第 0 列的示例代码:
```asm
Clrscr PROC ; 清屏并定位光标
pusha
mov ax, 0600h ; 滚动整个窗口向上
mov cx, 0 ; 左上角 (0,0)
mov dx, 184Fh ; 右下角 (24,79)
mov bh, 7 ; 正常属性
int 10h ; 调用 BIOS
mov ah, 2 ; 定位光标在 0,0
mov bh, 0 ; 视频页面 0
mov dx, 0 ; 第 0 行,第 0 列
int 10h
popa
ret
Clrscr ENDP
此代码使用 BIOS 中断
int 10h
来清屏并定位光标。
36、编写一个程序来创建一个新的文本文件。提示用户输入学生的学号、姓氏、名字和出生日期。将这些信息写入文件。以同样的方式输入更多的记录,然后关闭文件。
以下是一个使用 Python 语言实现该功能的示例代码:
# 打开一个新的文本文件以写入数据
with open('students.txt', 'w') as file:
while True:
# 提示用户输入学生信息
student_id = input('请输入学生学号(输入 q 退出):')
if student_id.lower() == 'q':
break
last_name = input('请输入学生姓氏:')
first_name = input('请输入学生名字:')
birth_date = input('请输入学生出生日期:')
# 将学生信息写入文件
file.write(f'{student_id},{last_name},{first_name},{birth_date}
')
代码解释:
文件打开
:使用
open('students.txt', 'w')
以写入模式打开一个名为
students.txt
的文件。
with
语句确保在操作完成后自动关闭文件。
循环输入
:使用
while True
创建一个无限循环,直到用户输入
q
退出。
信息输入
:提示用户输入学生的学号、姓氏、名字和出生日期。
信息写入
:将用户输入的信息以逗号分隔的形式写入文件,并在末尾添加换行符
。
退出条件
:如果用户输入
q
,则使用
break
语句退出循环,程序结束。
37、修改一个读取文件的汇编程序(原程序名为Readfile.asm),使其能够读取比输入缓冲区大的文件。将缓冲区大小减小到1024字节。使用循环继续读取并显示文件,直到无法读取更多数据。如果打算使用WriteString显示缓冲区,请记得在缓冲区数据末尾插入一个空字节。
修改Readfile.asm程序的步骤
可按以下步骤修改Readfile.asm程序:
将缓冲区大小设为1024字节;
用循环持续读取文件,直至无更多数据;
若用WriteString显示缓冲区,在数据末尾插入空字节。
示例代码修改思路
将
BUFFER_SIZE
定义为1024;
在读取文件部分添加循环逻辑;
每次读取后检查是否还有数据,若有则继续读取并显示;
每次读取后在缓冲区末尾插入空字节。
38、需要一个16位实模式C++编译器,在MS – DOS、Windows 95、98或千禧版系统下运行。编写一个C++程序,添加一个新的过程,该过程调用ReadSector过程(ReadSector过程用于读取扇区数据到缓冲区)。这个新过程应该以十六进制形式显示每个扇区。使用iomanip.setfill()为每个输出字节填充前导零。
可按以下步骤实现该需求:
首先,在C++程序中包含必要的头文件,如
<iomanip>
用于设置填充。
然后添加新过程,在新过程里调用
ReadSector
过程读取扇区数据到缓冲区,接着遍历缓冲区,使用
std::hex
和
std::setfill('0')
及
std::setw(2)
以十六进制形式输出每个字节,并填充前导零。
示例代码如下:
#include <iostream>
#include <iomanip>
#include <stdlib.h>
const int SECTOR_SIZE = 512;
extern "C" ReadSector( char * buffer, long startSector, int driveNum, int numSectors );
void DisplaySectorHex( char * buffer, long startSector, int numSectors ) {
for (int i = 0; i < numSectors; ++i) {
std::cout << "Sector " << startSector + i << std::endl;
for (int j = 0; j < SECTOR_SIZE; ++j) {
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<int>(static_cast<unsigned char>(buffer[i * SECTOR_SIZE + j]))
<< " ";
if ((j + 1) % 16 == 0) {
std::cout << std::endl;
}
}
std::cout << std::endl;
}
}
int main() {
int driveNum, numSectors;
long startSector;
std::cout << "Sector display program.
"
<< "Enter drive number [1=A, 2=B, 3=C, 4=D, 5=E,...]: ";
std::cin >> driveNum;
std::cout << "Starting sector number to read:";
std::cin >> startSector;
std::cout << "Number of sectors to read:";
std::cin >> numSectors;
char * buffer = new char[numSectors * SECTOR_SIZE];
ReadSector( buffer, startSector, driveNum, numSectors );
DisplaySectorHex( buffer, startSector, numSectors );
delete[] buffer;
return 0;
}
39、读取文本文件。打开一个文件用于输入,读取该文件,并以十六进制形式在屏幕上显示其内容。将输入缓冲区设置得小一些,大约256字节,这样程序就可以使用循环根据需要多次调用3Fh功能,直到整个文件处理完毕。
功能实现步骤
需要编写程序实现此功能,步骤如下:
打开文件用于输入;
定义一个约256字节的输入缓冲区;
使用循环多次调用3Fh功能读取文件内容;
将每次读取的内容以十六进制形式显示在屏幕上;
重复步骤3和4,直到整个文件处理完毕。
注意:
要在实地址模式下操作,不使用Irvine 16库,使用INT 21h功能调用进行所有输入输出。
40、复制文本文件。编写一个程序,使其能够读取任意大小的文件。假设缓冲区小于输入文件,使用循环读取所有数据。使用256字节的缓冲区大小。如果任何INT 21h函数调用后进位标志被设置,则显示相应的错误消息。
要完成此任务,可按以下步骤编写程序:
打开输入文件和输出文件;
定义一个256字节的缓冲区;
使用循环调用 INT 21h 的
3Fh
功能读取输入文件的数据到缓冲区,每次读取 256 字节;
每次读取后检查进位标志,如果进位标志被设置,显示相应的错误消息;
将缓冲区中的数据写入输出文件;
重复步骤 3 – 5,直到整个输入文件被处理完;
关闭输入文件和输出文件。
41、文本匹配程序。编写一个程序,打开一个最多包含 60K 字节的文本文件,并对一个字符串进行不区分大小写的搜索。字符串和文件名可由用户输入。显示文件中出现该字符串的每一行,并在每行前面加上行号。程序必须在实地址模式下运行。
程序需求说明
需编写程序实现以下功能:
运行环境
:实地址模式下运行。
文件操作
:
打开最大 60K 字节的文本文件。
字符串搜索
:
进行不区分大小写的字符串搜索。
用户交互
:
用户输入字符串和文件名。
输出要求
:
显示包含该字符串的所有行。
每行前显示对应的行号。
42、使用异或(XOR)进行文件加密。提示用户输入明文文件和密文文件的名称。打开明文文件用于输入,打开密文文件用于输出。让用户输入一个1到255之间的整数作为加密代码。将明文文件内容读入缓冲区,并将每个字节与加密代码进行异或运算。将处理后的缓冲区内容写入密文文件。只能使用INT 21h进行所有输入/输出操作(除了调用Readint过程获取加密代码)。编写的代码也可用于解密密文文件,恢复出原始的明文文件。
要实现该功能,可按以下步骤编写程序:
提示用户输入明文文件和密文文件的名称,使用
INT 21h
获取用户输入。
打开明文文件用于输入,打开密文文件用于输出,使用
INT 21h
的相应功能。
提示用户输入一个1到255之间的整数作为加密代码,调用
Readint
过程获取输入。
循环将明文文件内容读入缓冲区,每次读取一定数量字节(如256字节),使用
INT 21h
的功能。
对缓冲区中的每个字节与加密代码进行异或运算。
将处理后的缓冲区内容写入密文文件,使用
INT 21h
的功能。
重复步骤4 – 6,直到明文文件全部处理完。
解密时,使用相同的加密代码对密文文件进行同样的异或操作,即可恢复出原始的明文文件。
43、以滚动文本窗口练习为起点,进行如下更改:在循环开始前,随机选择每一列是向上还是向下滚动。在程序运行期间,每列应保持同一滚动方向。提示:将每列定义为一个单独的滚动窗口。
要实现这个需求,可按以下步骤操作:
在程序开始时,为每列随机选择滚动方向(向上或向下),可以使用随机数生成函数,生成0或1来代表不同方向。
将每列视为一个单独的滚动窗口,在滚动文本时,根据每列预先确定的方向进行滚动。
在滚动过程中,保持每列的滚动方向不变。
以下是一个示例代码框架,假设使用汇编语言实现:
TITLE Scrolling Text Window with Random Column Directions
INCLUDE Irvine32.inc
outHandle HANDLE ?
byteswritten DWORD ?
lineNum DWORD 0
columnDirections BYTE 80 DUP(?) ; 假设最多80列
windowRect SMALL_RECT <0,0,60,11> ; 窗口矩形
.data
message BYTE ": This line of text was written",0dh,0ah
messageSize = ($-message)
.code
main PROC
; 初始化每列的滚动方向
MOV ECX, 80 ; 假设最多80列
MOV ESI, OFFSET columnDirections
INIT_LOOP:
CALL RandomRange ; 随机生成0或1(假设RandomRange是生成随机数的函数)
MOV [ESI], AL
INC ESI
LOOP INIT_LOOP
; 获取标准输出句柄
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV outHandle, eax
; 写入50行文本
REPEAT
MOV eax, lineNum
CALL WriteDec
INVOKE WriteConsole, outHandle, ADDR message, messageSize, ADDR byteswritten, NULL
INC lineNum
.UNTIL lineNum > 50
; 滚动每列文本
MOV ECX, 80 ; 假设最多80列
MOV ESI, OFFSET columnDirections
SCROLL_LOOP:
MOV AL, [ESI]
CMP AL, 0
JE SCROLL_UP ; 如果为0,向上滚动
; 向下滚动的代码(这里需要实现具体的向下滚动逻辑)
JMP NEXT_COLUMN
SCROLL_UP:
; 向上滚动的代码(这里需要实现具体的向上滚动逻辑)
NEXT_COLUMN:
INC ESI
LOOP SCROLL_LOOP
call ReadChar
call Clrscr
call ReadChar
INVOKE ExitProcess, 0
main ENDP
END main
需要注意的是,上述代码中的
RandomRange
函数、向上和向下滚动的具体逻辑需要根据实际情况进行实现。同时,代码中的列数假设为80,可根据实际需求进行调整。该代码只是一个基本框架,实际实现时可能需要根据具体的编程环境和需求进行修改和完善。