Работа с текстом в языке Ада

Для работы с текстом в Аде существует несколько предопределённых типов: Character, Wide_Character, Wide_Wide_Character, String, Bounded_String, Unbounded_String и т.д. Тема весьма обширна, а так как со строками мы только знакомимся, то на данном этапе я ограничусь использованием самых "ходовых" типов и операций. В следующем разделе для решения задач нам понадобятся типы:

  • Character - для работы с простыми символами (ASCII, не Unicode) (в принципе, работа с Wide_Characters ничем не отличается);
  • String - для работы с простыми строками (фактически это массивы заданной длины, состоящие из символов типа Character);
  • Unbounded_String - для работы со строками неопределённой длины. Т.е. размер переменной, в которую будет считываться строка, меняет свой размер по мере необходимости.

Тип Character.

Используется для хранения символьных переменных. Примеры объявления и инициализации переменных:

ch : Character := 'a';

Num : Character := '1'

У типа Character также как и у других типов есть атрибуты. Нам понадобятся следующие:

Character'Pos(ch); - возвращает код символа, содержащегося в переменной ch, в данном случае число 97 (код символа 'a' (см. инициализацию выше))

Character'Val('97'); - возвращает символ, соответствующий коду, переданному в качестве параметра функции, т.е. в данном случае символ 'a'.

Также может пригодиться знание, что код клавиши <Enter> равен 13, а клавиши <ESC> - 27

Основные операции:

Put(ch); - вывод символа на экран.

Get(ch); - считывание символа с клавиатуры.

Get_Immediate(ch); - считывание символа с клавиатуры без эха, т.е. символ будет считан в переменную ch сразу, без отображения на экране, не дожидаясь нажатия клавиши <Enter>. Эта функция работает как в Windows, так и в Linux (у C/C++ с этим проблемы и на форумах объясняют, что в Linux для ввода без эха нужно перенастроить терминал)

Думаю, что Get() и Put() вопросов не вызовут, а вот Get_Immediate() рассмотрим на примере. Пусть нам нужно написать программу, которая будет реагировать только на ввод символов латиницы в нижнем регистре. Т.е. при вводе символов английского алфавита от 'a' до 'z' программа будет выводить их на экран, а нажатие всех остальных клавиш клавиатуры будет игнорировать. И добавим ещё выход из программы при нажатии клавиши <ESC>:

with Ada.Text_IO; use Ada.Text_IO;
 
procedure main is
	ch : Character;
begin
	loop
		Get_Immediate(ch); --Считывание символа без эха (т.е. в этом месте программы
				   --символ на экран не выводится)
		exit when Character'Pos(ch) = 27; --Обработка нажатия <ESC>
 
		--Обработка корректного ввода. Некорректный ввод игнорируется
		if ch >= 'a' and ch <= 'z' then
			Put(ch); --Вывод символа на экран
		end if;
 
	end loop;
 
end main;

Символы можно сравнивать друг с другом. Правила сравнения аналогичны числовым типам, при этом сравниваются коды символов.

Тип String.

Этот тип используется для работы со строками со времён царя-косаря 🙂 У него есть некоторые ограничения и недостатки (которые были преодолены в типах Bounded_String и Unbounded_String). Тем не менее, он вполне рабочий и удобен для решения многих задач. Фактически это массив символов типа Character. Для работы со строками нужно подключить соответствующую библиотеку:

with Ada.Strings; use Ada.Strings;

Хотя GNAT и GCC работают и без этого 😉

Объявление переменных:

str_1 : String(1..10); - под переменную str выделяется 10 символов. Сейчас строка str_1 заполнена "мусором"

str_2 : String := "hello!"; - Границы строки не задаются, т.к. её длина определена при инициализации.

str_1 : String(1..10) := (others => 'a'); - Вся строка сразу же заполняется символами 'a'.

Для ввода и вывода строк используются Get_Line(), Put_Line() и Put(). Здесь есть небольшие особенности. Рассмотрим операции ввода-вывода на примере. Задача: написать программу, которая запрашивает имя пользователя и здоровается с ним.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings; use Ada.Strings;
 
procedure main is
    name : String(1..25); --Создаём строку (массив из 25 символов)
    Last : Integer;       --Счётчик количества прочитанных символов
begin
    Put("Введите своё имя: ");
    Get_Line(name, Last); --Считывает имя и количество символов в имени (если символов
                          --будет больше, чем вмещает строка (25), то остальные
                          --символы будут отброшены, но останутся в буфере клавиатуры
                          --и будут прочитаны в следующем Get/Get_Line
    --Использование name(1..Last) выведет ровно количество прочитанных символов. Если
    --использовать просто name, то будут выведены все 25 символов строки, в т.ч. "мусор"
    Put_Line("Привет, " & name(1..Last) & "!");
end main;

В данной программе в строке 4 объявляется строка (массив) из 25 символов (name). Больше 25 символов строка вместить не может. В строке 6 создаётся счётчик количества прочитанных символов (Last). Он нужен для строки 9, в которой происходит считывание с клавиатуры. Считывание строки осуществляется в переменную name, а количество считанных символов сохраняется в переменной Last. При этом, если количество символов меньше 25 (размер строки), то если в строке 12 (вывод строки на экран) вместо среза name(1..Last) использовать просто name, то на экран будут выведены все 25 символов строки, а там, кроме нужной нам информации, содержится ещё и мусор. Поэтому при выводе строки стоит использовать срез. Исключение из этого правила - это если строка задаётся сразу при объявлении; тогда под неё будет выделено ровно столько места, сколько нужно для её хранения.

Есть ещё один нюанс. Допустим, у нас есть 2 переменных строкового типа по 10 символов: str_one(1..10) и str_two(1..10). Пусть нам последовательно нужно прочитать в эти переменные две строки: сначала в str_one, а затем в str_two():

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings; use Ada.Strings;
 
procedure main is
	str_one, str_two: String(1..5);
	one_len, two_len : Integer;
begin
	Put("Введите первую строку: ");
	Get_Line(str_one, one_len);
	Put("Введите вторую строку: ");
	Get_Line(str_two, two_len);
 
	Put_Line("Первая строка -> " & str_one(1..one_len));
	Put_Line("Вторая строка -> " & str_two(1..two_len));
end main;

Тогда если пользователь на первый запрос введёт больше 10 символов (пусть 12) или 10 символов и нажмёт <Enter>, то до считывания второй строки дело даже не дойдёт. Дело в том, что непоместившиеся в строку символы (2 из 12 или <Enter> во втором случае) остаются в буфере клавиатуры и при следующем Get_Line() считываются в переменную str_two. Для исправления данной ситуации можно использовать следующий алгоритм:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings; use Ada.Strings;
 
procedure main is
	str_one, str_two: String(1..5);
	one_len, two_len : Integer;
begin
 
	Put("Первая строка: ");
	Get_Line(str_one, one_len);
	if one_len = str_one'Last then --Если one_len содержит количество символов,
				       --равное размеру строки one_str
		Skip_Line;	       --то всё остальное отбрасывается
	end if;
	Put("Вторая строка: ");
	Get_Line(str_two, two_len);
	if one_len = str_one'Last then --аналогично one_str
		Skip_Line;
	end if;
 
	Put_Line("Первая строка -> " & str_one(1..one_len));
	Put_Line("Вторая строка -> " & str_two(1..two_len));
end main;

Таким образом функция Skip_Line пропускает строку (убирая при этом её из буфера клавиатуры).

Узнать длину строки можно с помощью атрибута String'Length. Однако он покажет не сколько символов записано в строку при вводе, а сколько всего символов может вместить строка.

Строки можно сравнивать: str_1 = str_2. Можно сравнивать между собой участки строк (т.н. срезы), например: str_1(1..4) = str_2(5..8). Также можно присвоить одну строку другой: str_1 := str_2. При этом строки должны быть одной длины. Также можно присваивать участок одной строки участку другой: str_1(1..2) = str_2(3..4).

Строки можно соединять. Например, пусть у нас есть строка str_1 длиной 10 символов и строка str_2 длиной 5 символов. Пусть строка str_1 содержит 5 символов. Тогда для присоединения строки str_2 к строке str_1 можно использовать оператор &: str_1 := str_1(1..5) & str_2; т.е. оператор "&" - это оператор конкатенации ("склеивания" строк). При этом нужно помнить, что в итоговой строке должно быть достаточно места для того, чтобы вместить обе "склеиваемые" строки.

Тип Ada.Strings.Unbounded

Почти всегда при считывании строк невозможно предугадать длину строки. В этом случае можно либо считывать строку кусками, с использованием типа String, либо использовать Unbounded_String и сразу прочитать всю строку целиком. Дело в том, то переменная этого типа "растёт" постепенно с увеличением вводимой строки. Объявляется строка этого типа следующим образом:

S : Ada.Strings.Unbounded.Unbounded_String;

В следующей программе рассмотрен ввод строки Unbounded_String с клавиатуры и вывод этой строки на экран:

--для объявления строк Unbounded_String
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
--для использования операций ввода-вывода строк Unbounded_String
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO; 
 
 
procedure main is
	--Объявляем строку типа Unbounded_String
	S : Unbounded_String;
begin
	Put(To_Unbounded_String("Введите строку: "));
	Get_Line(S);
	Put_Line(S);
end main;

Как видите, ничего сложного. Единственный вопрос может вызвать строка вывода сообщения: Put(To_Unbounded_String("Введите строку: ")); Дело в том, что так как мы не используем в этом примере пакет Ada.Text_IO, а вместо него задействуем пакет Ada.Text_IO.Unbounded_IO, то ввод-вывод простого текста (String) для нас недоступен. Поэтому для вывода обычной строки на экран её нужно преобразовать к типу Unbounded_String. Именно это и делает функция To_Unbounded_String().

Далее рассмотрим ввод и вывод Unbounded_String с использованием пакета Ada.Text_IO.

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
 
procedure main is
	str : Unbounded_String;
begin
	Put("Введите строку: ");
	str := To_Unbounded_String(Get_Line);
	Put_Line(To_String(str));
end main;

В этом примере для ввода строки используется конструкция

str := To_Unbounded_String(Get_Line);

Непосредственно сам ввод осуществляет функция Get_Line из пакета Ada.Text_IO.

Обратите внимание, функция Get_Line() и функция Get_Line - это разные функции. Первой для работы нужно передать два параметра: переменную, куда будет считываться строка и переменную, в которую будет записана длина считанной строки. Второй никакие параметры не передаются.

Get_Line считывает с устройства ввода обычную строку типа String. А функция To_Unbounded_String() преобразует эту строку к типу Unbounded_String. Таким образом осуществляется считывание строк любой длины.

После считывания строки Unbounded_String ее длину можно узнать с помощью функции Length() пакета Ada.Strings.Unbounded: Ada.Strings.Unbounded.Length(S);

Для того, чтобы окончательно всех запутать, рассмотрим ввод-вывод Unbounded_String без использования use. Шучу! Просто посмотрим т.н. полную запись программы и рассмотрим такой прием как переименования. Заодно посмотрим, как использовать ту или иную функцию из какого-либо пакета. Сразу оговорюсь, переименование (renames) даёт пакету ещё одно имя.

with
	Ada.Text_IO,
	Ada.Strings.Unbounded;
 
procedure main is
	--Дадим пакту Ada.Strinds.Unbounded имя покороче - ASU
	package ASU renames Ada.Strings.Unbounded;
 
	--Благодаря переименованию мы теперь можем использовать префикс ASU
	--вместо Ada.Strings.Unbounded
	str : ASU.Unbounded_String;
begin
	--Обращение к функции Put() пакета Ada.Text_IO
	Ada.Text_IO.Put("Введите строку: ");
 
	--Обращение к функции To_Unbounded_String пакета Ada.Strings.Unbounded
	--и к функции Get_Line пакета Ada.Text_IO
	str := ASU.To_Unbounded_String(Ada.Text_IO.Get_Line);
 
	--Обращение к функции Put_Line() пакета Ada.Text_IO и к функции
	--To__String пакета Ada.Strings.Unbounded (ASU)
	Ada.Text_IO.Put_Line(ASU.To_String(str));
end main;

Сравнение строк Unbounded_String

Если Вы хотите сравнивать строки Unbounded_String друг с другом, то для того, чтобы использовать знак равенства ('=') в операциях сравнения нужно либо прописать использование пакета Ada.Strings.Unbounded через use:

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

так как по умолчанию компилятор будет брать оператор сравнения для строк String и ругаться, либо оператор '=' можно использовать так:

Ada.Strings.Unbounded."="(str_1, str_2);

Второй вариант - это т.н. переопределение оператора (перегрузка в С++). Более подробно эту возможность мы рассмотрим немного позднее

Пример:

with Ada.Strings.Unbounded;
with Ada.Text_IO.Unbounded_IO;
 
procedure main is
    ustr : Ada.Strings.Unbounded.Unbounded_String := Ada.Strings.Unbounded.To_Unbounded_String("Hello, all!");
    str : String := Ada.Strings.Unbounded.To_String(ustr);
begin
    Ada.Text_IO.Unbounded_IO.Get_Line(Item => ustr);
    if Ada.Strings.Unbounded."="(ustr,Ada.Strings.Unbounded.To_Unbounded_String("Hello, world!")) then
        Ada.Text_IO.Unbounded_IO.Put_Line(ustr);
    end if;
end main;

Многословно? Да, наверное, но Ада этим отличается. Зато, на мой взгляд, все логично и понятно. Писать программу, конечно, долго, зато читать и сопровождать намного легче, чем, например, на С++. Ада проигрывает в скорости написания, зато выигрывает в сопровождении и отсутствии необходимости исправлять "внезапно" появляющиеся ошибки. Иными словами, если мы делаем самолет, то в результате мы сразу получим самолет, а не паровоз, который после сборки до состояния самолета надо дорабатывать напильником.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *