Обработка символьных и строковых данных
Компьютер способен обрабатывать не только числовые данные. Задачи обработки символьных данных распространены не менее, а возможно и более чем чисто арифметические расчеты. В этой главе мы изучим работу с символами и строками средствами Паскаля.
Работа с символами
Для работы с отдельными символами описываются переменные типа char: var ch:char; {... } ch:='y'; Одна переменная типа char хранит информацию об одном коде ASCII-символа. Это можно использовать, например, для отслеживания действий пользователя по нажатию клавиш. Приведем пример одного из вариантов такой программы: var ch:char; begin repeat writeln; write ('Продолжить? (Y/N)'); readln (ch); if (ch='Y') or (ch='y') then begin {Здесь программируется нужный вычислительный процесс} end else if (ch='N') or (ch='n') then halt {Завершение программы} else writeln ('Нажмите Y или N'); until false; end. Для работы с кодами символов существуют 2 основных функции: function ord(x):char; — возвращает ASCII-код символа x function chr(x: byte):char; — возвращает символ с указанным ASCII-кодом x. Применим последнюю функцию для того, чтобы узнать, как выглядит таблица символов кодировки DOS, используемой Паскалем: var i,j:integer; begin writeln; write ('ASCII-коды [32..255]'); for i:=2 to 15 do begin writeln; write (i*16:3,' '); for j:=0 to 16 do write(chr(i*16+j),' '); end; writeln; write ('ENTER для выхода...'); readln; end. Здесь печатаются только символы с кодами от 32 до 255 включительно, т. к. первые 32 символа с кодами от 0 до 31 -- непечатаемые (например, табуляция, перевод строки). Для определения того, попадает ли код символа в некоторый диапазон значений, удобно использовать оператор in, как это делает следующий фрагмент программы: write ('Введите символ: '); readln (ch); if ch in ['A'..'Z'] then write ('Большая латинская;') else if ch in ['a'..'z'] then write ('Малая латинская;')
else if (ch in ['А'..'Я']) then write ('Большая русская;') else if (ch in ['а'..'п']) or (ch in ['р'..'я']) then write ('Малая русская;') else if ch in ['0'..'9'] then write ('Цифра;') else write ('Это не алфавитно-цифровой символ;'); write (' Код Вашего символа= ',ord(ch)); Работая с алфавитными символами, приходится отдельно учитывать ввод строчных и прописных букв. Удобнее сначала преобразовать все символы к прописным с помощью функции upcase: var ch:char; i:integer; begin repeat for i:=1 to random(72) do write ('*'); writeln; write ('Продолжить? (Y/N)'); readln (ch); until upcase(ch)='N'; end. К сожалению, эта функция бесполезна при работе с символами русского и других национальных алфавитов, для ее замены напишем и протестируем собственную подпрограмму c названием upcase_ru: procedure upcase_ru (var s:string); var i,l,c:integer; begin l:=length(s); for i:=1 to l do begin c:=ord(s[i]); if (c>=ord('а')) and (c<=ord('п')) then c:=c-32 else if (c>=ord('р')) and (c<=ord('я')) then c:=c-80; s[i]:=Upcase(chr(c)); end; end;
var s:string; begin writeln ('Введите строку текста:'); readln (s); upcase_ru (s); writeln ('Преобразованная строка:'); writeln (s); end. Программа учитывает, что в кодировке DOS не все символы кириллицы закодированы идущими подряд числами (см. Приложение 1). Кроме того, в программе уже применяется массив символов, которому в Паскале соответствует тип данных string (строка). Мы упоминали этот тип данных, но еще не работали с ним. Как раз строкам посвящен п. 20.2.
Работа со строками
Строка -- это массив символов, т. е., элементов типа char. Нумерация символов в строке всегда выполняется с единицы. В Паскале строке соответствует тип данных string. Строка описывается оператором следующего вида: var Имя_строки: string [длина]; Если положительная целочисленная величина "длина" не указана, выделяется память под строку длиной до 255 символов. Приведем примеры описания строк: var s1:string; s2:string[20]; s3:array [1..20] of string; Здесь s1 -- строка с длиной по умолчанию, s2 -- строка из 20 символов, s3 -- массив из 20 строк, каждая из которых может занимать до 256 байт памяти (дополнительный байт нужен для хранения длины строки).
Со строками можно выполнять операцию присваивания. Покажем это на примере описанных выше строк. s1:='А.И. Иванов'; — строке s1 присвоено значение строковой константы. s1[3]:=’В’; — отдельному символу строки s1 присвоили символьную константу. s2:='2009'; — строке s2 присвоили строку, являющуюся записью целого числа. Позднее такая строка может быть преобразована в число стандартной процедурой Val. s3[1]:='Информатика'; s3[2]:=''; — s3 является строковым массивом. Его первому элементу присвоена строковая константа, второму -- пустая строка. Для ввода строк с клавиатуры следует применять оператор readln, т. к. ввод строки должен завершиться нажатием клавиши Enter: writeln ('Введите имя пользователя:'); readln (s1); Для вывода строк на экран или принтер можно использовать как оператор write, так и writeln: s2:='SUMMA'; write (s2); — на экран будет выведена строка "SUMMA". writeln ('Сумма':10); — будет выведена строка "_____Сумма" (5 пробелов перед словом) и курсор переведен на следующую строку экрана. Оператор сложения "+" переопределен для строк таким образом, что выполняет их сцепление (конкатенацию): s1:='2009' + ' год'; s2:='н.э.'; s3[3]:=s1+' '+s2; После этих действий значение строки s1 будет равно ''2009_год", а строка s3[3] -- ''2009_год_н.э.". Если при сложении строк превышена максимальная длина результирующей строки, лишние символы отсекаются. Для сцепления строк можно также использовать стандартную функцию concat. Операция отношения "=" позволяет посимвольно сравнить строки. При этом действуют следующие правила: · строки считаются равными только при одинаковом наборе символов и одинаковой длине; · иначе происходит поэлементное сравнение символов по их кодам. При этом, согласно таблице ASCII-кодов (см. Приложение 1) старшинство отдельных символов следующее: '0' < '1' <... < '9' < 'A' <... < 'Z' < 'a' <... < 'z' < символы кириллицы. Остальные операции отношения также применимы к строкам. В ядро Паскаля включен ряд стандартных подпрограмм для обработки строковых данных. Опишем их прототипы, то есть, перечислим заголовки подпрограмм с указанием типа и количества формальных параметров. function Length (s:string):integer; — определяет и возвращает длину строки s в символах;
function copy (s:string; N,L:integer): string; — возвращает часть строки s длиной L символов, начиная с позиции N; procedure Insert (s0:string; var s: string; N: integer); — в строку s вставляет строку s0, начиная с позиции N; procedure Delete (var s:string; N,L:integer); — в строке s удаляет L символов, начиная с позиции N; function Pos (s0, s:string): integer; — возвращает позицию, начиная с которой строка s0 содержится в строке s или значение 0, если s0 не содержится в s; procedure str (x: числовой; var s:string); — преобразует число x в строку s, параметр x может иметь любой числовой тип; procedure Val (s:string; var x: числовой; var error:integer); — преобразует строку s в число x. Параметр x может иметь любой числовой тип. Параметр-переменная error служит для контроля правильности преобразования. Если преобразовать удалось, то error=0, иначе error будет равен номеру первого непреобразуемого символа строки s. Приведенных стандартных подпрограмм достаточно для решения большинства несложных задач, связанных с обработкой текстовых данных. Рассмотрим ряд типовых задач на примерах. 1. Разобрать предложение на слова и вывести каждое слово на новой строке экрана. Алгоритм работы этой программы очень прост -- до тех пор, пока в исходной строке предложения s есть хотя бы один пробел, вся часть строки до пробела копируется в строковую переменную w (слово). Если пробелов уже нет (или не было изначально), то вся строка -- это одно слово. После обработки очередного слова (в нашем случае -- это вывод его на новую строку экрана оператором writeln) обработанная часть строки вместе с пробелом удаляются из s -- чтобы следующий шаг цикла не нашел то же самое слово. var s,w:string; {предложение и слово} p:integer; {позиция пробела} begin writeln ('Введите текст'); readln (s); repeat p:=pos (' ',s); if p>0 then w:=copy (s,1,p-1) else w:=s; writeln (w); delete (s,1,p); until p=0; end. Приведенная программа имеет ряд недостатков. Самый заметный из них -- не учтены дополнительные пробелы между словами, которые будут восприниматься программой как лишние "пустые слова". Избавимся от них с помощью следующего примера. 2. Удалить лишние пробелы между словами.
Алгоритм решения задачи также несложен. Пока в строке есть два подряд идущих пробела, следует удалять один из них. После этого остается проверить, нет ли в начале и конце строки по одному лишнему пробелу. Приведем только основную часть программы: repeat p:=pos (' ',s); if p>0 then delete (s,p,1); until p=0; if s[1]=' ' then delete (s,1,1); if s[length(s)]=' ' then delete (s, length(s),1); writeln (s); Однако, в примерах 1 и 2 есть более существенный недостаток -- проходы по строке неоднократны, тогда как разбор на слова можно сделать и за один цикл сканирования строки. Попытаемся реализовать это в следующей программе. 3. Разобрать предложение на слова за один цикл сканирования строки. Приведем полный текст программы, а затем -- комментарии. var s,word:string; c,c0:char; i,l,start:integer; inword:boolean; begin writeln ('Enter string:'); reset (input); readln (s); s:=' '+s+' '; l:=Length (s); inword:=false; for i:=2 to l do begin c0:=s[i-1]; c:=s[i]; if (c0=' ') and (c<>' ') then begin inword:=true; start:=i; end; if c=' ' then begin if inword=true then begin word:=copy (s,start,i-start); writeln ('''',word,''' is word'); end; inword:=false; end; end; end. По сути дела, у нашей программы всего 2 состояния -- внутри слова и вне его. Переключением состояний управляет флаг inword. Номер символа, с которого начинается очередное слово, запоминается в переменной start. Программа не учитывает знаки препинания и возможность разделения слов другими символами, кроме пробела. Тем не менее, избавившись еще и от функции copy, совершающей лишний проход по части строки (например, сразу же накапливая слово word по мере сканирования), можно было бы получить действительно эффективный алгоритм. Как обычно, платой за эффективность алгоритма является сложность программы. 4. Подсчитать количество пробелов в строке. Это пример реализует работу со строкой как с массивом символов. var s:string; k,i:integer; begin writeln ('text?'); readln (s); k:=0; for i:=1 to length (s) do if s[i]=' ' then k:=k+1; writeln ('k=',k); end. Текстовые файлы
Для написания большинства сложных программ требуется обмен данными с файлами. В этой главе мы рассмотрим только текстовые файлы, для работы с бинарными и типизированными файлами см. специальную литературу и гл. 22.
Общие операции
Для работы с каждым файлом описывается переменная типа text: var f:text; Ее называют файловой переменной. Если программа обрабатывает несколько файлов одновременно, для каждого из них описывается такая переменная. Можно использовать и одну переменную для нескольких файлов, если они обрабатываются последовательно, и каждый следующий файл открывается после завершения работы с предыдущим. Вся работа с файлом происходит через файловую переменную, имя файла указывается только один раз следующим оператором: assign (f,'имя_файла'); где f -- ранее описанная файловая переменная.
Этот оператор предшествует открытию файла и связывает переменную с файлом на жестком или гибком диске. В качестве имени файла может быть указан абсолютный или относительный путь к файлу на жестком или сменном диске: assign (f,'data.txt'); -- будет открываться файл с именем data.txt из текущей папки; assign (f,'a:\my.dat'); -- будет открыт файл с именем my.dat из корневой папки дискеты. Имя файла также может быть введено пользователем с клавиатуры: var name:string; f:text; begin writeln ('Введите имя файла:'); readln (name); assign (f,name); . Наконец, имя файла можно передать программе параметром командной строки (см. п. 5.3). После связывания следует открыть файл. Каждый файл может быть открыт только в одном из трех режимов -- для чтения данных, для записи новых данных (при этом, если файл уже существовал, его прежнее содержимое будет стерто) или для добавления данных в конец файла. Если требуется сначала прочитать данные из файла, а потом переписать этот же файл, следует открыть файл для чтения, после чтения закрыть его и вновь открыть для записи. Открытие файла выполняется одним из трех операторов: reset (f); -- открыть для чтения; rewrite (f); -- открыть для записи; append (f); -- открыть для добавления. Чтение или запись данных осуществляется знакомыми нам операторами read, readln, write и writeln, но первым параметром этих стандартных процедур указывается имя файловой переменной: var a,b,c:real; f1,f2:text; begin assign (f1,'read.txt'); assign (f2,'write.txt'); reset (f1); {открыли файл read.txt для чтения,} rewrite (f2); {а файл write.txt для записи} read (f1,a,b); {Прочитали 2 числа из файла read.txt} c:=(a+b)/2; writeln (f2,c:6:2); {записали значение c и перевод строки в файл write.txt} После того, как все операции с файлом выполнены, его следует закрыть, особенно если происходила запись или добавление данных: close(f); — закрыли файл, связанный с файловой переменной f. При работе с файлами могут возникнуть ошибки, связанные как с отсутствием нужного файла на диске, так и с проблемами чтения или записи (например, мы пытаемся открыть файл для записи на защищенном диске). Поэтому операторы открытия файла и чтения или записи данных следует защищать директивой компилятора {$I-}...{$I+}, а после оператора проверять статус операции ввода-вывода с помощью стандартной функции IoResult: var f:text; name,s:string; begin writeln ('Введите имя файла:'); readln (name); assign (f,name); {$I-}reset(f);{$I+} if IoResult<>0 then begin writeln ('Не могу открыть файл ',name, ' для чтения!'); writeln ('Нажмите Enter для выхода'); readln; halt; end; readln(f,s); writeln ('Первая строка файла:'); writeln (s); close(f); writeln ('Нажмите Enter для выхода'); readln; end. В дальнейшем мы для краткости не всегда будем выполнять эти проверки, но хороший стиль программирования предполагает, что в своих программах вы будете их делать. При чтении из файлов зачастую объем читаемых данных неизвестен заранее. Поэтому необходима функция, умеющая определять, прочитаны ли уже все данные: function Eof(var F: text): boolean; -- возвращает true, если при чтении достигнут конец файла. function Eoln (var F: text): boolean; -- возвращает true, если при чтении достигнут конец строки. Как правило, основной цикл чтения файла с заранее неизвестным количеством строк выглядит так: while not eof (f) do begin {операторы для чтения строк файла и работы с ними} end; При чтении из одного файла "смешанных" строковых и числовых данных следует проверять, действительно ли в нужных строках файла содержатся числа. Для этого можно использовать стандартную процедуру val. Как правило, формат файлов, понимаемых той или иной программой, выбирается программистом, и важно предусмотреть реакцию программы на ошибочный формат исходных данных. Допустим, наша программа представляет собой простейший телефонный справочник, включающий имена абонентов и по одному телефонному номеру на каждое имя. Формат файла, хранящего данные справочника, также выберем простейшим: Фамилия1 Номер1 Фамилия2 Номер2 и т. д., то есть, в строках 1, 3, 5,... файла содержатся фамилии абонентов, а в строках 2, 4, 6,... -- их номера телефонов. Примем также, что файл справочника называется phones.txt, существует и находится в той же папке, откуда запускается программа. Полный листинг программы приводится ниже. var f:text; name,phone,search:string; number,strings:longint; error:integer; found:boolean; begin assign (f,'phones.txt'); reset(f); writeln (Фамилия абонента для поиска:'); readln (search); strings:=1; {Счетчик прочитанных строк} found:=false; {Переключатель "найдено"-"не найдено"} while not eof(f) do begin readln (f,name); {Прочитали фамилию} readln (f,phone); {Прочитали номер} val (phone,number,error); {Пробуем номер-строку преобразовать в число} if error<>0 then begin {если это не удалось сделать - ошибка} writeln('Ошибка - нет номера телефона!', ' Номер строки=', strings); writeln ('Нажмите Enter для выхода'); reset (input); readln; halt; end; if name=search then begin writeln ('Телефон найден:',number); found:=true; break; end; strings:=strings+1; end; close (f); if found=false then writeln ('Телефон не найден!'); writeln ('Нажмите Enter для выхода'); reset (input); readln; end. Этой учебной программе недостает как умения редактировать открываемый ей справочник, так и гибкости при поиске -- например, она различает как разные символы строчные и прописные буквы во вводимой фамилии.
Читайте также: II. Проведение эксперимента и обработка результатов Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|