Nickolay.info. Обучение. Учебник по Паскалю. Глава 20

20. Обработка символьных и строковых данных

 

Компьютер способен обрабатывать не только числовые данные. Задачи обработки символьных данных распространены не менее, а возможно и более чем чисто арифметические расчеты. В этой главе мы изучим работу с символами и строками средствами Паскаля.

 

20.1. Работа с символами

 

Для работы с отдельными символами описываются переменные типа 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.

 

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.

Рейтинг@Mail.ru
вверх гостевая; E-mail