Символьный и строковый типы данных

Работа 4

Цель работы:

- изучить приемы работы с символьными и строковыми типами данных;

- приобрести навыки составления, отладки и тестирования программ;

- разработать пользовательский модуль работы со строками и символами.

1. Введение

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

1) множество допустимых значений переменной;

2) формат внутреннего представления на ЭВМ и объем памяти, занимаемый одним значением;

3) набор операций, которые можно применять к значениям данного типа.

Строгая типизация данных, принятая в языке Паскаль, на первый взгляд кажется излишней, однако она придает высокую надежность при работе с данными и позволяет создавать новые типы. Умение разрабатывать необходимые для решения данной задачи типы данных – одно из важнейших профессиональных качеств программиста, приобретаемое постепенно в процессе решения конкретных задач.

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

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

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

type <имя типа> = <описание типа>;

И далее в разделе описания переменных следует:

var <список переменных> : <имя типа>;

В последних версиях языка допускается использование так называемых типизированных констант, которые в отличие от констант, могут изменять свое значение в процессе выполнения программы, т.е. ведут себя как переменные, но задаются в разделе описания констант с указанием после имени ее типа. Например,

const f1: boolean = true;

a: real = pi/180;

Использование типизированных констант избавляет от необходимости присвоения начальных значений переменным непосредственно в исполняемой программе. Кроме того, значения типизированных констант, измененные при выполнении процедуры, сохраняют свои значения к следующему запуску процедуры и могут быть, таким образом, использованы для сохранения внутренних параметров процедур.

В этой работе остановимся подробнее на изучении стандартных символьных и строковых типов данных.

2. Символьный тип данных.

Символьный (или литерный) тип данных задает конечное и упорядоченное множество символов, каждый из которых занимает 1 байт памяти. В одном байте возможно 28=256 комбинаций из двоичных 0 и 1. Каждому символу соответствует свой числовой код. Существует много таблиц кодировок символов, наиболее распространенная из них – американский стандартный код для обмена информацией ASCII (American Standard Code Information Interchange). Первая половина этой таблицы (символы с кодами от 0..127) идентична для всех IBM-совместимых компьютеров. Так, арабским цифрам от 0 до 9 соответствуют коды от 48 до 57, прописным буквам латинского алфавита коды от 65 до 90, строчным буквам от 97 до 122. Вторая половина таблицы ASCII отведена для символов национальных алфавитов, псевдографики и др.

Переменные символьного типа задаются в разделе описания как переменные типа Char (от character – символ). Например,

var c1, c2: char;

Переменная типа char хранит только один символ.

Значения для этого типа переменных могут быть заданы двумя способами:

1) через апостроф, например, c1:=’g’; c2:=”n”;

2) через знак диеза (#) и номер символа в кодовой таблице ASCII, например, c1:=#103; c2:=#110.

Поскольку каждое значение символьного типа имеет порядковый номер, к ним могут быть применимы стандартные функции:

Chr (x:byte): char – преобразует значение целочисленного типа в символьный в соответствии с таблицей ASCII;

Ord(x):longint – возвращает порядковый номер, соответствующий значению х порядкового типа;

Pred(x) – возвращает предшествующее значение аргумента, причем тип результата соответствует типу аргумента;

Succ(x) – возвращает последующее значение аргумента.

При работе с символьным типом полезна также стандартная функция

Upcase(x:char): – преобразует строчные буквы латинского алфавита в прописные, но не изменяет другие символы.

Функции Chr и Ord обратны по отношению друг к другу:

Сhr (Оrd(x))=x;

Ord (Сhr(x))=i;

Chr (Оrd(x)-1)=Pred(x);

Chr (Оrd(x)+1)=Succ(x).

С переменными символьного типа можно производить операции присваивания, а также логические операции отношений. Причем сравнение по величине производится согласно порядкового номера в кодовой таблице: из двух символов меньше тот, который встречается в таблице раньше.

Символьный тип данных удобно использовать при организации диалога компьютер-пользователь, в операторах цикла и операторе выбора case.

Заметим, что в таблице ASCII кроме обычных символов имеются специальные управляющие символы, занимающие первые 32 позиции. Они представляют собой команды, приводящие к выполнению определенных действий. Исторически они обозначались двух- или трехбуквенными сокращениями. Например, мнемоническое обозначение Esc, соответствует 27 коду ASCII – символ Escape, HT – горизонтальная табуляция, расположена в коде 9. Существует несколько способов обращения к управляющим символам:

1. по его ASCII-коду;

2. по Ctrl-последовательности, т.е. коду, порождаемому одновременным нажатием клавиш Ctrl и какой-либо другой клавиши; например, Ctrl+H (или это обозначается как ^H);

3. с помощью функции Chr(i).

Например,

1. ch:=10; ch:=^J; ch:=chr(10)

эти операторы присваивают символьной переменной ch одно и тоже символьное значение, соответствующее переводу строки (мнемоническое обозначение LF).

В качестве демонстрационного примера рассмотрим фрагмент программы, работающей как калькулятор:

var x, y, z: real; operator: char;

begin

writeln (‘Введи число х=’); readln(x);

writeln (‘Действие +, -, *, /?’); readln(operator);

writeln (‘Введи число y=’); readln(y);

case operator of

‘+’: z:= x+y;

‘-‘: z:= x-y;

‘*’: z:=x*y;

‘/’: z:=x/y;

end;

writeln (‘z=’,z); readln

end.

Другой демонстрационный пример попробуйте разобрать самостоятельно:

var ch:char; k,m:byte;

begin

readln(ch); k:=0;

repeat

m:=Ord(ch) – Ord(‘0’);

k:=10*k+m; readln(ch)

until not ((ch>=’0’) and (ch<=’9’));

writeln(‘ k=’,k); readln

end.

Проведите отладку и тестирование указанных фрагментов программ. Оформить их в виде подпрограмм, проведя предварительно улучшение сервиса.

3. Строковый тип данных.

Последовательность символов можно представить в виде переменной типа массив array[1..n] of char. Однако в языке Паскаль вводится стандартный символьный тип данных string.

Например, var st1:string;

st2:string[8];

где целое число в квадратных скобках задает максимальную длину строки. В данном примере длина переменной st2 равна 8. Если длина строки не указана, как для переменной st1, то она по умолчанию автоматически принимается равной максимально возможному значению 255.

Все символы, номера которых превосходят заказанную при объявлении длину строки, будут отброшены. Значения строковых переменных заключаются в апострофы, например, st2:=’frequency’. Поскольку ее длина равна 8, то фактическое значение переменной st2 будет ’frequenc’.

В языке Паскаль существует такое понятие как текущая длина строки, которая показывает число реально используемых символов строки, но не превосходящих указанной в описании. Эта информация хранится в нулевом байте памяти, например st2[0], и автоматически обновляется при изменении длины строки.

Поскольку в каждой ячейке строки может храниться только переменная типа char, в нулевую ячейку записывается символ, номер которого в таблице ASCII совпадает с текущей длиной строки. В нулевую ячейку можно записать любой символ, задавая, таким образом, принудительно длину строки.

Из сказанного видно, что переменные типа string и char являются совместными типами с учетом того, что в переменных char может храниться только одни символ.

Длину строки можно определить с помощью функций Length(st2) или Ord(st2[0]). Стандартная функция Length считает все символы, включая и пробелы.

Строки можно сравнивать при помощи операций отношений =, <, <= и т.д. Сравниваются при этом коды символов, начиная с первых символов строк. Две строки будут равными, если они равны оп длине и совпадают посимвольно. Более короткая строка всегда меньше более длиной. Например, ‘150’ < ‘160’ ; ‘Max’< ‘mini’.

Строковый тип может использоваться для определения констант и типизированных констант. В первом случае конкретный тип строки определяется определенной длиной заданного текста, а во втором – описанием типа. Например, типизированная константа

const s1:string[50]=’Ваня’;

задает строку типа string[50], но имеет текущую длину равную 4.

Строковый тип данных похож на одномерный массив тем, что имеет определенную длину и к каждому символу которого можно обратиться по его номеру st2[i]. Однако имеется и ряд различий, например, вывод строк производится не поэлементно, как в массиве, а сразу целиком. Для символьных типов данных существует понятие текущей длины строки, имеется набор стандартных процедур и функций для их обработки.

4. Стандартные подпрограммы обработки строк.

Остановимся подробнее на некоторых стандартных функциях и процедурах для работы со строковыми переменными.

1.С функцией, определяющей длину аргумента str строкового типа, мы уже знакомы:

Length(str:string):byteю.

2. Функция Pos(substr, str:string):byte определяет позицию подстроки substr в строке str.Результат этой функции – целое число типа byte, равное номеру первого элемента, с которого начинается вхождение подстроки substr в строке str. Если такой подстроки нет, то значение функции равно 0.

Например, следующий фрагмент

st1:=’First’; st2:=’SetFirst’; k1:=Pos(st1,st2);

дает значение k1 равное 4.

3. К строкам можно применять операцию конкатенации – последовательное объединение нескольких строк (склеивания). Она осуществляется оператором конкатенации, обозначаемой знаком “плюс”:

st:=st1+st2+…+stn;

или функцией

st:=Concat(st1,st2,…,stn).

Длина результирующей строки st не должна превышать 255 литер.

4. Функция Copy(str:string, kstart,m:integer): string позволяет копировать m символов строки str, начиная с kstart символа. Исходная строка при этом не изменяется. Например,

gamma:= ‘SetColor’; beta:=Copy(gamma, 4,5); writeln(beta);

дает значение переменной beta=’Color’.

5. Процедура Delete (var str:string, kstart, m:integer) используется для удаления m символов строки str, начиная с позиции ksatr. Например,

alfa:=’element’;

Delete (alfa,4,3);

дает результат alfa=’elet’.Текущая длина строки уменьшается на m символов, оставшиеся в строке символы смещаются влево.

6. Процедура Insert(instr:string, var str:string, kstart:integer) позволяет делать вставку строки instr в строку str, начиная с позиции kstart. Например,

beta:=’123 ‘; alfa:=’element’;

Insert(beta, alfa,5);

дает результат alfa=’123_element’. В процедурах Delete и Insert параметр kstart может быть константой, переменной и любым выражением, имеющим целочисленное значение меньшее 255.

7. Процедура Str(n [:width[:decimals]]; var strn:string) переводит числовое значение n в строковое и присваивает результат строке strn. Здесь n – значение числового типа, strn – строковое значение, представляющее собой символьное изображение значения переменной n.

Например, Str(4.52, st1) переводит вещественное число в строковое st1=’4.52’.

8. Процедура Val (strn:sting; var n; var errcode:integer) выполняет обратное преобразование, переводит строковое значение strn в числовое n. Параметр errcode равен нулю при успешном выполнении преобразования. Если в strn имеются символы, недопустимые при записи числа, то значение параметра errcode равно номеру позиции с ошибочно заданным символом. Например,

Val(‘12345’, n,k);

здесь n = 12345; k=0.

Val (‘12_45’ n, k);

дает значение параметра k=3.

Экспериментальный раздел

Пример 1.Пусть задан не отформатированный текст. Требуется вставить пробел после точки и записать предложение с прописной буквы.

var str:string; k:integer;

begin

writeln(‘Введите текст’); readln(str);

for k:=1 to Length(str) do

if Copy(str,k,1)=’.’

then begin Insert(‘ ‘,str,k+1); str[k+2]:= UpCase(str[k+2] end;

writeln(str); readln

end.

Программа будет работать только с латинским шрифтом. Переделайте её так, чтобы можно было вести обработку кириллицы.

Пример 2. По правила машинописи после запятой в тексте всегда ставится пробел. Следующая программа вставляет недостающие пробелы.

var i: integer; s: string;

Begin

writeln (‘Введите текст’); readln (s);

i:=1;

while i<Length(s) do begin

if (s[i]=’ ,’) and not (s[i+1]=’ ‘)

then Insert(‘ ‘,s,i+1); Inc (i)

end;

writeln(s);

End.

Почему для организации цикла выбран оператор While, а не For? Почему в теле цикла нельзя использовать функцию Pos(“ “, s)? Обратите внимание на то, что если запятая — последний символ текста, то мы не добавляем пробел. Измените программу для исправления случаев нарушения правила “после символов “!”, “?” должен стоять пробел, а затем текст начинается с прописной бук­вы”. Вспомните еще ряд правил и традиционных ошибок и модифицируйте программу для их исправления.

Пример 3. Давайте разберемся со следующей рекурсивной процедурой, осуществляющей реверс введенного слова.

type strtype = string[20];

var strread,strrev:strtype;

function Reverse(str:strtype):strtype;

var firstch:char; rrest:strtype;

begin

if Length(str)=1

then Reverse :=str

else begin

firstch:=str[1]; Delete(str,1,1);

rrest:=Reverse(str);

Reverse:=Concat(rrest,firstch)

end

end;

Begin

repeat

writeln('Input text.Word endf - end'); readln(strread);

strrev:=Reverse(strread); writeln(strrev)

until strread = 'endf';

readln

End. ­

Поэкспериментируйте с данной программой. Исследуйте «слова-перевертыши» (палиндромы), которые читаются одинаково слева направо и справа налево: «доход», «заказ», «шалаш» и т.д.

Существуют целые предложения, стихи и рассказы – палиндромы. Приведем несколько смешных фраз: «торт с кофе не фокстрот», «удавы рвали лавры в аду», «Аргентина манит негра», «я и ты будем в аду бытия», «я не мил и не женили меня».

Попробуем составить программу, которая будет анализировать предложение на свойство палиндромности.

var x,y,z:string; k:integer;

function UpRusCase(w:char):char;

{Преобразование прописных русских слов в заглавные}

begin

if (w>=’ю’) and (w<=’ч ’)

then UpRusCase:=Chr(Ord(w)+32)

UpRusCase:=w

end;

Begin

y:=’ ’; z:=’ ‘;

writeln(‘Введите исседуемую фразу’); readln(x);

for k:=1 to Length(x) do

if (Copy(x,k,1)>=’ю’) and (Copy(x,k,1)<=’ч’)

then begin z:=Concat(z,UpRusCase(Copy(x,k,1)));

y:=Concat(UpRusCase(Copy(x,k,1)),y)

end;

if y=z

then writeln(‘да, палиндром’)

else writeln(‘увы, не палиндром’);

readln

End.

Поэкспериментируйте с данными программами.

Пример 4. Пусть дана строка. Будем считать ее отрывком текста. Группы символов, разделенных одним или несколькими пробелами, назовем словами. Пробелы могут находиться как в начале текста, так и в конце. Требуется выделить слова из текста и каждое слово записать в соответствующий элемент массива. Приведенная ниже программа решает эту задачу.

const n=20; m=10; {Количество слов в тексте и количество букв в слове}

type Tstring=string[m];

var a: array[1..n] of Tstring; s: string; k, i: Integer;

procedure DelPr (var s: string); { Удаляем пробелы из текста }

begin

while (s[1]=’ ‘) and (s<>’ ‘ ) do Delete (s,1,1)

end;

function GetWord (var s: string): Tstring;

{Выделяем слово, удаляем его из текста и убираем пробелы после слова}

begin

GetWord:=Copy(s,1,Pos(‘ ‘,s)-1);

Delete (s,1,Pos(‘ ‘ ,s));

DelPr(s)

end;

Begin

writeln (‘Введите текст’); readln (s);

s:=s + ’ ‘; {Добавляем символ пробела в конец текста. Зачем?}

DelPr(s); {Удаляем пробелы в начале текста }

k:=0;

while s<>’ ’ do begin {Пока текст не пустой }

Inc(k); a[k]:=GetWord(s) {Берем слово из текста }

end;

for i:=1 to k do writeln (A[i]); readln

End.

Удалите вызов процедуры DelPr(s) из основной программы, а в функции GetWord переставьте этот вызов в ее начало. Что изменится в работе программы? На каких исходных данных она не будет правильно работать? Что произойдет, если в конец текста не добавлять символ пробела? В процедуре DelPr измените цикл

while (s[1]=’ ‘) and (s<>’ ‘ ) do Delete (s,1,1)

на

while (s[1]=’ ‘) do Delete (s,1,1)

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

5.Даны две строки Х и Y. Назовем расстоянием r между строками Х и Y количество символов, которыми Х и Y различаются между собой. То есть нас интересует минимальное количество символов, которые необходи­мо добавить в строки Х и Y для того, чтобы после тако­го добавления они состояли из одних и тех же симво­лов. При этом существенно количество символов, но не их порядок. Например Х= 'abcd', Y= 'dxxc', r= 4, Х= '1111111', Y= '111222', r= 7. Приведем текст решения данной задачи:

var i,j,r: integer; s,x,y: string;

Begin

writeln ( ' Первая строка ' ); readln (x);

writeln (' Вторая строка' ); readln (y);

if Length(x)>Length(y)

then begin s:=x; x:=y; y:=s end;

{ Строка Y должна быть не короче X }

r:=Length(y);

for i:=l to Length (x) do begin

j:=1;

while (j<=Length(y)) and (y[j]<>x[i)) do Inc(j);

if j>Length(y)

then Inc(r)

else begin dec(r); Delete(y,j,1) end

{ Есть совпадение. Уменьшаем расстояние и удаляем символ из Y }

end;

writeln (' Расстояние ' , r) ; readln

End.

К каким последствиям приведет исключение из ре­шения строки со сравнением длин строк и записи в Y строки наибольшей длины? Проверьте. Исключим опе­ратор Delete(Y,j,l). Что получится? Измените программу так, чтобы в тексте находились два слова, расстояние между которыми имеет максимальное (минимальное) значение.

Наши рекомендации