Учебная деятельность    

Строки

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 значение можно задать:

В Turbo Pascal полноценная работа с ASCIIZ-строками возможна только при включенном расширенном синтаксисе: {$X+}. Это позволяет использовать следующие возможности компилятора:

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, можно

Наиболее простой способ перевести строку из представления 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;
}