Консультация № 178697
27.05.2010, 16:48
43.38 руб.
0 3 1
Здравствуйте уважаемы эксперты! Прошу помощи в решение такой задачки:

Сформировать файл со строками, состоящими из ИМЯ, ОТЧЕСТВО, ФАМИЛИЯ. Создать этот файл самому и заполнить его данными по своей группе. Организовать вывод на экран всех полных имён людей, имеющих задаваемое с клавиатуры имя.

Прошу написать все с комментариями чтоб понятно было) Программа Tasm.

Обсуждение

Неизвестный
27.05.2010, 16:57
общий
Петров Юрий Иванович:
Уточните, пожалуйста, что значит
Создать этот файл самому
?
Файл должен быть создан заранее, требуемая программа его только читает? Или же что и создаваться этот файл должен этой же программой?

Неизвестный
27.05.2010, 23:04
общий
это ответ
Здравствуйте, Петров Юрий Иванович.

Программа приведена в приложении. Файл с данными должен быть создан предварительно. Если необходимо, то можно добавить и создание файла.
Компилировать в COM-файл.

Пример данных:
Код:
Иван Петрович Сидоров
неверная строка
Николай Сергеевич, Смирнов...
Иван Андреевич Лисов
Петр Николаевич Иванов

Строка "неверная строка" будет пропущена при разборе файла (меньше 3-х слов). Если в строке больше 3-х слов, то разбор файла прекращается. Максимальный размер файла - 40К (программа проверяет, но я эту часть не тестировал).

С вопросами обращайтесь в мини-форум.

Успехов!

Приложение:
; Программа читает файл со строками, состоящими из ИМЯ, ОТЧЕСТВО, ФАМИЛИЯ.
; Организовать вывод на экран всех полных имён людей, имеющих задаваемое
; с клавиатуры имя.

locals @@
model tiny

t_person struc
pName dw ? ; адрес имени
pPatronymic dw ? ; адрес отчества
pSurname dw ? ; адрес фамилии
t_person ends

MAX_ENTRIES = 100 ; максимальное кол-во записей

.data

strMenu db 13,10,'-----------------',13,10
db '1- загрузить файл',13,10
db '2- показать файл',13,10
db '3- поиск по имени',13,10
db '-----------------',13,10
db 'Esc - выход',13,10
db '-----------------'
strEol db 13,10,'$'

msgEnterName db 13,10,'Введите имя файла: $'
msgOpenError db 13,10,'Ошибка при открытии файла.',13,10,'$'
msgFileTooBig db 13,10,'Файл слишком большой, читаем только первые 40 Кбайт.',13,10,'$'
msgReadError db 13,10,'Ошибка при чтении файла.',13,10,'$'
msgLimit db 13,10,'Слишком много записей.',13,10,'$'
msgWrongFormat db 13,10,'Неверный формат файла. Разбор файла прерван.',13,10,'$'
msgUnexpectedEnd db 13,10,'Неожиданный конец файла.',13,10,'$'
msgNoData db 13,10,'Нет данных.',13,10,'$'
msgSearchName db 13,10,'Поиск - введите имя: $'
msgNotFound db 13,10,'Имя не найдено.',13,10,'$'
strTableHead db 13,10
db '-----------------------------------------------------',13,10
db 'Фамилия Имя Отчество',13,10
strTableLine db '-----------------------------------------------------',13,10,'$'

db 13,10
delimiters db 9, ' .,;:!?$' ; массив разделителей слов
LEN_DELIM equ $-delimiters ; кол-во разделителей

bufInput db 254 ; размер буфера
lenInput db 0 ; длина введенной строки
szInput db 254 dup(0) ; сюда будет помещена введенная строка

bufFileName db 254 ; размер буфера
lenFileName db 0 ; длина введенной строки
szFileName db 254 dup(0) ; сюда будет помещено имя файла при вводе

; таблица указателей на функции
proc_table dw load_file, type_file, search

nPersons dw 0 ; кол-во записей в файле
screen_row db 0
bFound db 0

; для уменьшения размера файла не объявляем явно массив с указателями
; и буфер для данных, а используем метку конца данных (persons) и
; вычисляем адрес буфера

persons label byte
file_buffer EQU persons + MAX_ENTRIES*(SIZE t_person)

; организация памяти
; -----0
; PSP
; -----256 (100h)
; код программы
; данные
; ----- конец загруженной программы
; метка persons
; MAX_ENTRIES*(SIZE t_person) - массив структур с указателями
; буфер для данных (40 Кбайт)
; -----
; Стек
; -----64К


.code
org 100h
start:
cld ; направление для строковых команд
; выводим меню
mov ah,9 ; вывод строки на экран
mov dx,offset strMenu ; смещение выводимой строки
int 21h
; выбор из меню
@@wait_key:
xor ax,ax ; ожидаем нажатия клавиши
int 16h
; проверяем нажатую клавишу
cmp al,27 ; ESC - выход
je @@exit
cmp al,'1'
jb @@wait_key
cmp al,'3'
ja @@wait_key
; введенное число - индекс в массиве указателей на процедуры,
; соответствующие пунктам меню
and ax,000Fh ; символ преобразуем в число с одновременным обнулением AH
dec ax ; индексация с 0
shl ax,1 ; массив слов, поэтому индекс умножаем на 2
mov bx,ax ; для получения смещения в таблице
call [proc_table][bx]
jmp start
@@exit:
int 20h ; выход из программы (только .COM)


;-------------------------------------------------------------------------
; запрос имени файла, загрузка в память и формирование массива указателей
;-------------------------------------------------------------------------
load_file proc
mov ah,9 ; выводим предложение ввести имя файла
mov dx,offset msgEnterName ; смещение выводимой строки
int 21h

mov ah,0Ah ; ввод строки с клавиатуры
mov dx,offset bufFileName ; смещение буфера
int 21h

; преобразуем имя файла в ASCIIZ строку (с конечным нулем)
xor bh,bh
mov bl,[lenFileName]
mov [szFileName][bx],0

; открываем файл
mov ax,3D00h ; открытие файла (AH=3Dh) с доступом по чтению (AL=0)
mov dx,offset szFileName ; DS:DX - адрес имени файла
int 21h
jnc @@open_ok

; AX содержит код ошибки, для простоты его игнорируем
; В серьезной программе надо выводить осмысленное сообщение
; в соответствии с кодом.

mov ah,9 ; сообщение об ошибке создания
mov dx,offset msgOpenError
int 21h
ret

@@open_ok:
; Читаем файл в память.
; Сначала определяем размер
mov bx,ax ; handle
mov ax,4202h ; AL=2 - позиция задается относительно конца файла
xor cx,cx ; CX:DX - позиция
xor dx,dx
int 21h ; DX:AX - текущая позиция = размер файла
test dx,dx ; проверим размер
jnz @@too_big
cmp ax,40*1024 ; максимум 40K
jbe @@size_ok

@@too_big: ; слишком большой файл
mov ah,9 ; выводим сообщение
mov dx,offset msgFileTooBig
int 21h
mov ax,40*1024 ; максимум 40K

@@size_ok:
push ax ; сохраняем размер

; возвращаем указатель текущей позиции на начало файла
mov ax,4200h ; AL=0 - позиция задается относительно начала файла
xor cx,cx ; CX:DX - позиция
xor dx,dx
int 21h

pop cx ; CX - сколько читать
mov ah,3Fh ; ф-я чтения файла, BX = handle
mov dx,offset file_buffer ; смещение буфера (после всех данных)
int 21h
jnc @@read_ok

; AX содержит код ошибки, для простоты его игнорируем
mov ah,9 ; выводим сообщение об ошибке чтения
mov dx,offset msgReadError
int 21h
mov ah,3Eh ; закрываем файл
int 21h
ret

@@read_ok:
mov cx,ax ; сколько реально прочитано

mov ah,3Eh ; закрываем файл
int 21h

mov [nPersons],0
jcxz @@ret

mov bx,offset persons
mov si,dx ; начало буфера
@@next_line:
call skip_all_delimiters ; пропускаем разделители, включая концы строк
jcxz @@ret
cmp bx,offset persons + MAX_ENTRIES*(SIZE t_person) ; проверяем на переполнение
jb @@ok
mov dx,offset msgLimit ; слишком много записей
jmp short @@msg
@@ok:
; (SI-1) - адрес первого символа следующего слова
lea ax,[si-1] ; AX = адрес первого символа имени
mov [bx],ax ; сохраняем адрес имени
call read_word ; пропускаем имя
jcxz @@unexpected_end
jz @@end_of_line ; неполная строка, пропускаем
mov byte ptr [si],'$'

call skip_delimiters ; пропускаем разделители
jcxz @@unexpected_end
jz @@end_of_line ; неполная строка, пропускаем
; (SI-1) - адрес первого символа следующего слова
lea ax,[si-1] ; AX = адрес первого символа отчества
mov [bx+2],ax ; сохраняем адрес отчества
call read_word ; пропускаем отчество
jcxz @@unexpected_end
jz @@end_of_line ; неполная строка, пропускаем
mov byte ptr [si],'$'

call skip_delimiters ; пропускаем разделители
jcxz @@unexpected_end
jz @@end_of_line ; неполная строка, пропускаем
; (SI-1) - адрес первого символа следующего слова
lea ax,[si-1] ; AX = адрес первого символа фамилии
mov [bx+4],ax ; сохраняем адрес фамилии
call read_word ; пропускаем фамилию
mov byte ptr [si],'$'
jz @@eol ; нормально, конец строки сразу после фамилии
jcxz @@eol ; нормально, конец файла сразу после фамилии

; разделитель, но не конец строки
; до конца строки допустимы только разделители (строго говоря, только пробелы)
call skip_delimiters ; пропускаем разделители
jz @@eol
; неверный формат файла - лишние символы после фамилии
; эта строка предполагается неверной, дальнейший анализ прекращается
mov dx,offset msgWrongFormat
@@msg:
mov ah,9
int 21h
ret

@@eol:
add bx,SIZE t_person ; продвигаем указатель в массиве записей
inc [nPersons] ; увеличиваем счетчик записей
@@end_of_line:
test cx,cx ; здесь нельзя использовать loop, поскольку CX
jnz @@next_line ; может быть равно 0. Кроме того, здесь нельзя уменьшать CX.
; дошли до конца прочитанного блока
@@ret:
ret

@@unexpected_end:
; Неожиданный конец прочитанного блока - в середине записи.
; Это возможно при ошибке или если файл был прочитан не полностью
mov dx,offset msgUnexpectedEnd
jmp short @@msg
load_file endp

;--------------------------------------------------------------
; пропуск разделителей
; вход:
; SI - указатель на строку
; CX - длина строки
; выход:
; (SI-1) - адрес первого символа следующего слова
; CX - длина остатка строки
;--------------------------------------------------------------
skip_delimiters proc
@@next:
push cx ; длина остатка строки
lodsb ; очередной символ строки
mov cx,LEN_DELIM
mov di,offset delimiters
repne scasb ; сравниваем с возможными разделителями
pop cx
loope @@next ; цикл, пока разделитель и строка не кончилась
cmp al,13 ; конец строки в стиле DOS - пара 13,10 (CR/LF)
je @@ret
cmp al,10 ; конец строки в стиле UNIX - LF
@@ret: ret
skip_delimiters endp

;--------------------------------------------------------------
; пропуск разделителей, включая CR/LF
; параметры - аналогично skip_delimiters
;--------------------------------------------------------------
skip_all_delimiters proc
@@next:
push cx ; длина остатка строки
lodsb ; очередной символ строки
mov cx,LEN_DELIM+2
mov di,offset delimiters-2
repne scasb ; сравниваем с возможными разделителями, включая CR/LF
pop cx
loope @@next ; цикл, пока разделитель и строка не кончилась
ret
skip_all_delimiters endp

;--------------------------------------------------------------
; пропуск слова
; вход:
; SI - указатель на первый символ слова + 1
; (к этому моменту первый символ слова уже проверен)
; CX - длина строки
; выход:
; SI - указывает на первый символ за концом слова
; CX - длина остатка строки
; ZF (флаг нуля)=1 - конец строки
;--------------------------------------------------------------
read_word proc
@@next:
push cx ; длина остатка строки
lodsb ; очередной символ строки
mov cx,LEN_DELIM+2
mov di,offset delimiters-2
repne scasb ; сравниваем с возможными разделителями, включая CR/LF
pop cx
loopne @@next ; цикл, пока НЕ разделитель и строка не кончилась
jne @@ret ; эта проверка важна для последнего введенного слова
dec si ; теперь SI указывает на первый символ за концом слова
inc cx
cmp al,13 ; конец строки в стиле DOS - пара 13,10 (CR/LF)
je @@ret
cmp al,10 ; конец строки в стиле UNIX - LF
@@ret: ret
read_word endp


;--------------------------------------------------------------
; Вывод на экран всех записей в табличном виде
;--------------------------------------------------------------
type_file proc
mov cx,[nPersons]
jcxz _no_data
mov ah,9 ; вывод заголовка таблицы
mov dx,offset strTableHead
int 21h

mov si,offset persons
@@next:
call type_line
add si,SIZE t_person
loop @@next

_bottom_line:
mov ah,9 ; нижняя черта
mov dx,offset strTableLine
int 21h
ret

_no_data:
mov ah,9 ; сообщение "нет данных"
mov dx,offset msgNoData
int 21h
ret
type_file endp

;--------------------------------------------------------------
; Запрос имени и поиск
; Замечание: если пользователь введет что-то вроде
; Иван$Петрович
; и в файле с данными 'Иван' и 'Петрович' разделены
; одним символом (например, 'Иван Петрович Сидоров'),
; то будет найдена эта запись
;--------------------------------------------------------------
search proc
mov cx,[nPersons]
jcxz _no_data

mov ah,9 ; выводим предложение ввести имя для поиска
mov dx,offset msgSearchName ; смещение выводимой строки
int 21h

mov ah,0Ah ; ввод строки с клавиатуры
mov dx,offset bufInput ; смещение буфера
int 21h

xor bh,bh
mov bl,[lenInput] ; BX= длина введенной строки
mov [bFound],bh ; пока ничего не найдено
mov bp,bx
mov [szInput][bx],'$' ; записываем терминатор

mov bx,offset persons ; адрес массива указателей

@@next:
push cx
mov si,offset szInput
mov di,[bx].pName
mov cx,bp
repe cmpsb
jne @@skip
cmp [bFound],cl ; здесь CL=0
jnz @@lin
mov [bFound],1 ; что-то нашли
mov ah,9 ; вывод заголовка таблицы
mov dx,offset strTableHead
int 21h
@@lin:
mov si,bx
call type_line
mov bx,si
@@skip:
add bx,SIZE t_person ; к следующей записи
pop cx
loop @@next

cmp [bFound],cl ; здесь CL=0
jne _bottom_line ; что-то было найдено, отчеркиваем таблицу

@@not_found:
mov ah,9 ; ничего не найдено
mov dx,offset msgNotFound
int 21h
ret
search endp

;--------------------------------------------------------------
; вывод одной строки на экран в таблицу
; SI - адрес структуры в массиве persons
; Сохраняет значение CX,SI
;--------------------------------------------------------------
type_line proc
push cx
mov ah,3 ; запрос позиции и размера курсора
xor bh,bh ; видеостраница
int 10h ; Выход: DH - строка, DL - колонка
mov [screen_row],dh

mov ah,9
mov dx,[si].pSurname ; фамилия
int 21h

mov ah,2 ; установить позицию курсора
mov dh,[screen_row]
mov dl,15 ; позиция имени
int 10h

mov ah,9
mov dx,[si].pName ; имя
int 21h

mov ah,2 ; установить позицию курсора
mov dh,[screen_row]
mov dl,30 ; позиция отчества
int 10h

mov ah,9
mov dx,[si].pPatronymic ; отчество
int 21h

mov dx,offset strEol ; на новую строку
int 21h
pop cx
ret
type_line endp

end start
Неизвестный
28.05.2010, 13:30
общий
Замечание по программе:

В текущей версии поиск считается успешным, если начало имени совпало с введенной строкой. Для сравнения по полному имени надо учесть в длине искомой строки терминатор, например, так:

Код:
388:	mov	bp,bx
389: mov [szInput][bx],'$' ; записываем терминатор
inc bp ; учитываем терминатор в длине строки
mov bx,offset persons ; адрес массива указателей


388 и 389 - не метки, а номера строк исходного файла


Форма ответа