Модули и текстовые файлы
Модули в Turbo Pascal
В Turbo Pascal каждый модуль представляет собой отдельный файл (один файл!), который имеет расширение .pas.
Исходный текст модуля имеет следующую структуру:
1. Заголовок
2. Интерфейсная часть
3. Исполнительная часть
4. Секция инициализации
Заголовок
Заголовок модуля состоит из ключевого слова UNIT и идентификатора – имени модуля. Модуль должен быть сохранен в файле, имя которого совпадает с именем модуля. Например, если модуль начинается со строки
Unit MyUnit;
тогда этот модуль должен быть сохранен в файле с именем MyUnit.pas
Интерфейсная часть
Дадим краткое определение:
интерфейс – это способ взаимодействия.
В данном случае интерфейс – способ взаимодействие основной программы (или другого модуля) с нашим модулем. Взаимодействие с модулем производится через использование определенных в модуле констант, типов, глобальных переменных (чего делать не надо!!!), и через вызов определенных в модуле процедур и функций.
Модульный подход подразумевает, что не все константы, типы, переменные, процедуры и функции, определенные в модуле, могут быть использованы вне модуля. Могут быть использованы лишь те из них, которые объявлены в интерфейсной части. Если в интерфейсной части нет упоминания – то такую константу, тип, переменную, процедуру или функцию можно использовать только внутри модуля – извне она недоступна.
Интерфейсная часть модуля начинается с ключевого слова interface и заканчивается перед ключевым словом implementation. Интерфейсная часть модуля содержит почти те же разделы что и основная программа:
- раздел объявления используемых модулей
- раздел объявления констант
- раздел объявления типов
- раздел объявления переменных
- раздел объявления процедур и функций
Как и в основной программе, эти разделы могут идти в любом порядке (за исключением первого), каждый из них (опять же, за исключением первого) может встречаться несколько раз, либо вообще не разу.
Заметим, что в разделе объявления процедур и функций указываются только заголовки подпрограмм.
Исполнительная часть
Если в интерфейсной части указываются только заголовки подпрограмм, (подпрограммы объявляются), то в исполнительную часть модуля включаются тела подпрограмм, (подпрограммы определяются). Исполнительная часть модуля начинается с ключевого слова implementation и заканчивается началом секции инициализации, если она есть, либо словом end с точкой. Исполнительная часть модуля содержит те же разделы что и основная программа:
- раздел объявления используемых модулей
- раздел объявления констант
- раздел объявления типов
- раздел объявления переменных
- раздел определения процедур и функций
Как и в основной программе и интерфейсной части эти разделы могут идти в любом порядке, каждый из них может встречаться несколько раз, либо вообще не разу (раздел объявления используемых модулей всегда идет первым, и встречается только один раз).
Все что объявлено в исполнительной части может быть использовано только внутри исполнительной части (!!! Обратите на это внимание !!!). С другой стороны в исполнительной части может быть использовано все, что объявлено в интерфейсной части.
Секция инициализации
Иногда перед использованием подпрограмм, включенных в модуль, требуется произвести инициализацию некоторых глобальных переменных этого модуля. Необходимые действия можно произвести в секции инициализации модуля. Операторы этой секции выполняются единственный раз в момент запуска программы. Секция инициализации размещается в самом конце модуля, начинается она словом begin, заканчивается end (с точкой). Если инициализация в модуле не требуется, то в секции помещается только end (с точкой).
Пользовательские модули для работы с текстом
Рассмотрим использование модулей в задачах обработки текстов.
Разработаем модуль, в котором соберем функции и процедуры, используемые в двух задачах:
Задача 1.
Переписать входной файл в выходной файл. При этом самое длинное слово в каждой строке выделить угловыми скобками.
Ограничение: считаем, что строки в файле не превышают нормальной длины в 70-80 символов.
Задача 2.
Дан исходный файл с текстом на русском языке. Вывести на экран содержимое исходного файла постранично. При выводе на экран выделить цветом все слова, в которых чередуются гласные/согласные.
Переписать содержимое исходного файла в выходной файл, выделяя БОЛЬШИМИ буквами все выделенные при выводе на экран слова.
Примечание: Имя входного файла вводится с клавиатуры. Имя выходного файла отличается от имени исходного файла только расширением.
Создадим модуль, в котором соберем функции по работе со словами: поиск слова в строке, поиск самого длинного слова в строке, определение, состоит ли слово из чередующихся гласных и согласных букв.
Вот этот модуль:
{Модуль Words.
Содержит функции поиска и анализа слов в строке.
}
unit Words;
interface {интерфейсная часть модуля}
{Функция FindNextWord.
Ищет в строке S следующее слово начиная с символа Start.
Если слово найдено, то возвращается True,
и возвращается индекс первого символа слова (через BeginWord)
и его длина (через LengthWord).
Если слово не найдено, возвращается False.}
function FindNextWord( const S : String;
Start : Integer;
var BeginWord : Byte;
var LengthWord : Byte) : Boolean;
{Функция FindMaxLenWord.
Ищет в строке S самое длинное слово.
Если ни одного слова в строке S не найдено, то возвращается False.
В противном случае возвращается True,
при этом через BeginMaxWord возвращается индекс
первого символа самого длинного слова,
а через LengthMaxWord - его длина.}
function FindMaxLenWord( const S : String;
var BeginMaxWord : Byte;
var LengthMaxWord : Byte
) : Boolean;
{Функция IsChereda.
Возвращает True, если S содержит строку, состоящую только
из чередующихся русских согласных и русских гласных букв.
Возвращает False в противном случае. }
function IsChereda(const S:string):Boolean;
implementation {Исполнительная часть модуля}
const
{множества символов}
SmallRusLetters : set of char = ['а'..'п','р'..'я','ё'];
BigRusLetters : set of char = ['А'..'Я','Ё'];
SmallLatLetters : set of char = ['a'..'z'];
BigLatLetters : set of char = ['A'..'Z'];
Digits : set of char = ['0'..'9'];
RusGlasn : set of char = ['а','е','ё','и','о',
'у','ы','э','ю','я',
'А','Е','Ё','И','О',
'У','Ы','Э','Ю','Я'];
var
RusLetters : set of char; {все русские буквы}
LatLetters : set of char; {все латинские буквы}
Letters : set of char; {все буквы и цифры,
т.е. те символы, из которых
могут состоять слова}
RusSoglasn : set of char; {русские согласные буквы}
{=====================================================}
{Функция IsLetter.
Возвращает True, если C является символом слова.
Возвращает False, если C является разделителем. }
function isLetter(c: char): boolean;
begin
isLetter:=c in Letters;
end;
{=====================================================}
{Функция IsRusGlasn.
Возвращает True, если C является русской гласной буквой.
Возвращает False в противном случае. }
function isRusGlasn(c: char): boolean;
begin
isRusGlasn := c in RusGlasn;
end;
{=====================================================}
{Функция IsRusSoglasn.
Возвращает True, если C является русской согласной буквой.
Возвращает False в противном случае. }
function isRusSoglasn(c: char): boolean;
begin
isRusSoglasn := c in RusSoglasn;
end;
{=====================================================}
{Функция IsChereda.
Возвращает True, если S содержит строку, состоящую только
из чередующихся русских согласных и русских гласных букв.
Возвращает False в противном случае. }
function IsChereda(const S:string):Boolean;
var
IsSogl : Boolean; {текущая буква является согласной}
IsPrevSogl : Boolean; {предыдущая буква является согласной}
i : Byte; {счетчик цикла}
len : Byte; {длина строки}
begin
len := length(S); {вычисляем длину строки}
{проверяем, состоит ли строка только из русских гласных
и согласных букв}
for i := 1 to len do
if not IsRusGlasn(s[i]) and not IsRusSoglasn(s[i]) then
begin
{если нет, то завершаем работу функции}
IsChereda := false;
exit;
end;
{начиная со второго символа слова, и до конца слова}
for i := 2 to len do
{ЭТОТ символ должен быть НЕ ТАКИМ как ПРЕДЫДУЩИЙ}
if IsRusSoglasn(s[i-1]) <> IsRusGlasn(s[i]) then
begin
{если нет, то завершаем работу функции}
IsChereda := false;
exit;
end;
IsChereda := true; {все символы чередуются}
end;
{=====================================================}
{Функция FindNextWord.
Ищет в строке S следующее слово начиная с символа Start.
Если слово найдено, то возвращается True,
и возвращается индекс первого символа слова (через BeginWord)
и его длина (через LengthWord).
Если слово не найдено, возвращается False.}
function FindNextWord( const S : String;
Start : Integer;
var BeginWord : Byte;
var LengthWord : Byte) : Boolean;
var
i : Integer; {индекс может выйти за границы 255 - поэтому Byte
использовать нельзя!!!}
Len : Byte; {длина строки}
Begin
{вычисляем длину строки}
Len := length(s);
{ищем начало слова, начиная со стартового символа строки}
i := Start;
{в цикле продвигаем i вперед по строке, до тех пор
пока не встретиться буква, или пока не кончится строка }
while not isLetter(S[i]) and (i <= Len ) do
i := i + 1;
{сейчас i указывает на первый символ найденного слова}
BeginWord := i;
{ищем конец слова}
{для этого продвигаем i вперед, до тех пор пока не встретиться
НЕ БУКВА, или пока i не выйдет за пределы строки}
while isLetter(S[i]) and ( i <= Len ) do
i := i + 1;
{сейчас i указывает на первый символ-разделитель, следующий
за словом (или i указывает на символ за пределами границ
строки).
Длину слова вычисляем как разность между индексами его
последнего и первого символов }
LengthWord := i - BeginWord;
{Если вычисленная длина слова больше 0, значит слово в строке
найдено - возвращаем True.
Иначе - слова в строке нет - возвращаем False }
if LengthWord > 0
then FindNextWord := true
else FindNextWord := false;
end;
{=====================================================}
{Функция FindMaxLenWord.
Ищет в строке S самое длинное слово.
Если ни одного слова в строке S не найдено, то возвращается False.
В противном случае возвращается True,
при этом через BeginMaxWord возвращается индекс
первого символа самого длинного слова,
а через LengthMaxWord - его длина.}
function FindMaxLenWord( const S : String;
var BeginMaxWord : Byte;
var LengthMaxWord : Byte
) : Boolean;
var
i : Integer; {индекс может выйти за границы 255 - поэтому Byte
использовать нельзя!!!}
Beg : Byte; {начало строки}
Len : Byte; {длина строки}
begin
I := 1; {начинаем поиск слов с начала строки}
LengthMaxWord := 0; {длина самого длинного слова в строке =0,
потому что еще не одного слова не нашли }
{ищем все слова в строке}
while FindNextWord(S,i,Beg,Len) do
begin
{Если найденное слово длиннее всех ранее найденных}
if len>lengthMaxWord then
begin {запоминаем начало и длину найденного слова}
lengthMaxWord:=len;
BeginMaxWord:=beg;
end;
i:=beg+len; {продолжаем поиск с символа, следующего за
концом последнего найденного слова}
end;
{если не одного слова в строке не найдено,
то длина самого длинного слова будет равна 0.
В этом случае возвращаем False.
В противном случае возвращаем True.}
if lengthMaxWord=0
then findMaxLenWord:=False
else findMaxLenWord:=True;
end;
begin {Секция инициализации}
{"собираем" множества из подмножеств }
RusLetters := smallRusLetters + bigRusLetters;
LatLetters := smallLatLetters + bigLatLetters;
Letters := RusLetters + LatLetters + Digits;
RusSoglasn := RusLetters - RusGlasn;
end.
Теперь приведем текст программы – решение первой задачи:
{
Задача 1.
Переписать входной файл в выходной файл.
При этом самое длинное слово в каждой строке
выделить угловыми скобками.
Ограничение: считаем, что строки в файле не превышают
нормальной длины в 70-80 символов.
}
uses Words; {В программе используется функция findMaxLenWord}
var
inFileName : string; {имя входного файла}
outFileName : string; {имя выходного файла}
inFile : text; {входной файл}
outFile : text; {выходной файл}
S : string; {строка, читаемая из файла}
beg : Byte; {начало самого длинного слова}
len : Byte; {длина самого длинного слова}
begin
{Ввод имен входного и выходного файлов}
write('Введите имя входного текстового файла : ');
readln(inFileName);
write('Введите имя выходного текстового файла : ');
readln(outFileName);
{Открытие входного файла на чтение}
assign(inFile, inFileName);
reset(inFile);
{Открытие выходного файла на запись}
assign(outFile, outFileName);
rewrite(outFile);
{пока не кончится входной файл}
while not eof(inFile) do
begin
{читаем из входного файла строку}
readln(inFile, s);
{если в строке найдено хоть одно слово}
if findMaxLenWord(s, beg, len) then
begin
{самое длинное слово выделяем угловыми скобками}
Insert('<',S,beg);
Insert('>',S,beg+len+1);
end;
{выводим в выходной файл измененную строку S}
writeln(outFile, s);
end;
{Закрываем входной и выходной файлы}
close(inFile);
close(outFile);
end.
Как мы видим, в этой программе используется наш модуль Words. За счет этого текст программы достаточно краток.
Теперь решим вторую задачу. В ней кроме всего прочего нужно слово переписать большими буквами. Сделаем для этого функцию и поместим эту функцию и вспомогательные функции в модуль Chars:
{Модуль Chars.
Содержит функции конверсии символов и строк.}
unit chars;
interface
{превратить маленькие буквы в большие}
function ToUpper(ch: char): char;
{превратить большие буквы в маленькие}
function ToLower(ch: char): char;
{превратить маленькие буквы в большие для строки S}
function StringToUpper(const S: string): string;
implementation
{превратить маленькие буквы в большие}
function ToUpper(ch:char):char;
begin
{Для понимания алгоритма загляни в таблицу ASCII кодов}
{Общая схема такая :
ord(ch)-ord('x') - вычисление "расстояния"
до опорного символа
+ ord('X') - вычисленное расстояние прибавляется
к новому опорному символу
chr(...) - превращения порядкового номера символа в символ
}
if ch in ['а'..'п'] then
ToUpper := chr(ord(ch) - ord('а') + ord('А'))
else if ch in ['р'..'я'] then
ToUpper := chr(ord(ch) - ord('р') + ord('Р'))
else if ch = 'ё' then
ToUpper := 'Ё'
else if ch in ['a'..'z'] then
ToUpper := chr(ord(ch) - ord('a') + ord('A'))
else
ToUpper := ch;
end;
{превратить большие буквы в маленькие}
function ToLower(ch:char):char;
begin
if ch in ['А'..'П'] then
ToLower := chr(ord(ch) - ord('А') + ord('а'))
else if ch in ['Р'..'Я'] then
ToLower := chr(ord(ch) - ord('Р') + ord('р'))
else if ch = 'Ё' then
ToLower := 'ё'
else if ch in ['A'..'Z'] then
ToLower := chr(ord(ch) - ord('A') + ord('a'))
else
ToLower := ch;
end;
{превратить маленькие буквы в большие для строки S}
function StringToUpper(const S:string):string;
var
i : Byte;
resString: string;
begin
{результат заносится в строку resString}
resString:='';
{по очереди все символы исходной строки S превращаются
в большие}
for i := 1 to length(s) do
resString := resString + ToUpper(s[i]);
{получившаяся строка возвращается}
StringToUpper := resString;
end;
begin
end.
И наконец, текст программы – решение второй задачи:
{
Задача 2.
Дан исходный файл с текстом на русском языке.
Вывести на экран содержимое исходного файла постранично.
При выводе на экран выделить цветом все слова, в которых
чередуются гласные/согласные.
Переписать содержимое исходного файла в выходной файл,
выделяя БОЛЬШИМИ буквами все выделенные при выводе на экран
слова.
Примечание: Имя входного файла вводится с клавиатуры. Имя
выходного файла отличается от имени исходного файла только
расширением.
}
uses Words, {В программе используется функция FindNextWord
и IsChereda}
Chars, {используется StringToUpper}
Crt; {используется ReadKey и др.}
{Процедура PrintFile.
Вывод содержимого файла FileName на экран постранично.
Одна страница - 20 строк.
При выводе на экран выделяются цветом все слова, в которых
чередуются гласные/согласные.}
procedure PrintFile(const FileName:string);
var
InFile : text; {входной файл}
S : string; {строка, читаемая из файла}
NumLines : LongInt; {номер строки, читаемой из файла}
NumPages : LongInt; {номер страницы, выводимой на экран}
i : Integer; {счетчик}
beg, len : byte; {начало и длина слова}
w : string; {слово}
begin
{Открытие входного файла на чтение}
assign(inFile, FileName);
reset(inFile);
{Инициализация количества прочитанных строк
и выведенных страниц}
NumLines := 0;
NumPages := 0;
{пока не кончится входной файл}
while not eof(inFile) do
begin
readln(inFile, s); {читаем из входного файла строку}
inc(NumLines); {увеличиваем номер прочитанной строки}
write(NumLines:3,'> '); {выводим номер строки на экран}
{ищем по очереди все слова}
i:=1;
while FindNextWord(s,i,beg,len) do
begin
{серым цветом выводим все разделители
перед очередным словом}
textcolor(lightgray);
write( copy(s, i, beg-i) );
{в W заносим найденное слово}
w:=copy(s,beg,len);
{если в слове W чередуются гласные и согласные}
{тогда устанавливаем ДРУГОЙ цвет вывода}
if IsChereda(w) then textcolor(yellow);
{выводим слово}
write(w);
{продолжать поиск слов...}
i:=beg+len;
end;
{разделители за последим словом выводим серым цветом}
textcolor(lightgray);
writeln(copy(s,i,length(s)-i+1));
{Если очередные 20 строк выведены - страница}
if NumLines mod 20 = 0 then
begin
{увеличиваем номер страницы}
inc(NumPages);
{выводим номер страницы }
write('======================= ');
write('Страница ', NumPages:4);
write(' =======================');
{выводим пустые строки}
writeln;
writeln;
writeln;
writeln;
{ждем нажатия какой-нибудь клавиши}
ReadKey;
end;
end;
{после последней страницы выводим сообщение
об окончании вывода}
inc(NumPages);
write('=======================');
write('Страница ', NumPages:4);
writeln('=======================');
writeln('Весь текст выведен!');
ReadKey;
{Закрываем входной файл}
close(inFile);
end;
{
Функция CreateOutFileName.
Создает и возвращает имя выходного файла путем
замены расширения в имени входного файла InFileName.}
function CreateOutFileName( const InFileName : string
) : string;
var
OutFileName: string; {имя выходного файла}
PosPoint: Byte; {позиция точки в имени входного файла}
begin
{ищем точку в имени входного файла}
PosPoint := Pos('.', InFileName);
if PosPoint = 0 then {точки в имени нет}
OutFileName := InFileName + '.'
else {точка в имени файла есть}
OutFileName := Copy(InFileName, 1, PosPoint);
{считаем, что входной файл имеет расширение отличное от OUT!!!}
OutFileName := OutFileName + 'out';
CreateOutFileName := OutFileName;
end;
{Процедура CopyText.
Копирует содержимое исходного файла InFileName
в выходной файл, выделяя БОЛЬШИМИ буквами все слова,
в которых чередуются гласные/согласные.}
procedure CopyText(const InFileName: string);
var
InFile : text; {входной файл}
OutFileName : string; {имя выходного файла}
OutFile : Text; {выходной файл}
S : string; {строка, читаемая из файла}
i : Integer; {счетчик}
beg, len : byte; {начало и длина слова}
w : string; {слово}
begin
{создаем имя выходного файла на основании имени входного}
OutFileName := CreateOutFileName(InFileName);
{Открытие входного файла на чтение}
assign(inFile, InFileName);
reset(inFile);
{Открытие выходного файла на запись}
assign(outFile, OutFileName);
rewrite(outFile);
{пока не кончится входной файл}
while not eof(inFile) do
begin
readln(inFile, s); {читаем из входного файла строку}
{ищем в строке по очереди все слова}
i:=1;
while FindNextWord(s,i,beg,len) do
begin
{выводим все разделители перед очередным словом}
write(outFile, copy(s,i,beg-i));
{в W заносим найденное слово}
w:=copy(s,beg,len);
{если в слове W чередуются гласные и согласные}
{тогда делаем все буквы этого слова большими}
if IsChereda(w) then W:=StringToUpper(W);
{выводим слово}
write(outFile, w);
{продолжать поиск слов...}
i:=beg+len;
end;
{выводим разделители за последним словом}
writeln(outFile, copy(s,i,length(s)-i+1));
end;
{закрытие файлов}
close(inFile);
close(outFile);
end;
var
inFileName : string; {имя входного файла}
begin
{Ввод имени входного файла}
write('Введите имя входного текстового файла : ');
readln(inFileName);
{Печатаем входной файл (на экране, постранично)}
PrintFile(inFileName);
{Копируем входной файл в выходной}
CopyText(InFileName);
end.
ЗАМЕЧАНИЯ:
Использование модулей позволяет улучшить качество программы, уменьшить затраты на ее разработку лишь в том случае, когда соблюдаются следующие принципы:
1. Пользователь модуля имеет полную информацию об использовании подпрограмм модуля. И при этом он не имеет никакой информации о способе реализации подпрограмм.
2. Модули должны быть, как можно меньше зависимы друг от друга (в идеале модули должны быть полностью независимы друг от друга)
Первый принцип реализуется за счет:
- разбиения констант, типов, переменных и подпрограмм модуля на те, которые необходимо открыть пользователям (их надо разместить в интерфейсной части), и на те, которые должны быть закрыты от пользователя (их надо разместить в исполнительной части);
- наиподробнейшего комментирования интерфейсной части модуля
- использование смысловых имен для констант, типов, переменных и подпрограмм.
Второй принцип реализуется за счет отказа от использования глобальных переменных – все данные, обрабатываемые в подпрограмме, должны передаваться через параметры.