Символьный и строковый типы (Char и String)




Язык Турбо Паскаль поддерживает стандартный символьный тип Char и, кроме того, динамические строки, описываемые типом String или String[ n ].

Значение типа Char — это непустой символ из алфавита ПЭВМ, заключенный в одинарные кавычки, например, ' ', 'А', '7' и т.п. Кроме этой, классической, формы записи символов, Турбо Паскаль вводит еще две. Одна из них — представление символа его кодом ASCII с помощью специального префикса #:

#97 = Chr(97) = 'а' (символ 'а'),

#0 = Chr(0) (нулевой символ),

#32 = Chr(32) = ' ' (пробел).

Символы, имеющие коды от 1 до 31 (управляющие), могут быть представлены их «клавиатурными» обозначениями — значком «^» и буквой алфавита с тем же номером (для диапазона кодов 1...26) или служебным знаком (для диапазона 27...31):

^A = #1 = Chr(1) – код 1,

^B = #2 = Chr(2) – код 2,

...

^ = #26 = Chr(26) – код 26.

^[ = #27 = Chr(27) – код 27,

^_ = #31 = Chr(31) – код 31,

в том числе ^G — звонок (7), ^I — TAB (код 9), ^J — LF (код 10), ^M — CR (код 13) и т.п.

Рассмотрим более подробно строковый тип. Максимальная длина строки составляет 255 символов. Строки называются динамическими, поскольку могут иметь различные длины в пределах объявленных границ. Например, введя переменные {148}

VAR

S32: String[ 32];

S255: String[255];

мы можем хранить в S32 строчные значения, длиной не более 32 символов, а в S255 — не более 255. Описание String без указания длины отводит место, как и описание String[255].

Тип String без длины является базовым строковым типом, и он совместим со всеми производными строковыми типами.

При попытке записать в переменную строку длиннее, чем объявлено в описании, «лишняя» часть будет отсечена. Можно присваивать пустые строки. Их обозначение " (две одинарные кавычки подряд).

Значением строки может быть любая последовательность символов, заключенная в одинарные кавычки:

'abcde-абвгд'

'123? 321'

' '

''

'"'

Последняя конструкция есть строка из одной одинарной кавычки.

При необходимости включать в строку управляющие коды можно пользоваться «клавиатурными» обозначениями. В этом случае значение состоит из как бы склеенных кусков:

^G 'После сигнала нажмите '^J' клавишу пробела '^M^J

В такой записи не должно быть пробелов вне кавычек.

Более общим способом включения символов (любых) в строку является их запись по ASCII-коду через префикс #. Механизм включения в строку остался тем же:

'Номер п/п'#179' Ф.И.О. '#179' Должность '#179

#7#32#179#32#32#179 (то же, что ^G'| |')

Строки различных длин совместимы между собой в операторах присваивания и сравнения, но «капризно» ведут себя при передаче в процедуры и функции. Если тип объявленного строкового параметра в процедуре или функции является производным от типа String, то при вызове процедуры или обращении к функции тип передаваемой строки должен совпадать с типом параметра. Имеется ключ компилятора $V, управляющий режимом проверки типов соответствующих формальных и фактических параметров-строк. В режиме {$V+} такая проверка проводится и при несогласованности типов выдает сообщение об ошибке. Но если отключить ее, то можно подстав-{149}лять в вызовы процедур и функций фактические параметры, имеющие другой, отличающийся от формальных, производный от String тип. При этом во время работы программы могут возникнуть наложения значений одних строк на другие и путаница в значениях. Для того чтобы писать процедуры, работающие со строками любых строковых типов, нужно описывать формальные параметры исключительно типом String и компилировать программу в режиме {$V-}. Это даст процедурам и функциям гибкость и безопасность данных.

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

FUNCTION Xstr(S: String[32]): String[6];

совершенно неправильно. Корректно оно выглядит таким образом:

TYPE

String32 = String[32];

String6 = String[6];

...

FUNCTION Xstr(S: String32): String6;

Можно, вообще говоря, рассматривать строки как массив символов. Турбо Паскаль разрешает такую трактовку, и любой символ в строке можно изъять по его номеру (рис. 8.1).

VAR i: Byte; S: String[20]; BEGIN S:= 'Массив символов.....'; for i:=1 to 20 do WriteLn(S[i]); ReadLn {Пауза до нажатия клавиши ввода} END.

Рис. 8.1

Отдельный символ совместим по типу со значением Char. Иными словами, можно совершать присваивания, представленные на рис. 8.2.

Каждая строка всегда «знает», сколько символов в ней содержится в данный момент. Символ S[0] содержит код, равный числу символов в значении S, т.е. длина строки S всегда равна Ord(S[0]). Эту особенность надо помнить при заполнении длинных строк {150}

VAR ch: Char; st: String; BEGIN st:= 'Hello'; ch:= st[1]; { ch = "H" } st[2]:= 'E'; { st = 'HEllo' } ch:= 'x'; st:= ch; { st = 'x' } END.

Рис. 8.2

одинаковым символом. В самом деле, не писать же в программе S:= '... и далее, например, 80 пробелов в кавычках! Проблему заполнения решает встроенная процедура FillChar:

VAR St: String;

...

FillChar(St[1], 80, ' ');

St[0]:= Chr(80); {!!!}

Здесь St[1] указывает, что надо заполнять строку с первого элемента. Можно начать заполнение и с любого другого элемента. В данном случае строка заполняется с начала 80 пробелами (' ' или #32). Очень важен последующий оператор: так как строка «закрашивается» принудительно, ей надо сообщить, сколько символов в ней стало, т.е. записать в St[0] символ с кодом, равным ее новой длине.

Первоначально строка содержит «мусор», и всегда рекомендуется перед использованием инициализировать строки пустыми значениями или чем-либо еще.

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

Операции над символами

Символы можно лишь присваивать и сравнивать друг с другом. При сравнении символов они считаются равными, если равны их ASCII-коды; и один символ больше другого, если имеет больший ASCII-код: {151}

'R' = 'R'

'r' > 'R' (код 114 > кода 82).

Операции сравнения записываются традиционным способом:

<, <=, =, >=, >, <>.

Каждый символ можно рассматривать как элемент множества Set of Char и применять к нему операцию проверки на включение in:

VAR Ch: Char;

...

ch:= 'a';

if Ch in ['a'..'z'] then...

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

Таблица 8.1

Функция Назначение
Chr(X:Byte): Char Возвращает символ ASCII-кода X
Ord(C:Char): Byte Возвращает ASCII-код символа C
Pred(C:Char): Char Выдает предшествующий C символ
Succ(C:Char): Char Выдает последующий за C символ
UpCase(C: Char): Char Переводит символы 'a'..’z’ в верхний регистр: ’A’..’Z’

Функции Succ и Pred хороши для последовательного перебора символов. Следует только помнить, что не определены значения Succ(#255) и Pred(#0).

Функция UpCase переводит в верхний регистр символы латинского алфавита, возвращая все остальные, в том числе и кириллицу, в исходном виде.

Операции над строками

Строки можно присваивать, сливать и сравнивать. Слияние строк записывается в естественном виде (рис. 8.3). Если сумма получается длиннее, чем описанная длина левой части оператора присваивания, излишек отсекается.

Сравнение строк происходит посимвольно, начиная от первого символа в строке. Строки равны, если имеют одинаковую длину и посимвольно эквивалентны: {152}

VAR S1, S2, S3: String; BEGIN S1:= 'Вам '; S2:= 'привет'; S3:= S1 + S2; { S3 = 'Вам привет' } S3:= S3 + '!'; { S3 = 'Вам привет!' } END.

Рис. 8.3

'abcd' = 'abcd' --> TRUE,

'abcd' <> 'abcde' --> TRUE,

'abcd' <> ' abcd' --> TRUE.

Если при посимвольном сравнении окажется, что один символ больше другого (его код больше), то строка, его содержащая, тоже считается большей. Остатки строк и их длины не играют роли. Любой символ всегда больше «пустого места»:

'abcd' > 'abcD' (так как 'd'>'D'),

'abcd' > 'abc ' (так как 'd'>' '),

'aBcd' < 'ab' (так как 'B'<'b'),

' ' > " (так как #32>").

Можно, конечно, использовать нестрогие отношения: >= и <=.

Для работы со строками реализовано большое количество процедур и функций (табл. 8.2):

Таблица 8.2

Процедуры и функции Назначение
РЕДАКТИРОВАНИЕ СТРОК
Length(S:String): Byte Выдает текущую длину строки
Concat(S1, S2,…,Sn): String Возвращает конкатенацию или слияние строк S1 … Sn
Copy(S:String; Start, Len: Integer): String Возвращает подстроку длиной Len, начинающуюся с позиции Start строки S
Delete(Var S: String; Start, Len: Integer) Удаляет из S подстроку длиной Len, начинающуюся с позиции Start строки S {153}
Insert(VAR S: String; SubS: String; Start: Integer) Вставляет в S подстроку SubS, начиная с позиции Start
Pos(SubS, S: String): Byte Ищет вхождение подстроки SubS в строке S и возвращает номер первого символа SubS в S или 0, если S не содержит SubS
ПРОЦЕДУРЫПРЕОБРАЗОВАНИЯ
Str(X[:F[:n]]; VAR S: String) Преобразует числовое значение X в строковое S. Возможно задание формата для X
Val(S: String; VAR X; VAR ErrCode: Integer) Преобразует строковое значение S (строку цифр) в значение числовой переменной X

Редактирование строк

Функция Length(S: String) возвращает текущую длину строки S. Вообще говоря, можно вместо нее пользоваться конструкцией Ord(S[0]), что то же самое.

Функция Concat производит слияние переданных в нее строк. Вместо нее всегда можно пользоваться операцией «+»:

S3:= Concat(S1,S2); { то же, что S3:= S1 + S2 }

S3:= Concat(S3,S1,S2); { то же, что S3:= S3 + S1 + S2 }

Если сумма длин строк в Concat превысит максимальную длину строки в левой части присваивания, то излишек будет отсечен.

8.3.1.3. Функция Сору(S: String; Start, Len: Integer) позволяет выделить из строки последовательность из Len символов, начиная с символа Start. Если Start больше длины всей строки S, то функция вернет пустую строку, а если Len больше, чем число символов от Start до конца строки S, то вернется остаток строки S от Start до конца. Например:

SCopy = Сору('АВС***123', 4, 3) { SСору='***' }

SCopy = Copy('ABC', 4, 3) { SCopy=' '}

SCopy = Copy('ABC***123', 4,11) { SCopy='***123' } {154}

Используя процедуру Copy, построим игровой пример появления строки на экране (рис. 8.4).

USES CRT; { используется модуль CRT } { Процедура выводит строку S в позиции (X,Y), } { с эффектом раздвижения и звуковым сигналом. } PROCEDURE ExplodeString(X,Y: Byte; S: String; С: Word); VAR i,L2: Byte; BEGIN L2:= (Length(S) div 2) + 1; { середина строки } if X < L2 then X:=L2; { настройка X } for i:=0 to L2-1 do begin { цикл вывода: } GotoXY(X-i, Y); { начало строки } { Вывод расширяющейся центральной части строки: } Write(Copy(S, L2-i, 2*i+1)); Sound(i*50); { подача звука } Delay(С); { задержка С мс } NoSound { отмена звука } end { конец цикла } END; { ========= ПРИМЕР ИСПОЛЬЗОВАНИЯ ПРОЦЕДУРЫ======== } BEGIN ClrScr; { очистка экрана } ExplodeString(40, 12, '12345678900987654321', 30); ReadLn { пауза до нажатия клавиши ввода } END.

Рис. 8.4

8.3.1.4. Процедура Delete(VAR S: Siring; Start, Len: Integer) видоизменяет сроку S, стирая Len символов, начиная с символа с номером Start:

S:= 'СТРОКА';

Delete(S, 2, 4); { S='CA')

После стирания подстроки ее оставшиеся части как бы склеиваются.

Если Start=0 или превышает длину строки S, то строка не изменится. Также не изменит строку значение Len=0. При Len, большем чем остаток строки, будет удалена подстрока от Start и до конца S. Это можно использовать при «подрезании» строк до заданной величины: {155}

Delete(S, 16, 255)

Здесь строки S длиною менее 17 символов пройдут через процедуру неизменными, а все остальные будут укорочены до длины в 16 символов.

8.3.1.5. Процедура Insert(Subs: String; VAR S: String; Start: Integer) выполняет работу, противоположную той, что делает Delete. Insert вставляет подстроку Subs в строку S, начиная с позиции Start:

S: = 'Начало-конец';

Insert('середина-', S, 8);

{ теперь S = 'Начало-середина-конец' }

Если измененная строка S оказывается слишком длинной, то она автоматически укорачивается до объявленной длины S (при этом, как всегда, «теряется» правый конец).

Пример использования пары процедур Insert и Delete можно увидеть на рис, 8.5, где приводится функция создания строки с текстом посередине.

{Функция возвращает строку длиной Len, заполненную символом Ch, со вставленной в середину подстрокой S } FUNCTION CenterStr(S: String; Len: Byte; Ch: Char): String; VAR fs: String; { промежуточная строка-буфер } ls, l2: Byte; { вспомогательные переменные } BEGIN FillChar(fs[1], Len, Ch); { заполнение строки fs Ch } fs[0]:= Chr(Len); { восстановление длины fs } ls:= Length(S); { длина входной подстроки S } if ls>=Len then begin { если некорректны параметры} CenterStr:=S; Exit { то ничего с S не делается } end; l2:= (Len-ls } div 2 +1;{место начала вставки в fs } Delete(fs, l2, ls); {очистка места в центре fs } Insert(S, fs, l2); {и вставка туда строки S } CenterStr:= fs {итоговое значение функции } END; {=== ПРИМЕР ВЫЗОВА ФУНКЦИИ ===} BEGIN WriteLn(CenterStr('Работает!', 80, '=')); ReadLn { пауза до нажатия клавиши ввода } END.

Рис. 8.5 {156}

8.3.1.6. Функции Pos(Subs, S: String): Byte возвращает номер символа в строке S, с которого начинается включение в S подстроки Subs. Если же S не содержит в себе Subs, то функция вернет 0. Пример использования функции дан на рис. 8.6, где построена модификация процедуры преобразования Str.

{ Процедура преобразования числа X в строку S} { Вход: X — числовое значение; } { F — полная длина поля числа; } { N — число цифр после запятой. } { Выход: строка S, в которой предшествующие } { числу пробелы заменены на 0. } PROCEDURE ZStr(X: Real; F,N: Byte; VAR S: String); VAR p: Byte; BEGIN Str(X:F:N, S); { строка с пробелами } { Цикл замены пробелов на нули: } while Pos(' ', S) > 0 do S[Pos(' ', S)]:= '0'; p:= Pos('-',S); { позиция минуса в числе } if р <> 0 then begin { Если минус имеется, то } S[p]:= '0'; S[1]:= '-' { переместить его в нача-} end; { ло строки S. } END; { ======= ПРИМЕРЫВЫЗОВОВ ФУНКЦИИ ======= } CONST r: Real = 123.456; b: Byte = 15; i: Integer = -3200; St: String = ' '; BEGIN ZStr(r, 10, 5, St); WriteLn(St); { 0123.4560 } ZStr{ b, 10, 1, St); WriteLn(St); { 0000015.0 } ZStr(i, 10, 0, St); WriteLn(St); { -00003200 } ReadLn { пауза до нажатия клавиши ввода } END.

Рис. 8.6

Очевидным недостатком функции Pos является то, что она возвращает ближайшую стартовую позицию Subs в S от начала строки, т.е. вызов

P:= Pos('noo', 'Boonoonoonoos');

завершит свою работу, вернув значение 4, хотя есть еще и 7, и 10. {157}

На рис. 8.7 приведен вариант функции, использующей функцию Pos и возвращающей позицию любого вхождения Subs в S, если оно существует.

{Функция возвращает номер символа, с которого начинается N-e вхождение подстроки Subs в строку S. Одновременно возвращается общее число вхождений Count. При неудаче поиска функция возвращает значение 0. } FUNCTION PosN(Subs, S: String; N: Byte; VAR Count: Byte): Byte; VAR p, PN: Byte; { вспомогательные переменные } BEGIN Count:=0; PN:=0; repeat { Цикл по вхождениям: } p:= Pos(Subs, S); { поиск вхождения } if Count<N then Inc(PN,p);{ суммирование позиций } Inc(Count); { счетчик вхождений } Delete(S, 1, p) { уменьшение строки } until p=0; { конец цикла, если р=0 } Dec(Count); { надо уменьшить Count } if N<=Count { N не больше, чем Count? } then PosN:= PN {Да, возвращаем позицию } else PosN:= 0; { Нет, возвращаем 0 } END; VAR { ===== ПРИМЕР ВЫЗОВА ФУНКЦИИ ===== } C: Byte; {количество вхождений подстроки в строку } BEGIN WriteLn('3-я позиция noo в Boonoonoonoos начинается', ' с символа ', PosN('noo','Boonoonoonoos',3,С):3); WriteLn('Всего найдено вхождений: ', С); ReadLn { пауза до нажатия клавиши ввода } END.

Рис. 8.7

Преобразование строк

8.3.2.1. Процедура Str(X [: Width [: dec ] ]; VAR S: String)

служит для преобразования числовых значений в строковые. Это, в частности, необходимо для работы с процедурами модуля Graph OutText и OutTextXY. X может быть переменной или значением целого или вещественного типов. Можно задавать поля формата, указывая ширину поля для числа и число знаков после десятичной {158} точки. Для целых значений можно задать только поле Width, для вещественных — либо оба поля (формат с фиксированной точкой), либо одно — Width. В последнем случае задается экспоненциальный формат общей длиной Width. Напомним, что экспоненциальный формат чувствителен к использованию сопроцессора. Так, для поля Width, равного 13, будем иметь

в режиме {$N-}: -1.234567E+10,

в режиме {$N+}: -1.2346Е+0010.

Если число имеет меньше знаков, чем дано в поле, то оно будет выровнено по правому краю, пустое место заполнится пробелами. Можно задать поле Width отрицательным, в этом случае выравнивание происходит по левому краю, а излишки как бы стираются:

Str(6.66: 8: 2, S); { S=' 6.66' }

Str(6.66: -8: 2, S); { S='6.66' }

Str(6.66: 8: 0, S); { S=' 7' }

Можно задать значения полей формата целочисленными переменными или константами:

VAR

F, n: Integer;

S: String;

...

F:=-5; n:=1;

Str(-123.456: F: n, S);

Возможность задания поля константой решает проблему экспоненциального формата для случаев использования математического сопроцессора. Достаточно применить команды условной компиляции:

{$IFOPT N+}

CONST FORMAT_E =14; { формат: +n.nnnnnE+NNNN }

{$ELSE}

CONST FORMAT_E =12; { формат: +n.nnnnnE+NN }

{$ENDIF}

...

Str(x: FORMAT_E, S);

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

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

Str(1.234567:6:4, S); {S='1.2346'}

Само значение числа при этом не изменится. {159}

8.3.2.2. Процедура Val(S: String; VAR V; VAR ErrCode: Integer) преобразует числовые значения, записанные в строке S в числовую переменную V. Если преобразование возможно, то переменная ErrCode равна нулю, в противном случае она содержит номер символа в S, на котором процедура застопорилась. Тип V должен соответствовать содержимому строки S. Если в S имеется точка или степень числа Е+nn, то V должна быть вещественного типа, в остальных случаях может быть и целой. Массу сложностей доставляют проблемы переполнения: если S='60000', a V в вызове процедуры будет подставлена типа Byte, то что получится в итоге?

Возможны два варианта. Первый — при работе программы проверяются диапазоны и индексы (режим $R+); при переполнении возникнет фатальная ошибка счета, и программа прервется. Второй — проверка отключена (режим $R-); все зависит от типа V: если он вещественный или LongInt, то при переполнении ErrCode<>0, a V содержит числовой «мусор». Но если V имеет тип «короче» чем LongInt, то ErrCode при переполнении молчит (равно 0), а в V записывается результат переполнения, мало похожий на содержимое S. Не слишком «прозрачно», не так ли? В техническом описании языка после изложения всего этого дается совет, как обойти все эти условности. При преобразовании строк с целыми числами рекомендуется на место V подставлять переменную типа LongInt. Пусть надо перевести строку S с целым значением в переменную WordV типа Word. Схема будет такой:

{$r-}

VAR LongV: LongInt; WordV: Word;

WordV:= 0; { начальная очистка WordV }

Val(S, LongV, ErrCode }; { вызов преобразования }

if ErrCode=0

then begin { в S записано число }

if (LongV >= 0) and (LongV <= 65535)

then { Все впорядке! }

WordV:= LongV

else { Иначе несовместимость! }

WriteLn('Ошибка диапазона при преобразовании ',LongV);

end {then}

else { содержимое S не годится }

WriteLn('Ошибка в строке ',S,' в символе ',S[ErrCode]);

При преобразовании строк в другие целые типы достаточно лишь менять диапазон разрешенных значений в операторе IF. {160}



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2022-11-01 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: