l

String – строчный тип данных, процедуры и функции для работы со строками

22.02.2021 4550 Программируем на Паскале Ваш комментарий

Сегодня мы познакомимся со строковым типом данных — String. Как ясно из определения, в переменных такого типа можно сохранять строки.

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

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

Когда при помощи процедур write и writeln мы выводили сообщения в программах на экран, то так или иначе касались строк. И строки эти мы обособляли одиночными кавычками — апострофами «’». Из этого вытекает первое ограничение: апостроф не может являться частью строки. Точнее, может, но его для этого нужно «экранировать», мы об этом поговорим позже.

Второе ограничение накладывается на длину передаваемых в программе строк. И длина эта не может превышать 255 символов. При этом на каждую переменную такого типа отводится до 256 байтов: 255 — собственно на строку, а в нулевом байте сохраняется реальная длина переданных данных. Вдумайтесь, для хранения чисел мы использовали 1, 2, 4 или 6 байтов памяти, а для строк отводится до 256 байтов! Если вспомнить, что Паскаль как язык программирования появился в 1968-1969 годах, и на тогдашних компьютерах было очень мало оперативной памяти, то отдавать по 256 байтов на данные какого-то одного типа — было почти расточительством. Сегодня же мы эту оперативку не считаем вовсе.

Если честно, то современные среды разработки позволяют сохранять в переменную типа String почти ничем не ограниченные строки. Строго говоря, ограничения всё-таки есть, но они находятся близко к объёму оперативной памяти вашего компьютера. Я же с самого начала решил рассказывать в своих статьях и видео о традиционном паскале, поэтому мы эту скользкую тему обойдём.

При наличии современных кодировок и кодовых страниц, я даже не берусь считать, сколько байтов отводит для хранения каждой переменной типа String наша среда разработки — Pascal ABC.Net. Однако и в прежние времена механизм хранения строк был относительно гибким.

1 байт = 8 битов. Бит — минимальная единица информации, в которой может в любой момент времени храниться 0 или 1. Если число состоит из восьми цифр, каждая из которых может принимать только два значения, то мы можем записать 28 различных чисел. 256 чисел от 0 до 255.

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

Листинг 1.

program TypeString;

var
  s: String[20];

BEGIN
  
END.

При попытке сохранить большее количество символов (байтов) в объявленную таким образом переменную, Паскаль выдаст ошибку. Пример вышел так себе, мы только объявили в нём переменную. Теперь напишем совсем другую программу. В ней мы объявим две строковые переменные и с их помощью выведем на экран текст «Привет, мир!», то есть выполним те же действия, что и на первом нашем уроке. Также воспользуемся процедурой writeln, но на этот раз не будем помещать в неё строку в явном виде, а воспользуемся для этого двумя переменными.

Листинг 2.

program TypeString;

var
  s1, s2: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';

  writeln(s1, s2);
  readln;
END.
Кстати, если строковая переменная объявлена без ограничений, то в её нулевом байте также хранится реальный размер строки, а не гипотетические 255 байтов. В частности, длина переменной s1 из примера выше равна 8 символам, а для s2 — 4 символа.

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

Если бы строковые данные можно было только выводить на экран, то они не стоили бы даже отведённой на их хранение оперативной памяти. Например, строки можно складывать. Давайте перепишем второй пример так, как показано ниже и запустим нашу программу на исполнение.

Листинг 3.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;

  writeln(s3);
  readln;
END.

В окне вывода данных PascalABC или в окне программы при её компиляции, вы увидите тот же результат, что и раньше, однако внутренне программа устроена и работает несколько иначе. В переменную s3 мы поместили результат «сложения» двух других строк — переменных s1 и s2. Пишу сложение в кавычках, так как это не сложение двух аргументов как в математике. Во всяком случае, от перемены мест «слагаемых» при использовании строк, зависит очень многое. Попробуйте у себя в примере поменять переменные s1 и s2 местами и снова запустите программу. Легко убедиться, что на экран по-прежнему выводится объединённая строка, вот только теперь в ней мало смысла. Итак, сложение строк называется мудрёным словом конкатенация и обозначает их объединение в том порядке, как они и записаны.

В новую переменную сначала помещается строка из первого аргумента, затем в её конец без каких-либо разделителей (о пробелах, если они нужны, мы должны позаботиться сами) помещается второй аргумент и так далее. Таким образом можно конкатенировать — объединять более двух строчек.

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

Я обещал, что расскажу, как можно обмануть Паскаль и всё-таки поместить одиночную кавычку-апостроф в строку как её часть. Всё просто, для этого сам символ необходимо продублировать — написать слитно не один, а сразу два апострофа. Вот вам очередной пример:

Листинг 4.

program TypeString;

var
  s1, s2: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'Д''Артаньян';

  writeln(s1, s2);
  readln;
END.

Как видите, ничего сложного.

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

Листинг 5.

program TypeString;

var
  s1: String;
  userName: String[20];

BEGIN
  s1 := 'Привет, ';
  write('Введите пожалуйста ваше имя: ');
  readln(userName);

  writeln(s1, userName);
  readln;
END.

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

Когда мы непосредственно в программе присваиваем переменным какие-то строковые значения, нам несложно сосчитать длину этих строк, но как только мы вносим в программу интерактив — отдаём хотя бы на время управление пользователю, то теряем контроль над ситуацией и мы точно не сможем знать количество символов, хранимых в той или иной переменной типа String. А как быть, если нам необходимо узнать фактическую длину строковой переменной? Для этого у Паскаля есть функция Length. Она как раз и возвращает в качестве значения целое число — фактическую длину строки. Смотрите пример ниже.

Листинг 6.

program TypeString;

var
  s1, s2, s3: String;
  i: Integer;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;
  i := length(s3);

  writeln('Длина строки, записанной в переменную s3 равна ', i);
  readln;
END.

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

Для обращения к конкретному символу в строковой переменной следует после её имени в квадратных скобках передать его номер. Приведу ещё один пример:

Листинг 7.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;

  writeln(s3[1]);
  readln;
END.

Запустив программу мы убеждаемся, что символы в строке нумеруются, начиная с единицы. Это вызывает путаницу у новичков. Очень многие сущности в программировании начинают нумероваться с нуля, а в строке нулевой байт отведён для хранения её фактической длины, это мы уже неоднократно обсуждали. Следовательно, номера элементов строки — обычные натуральные числа.

Прекрасно, распечатать первый, второй или третий символ из нашей строки мы можем. А как напечатать самый последний, да ещё и в том случае, когда нам не известна заранее длина строки? Здесь нам также поможет функция Length.

Листинг 8.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;

  writeln(s3[length(s3)]);
  readln;
END.

Может показаться, что код выглядит громоздко. Если вам так кажется, воспользуйтесь листингом 7 и подставьте в квадратные скобки переменную i. Во всяком случае, в современных компьютерах более чем достаточно оперативки и от одной дополнительной переменной мы потеряем немного.

Мы уже умеем «складывать» строки, вычислять их длину, но всё ли это? — Нет. Мы также можем искать подстроку в строке. Причём, в качестве подстроки может выступать как небольшая строка, так и отдельные символы. Не буду вас томить, в Паскале имеется функция Pos, и работает она следующим образом: в скобках ей передаются два аргумента. Сначала мы должны указать что мы ищем, а затем — где мы это делаем. То есть сначала указывается подстрока или символ, а затем строка, где мы и ведём поиск. Наберите в среде разработки следующий пример и запустите программу на выполнение.

Листинг 9.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;

  writeln( pos('р', s3) );
  readln;
END.

Что мы видим? Во-первых, функция нашла только первое вхождение искомой подстроки. В результате напечаталось число 2 — буква «р» действительно находится во второй позиции в строке s3. Однако, предпоследний символ в этой строке — тоже буква «р», но до неё наша функция не дошла. Это всегда следует помнить! Функция Pos находит лишь первое вхождение подстроки в строке. Во-вторых, она ищет буквально то, что было велено. Если бы мы заставили искать в нашей строке прописную — заглавную «Р», то функция вернула бы нам 0, так как подобной буквы в нашей строке нет.

Как же быть, если строку будет вводить пользователь, и мы не знаем заранее, как именно он её внесёт с клавиатуры? — В этом случае обе строки — искомую фразу, и ту, где мы ведём поиск, желательно перевести в нижний или верхний регистр. Этой цели служат специальные функции Паскаля: LowerCase — переводит аргумент в строчные (маленькие) буквы, а UpperCase — в прописные (заглавные).

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

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

Обратите внимание, мы не указываем подстроку для копирования в явном виде. Вместо этого мы указываем числа.

Листинг 10.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;

  writeln( copy(s3, 9, 3) );
  readln;
END.

Если вы запустите программу из десятого примера, то сможете убедиться, что на экране появится слово «мир». Во-первых, я не стал присваивать скопированное значение в строчную переменную, а сразу вывел на экран. Во-вторых, я немного схитрил. Зная, что именно сохранено в переменной s3, мне было легко вычислить позицию 9 — с которой собственно и начинается наше слово, ну а длина его мне была известна заранее. Когда мы имеем дело с не столь очевидными примерами, тут-то нам и может пригодиться функция Pos, которая могла бы найти индекс — первое число.

До сих пор мы имели дело с функциями, которые бережно относятся к переданным в скобках аргументам. Во всяком случае, вы можете доработать листинг 10 так, чтобы в этом убедиться. Добавьте ещё одну строковую переменную. Присвойте в неё значение функции Copy, а на экран выведите переменную s3. Легко будет убедиться, что данные в ней остались неизменными.

Теперь пришло время познакомиться с процедурой и это процедура Delete. Данная процедура удаляет подстроку. Здесь тоже удаляемая подстрока не указывается явно. Вместо этого из строчной переменной удаляются символы — их количество снова указывается числом, и удаление начинается с определённого символа в строке.

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

Листинг 11.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;
  delete(s3, 7, 5);

  writeln(s3);
  readln;
END.

Запускаем пример 11 и получаем в результате «Привет!». Что это значит? — В переменной s3 мы удалили 5 символов, начиная с седьмого. При этом, если в строке символов было больше, то все прочие — в нашем случае это восклицательный знак — никуда не исчезают. Тем не менее, мы удалили подстроку «, мир» — запятая, пробел и слово мир из нашей переменной s3. Процедура изменила сам аргумент.

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

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

Листинг 12.

program TypeString;

var
  s1, s2, s3: String;

BEGIN
  s1 := 'Привет, ';
  s2 := 'мир!';
  s3 := s1 + s2;
  delete(s3, 9, 3);
  insert('Владимир', s3, 9);

  writeln(s3);
  readln;
END.

В этой программе я использовал обе процедуры. Сначала из строки «Привет, мир!» я удаляю 3 символа, начиная с девятого — удаляю слово «мир». Затем добавляю строчку «Владимир» с того же девятого символа. Таким образом, я заменяю слово «мир» собственным именем.

В процедуру Insert аргументы подаются в следующем порядке: первым идёт строка, которую мы собираемся вставить, она может быть задана как переменная или явно. Вторым аргументом мы указываем переменную типа String, в которую и будет производиться вставка. В последнюю очередь указывают число — номер символа, начиная с которого и должна быть произведена вставка.

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

Напишем очередную короткую программу.

Листинг 13.

program TypeString;

var
  s1, s2: String;

BEGIN
  s1 := 'Ваня';
  s2 := 'Вова';

  if s1 >= s2 then
    writeln(s1, ' больше или равно ', s2)
  else
    writeln(s2, ' больше ', s1);
  readln;
END.

На этот раз оставляем лишь две строковые переменные. Присвоим им похожие имена. Дальше воспользуемся материалами прошлого урока — устроим логическое ветвление программы по условию.

Если вы запустите программу, то легко сможете убедиться, что переменная s2, содержащая «Вова», оказывается строго больше переменной s1, в которой записано имя «Ваня».

Мы уже говорили, что строки можно интерпретировать как массив, состоящий из отдельных символов. И когда нам в голову приходит сравнить между собою строки, происходит последовательное сравнивание символов каждой из строк. А вернее, сравниваются между собою числа, которыми кодируются символы в той или иной кодовой странице (кодировке). Однако, об этом мы поговорим в далёком 16 уроке.

Чем дальше та или иная буква от начала алфавита, тем больше код, которым она кодируется. В этом смысле, русская буква «о» явно больше буквы «а» — которая в алфавите стоит первой.

В дальнейших уроках мы ещё неоднократно будем касаться строк и строковых переменных, но на сегодня это всё. Дочитавшим до конца — бонус, «кино» на заданную тему.

Репозиторий листингов программ.

Оставьте ваш отзыв: