Язык Турбо Паскаль поддерживает стандартный символьный тип 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}