Строки
Free Pascal Reference Guide
Free Pascal Standard Units Reference Manual
В.Д.Далека и др. "Модели и структуры данных"
Строка - это линейно упорядоченная последовательность символов, принадлежащих конечному множеству символов, называемому алфавитом. Строки обладают следующими важными свойствами:
- их длина, как правило, переменна, хотя алфавит фиксирован;
- обычно обращение к символам строки идет с какого-нибудь одного конца последовательности, т.е. важна упорядоченность этой последовательности, а не ее индексация; в связи с этим свойством строки часто называют также цепочками;
- чаще всего целью доступа к строке является не отдельный ее элемент (хотя это тоже не исключается), а некоторая цепочка символов в строке.
Говоря о строках, обычно имеют в виду текстовые строки - строки, состоящие из символов, входящих в алфавит какого-либо выбранного языка, цифр, знаков препинания и других служебных символов. Однако, следует иметь в виду, что символы, входящие в строку могут принадлежать любому алфавиту. Если использовать алфавит {0, 1}, то строки, составленные из таких однобитных символов, называются битовыми строками. И к битовым, и к символьным строкам применяют сходные операции: определение длины, конкатенация, поиск подстроки и т.п.
Существуют несколько вариантов представления строк в памяти. Наиболее часто используются:
- представление строк вектором переменной длины с признаком конца,
- представление строк вектором переменной длины со счетчиком.
Представление строк в виде векторов, принятое в большинстве универсальных языков программирования, позволяет работать со строками, размещенными в статической памяти. Кроме того, векторное представление позволяет легко обращаться к отдельным символам строки как к элементам вектора - по индексу.
Признак конца - это особый символ, принадлежащий алфавиту (таким образом,
полезный алфавит оказывается меньше на один символ), и занимает то же количество
разрядов, что и все остальные символы. Чаще всего в качестве маркера конца
строки используется символ с ASCII-кодом 0. Представление строк вектором
с нулевым завершающим байтом (обозначается null terminated string или
ASCIIZ-строка) требует памяти на один байт больше длины строки.
'H' |
'E' |
'L' |
'L' |
'O' |
#0 |
Счетчик символов - это целое число, и для него отводится достаточное количество
битов, чтобы их с избытком хватало для представления длины самой длинной строки,
какую только можно представить в данной машине. Обычно для счетчика отводят от 8
до 32 битов. При использовании счетчика символов возможен произвольный доступ к
символам в пределах строки, поскольку можно легко проверить, что обращение не
выходит за пределы строки. Счетчик размещается в таком месте, где он может быть
легко доступен - начале строки или в дескрипторе строки. Максимально возможная
длина строки, таким образом, ограничена разрядностью счетчика.
#5 |
'H' |
'E' |
'L' |
'L' |
'O' |
И счетчик символов, и признак конца могут быть доступны для программиста как элементы вектора.
В Паскале стандартный строковой тип (string) представляется в виде массива символов, индексация в котором начинается с 0. Однобайтный счетчик числа символов в строке является нулевым элементом этого массива. Максимальная длина такой строки - 255 символов.
В языке Си типы данных максимально приближены к тем типам, с которыми работают машинные команды. Поскольку машинные команды не работают со строками, в Си нет специального типа данных для строк. Строки в Си представляются в виде обычных массивов символов и включают признак конца - нулевой байт (ASCIIZ-строки). Операции над строками выполняютя как операции обработки массивов. Несомненным достоинством ASCIIZ-строк является то, что их максимальный размер, как правило, ограничен лишь ресурсами системы.
В расширенных вариантах языка Паскаль (Turbo Pascal, Free Pascal, Delphi) также есть возможность работать с ASCIIZ-строками, для этого используется тип PChar. Фактически, переменные этого типа являются указателями на первый элемент вектора, содержащего ASCIIZ-строку:
Type PChar=^Char;
Как и любому другому указателю, переменным типа PChar значение можно задать:
- резервированием памяти при помощи процедур New или GetMem;
Var P:PChar; i:integer; Begin GetMem(P,1024); for i:=0 to 1022 do read(P[i]); P[1023]:=#0; End.
- присваиванием адресов существующих объектов (оператор @, функции Addr и Ptr).
Var S:String; P:PChar; Begin S := 'It will be a null-terminated string soon.'#0; P := @S[1]; End.
В Turbo Pascal полноценная работа с ASCIIZ-строками возможна только при включенном расширенном синтаксисе: {$X+}. Это позволяет использовать следующие возможности компилятора:
- автоматическая конвертация строковых констант в тип PChar;
- вывод содержимого ASCIIZ-строки в файл типа text;
- обращение к переменной типа PChar, как к массиву (array [0..n] of char), и наоборот (использование массива вместо PChar).
Var P:PChar; i:integer; P1:array [0..1023] of char; Begin P:='This is a null-terminated string.'; Writeln(P); Writeln(P[0]); for i:=0 to 1022 do read(P1[i]); P1[1023]:=#0; Writeln(PChar(P1)); P:=P1; End.
Для выполнения различных операций над ASCIIZ-строками предназначены функции модуля Stings. Вот специфические для Паскаля функции, связанные с конвертированием строк между различными представлениями:
uses Strings; function StrPas(p:PChar):String; function StrPCopy(dest:PChar; src:String):PChar;
Чтобы перевести строку из представления String в PChar, можно
- воспользоваться тем же буфером, в котором хранится String:
Var S:String; P:PChar; (* ... *) S[Length(S)+1]:=#0; {записываем символ-ограничитель} P:=@S[1]; {получаем указатель на первый символ в строке}
- скопировать строку в новый буфер при помощи функции StrPCopy,
при этом необходимо дополнительно позаботиться о резервировании достаточного
количества памяти:
Var S:String; P:PChar; (* ... *) GetMem(P,Length(S)+1); {В Free Pascal - P:=StrAlloc(Length(S)+1);} StrPCopy(P,S);
Наиболее простой способ перевести строку из представления PChar в String - воспользоваться функцией StrPas. Следует иметь в виду, что символы после 255-го будут отброшены (это максимальная длина строки в представлении String);
Для определения длины ASCIIZ-строки используется функция StrLen.
Она возвращает количество символов в строке без завершающего нуля.
uses Strings; function StrLen(p:PChar):SizeInt; | #include <string.h> size_t strlen(const char *p); |
Приведённые ниже примеры на Си расчитаны на возможность записи в строковые константы. В GCC ver. 3.x можно использовать опцию -fwritable-strings. В поздних версиях GCC эта возможность недоступна.
В отличие от строк со счетчиком (string) простое присваивание ASCIIZ-строк
не означает копирования их содержимого:
uses Strings; Var P1:PChar; Const P2:PChar='Some string'; Begin P1:=P2; {просто дублирование указателя на строку} P2[1]:='a'; Writeln(P1); {выводится "Same string"} End. | #include <string.h> #include <stdio.h> char *P1; char *P2="Some string"; int main() { P1=P2; P2[1]='a'; puts(P1); } |
Для копирования содержимого ASCIIZ-строк используется ряд функций. Функция
StrNew резервирует необходимое количество динамической памяти под
копию указанной строки и копирует туда ее содержимое. Чтобы освободить память
необходимо воспользоваться процедурой StrDispose. Для копирования
строки src в предварительно зарезервированный буфер dest используются функции
StrCopy и StrLCopy, при этом необходимо помнить, что
переполнение буфера не проверяется, однако вторая функция позволяет указать
максимальное количество копируемых символов maxlen, превысив которое остальные
символы исходной строки будут отброшены. Функции StrCopy и StrLCopy
работают некорректно, если dest и src перекрываются. В таком случае необходимо
использовать функцию StrMove, ее третий параметр - точное количество
копируемых символов.
uses Strings; function StrNew(p:PChar):PChar; procedure StrDispose(p:PChar); function StrCopy(dest,src:PChar):PChar; function StrLCopy(dest,src:PChar; maxlen:Intege):PChar; function StrMove(dest,src:PChar; len:SizeInt):PChar; | #include <string.h> char* strdup(const char *p); void free(void *p); /* из stdlib.h */ char* strcpy(char *dest, const char *src); char* strncpy(char *dest, const char *src, size_t maxlen); void* memmove(void *dest, const char *src, size_t len); |
В Паскале ASCIIZ-строки, в отличие от обычных строк, нельзя складывать.
Вместо этого используются функции конкатенации StrCat и StrLCat.
Эти функции в конец строки, определяемой первым параметром, дописывают
содержимое второго параметра-строки. Программист должен позаботится о том,
чтобы в буфере dest было достаточное количество места под композицию обоих строк
и завершающий нулевой символ. Функция StrLCat проверяет, чтобы длина
строки dest после конкатенации не превысила maxlen, при необходимости отбрасывая
лишние символы.
uses Strings; function StrCat(dest,src:PChar):PChar; function StrLCat(dest,src:PChar; maxlen:SizeInt):PChar; | #include <string.h> char* strcat(char *dest, const char *src); char* strncat(char *dest, const char *src, size_t maxlen); |
В приведенном примере сначала дублируется строка P, причем адрес нового
буфера сохраняется в P1. Затем внутри новой копии перемещается подстрока
"text", так что получается строка "Some text text" (если перемещать не 5, а 6
символов, будет скопирован завершающий нуль, тогда новая строка будет
интерпретироваться как "Some text"). После чего исходная строка копируется в
статический буфер P2. И, наконец, в конец этого буфера дописывается измененная
строка из P1. В результате на экран выводится "Some long textSome text text".
Uses Strings; Var P1:PChar; P2:array [0..63] of char; Const P:PChar='Some long text'; Begin P1:=StrNew(P); StrMove(P1+4,P1+9,5); StrCopy(P2,P); StrCat(P2,P1); Writeln(P2); End. | #include <string.h> #include <stdio.h> char *P1; char P2[64]; char *P="Some long text"; int main() { P1=strdup(P); memmove(P1+4,P1+9,5); strcpy(P2,P); strcat(P2,P1); puts(P2); } |
Для сравнения ASCIIZ-строк необходимо использовать функции серии Str*Comp.
Функции возвращают 0, если строки совпадают, или целочисленный результат
лексикографического сравнения. StrLComp и StrLIComp
сравнивают только первые maxlen символов. StrIComp и StrLIComp
выполняют сравнение без учета регистра буквенных символов.
uses Strings; function StrComp(str1,str2:PChar):SizeInt; function StrIComp(str1,str2:PChar):SizeInt; function StrLComp(str1,str2:PChar; maxlen:SizeInt):SizeInt; function StrLIComp(str1,str2:PChar; maxlen:SizeInt):SizeInt; |
#include <string.h> int strcmp(const char *str1, const char *str2); int strcasecmp(const char *str1, const char *str2); int strncmp(const char *str1, const char *str2, size_t maxlen); int strncasecmp(const char *str1, const char *str2, size_t maxlen); |
В следующем примере программа сравнивает первый параметр командной строки
с подстроками "add" и "sub" и в случае совпадения выполняет соответствующие
действия.
Uses Strings; Var P:PChar; S:String; Begin S:=ParamStr(1); S[Length(S)+1]:=#0; P:=@S[1]; if StrComp(P,'add')=0 then begin (* ... *) end; if StrComp(P,'sub')=0 then begin (* ... *) end; End. | #include <string.h> char *P; int main(int argc, char **argv) { P=argv[1]; if (!strcmp(P,"add")) { /* ... */ } if (!strcmp(P,"sub")) { /* ... */ } } |
Поиск подстроки str2 в строке str1 осуществляется функциями StrPos
и StrIPos. Функции возвращают указатель на первое вхождение подстроки
str2 внутри str1 или nil, если подстрока не найдена. StrIPos выполняет
поиск без учета регистра. Для поиска одиночного символа в строке используются
функции StrScan (поиск первого (самого левого) вхождения символа c
в строке p) и StrRScan (поиск последнего (самого правого) вхождения
символа c в строке p).
uses Strings; function StrPos(str1,str2:PChar):PChar; function StrIPos(str1,str2:PChar):PChar; function StrScan(p:PChar; c:Char):PChar; function StrRScan(p:PChar; c:Char):PChar; | #include <string.h> char* strstr(const char *str1, const char *str2); char* strcasestr(const char *str1, const char *str2); char* strchr(const char *p, int c); char* strrchr(const char *p, int c); |
Приведенный фрагмент после манипуляций со строками выводит текст "Some text".
Uses Strings; Var P1,P2:PChar; Const P:PChar='Some long text'; Begin P1:=StrPos(P,'long'); P2:=StrPos(P,'text'); if (P1<>nil) and (P2<>nil) then StrMove(P1,P2,5); Writeln(P); End. | #include <string.h> #include <stdio.h> char *P1, *P2; char *P="Some long text"; int main() { P1=strstr(P,"long"); P2=strstr(P,"text"); if (P1 && P2) memmove(P1,P2,5); puts(P); } |
Описанная ниже функция считает количество пробелов в строке.
Function SpaceCount(p:PChar):integer; Var ptr:PChar; i:integer; Begin i:=0; ptr:=p; repeat ptr:=StrScan(ptr,' '); if ptr<>nil then begin inc(i); inc(ptr); end; until (ptr=nil) or (ptr[0]=#0); SpaceCount:=i; End; | int SpaceCount(char *p) { int i=0; char *ptr=p; do { ptr=strchr(ptr,' '); if (ptr) { i++; ptr++; } } while (ptr && *ptr); return i; } |